Skip to content

server: Add end-to-end item submission integration test and harness#488

Open
julietshen wants to merge 4 commits into
mainfrom
julietshen/testing
Open

server: Add end-to-end item submission integration test and harness#488
julietshen wants to merge 4 commits into
mainfrom
julietshen/testing

Conversation

@julietshen
Copy link
Copy Markdown
Member

@julietshen julietshen commented May 15, 2026

Context & Requests for Reviewers

Implements #339 (sub-issue of #288). Adds a real-infra integration test harness under server/test/integ/ and the first scenario: end-to-end item submission landing in Scylla and ClickHouse.

The harness is intentionally generic — the four remaining #288 sub-issues (#340 rule change, #341 report flow, #342 background jobs, #343 outage) can build on top of it without rebuilding the bootstrap.

What's new:

  • setupIntegrationServer.ts — boots the real IoC container, starts the express app, and runs ItemProcessingWorker inline; exposes { deps, request, shutdown }.
  • wait.ts — polling helpers (waitForItemInScylla, waitForItemInClickHouse, generic waitFor).
  • items-submission.integ.test.ts — the first scenario: POST /api/v1/items/async → row in Scylla item_submission_by_thread and ClickHouse analytics.CONTENT_API_REQUESTS.
  • README.md — how to run, layout, conventions.

Reuses existing server/test/fixtureHelpers/ (createOrg, createContentItemTypes).

