Skip to content

Testing plan: the gateway learns a work item's collaboration channel ​

Four kinds of check prove this change: unit tests on the store, the Slack resolution, the consume route, the client and the store hook; one Gherkin-documented hosted integration test that replays the ticket's reproduction; abuse-case tests for each row of design.md § Security design; and the OpenAPI contract parity test. Everything runs in this repository with pytest, against an in-process gateway (tests/gateway_support.py). Nothing needs Slack.

Test matrix ​

#TypeApplies?Scope / what it provesWhere it runs
T1Unityespublish_rooms / resolve_room / rooms_of, the backlog drain, pruning; _resolve_slack order; CollaborationChannelStore.on_change; GatewayClient.publish_rooms, register, the reconcile throttlecd cli && uv run python -m pytest -q tests/test_gateway_rooms.py
T2Integration (scenario)yesthe ticket's reproduction through the HTTP consume API and the Slack ingress: a collaborator's top-level message routes by: claim; a held backlog drains; retraction; re-registration after a store loss. Gherkin-documentedcd cli && uv run python -m pytest -q tests/test_gateway_rooms_integration.py
T3Contract (OpenAPI)yesPUT /api/v1/gateway/rooms is in the-loop-gateway.v1.yaml and the served surface matchescd cli && uv run python -m pytest -q tests/test_api_contract_parity.py
T4End-to-endn/a — a live run needs a gateway, an instance and a real Slack app. T2 drives the same HTTP routes and ingress the gateway serves, with a fake Slack payload
T5UI / visualn/a — no visual surface
T6Snapshotn/a — JSON answers are asserted field by field
T7Performance / loadn/a — one indexed lookup per unbound Slack event, and a directory scan every 30 s that the poll transport already does
T8Security / abuse caseyesabuse cases 1–5: allow-list, live holder kept, other instance's room untouched, malformed or oversized body → 422, a stranger's routed message still checked by the instance; an unreadable portable dir does not retractcd cli && uv run python -m pytest -q tests/test_gateway_rooms.py tests/test_gateway_rooms_integration.py -k abuse
T9Accessibilityn/a — no UI
T10Migration / upgradeyesa store created by 20.4.0 (no rooms table) opens and gains it; existing events and bindings survivecd cli && uv run python -m pytest -q tests/test_gateway_rooms.py -k migration
T11Regression (whole suite)yesbehaviour preserved without rooms; the full suite, lint, format, types and markdownuv run pre-commit run --all-files

Scenarios & requirement trace ​

RowRequirement(s)Scenario / case
T1R1.1, R1.5the client publishes targets(); a contested channel is not published
T1R1.2add / remove / clear call on_change; an unchanged add does not; a raising hook does not fail add
T1R1.3step() reconciles only after ROOMS_RECONCILE_S; an unchanged set makes no call
T1R1.4register() publishes the full set even when unchanged
T1R1.6a transport error or a 500 logs a warning and returns False; the next reconcile retries
T1R2.1, R2.3, R2.4resolution order: thread binding → published room → static room → held
T1R2.4, R3.1critic C1: a drain leaves an event a thread binding moved; critic C2: a room published between resolve and append still routes the event
T1R5.2critic C4: a refused room is retried and taken once the holder lets go
T1R3.1–R3.3drain re-subjects and routes held <channel>:* events in order; held on the subject without claimants, routed by a later claim; routed events untouched; another channel's held events untouched
T1R4.1, R4.2, R4.4a set without a room deletes it; a moved room swaps
T1R4.5pruning a lapsed instance drops its rooms
T1R5.1–R5.3a live holder's room refused, the rest applied; an expired holder taken over; another instance's room untouched
T1R7.1rooms_of / GET /instances lists rooms
T2R1.1, R2.2, R6.1Scenario: a collaborator's top-level message in a declared room reaches the work item
T2R3.1Scenario: messages held before the room was published are routed when it is
T2R4.1, R4.3Scenario: clearing the declaration retracts the room
T2R1.4Scenario: a gateway that lost its store gets the rooms back on re-registration
T2R7.2gateway.rooms and gateway.refused records asserted in the scenarios above
T3NFR contractparity of path, method and operationId
T8R5.4, abuse 1an instance outside consume.allowedInstances → refused; a second peer under a live name → 409
T8abuse 2, 3covered by the R5.1–R5.3 cases, run under -k abuse
T8abuse 5a key that is not a Slack id, a ref that does not parse, more than ROOMS_MAX rooms → 422 and nothing stored
T8fail closedtargets() over an unreadable directory → no publish
T10NFR store compatibilityopen a store made with the 20.4.0 schema, then publish a room

Verification environment ​

  • Repositories: this repository only.
  • Services / containers: none. The gateway runs in-process through FastAPI's TestClient (GatewayHarness); the instance side uses GatewayClient with an in-process transport, as test_gateway_client_integration.py does.
  • Fixtures & data: tmp_path stores and portable directories; Slack event payloads built in the test.
  • Credentials: none.
  • Bring-up: uv sync --locked · Tear-down: none.
  • If bring-up fails: record it below, leave the dependent activities unticked, and escalate.

Evidence plan ​

RowEvidencePath under evidence/
T1, T8, T10pytest summary and red → green noteunit.md
T2scenario list (the-loop scenarios) and run outputintegration.md
T3parity test output and the operation listcontract.md
T11pre-commit summarylint-and-typecheck.md

Verification activities ​

  • [x] T1 — cd cli && uv run python -m pytest -q tests/test_gateway_rooms.py
  • [x] T2 — cd cli && uv run python -m pytest -q tests/test_gateway_rooms_integration.py
  • [x] T3 — cd cli && uv run python -m pytest -q tests/test_api_contract_parity.py
  • [x] T8 — cd cli && uv run python -m pytest -q tests/test_gateway_rooms.py tests/test_gateway_rooms_integration.py -k abuse
  • [x] T10 — cd cli && uv run python -m pytest -q tests/test_gateway_rooms.py -k migration
  • [x] T11 — uv run pre-commit run --all-files

Verification results ​

All six activities ran and passed. The two new test files are red on origin/main (35 failed, 3 passed, 5 errors) and green here. Every command ran with THE_LOOP_CLI_CONFIG unset (see unit.md).

ActivityCommand / procedureOutcomeEvidence
T1cd cli && uv run python -m pytest -q tests/test_gateway_rooms.py43 passed, including one regression test per critic finding (C1–C4)unit.md
T2cd cli && uv run python -m pytest -q tests/test_gateway_rooms_integration.py; the-loop scenarios --glob …4 passed; 4 scenarios listed with requirement linksintegration.md
T3cd cli && uv run python -m pytest -q tests/test_api_contract_parity.py6 passed; PUT /api/v1/gateway/rooms in the contractcontract.md
T8… tests/test_gateway_rooms.py tests/test_gateway_rooms_integration.py -k abuse12 passed (abuse cases 1, 2, 3, 5, the peer rule, fail closed on an unreadable dir and on a corrupt record); abuse case 4 asserted in T2 scenario 1unit.md
T10… tests/test_gateway_rooms.py -k migration1 passed: a 20.4.0 store reopens and gains roomsunit.md
T11uv run pre-commit run --all-filesall six hooks passed, after fixing five pyright errors from the first runlint-and-typecheck.md

Review comments ​

Released under the MIT License.