feat(FN-3926): add chat room integration tests across core and dashboard
This merge adds comprehensive test coverage for chat room functionality across the core store, ChatView component, HTTP/SSE routes, and integration layer (845 lines added across four test files), guided by a test plan for FN-3812. Fusion-Task-Id: FN-3926
This commit is contained in:
@@ -1,67 +1,72 @@
|
||||
# FN-3812 Room Test Plan
|
||||
# FN-3812 Room Test Plan (Rebuilt for FN-3926)
|
||||
|
||||
This plan defines contract-neutral coverage for room creation, room switching, persisted history, mention routing, and hybrid room response behavior. Each bullet below maps 1:1 to a single `it.todo(...)` title in the matching scaffold file.
|
||||
## Preflight ownership decisions
|
||||
|
||||
## Layer 1 — Core chat-store (persistence)
|
||||
- The four dedicated room suites already existed as `it.todo` scaffolds and are being populated with real assertions.
|
||||
- Existing room coverage in broad suites is **kept** (Leave + Annotate) unless duplication becomes confusing or brittle.
|
||||
- `chat-routes.rooms.test.ts` will focus on missing room HTTP assertions and room SSE payload/cleanup assertions; legacy `chat-room-routes.test.ts` and `sse-chat-rooms.test.ts` remain valid broad/smoke coverage.
|
||||
- FN-3812 prompt file was not present in this worktree, so this rebuilt plan is derived from shipped source contracts.
|
||||
|
||||
### Room lifecycle and membership
|
||||
- room creation persists a new room record with creator context and retrievable metadata — creating a room must make it available to later room reads/lists.
|
||||
- member add/remove updates room membership deterministically — adding a member makes them present in membership reads and removing them makes them absent.
|
||||
## Layer 1 — Core store (`chat-store.rooms.test.ts`)
|
||||
|
||||
### Room message persistence and retrieval
|
||||
- room-scoped append + list preserves message order and payload fields — appending messages to a room and listing them must return them in stable chronological order with stored content.
|
||||
- cross-room isolation keeps each room history independent — reading room A history must never include messages appended to room B.
|
||||
- room-vs-direct isolation keeps room history separate from direct sessions — room message reads must not surface direct-chat messages, and direct message reads must not surface room messages.
|
||||
- `createRoom` normalizes `#Engineering Team` to `name: "Engineering Team"`, `slug: "engineering-team"`.
|
||||
- `createdBy` member receives `role: "owner"`; other listed members receive `"member"`.
|
||||
- Same-project slug collisions throw; same slug in different project is allowed.
|
||||
- `getRoom`, `getRoomBySlug`, `listRooms`, `updateRoom`, `deleteRoom` lifecycle assertions.
|
||||
- `addRoomMember` idempotency and `removeRoomMember` true/false behavior.
|
||||
- `listRoomsForAgent` respects membership/project/status filters.
|
||||
- `deleteRoom` cascades room members and room messages.
|
||||
- `addRoomMessage` + `getRoomMessages({ before })` preserve timeline and cursor behavior.
|
||||
- `mentions` metadata round-trips via `addRoomMessage` / `getRoomMessage`.
|
||||
- `addRoomMessageAttachment` appends attachment metadata and emits updated message.
|
||||
- Room event emission: created/updated/deleted/member added/member removed/message added/message updated/message deleted.
|
||||
|
||||
### Persistence round-trip and metadata fidelity
|
||||
- close/reopen round-trip preserves room, membership, and room history state — reopening the store/database must return the same room data and history without loss.
|
||||
- mention metadata round-trip persists and rehydrates mention routing context — stored mention markers on room messages must be returned unchanged on read.
|
||||
- responder metadata round-trip persists and rehydrates responder attribution — stored responder identity/role markers on assistant room messages must be returned unchanged on read.
|
||||
## Layer 2 — Orchestration (`chat.rooms.test.ts`)
|
||||
|
||||
## Layer 2 — Chat orchestration (routing + dispatch)
|
||||
- `resolveRoomResponders` returns direct/ambient/nonMemberMentions partitions.
|
||||
- Mentioned room members are direct responders; remaining room members become ambient responders.
|
||||
- Non-member mentions are excluded from responders and returned in `nonMemberMentions`.
|
||||
- Duplicate mentions dedupe to one direct responder.
|
||||
- `sendRoomMessage` persists user room message + assistant replies for resolved responders.
|
||||
- `sendRoomMessage` emits non-member explanatory assistant note when non-member mentions are present.
|
||||
|
||||
### Mention routing in rooms
|
||||
- direct mention in room routes targeted response from addressed room member — mentioning a room member should produce a targeted responder output from that member.
|
||||
- non-member mention behavior does not dispatch to out-of-room agents and surfaces explicit feedback — mentioning an agent outside the room should not trigger that agent and should produce a user-visible non-member notice.
|
||||
## Layer 3 — HTTP + SSE (`chat-routes.rooms.test.ts`)
|
||||
|
||||
### Hybrid dispatch behavior
|
||||
- hybrid ambient response includes non-mentioned room members when room dispatch mode allows ambient participation — room messages with mentions can still trigger additional ambient responders.
|
||||
- mention suppresses ambient on the addressed agent to avoid duplicate responses — the directly mentioned agent should respond once, not once direct plus once ambient.
|
||||
- Room CRUD + member route contract assertions:
|
||||
- create/list/get/update/delete success paths
|
||||
- missing name => 400
|
||||
- same-project slug collision => 409
|
||||
- unknown room => 404
|
||||
- member add/remove including remove-not-found 404
|
||||
- Message route contract assertions:
|
||||
- POST trims content and rejects `senderAgentId` unless null/omitted
|
||||
- POST path uses injected `chatManager.sendRoomMessage` assistant-reply workflow
|
||||
- pagination and `before` behavior
|
||||
- delete message is idempotent via 404 on repeat
|
||||
- attachment metadata route updates room message attachments
|
||||
- Room SSE assertions against real `ChatStore`:
|
||||
- `chat:room:created`, `chat:room:updated`, `chat:room:deleted`
|
||||
- `chat:room:member:added`, `chat:room:member:removed`
|
||||
- `chat:room:message:added`, `chat:room:message:updated`, `chat:room:message:deleted`
|
||||
- deleted room/message payloads are wrapper objects `{ id }`
|
||||
- cleanup leaves `EventEmitter.listenerCount(chatStore, event) === 0` after close
|
||||
|
||||
### Regression guard
|
||||
- direct-chat regression guard keeps legacy direct send path unchanged — non-room chat routing should continue to behave as before room support.
|
||||
## Layer 4 — UI (`ChatView.rooms.test.tsx`)
|
||||
|
||||
## Layer 3 — HTTP + SSE
|
||||
- Rooms scope toggle renders and switches Direct/Rooms (`chat-sidebar-scope-*`).
|
||||
- Room list selection switches active room without message leakage.
|
||||
- Create-room modal submit calls `createRoom` and updates active room context.
|
||||
- Mobile room mode back button returns from thread to sidebar.
|
||||
- Room send path uses `sendRoomMessage` (Enter and send button).
|
||||
- Delete-room confirmation confirm/cancel behavior.
|
||||
- Hook-driven room message rerender when room message state updates.
|
||||
- Direct-mode regression guard: direct scope still uses `sendMessage` path.
|
||||
- FN-3811 mention/member-order note: assert only behavior exposed by current UI contract; do not invent additional mention ordering behavior.
|
||||
|
||||
### Room API endpoints
|
||||
- room create + list endpoints return created room and include it in subsequent listings — API callers can create a room then retrieve it via list/read endpoints.
|
||||
- per-room history read returns only the selected room timeline — room history endpoint must scope results to the requested room.
|
||||
- send room message with mention records mention data and triggers routed responder behavior — room send endpoint must accept mention text and emit resulting room messages.
|
||||
## Broad suite cross-reference (kept coverage)
|
||||
|
||||
### Streaming scope and permissions
|
||||
- SSE room channel scoping delivers events only to matching room subscribers (`it.each` over A↔B subscribers) — a subscriber for room A receives A events and not B events, and vice versa.
|
||||
- v1 permissions allow same-project room operations without cross-user 403 checks — current project-scoped room routes should not reject same-project callers for cross-user constraints.
|
||||
|
||||
## Layer 4 — Dashboard ChatView (UI)
|
||||
|
||||
### Mode and navigation
|
||||
- Direct/Rooms toggle render exposes both scopes when room mode is enabled — users should see and switch between Direct and Rooms modes.
|
||||
- room switching loads selected room history without leakage (`it.each` A↔B matrix) — switching rooms should show only the selected room thread and no carryover from other rooms.
|
||||
|
||||
### Mention UX in room mode
|
||||
- mention popup in room mode prioritizes room members before non-members when filtering — member suggestions appear first for the same query.
|
||||
- non-member mention chip class marks out-of-room mentions in rendered messages — rendered mention chips for non-members should include the non-member styling/state marker.
|
||||
|
||||
### Persistence + regression
|
||||
- persisted history survives remount and reload in room mode — room thread content should still render after component remount/re-init.
|
||||
- direct-chat parity regression guard keeps direct mode behavior unchanged in the same view — existing direct-chat composer/render/send behavior remains intact.
|
||||
|
||||
## Handoff: converting `it.todo` to real assertions
|
||||
|
||||
1. Re-read the merged FN-3805..FN-3811 implementation across core store, orchestration, HTTP/SSE routes, and ChatView to confirm the final shipped contracts.
|
||||
2. Record actual discovered symbols (type names, method names, route paths, SSE event names, prop names, CSS class names) next to each planned assertion before editing test bodies.
|
||||
3. Replace each `it.todo("...")` entry with a concrete `it("...", async () => { ... })` assertion against the merged contract; keep behavior-focused titles but make them implementation-specific where necessary.
|
||||
4. Run targeted suites first (the four rooms scaffold files and adjacent existing room tests), then run `pnpm test` to validate whole-workspace integration.
|
||||
5. Update this plan with any contract surprises or changed assumptions found during conversion so future maintainers can trace why assertions differ from the original stub wording.
|
||||
|
||||
**Do not weaken coverage:** every `it.todo` in these scaffolds must become a real assertion (or a stricter split/consolidation with `it.each` for true matrices). Coverage shrinkage is not acceptable except legitimate matrix consolidation.
|
||||
- `packages/core/src/__tests__/chat-store.test.ts` retains broad room CRUD/message smoke.
|
||||
- `packages/dashboard/src/__tests__/chat-manager-room-hybrid.test.ts` remains helper-focused room responder suite.
|
||||
- `packages/dashboard/src/__tests__/chat-room-routes.test.ts` retains broad room route coverage.
|
||||
- `packages/dashboard/src/__tests__/sse-chat-rooms.test.ts` retains broad SSE room smoke.
|
||||
- `packages/dashboard/app/components/__tests__/ChatView.test.tsx` retains direct-chat + mixed integration coverage.
|
||||
|
||||
Reference in New Issue
Block a user