Перейти к содержанию

Notifications Service — Testing

Running Tests

npm test

Uses Node.js built-in test runner (node:test). No external test framework install required. PostgreSQL and Redis are not needed — all external dependencies are mocked.

The test command runs three test files:

node --test src/handler.test.js src/consumer.test.js src/fcm.test.js

Test Files

src/handler.test.js

Tests processJob(), parsePayload(), and normalizePayload() from src/handler.js.

Test What it checks
parsePayload: flat XADD fields Flat key/value array parses correctly, userId is a number
parsePayload: payload JSON Single payload field containing JSON string is parsed
normalizePayload: default channel=system Missing channel defaults to 'system'
normalizePayload: missing userId throws Throws with /userId/ in error message
processJob: no tokens → status=no_tokens, ack=true When public.device_token returns no rows: log updated with no_tokens, returns { ack: true }, FCM not called
processJob: invalid-registration-token → DELETE token Token with action='remove' triggers DELETE FROM public.device_token
processJob: all tokens OK → status=sent All tokens succeed → UPDATE sets status='sent' as first parameter

Mocking approach: pool and fcm dependencies are injected as the second argument to processJob(fields, { pool, fcm }). Tests provide inline fake implementations with query routing by SQL prefix.

src/consumer.test.js

Tests parseFields() from src/consumer.js.

Test What it checks
parseFields: alternating array → object ['userId', '1', 'title', 'T']{ userId: '1', title: 'T' }
parseFields: empty array → empty object Edge case

src/fcm.test.js

Tests classifyError() and stringifyAll() from src/fcm.js. No Firebase SDK calls are made.

Test What it checks
classifyError: invalid-registration-token → remove Via errorInfo.code
classifyError: registration-token-not-registered → remove Via err.code
classifyError: server-unavailable → retry Transient errors return retry
classifyError: third-party-auth-error → fatal APNs auth failure returns fatal
classifyError: unknown → retry Safe default for unknown errors
stringifyAll: all values to string, null/undefined dropped { a: 1, c: null }{ a: '1' }

Test Design Principles

  • No real I/O: Redis, PostgreSQL, and FCM are all mocked inline — npm test works without any running services
  • Dependency injection: processJob(fields, { pool, fcm }) accepts mock deps as second argument
  • SQL routing: fake pool implementations route queries by SQL string prefix (e.g., INSERT INTO, SELECT id, "token", UPDATE notifications.notification_log)
  • FCM isolation: src/fcm.js exports pure functions (classifyError, stringifyAll) that do not require Firebase to be initialized

Key Scenarios Verified

  • Message with no registered device tokens: logged, ACK'd, no FCM call
  • Stale token (invalid-registration-token): deleted from public.device_token, message ACK'd
  • All tokens succeed: log status set to sent
  • XAUTOCLAIM retry logic: tested via parseFields (stream field parsing is the XAUTOCLAIM code path)
  • Error classification: all documented FCM error codes map to correct actions

SIT / Staging

When testing against a real Redis + Firebase stack:

  1. Publish a test message to the stream:
redis-cli XADD stream.notifications.jobs '*' \
  userId 1 \
  channel system \
  title "Test notification" \
  body "Integration test"
  1. Watch service logs for notifications.processed event
  2. Check notifications.notification_log for a row with the expected user_id and status

Device tokens in public.device_token must exist for userId=1 for FCM sends to occur.