Findings worth flagging (also posted on #339):

  • test:integ doesn't preload dotenv like server:start does — the harness imports dotenv/config so the IoC container sees env vars.
  • ClickHouse queries use ? placeholders + positional binds (formatClickhouseQuery convention), not the native {name:Type} substitution.
  • BullMQ's Worker.close() closes the shared ioredis connection; closeSharedResourcesForShutdown then errors trying to quit() it again. Harness swallows that one specific error — a cleaner fix lives upstream (idempotent quit or sharedConnection: true for BullMQ workers).

Not in this PR (filed as follow-ups):

  • CI workflow that boots docker-compose and runs npm run test:integ. Local cycle works today via npm run up && npm run db:update && npm run test:integ.
  • Adoption for Rule change integration test #340-Health integration test #344. Whichever goes second is the right time to extract any shared scenario glue beyond what's already in fixtureHelpers/ and wait.ts.

Tests

The test itself is the test. Locally:

npm run up           # docker-compose: postgres, clickhouse, scylla, redis, hma, otel
npm run db:update -- --env staging
(cd server && npm run test:integ)

Output:

PASS test/integ/items-submission.integ.test.ts
  Items submission (integration)
    ✓ submitted item lands in Scylla and ClickHouse
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Time:        ~8.6s

Reviewer checks:

  • npm run up && npm run db:update -- --env staging succeeds (Node ≥22 required — db-migrator uses fs/promises's glob export)
  • (cd server && npm run test:integ) reports 1 passing test
  • (cd server && npm run lint) — no new errors; 3 lint warnings in the integ test file were cleaned up before push
  • (cd server && npx tsc --noEmit) — exit 0

(Optional) Rollout Plan

None — test-only change. No production code touched, no schema changes, no deps added. Test does not run in CI yet (follow-up issue to wire it in).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation

    • Added a README describing how to run the integration (end-to-end) test suite, required infrastructure, setup commands, test layout, conventions, and teardown notes.
  • Tests

    • Added an end-to-end test for async item submission that verifies results in storage and analytics and includes safe teardown guards.
    • Added a reusable integration test harness and polling utilities to wait for asynchronous processing and assert outcomes.

Review Change Stack

julietshen and others added 2 commits May 15, 2026 18:02
Adds a real-infra integration test harness under server/test/integ/ that
boots the IoC container, starts the express app, and runs the
ItemProcessingWorker inline so submissions flow end-to-end without mocks.

- setupIntegrationServer.ts — harness
- wait.ts — poll helpers for Scylla and ClickHouse
- items-submission.integ.test.ts — first scenario (#339)
- README.md — how to run, layout, conventions

Server typecheck passes. Not yet validated against a running stack.
CI workflow to be wired in a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three corrections from running against the real docker-compose stack:

- setupIntegrationServer: import dotenv/config so the IoC container sees
  env vars. test:integ does not preload dotenv like server:start does.
- setupIntegrationServer: swallow the benign "Connection is closed"
  error during shutdown. BullMQ's Worker.close() closes the shared
  ioredis connection, then closeSharedResourcesForShutdown errors when
  it tries to quit() it again.
- wait: switch the ClickHouse query from {name:Type} substitutions to ?
  placeholders + positional binds, matching coop's formatClickhouseQuery
  convention.

Also drops three unnecessary optional chains in afterAll that ESLint
flagged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bf88550c-b846-4fb8-a9b1-55274e8c9421

📥 Commits

Reviewing files that changed from the base of the PR and between fe311d5 and 7aebf8d.

📒 Files selected for processing (1)
  • server/test/integ/wait.ts

📝 Walkthrough

Walkthrough

Adds integration-test infrastructure: a real-server test harness with background item-processing worker and shutdown, polling helpers for eventual-consistency checks against Scylla and ClickHouse, an integration-test README, and an end-to-end item-submission integration test.

Changes

Integration Test Infrastructure

Layer / File(s) Summary
Test Server Harness Bootstrap
server/test/integ/setupIntegrationServer.ts
makeIntegrationServer() initializes the real IoC container, starts the server, launches ItemProcessingWorker asynchronously with abort support, provides a supertest request agent, and exposes async shutdown() that cleans up worker, server, and shared resources while handling benign Redis closure errors.
Async Polling Helpers
server/test/integ/wait.ts
Generic waitFor() primitive polls an async check callback with configurable timeout and interval. Domain-specific wrappers waitForItemInScylla and waitForItemInClickHouse poll the respective datastores for the submitted item.
Integration Test Documentation and Example
server/test/integ/README.md, server/test/integ/items-submission.integ.test.ts
README documents test suite purpose, infrastructure setup, test layout, fixture/lifecycle conventions, and polling patterns. Example test creates an org and item type, submits an async item via the real API endpoint, asserts 202, then waits for and validates rows in both Scylla and ClickHouse and tears down created fixtures.

Sequence Diagrams

sequenceDiagram
  participant Test
  participant makeIntegrationServer
  participant IoC as IoC Container
  participant Server
  participant ItemProcessingWorker
  participant Cleanup as closeSharedResources

  Test->>makeIntegrationServer: call()
  makeIntegrationServer->>IoC: initialize dependencies
  makeIntegrationServer->>Server: create and bind
  makeIntegrationServer->>ItemProcessingWorker: start background task
  makeIntegrationServer-->>Test: IntegrationServer{deps, request, shutdown}
  Test->>Server: make requests via supertest agent
  Server-->>Test: responses
  Test->>IntegrationServer: shutdown()
  IntegrationServer->>ItemProcessingWorker: signal abort
  IntegrationServer->>ItemProcessingWorker: await shutdown
  IntegrationServer->>Server: await shutdown
  IntegrationServer->>Cleanup: call to release resources
  Cleanup-->>IntegrationServer: cleanup complete
Loading
sequenceDiagram
  participant Test
  participant waitFor
  participant Check as check_callback
  participant Timeout

  Test->>waitFor: call with check and timeout
  loop Until result or deadline
    waitFor->>Check: invoke
    Check-->>waitFor: null/undefined or value
    alt Value returned
      waitFor-->>Test: return value
    else Null/undefined
      waitFor->>waitFor: sleep intervalMs
    end
  end
  alt Deadline exceeded
    waitFor->>Timeout: throw timeout Error labeled with what
    Timeout-->>Test: error
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

adoption

Suggested reviewers

  • dom-notion
  • vinaysrao1
  • cassidyjames

Poem

🐰 I boot the IoC, spin the test bed bright,
Polling patiently through async night,
Scylla and ClickHouse, both in view,
A tiny item lands — hooray! — true blue,
The harness hums, the teardown sleeps light.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding an end-to-end item submission integration test along with the supporting harness infrastructure.
Description check ✅ Passed The description follows the template with comprehensive Context & Requests for Reviewers, detailed Tests section with setup commands and expected output, and a Rollout Plan. All required sections are present and well-populated.
Linked Issues check ✅ Passed The code changes fully address #339 by implementing an end-to-end item submission test that verifies items land in both Scylla and ClickHouse, plus a reusable integration harness for future #288 sub-issues.
Out of Scope Changes check ✅ Passed All changes are directly related to #339 and #288: test infrastructure, integration test harness, polling utilities, documentation, and the first integration test scenario. No unrelated production code modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch julietshen/testing

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/test/integ/items-submission.integ.test.ts`:
- Around line 24-27: The teardown callbacks orgCleanup and itemTypeCleanup (and
any similar vars like harness/afterAll usage) can be undefined if beforeAll
fails; update the cleanup logic so afterAll only calls them when they exist
(e.g., check if typeof orgCleanup === "function" or if orgCleanup is truthy
before awaiting), or initialize them to a no-op async function at declaration;
ensure the afterAll block guards each call to orgCleanup() and itemTypeCleanup()
to avoid masking beforeAll failures.

In `@server/test/integ/setupIntegrationServer.ts`:
- Around line 43-57: The shutdown() method should be made best-effort so one
thrown error doesn't stop later teardown steps; wrap each async teardown
(workerAbort.abort(), deps.ItemProcessingWorker.shutdown(), shutdownServer(),
deps.closeSharedResourcesForShutdown()) in its own try/catch (or use
Promise.allSettled) to ensure all teardown calls run, log or collect errors
instead of rethrowing immediately, and still handle the known benign redis error
from deps.closeSharedResourcesForShutdown() as currently done; update the
shutdown() implementation to perform each step regardless of earlier failures
and surface a combined/logged result.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bf5b4443-9a6a-427d-9301-92863cda8ed5

📥 Commits

Reviewing files that changed from the base of the PR and between 61985f9 and bba41e8.

📒 Files selected for processing (4)
  • server/test/integ/README.md
  • server/test/integ/items-submission.integ.test.ts
  • server/test/integ/setupIntegrationServer.ts
  • server/test/integ/wait.ts

Comment thread server/test/integ/items-submission.integ.test.ts Outdated
Comment thread server/test/integ/setupIntegrationServer.ts Outdated
@cassidyjames cassidyjames changed the title Add end-to-end item submission integration test and harness (#339) server: Add end-to-end item submission integration test and harness May 18, 2026
Comment thread server/test/integ/wait.ts
Comment on lines +34 to +51
export async function waitForItemInScylla(
deps: Pick<Dependencies, 'ItemInvestigationService'>,
opts: {
orgId: string;
itemIdentifier: ItemIdentifier;
timeoutMs?: number;
},
) {
return waitFor(
`item ${opts.itemIdentifier.id} in Scylla`,
async () =>
deps.ItemInvestigationService.getItemByIdentifier({
orgId: opts.orgId,
itemIdentifier: opts.itemIdentifier,
}),
{ timeoutMs: opts.timeoutMs },
);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly due to how we had to get things working we have a fallback and ignore scylla errors. Given scylla data is ephemeral and only up to 6 months.

If we want a true e2e integration test we need to query scylla directly to avoid fallback showing a false positive.

export async function waitForItemInScylla(
  deps: Pick<Dependencies, 'Scylla'>,
  opts: { orgId: string; itemIdentifier: ItemIdentifier; timeoutMs?: number },
) {
  return waitFor(
    `item ${opts.itemIdentifier.id} in Scylla item_submission_by_thread`,
    async () => {
      const rows = await deps.Scylla
        .select({
          from: 'item_submission_by_thread',
          select: '*',
          where: [['item_identifier', '=', itemIdentifierToScyllaItemIdentifier(opts.itemIdentifier)]],
        })
        .catch(() => []);
      return rows.length > 0 ? rows[0] : null;
    },
    { timeoutMs: opts.timeoutMs },
  );
}

Something like this would be best.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gotcha, going to add that into waitForItemInScylla

Comment thread server/test/integ/items-submission.integ.test.ts Outdated
- waitForItemInScylla now queries Scylla directly via deps.Scylla.select
  rather than ItemInvestigationService.getItemByIdentifier. The service
  falls back to the partial-items endpoint and the data warehouse when
  Scylla returns nothing, which would let a real Scylla write failure
  pass the test silently. (juanmrad)
- Cleanup callbacks in items-submission.integ.test.ts are now optional
  and guarded with optional chaining + try/finally so a beforeAll
  failure surfaces the root cause instead of an "X is not a function"
  in afterAll. (juanmrad, coderabbit)
- makeIntegrationServer().shutdown() is now best-effort: each teardown
  step runs through a runStep helper, errors are collected, and the
  combined failures are surfaced via AggregateError so one bad step
  doesn't leak the server or shared resources. (coderabbit)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@julietshen julietshen requested a review from juanmrad May 19, 2026 22:16
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/test/integ/wait.ts`:
- Around line 41-67: The query in waitForItemInScylla omits the orgId, causing
wrong partitions to be queried; update deps.Scylla.select (inside
waitForItemInScylla) to include an additional WHERE predicate for 'org_id' =
opts.orgId alongside the existing 'item_identifier' predicate (use the same
itemIdentifierToScyllaItemIdentifier(opts.itemIdentifier) value), so the select
filters by both org_id and item_identifier and returns rows only from the
correct partition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 77c929bf-5ef5-4f13-abbb-da4d25c9f227

📥 Commits

Reviewing files that changed from the base of the PR and between bba41e8 and fe311d5.

📒 Files selected for processing (3)
  • server/test/integ/items-submission.integ.test.ts
  • server/test/integ/setupIntegrationServer.ts
  • server/test/integ/wait.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/test/integ/items-submission.integ.test.ts
  • server/test/integ/setupIntegrationServer.ts

Comment thread server/test/integ/wait.ts
waitForItemInScylla had a .catch(() => []) that turned any Scylla
ResponseError into a 30s waitFor timeout — masking structural problems
(bad query, schema drift) as flaky tests. cassandra-driver returns
empty rows for "no match", not an exception, so the catch was never
buying us "not yet" semantics anyway; it was only ever hiding bugs.

Document why we don't add org_id to the WHERE clause: partition key is
(org_id, synthetic_thread_id), so a partial-PK restriction would need
ALLOW FILTERING. The secondary index on item_identifier is what the
production lookup path uses, and the test already asserts org_id on
the returned row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

End-to-end item submission integration test

2 participants