From 7c56c740777e3f776a2924783db69c199e5e038e Mon Sep 17 00:00:00 2001 From: Maurice Date: Sat, 10 Oct 2026 23:37:19 +0200 Subject: [PATCH] test(server): cover the env mapping of every registered setting and the packing write paths the ratchet counts --- ...admin-packing-templates.controller.test.ts | 7 +++++ .../unit/nest/app-settings.registry.test.ts | 30 +++++++++++++++++++ .../unit/nest/packing-writes.service.test.ts | 22 ++++++++++++++ .../tests/unit/nest/packing.service.test.ts | 10 +++++++ 4 files changed, 69 insertions(+) diff --git a/server/tests/unit/nest/admin-packing-templates.controller.test.ts b/server/tests/unit/nest/admin-packing-templates.controller.test.ts index 767bffcb5..9199f9dad 100644 --- a/server/tests/unit/nest/admin-packing-templates.controller.test.ts +++ b/server/tests/unit/nest/admin-packing-templates.controller.test.ts @@ -85,6 +85,13 @@ describe('AdminPackingTemplatesController', () => { ); }); + it('PACKTPL-004b a rename hands the body to the service and answers its { template } as it stands, unaudited', async () => { + const { c, packing } = controller(); + expect(await c.update('1', { name: 'Mountains' })).toEqual({ template: { id: 1 } }); + expect(packing.updatePackingTemplate).toHaveBeenCalledWith('1', { name: 'Mountains' }); + expect(writeAudit).not.toHaveBeenCalled(); + }); + it('PACKTPL-005 delete audits the removed name and answers { success: true }', async () => { const { c } = controller(); expect(await c.remove(user, '1', req)).toEqual({ success: true }); diff --git a/server/tests/unit/nest/app-settings.registry.test.ts b/server/tests/unit/nest/app-settings.registry.test.ts index 8b82f1953..cd8918ed3 100644 --- a/server/tests/unit/nest/app-settings.registry.test.ts +++ b/server/tests/unit/nest/app-settings.registry.test.ts @@ -115,6 +115,36 @@ describe('reading', () => { // A key without an environment variable reads the row. expect(await resolveAppSetting(repoOf({ require_mfa: 'true' }), 'require_mfa')).toBe('true'); }); + + it('APPSET-012: every env-backed key reads its own variable, the secrets included', async () => { + // The two secrets are read past the register by their callers (they are stored + // encrypted), so this is the one place their mapping is pinned. + const variables: Record = { + smtp_host: ['SMTP_HOST', 'smtp.env.example'], + smtp_port: ['SMTP_PORT', '2525'], + smtp_user: ['SMTP_USER', 'mailer'], + smtp_pass: ['SMTP_PASS', 'env-secret'], + smtp_from: ['SMTP_FROM', 'trek@env.example'], + oidc_issuer: ['OIDC_ISSUER', 'https://idp.env.example'], + oidc_client_id: ['OIDC_CLIENT_ID', 'trek-env'], + oidc_client_secret: ['OIDC_CLIENT_SECRET', 'oidc-env-secret'], + oidc_display_name: ['OIDC_DISPLAY_NAME', 'Company SSO'], + oidc_discovery_url: ['OIDC_DISCOVERY_URL', 'https://idp.env.example/.well-known/openid-configuration'], + }; + const withEnv = (Object.entries(APP_SETTINGS) as Array<[string, AppSettingDef]>) + .filter(([, def]) => def.env) + .map(([key]) => key); + expect(withEnv.sort()).toEqual(Object.keys(variables).sort()); + + const keys = Object.keys(variables) as Array; + const stored = Object.fromEntries(keys.map((key) => [key, `stored-${key}`])); + const resolveAll = () => Promise.all(keys.map((key) => resolveAppSetting(repoOf(stored), key))); + + for (const [name] of Object.values(variables)) vi.stubEnv(name, ''); + expect(await resolveAll()).toEqual(keys.map((key) => `stored-${key}`)); + for (const [name, value] of Object.values(variables)) vi.stubEnv(name, value); + expect(await resolveAll()).toEqual(keys.map((key) => variables[key][1])); + }); }); describe('isOidcConfigured', () => { diff --git a/server/tests/unit/nest/packing-writes.service.test.ts b/server/tests/unit/nest/packing-writes.service.test.ts index 0e9e3411e..a4ee9b26b 100644 --- a/server/tests/unit/nest/packing-writes.service.test.ts +++ b/server/tests/unit/nest/packing-writes.service.test.ts @@ -130,6 +130,28 @@ describe('packing item events: REST == MCP', () => { }); }); +describe("REST writer: PackingService's broadcasts", () => { + it('PACK-REST-001: carries the user and the socket, and emit skips the sender for the room or names each viewer', () => { + const s = setup(); + const writer = s.writes.restWriter('5', user, 'sock'); + expect(writer).toMatchObject({ userId: 1, role: 'user', socketId: 'sock', surface: 'rest' }); + + writer.events.emit('packing:deleted', { itemId: 3 }); + writer.events.emit('packing:deleted', { itemId: 4 }, [1, 2]); + expect(s.realtime.broadcast.mock.calls).toEqual([ + ['5', 'packing:deleted', { itemId: 3 }, 'sock'], + ['5', 'packing:deleted', { itemId: 4 }, 'sock', 1], + ['5', 'packing:deleted', { itemId: 4 }, 'sock', 2], + ]); + }); + + it('PACK-REST-002: emitAll reaches every socket in the room, the sender included', () => { + const s = setup(); + s.writes.restWriter('5', user, 'sock').events.emitAll('packing:bag-totals', {}); + expect(s.realtime.broadcast.mock.calls).toEqual([['5', 'packing:bag-totals', {}, undefined]]); + }); +}); + describe('packing item use cases: refusals', () => { const mcpText = (err: unknown) => (err as DomainError).mcpMessage ?? (err as DomainError).publicMessage; diff --git a/server/tests/unit/nest/packing.service.test.ts b/server/tests/unit/nest/packing.service.test.ts index b2d24c4cf..0b07a1903 100644 --- a/server/tests/unit/nest/packing.service.test.ts +++ b/server/tests/unit/nest/packing.service.test.ts @@ -1110,6 +1110,16 @@ describe('notifyTagged', () => { expect.objectContaining({ params: expect.objectContaining({ trip: 'Untitled' }) }), ); }); + + it('swallows a failed send: tagging stays fire-and-forget and never rejects the write', async () => { + send.mockRejectedValueOnce(new Error('smtp down')); + await expect( + svc.notifyTagged('999999', { id: 1, email: 'a@b.c' } as never, 'Clothes', [2]), + ).resolves.toBeUndefined(); + // Let the rejected send settle; an unhandled rejection would fail the run here. + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(send).toHaveBeenCalledTimes(1); + }); }); // ── Bridge delegation ─────────────────────────────────────────────────────────