The integration reality check
June 24, 2025
Everything passed unit tests. Nothing worked when I wired it all together.
This is the humbling reality of integration testing: all your beautiful, isolated components can work perfectly in theory and completely fail when they meet the messy real world of API contracts, field naming conventions, and actual user workflows.
But here’s the thing about integration reality checks — they don’t just reveal bugs, they reveal truths about your system that unit tests can’t capture.
The morning of confidence
The day started well. File upload endpoints were working, conversation handling was solid, and all the pieces seemed ready to connect. Time for the big moment: fire up the web interface (that’s what we were supposed to be making work after all), upload a file, ask Piper to summarize or analyze it in chat, watch the magic happen.
Spoiler alert: no magic happened.
Field-naming chaos
First problem: the frontend expected a response field, the backend was sending message. Classic integration mismatch that no unit test catches because each side works perfectly within its own assumptions.
Also, a bad sign that along the way we were not checking what had already been built. Whenever I find a bug like this I am reminded that I allowed an AI assistant to write code without checking what was already written, which leads to… well, you can’t call it hallucination in this case, but let’s just call it “making stuff up.”
Another example: the frontend called /api/v1/knowledge/upload, but the backend had /api/v1/files/upload. There were other random (if plausible) field mismatches: document_id vs file_id. The kind of inconsistencies that accumulate when you're building too fast and not sweating the details.
The LLM-provider plot twist
Just as things started working, I hit Anthropic’s API credit limit. In production. During testing. This led to a bit of misdirected debugging till I looked more closely at the terminal outfit, which included an email address to contact if I wanted them to up my quota.
So, weturned this into a feature by implementing automatic LLM provider fallback (Anthropic → OpenAI), along the way discovering enum mismatches (GPT4_TURBO vs GPT4) that would have been painful to debug later. Sometimes constraints force you to build better architecture!
Python compatibility strikes again
Remember asyncio.timeout()? Noooo! Ay-sink-ee-oh! Turns out it doesn’t exist in Python 3.9. So the orchestration engine that worked perfectly in development broke immediately in the testing environment.
The fix was simple enough (asyncio.wait_for() everywhere), but it reinforced something important: integration testing catches environment assumptions that unit tests never see.
The workflow mapping mystery
My favorite integration bug: OpenAI correctly identified the analyze_file intent, but the workflow factory had no mapping for it. The AI understood what the user wanted, but the system didn't know how to execute it. This wouldn’t be the last time I started testing something Claude told me was complete only to find just the first layer built or nothing connected.
One line fix: 'analyze_file': WorkflowType.ANALYZE_FILE. But it revealed how easy it is for different parts of the system to evolve at different rates.
UI polling madness
The status checks were uppercase (COMPLETED) but the backend sent lowercase. So the UI kept polling forever, convinced the workflow was still running when it had finished minutes ago.
These aren’t bugs exactly — they’re integration contract mismatches. Each component worked correctly within its own understanding of the world. It’s a tricky concept to wrap your head around. Every piece you built works and passes its own tests in isolation, but when you put them together it fails. It’s like going from atoms to molecules to organs to a body. You need to make sure every level if working.
Any these mismatches around things like capitalization conventions were another sign we were not yet disciplining the process in a way that respected past work.
When it all clicked
After fixing the field mismatches, adding LLM fallbacks, updating the Python compatibility, mapping the workflows, and synchronizing the case conventions, something beautiful happened: the complete file analysis slice worked end-to-end.
User uploads file → references it naturally → system resolves, creates workflow, executes analysis → UI shows success. The whole vertical slice, working as intended.
(Well, almost. The analysis was still just a placeholder returning “success” without actually reading files. But the integration points were solid.)
What integration testing taught me
Unit tests are necessary but not sufficient. They validate individual component behavior but can’t catch contract mismatches, environment assumptions, or protocol violations.
Integration failures compound. One field naming issue becomes three API mismatches becomes five UI bugs. Fix the pattern, not just the instance.
Resilience through fallbacks. The LLM provider switching saved the session and revealed a better architecture. Build expecting failures.
Real data reveals real problems. Mock implementations can hide issues that only surface with actual API calls, real file uploads, and genuine user workflows.
The architectural insight
Integration testing isn’t just debugging — it’s architectural validation. Every mismatch reveals an assumption that needs to be made explicit. Every fallback reveals a dependency that needs to be managed.
The gaps between components aren’t bugs to be fixed, they’re design decisions to be made consciously. Making them ahead of time can save a lot of grief, I am gradually discovering.
We see this pattern beyond the code level as well. Building a product involves more than integrating modules into coherent functional workflows. You also have to integrate user feedback, what the market is telling you, and more.
What’s next
With the integration points working and fallbacks in place, we were finally ready to implement actual file reading and analysis logic. The foundation proved it can handle real workflows under real constraints.
But first: more integration tests. Because if there’s one thing this taught me, it’s that the gaps between the pieces are where the interesting problems hide.
