All unit tests passing for both packages after monorepo restructure.
All code changes MUST follow Test-Driven Development:
- Red - Write a failing test first
- Green - Write minimal code to make it pass
- Refactor - Clean up while tests stay green
No exceptions. If you're touching code, you're touching tests first.
Slow tests don't get run. Fast tests catch bugs early.
Rules for fast tests:
- Prefer in-memory databases - Use
createInMemorySwarmMail()over file-based PGLite - Share instances when possible - Use
beforeAll/afterAllfor expensive setup, notbeforeEach/afterEach - Don't skip tests - If a test needs external services, mock them or make them optional with clear error messages
- Clean up after yourself - But don't recreate the world for each test
Anti-patterns to avoid:
- Creating new database instances per test (slow, wasteful)
test.skip()without a tracking issue- Tests that pass by accident (no assertions, wrong assertions)
- Tests that only run in CI
| Tier | Suffix | Speed | Dependencies | When to Run |
|---|---|---|---|---|
| Unit | .test.ts |
<100ms | None | Every save |
| Integration | .integration.test.ts |
<5s | PGLite, filesystem | Pre-commit |
| E2E | .e2e.test.ts |
<30s | External services | CI only |
PGLite (embedded Postgres via WASM) is central to swarm-mail. Here's how to test with it:
// GOOD: In-memory for unit tests (fast, isolated)
const swarmMail = await createInMemorySwarmMail("test-project");
// GOOD: Shared instance for related tests
describe("feature X", () => {
let swarmMail: SwarmMailAdapter;
beforeAll(async () => {
swarmMail = await createInMemorySwarmMail("test");
});
afterAll(async () => {
await swarmMail.close();
});
test("does thing A", async () => { /* uses swarmMail */ });
test("does thing B", async () => { /* uses swarmMail */ });
});
// BAD: New instance per test (slow)
beforeEach(async () => {
swarmMail = await createInMemorySwarmMail("test");
});When testing error recovery (like WASM abort from corrupted databases):
- Create the corrupted state explicitly (don't rely on flaky failures)
- Verify recovery actually works (call methods, check results)
- Clean up in
afterAll, notafterEach
Test Command: bun test src/
src/pglite.test.ts- 16 tests (path hashing, singleton, WASM recovery)src/streams/events.test.ts- 55 tests (event schemas)src/streams/migrations.test.ts- 15 tests (migration system)src/hive/adapter.test.ts- Hive adapter testssrc/daemon.test.ts- Daemon lifecycle testssrc/socket-adapter.test.ts- Socket adapter tests
src/streams/*.integration-test.ts- Full event store flowssrc/hive/*.integration-test.ts- Hive with real PGLite
Integration tests use file-based PGLite. They may be slower but test real behavior.
Test Command: bun test src/
src/schemas/index.test.ts- 14 tests (Zod schemas)src/structured.test.ts- 73 tests (structured output parsing)src/skills.test.ts- 38 tests (skills system)src/anti-patterns.test.ts- Anti-pattern detectionsrc/planning-guardrails.test.ts- Planning validationsrc/output-guardrails.test.ts- Output validation
src/*.integration.test.ts- Tests requiring swarm-mail
# Run all tests (both packages)
bun turbo test
# Run specific package
bun turbo test --filter=swarm-mail
bun turbo test --filter=opencode-swarm-plugin
# Run specific test file
cd packages/swarm-mail && bun test src/pglite.test.ts
# Run with watch mode
cd packages/swarm-mail && bun test --watch src/
# Run only unit tests (fast)
bun test src/*.test.ts
# Run integration tests
bun test src/*.integration.test.tsBoth packages integrated into turbo pipeline:
bun turbo test # Runs all package tests with dependency orderingPipeline configuration in turbo.json:
- Depends on
^build(builds dependencies first) - Caches test results based on input files
- Integration tests may be slower - this is expected
- Unit tests have zero external dependencies
- Both packages use bun test runner
- All test commands in package.json are correct and functional
- PGLite WASM may fail in parallel test runs - tests handle this gracefully