Skip to content

feat(backend): presence/typing tests and file message handling#256

Open
DeJune06 wants to merge 2 commits into
codebestia:mainfrom
DeJune06:fix/onyedika-clicked-222-228
Open

feat(backend): presence/typing tests and file message handling#256
DeJune06 wants to merge 2 commits into
codebestia:mainfrom
DeJune06:fix/onyedika-clicked-222-228

Conversation

@DeJune06

@DeJune06 DeJune06 commented Jun 27, 2026

Copy link
Copy Markdown

closes #222
closes #228
closes #226
closes #230

Summary

  • Presence + typing tests #222 — Presence + typing tests: Added src/__tests__/presence.typing.test.ts with 20 tests covering:

    • Multi-device aggregation: user stays online while any socket remains connected
    • Heartbeat timeout → offline: TTL expiry drives offline state, verified via simulated expiry
    • Typing auto-expiry with fake timers: indicators time out deterministically, zero DB writes asserted
    • Privacy suppression: non-members receive an error and no room broadcast
    • Debounced transitions: rapid connect/disconnect sequences leave user online correctly
  • File message construction (key inside the E2EE envelope) #228 — File message construction: Extended schema with a files table (pending → ready → deleted lifecycle) and messageContentTypeEnum; added contentType + fileId columns to messages. Implemented send_file_message socket handler that:

    • Validates sender is a conversation member
    • Validates file exists, is ready, belongs to the conversation, and was uploaded by the sender
    • Fans out via io.to(conversationId).emit('new_message', …) — identical path to text messages
    • Never stores or parses fileKey server-side; it remains inside the encrypted envelope ciphertext only
    • Added 11 tests in src/__tests__/file.messages.test.ts
  • POST /uploads — issue presigned upload + file record #226POST /uploads presigned upload slot: src/routes/uploads.ts validates { size, mimeType, sha256, conversationId } against size limit (100 MB) and MIME allowlist, verifies conversation membership, inserts a pending file row, returns { fileId, uploadUrl }. POST /uploads/:id/confirm transitions to ready after the client completes the PUT. Extended files table with size, mimeType, sha256, storageKey, isThumbnail columns. Added migration drizzle/0008_files_uploads.sql. 15 unit tests.

  • Client-encrypted thumbnails #230 — Client-encrypted thumbnails: Thumbnails upload via the same POST /uploads endpoint with isThumbnail: true; they are separate files rows. The fileId of the thumbnail rides inside the parent file message payload (E2EE envelope). Server never generates previews from ciphertext — structurally asserted in tests. Missing/optional thumbnail handled gracefully (isThumbnail defaults to false). 5 unit tests.

Test plan

  • npx vitest run — all tests pass
  • New tests: 20 presence/typing + 11 file message + 20 uploads/thumbnails = 51 tests added
  • Fake timers used throughout TTL/timeout assertions for determinism
  • Thumbnail server-side preview assertion is structural (route source checked for image-processing imports)

…ssage handling (codebestia#228)

Issue codebestia#222 — add src/__tests__/presence.typing.test.ts covering multi-device
aggregation, heartbeat timeout → offline, typing auto-expiry with zero DB
writes, privacy suppression for non-members, and debounced transitions.
Fake timers are used for all TTL/timeout assertions.

Issue codebestia#228 — extend schema with a `files` table (pending→ready→deleted
lifecycle) and `messageContentTypeEnum`; add `contentType` + `fileId` columns
to `messages`; implement `send_file_message` socket handler that validates the
referenced file is `ready` and belongs to the sender, then fans out via
`io.to(conversationId).emit('new_message', …)` identical to text messages.
`fileKey` is never stored or parsed server-side — it lives only inside the
encrypted envelope ciphertext. Tests added in file.messages.test.ts.
@drips-wave

drips-wave Bot commented Jun 27, 2026

Copy link
Copy Markdown

@DeJune06 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

…bnail support

- apps/backend/src/db/schema.ts — extended `files` table with `size`, `mimeType`,
  `sha256`, `storageKey`, and `isThumbnail` columns; all required by the upload
  slot and thumbnail flows
- apps/backend/src/lib/storage.ts — `generatePresignedPut` (returns a presigned
  PUT URL; swappable for S3/GCS SDK in production) and `generateStorageKey`
  (deterministic per conversation+content)
- apps/backend/src/routes/uploads.ts — `POST /uploads` validates size (≤100 MB)
  and MIME type against allowlist, verifies conversation membership, inserts a
  `pending` file row, and returns `{ fileId, uploadUrl }`; `POST /uploads/:id/confirm`
  transitions the row to `ready` after the client completes the PUT
- apps/backend/src/app.ts — registers `/uploads` router
- apps/backend/drizzle/0008_files_uploads.sql — migration for files table and
  messages content_type/file_id columns
- apps/backend/src/__tests__/uploads.test.ts — 20 unit tests covering: valid slot
  creation, size/MIME enforcement, membership check, pending insertion, confirm
  lifecycle (200/404/403/409), thumbnail isThumbnail flag, missing thumbnail
  graceful handling, and structural assertion that the route never imports any
  image-processing library (server never generates previews from ciphertext)

closes codebestia#226
closes codebestia#230
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant