The Lie of the Passing Test Suite
There is a distinct flavor of dread that only reveals itself when two autonomous AI subagents finish their tasks, commit their work to separate worktree branches, and merge back into mainβonly for a test suite that passed 100% locally to erupt into red cascading failures.
In citation-manager, our multi-agent workflow hit a classic distributed trap: shared mutable persistence.
The backend test suite was executing against a persistent SQLite file on disk (citation_manager.db). When Agent A tested user registration, it created users with fixed emails. When Agent B simultaneously tested citation deduplication or admin authorization sweeps, the tests clashed over primary keys, foreign constraints, and residual session cookies.
Individual tests passed when run with --test-name-pattern, but running the whole suite in parallel turned into a nondeterministic roll of the dice.
Here is the anatomy of how we eradicated state leaks, enforced strict negative Role-Based Access Control (RBAC), and purged 25+ useState hooks into an atomic React context.
1. The :memory: SQLite Reset Invariant
The first law of robust automated testing: no test should ever know another test was born before it.
Switching Bun/Node SQLite to :memory: is easy; keeping it cleanly isolated across rapid test lifecycles without leaking prepared statement handles or deadlocks is where standard setups break down.
We refactored server/db.ts to support dynamic database binding and an uncompromising resetDB() utility:
// server/db.ts
import { Database } from "bun:sqlite";
let dbInstance: Database | null = null;
export function getDB(path: string = process.env.DB_PATH || "citation_manager.db"): Database {
if (!dbInstance) {
dbInstance = new Database(path, { create: true });
initDB(dbInstance);
}
return dbInstance;
}
export function resetDB(): void {
if (!dbInstance) return;
// Disable foreign keys temporarily for clean truncation
dbInstance.exec("PRAGMA foreign_keys = OFF;");
const tables = dbInstance
.query<{ name: string }, []>(
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';"
)
.all();
for (const { name } of tables) {
dbInstance.exec(`DELETE FROM "${name}";`);
}
dbInstance.exec("PRAGMA foreign_keys = ON;");
}
In the test harness (server/tests/api.test.ts), beforeEach wipes every table down to absolute zero, guaranteeing that whether a test creates one citation or ten thousand faculty profiles, the next test boots on an unblemished canvas.
beforeEach(() => {
resetDB();
// Reseed core deterministic reference fixtures
seedCoreTestFixtures();
});
2. Negative RBAC: Testing the Doors That Shouldnβt Open
Most agent-generated tests make a fatal assumption: they only test the happy path.
They create an admin, log in with an admin token, verify the admin endpoint returns 200 OK, and declare victory. But what happens when:
- A regular researcher token hits
/api/admin/audit-logs? - An unauthenticated request tries to delete a citation?
- A forged JWT with an altered
role: "admin"payload but an invalid HMAC signature arrives at the gate?
We introduced strict negative assertion suites for every single protected route. A security gate is only as strong as its rejection behavior:
describe("RBAC & Security Perimeter", () => {
it("strictly rejects regular user tokens on admin routes with 403 Forbidden", async () => {
const userSession = await registerTestUser({ role: "member" });
const res = await app.request("/api/admin/domains", {
method: "POST",
headers: {
Authorization: `Bearer ${userSession.token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ domain: "malicious-domain.edu" }),
});
expect(res.status).toBe(403);
const body = await res.json();
expect(body.error).toMatch(/admin privileges required/i);
});
it("rejects forged tokens with tampered role claims", async () => {
// Forged token signed with bogus key
const forgedToken = signBogusToken({ id: 1, role: "admin" });
const res = await app.request("/api/admin/users", {
headers: { Authorization: `Bearer ${forgedToken}` },
});
expect(res.status).toBe(401);
});
});
If an endpoint does not have an explicit negative assertion proving unauthorized callers are shut out with the exact error contract, the test suite considers the route unprotected.
3. Frontend: Killing 25+ Props with AuthContext
On the frontend, App.tsx had metastasized into an 800-line behemoth carrying over 25 independent useState hooks. Auth state, user profiles, session tokens, and modal flags were being manually drilled down through four layers of child components.
When an agent attempted to fix a modal bug in AdminDashboardPage.tsx, it had to modify five intermediate component signatures.
We excised the prop sprawl and created an encapsulated AuthContext:
// client/src/context/AuthContext.tsx
interface AuthContextType {
user: User | null;
token: string | null;
login: (credentials: LoginCredentials) => Promise<void>;
logout: () => void;
isAuthenticated: boolean;
isAdmin: boolean;
}
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [token, setToken] = useState<string | null>(() => localStorage.getItem("auth_token"));
const logout = useCallback(() => {
localStorage.removeItem("auth_token");
setToken(null);
setUser(null);
}, []);
const isAdmin = useMemo(() => user?.role === "admin", [user]);
return (
<AuthContext.Provider value={{ user, token, login, logout, isAuthenticated: !!token, isAdmin }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = (): AuthContextType => {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("useAuth must be used within an AuthProvider");
return ctx;
};
We backed this with dual-layer Vitest + React Testing Library suites in client/src/context/__tests__/AuthContext.test.tsx and client/src/pages/__tests__/AdminDashboardPage.test.tsx, asserting token hydration from localStorage, automatic logout on 401 interceptors, and strict RBAC rendering logic in the UI.
4. Citation Engine: The TeX & HTML Escaping Gauntlet
Citations are notoriously hostile to naive string interpolation. An academic paper titled 100% Efficiency in C&A Systems: The $5 Solution will immediately break:
- BibTeX parsers (unescaped
%,&, and$). - HTML metadata meta tags (unescaped quotes and angle brackets in OpenGraph/Dublin Core headers).
- APA/MLA et al. rules (variable author counts and punctuation placement).
In server/formatter.ts, we hardened sanitizers against injection and syntax corruption:
export function sanitizeLatex(str?: string | null): string {
if (!str) return "";
return str
.replace(/\\/g, "\\textbackslash{}")
.replace(/([%&$#_{}])/g, "\\$1")
.replace(/~/g, "\\textasciitilde{}")
.replace(/\^/g, "\\textasciicircum{}");
}
export function escapeAttr(str?: string | null): string {
if (!str) return "";
return str
.replace(/&/g, "&")
.replace(/"/g, """)
.replace(/'/g, "'")
.replace(/</g, "<")
.replace(/>/g, ">");
}
With 300+ lines of unit tests in server/tests/formatter.test.ts, the engine now handles edge-case author arrays ("von Neumann, John", single-name mononyms like "Plato", and institutional authors like "World Health Organization") without degrading citation style fidelity.
The Takeaway
When pair-programming with autonomous agents across complex codebases:
- Never trust shared disk state in test suites. Force
:memory:or disposable containers with zero-cost resets. - Positive tests prove functionality; negative tests prove safety. Always assert the 401s and 403s.
- Centralize state early. If props traverse more than two component boundaries, pull them into Context before subagents fracture the interfaces.
- Assume user input will contain raw TeX and raw HTML. Escape early, escape strictly.