// Copyright: (c) 2026 TWWIM UG. All rights reserved. (www.twwim.com) import { describe, it, expect } from 'vitest'; import { AuthOrigin } from '@archer/domain'; import { AuthenticatedUserMapper } from '../AuthenticatedUserMapper'; /** * Regression tests for the structural bug where /auth/refresh would silently * overwrite AuthenticatedUser.authOrigin with `undefined` because the response * body schema has no authOrigin field — the claim lives only on the JWT. * * The mapper now decodes validated.accessToken inside fromTokenResponse and * seeds authOrigin from the payload. Single canonical "auth response → user" * conversion, used by login + refresh + every embed-callback route. */ function base64UrlEncode(payload: object): string { // Tests run under jsdom; use Buffer when available, atob/btoa otherwise. const json = JSON.stringify(payload); const b64 = typeof Buffer !== 'undefined' ? Buffer.from(json, 'utf-8').toString('base64') : btoa(json); return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); } /** Build an unsigned JWT (the dashboard's decodeJwt only base64url-decodes). */ function synthJwt(payload: Record): string { const header = base64UrlEncode({ alg: 'none', typ: 'JWT' }); const body = base64UrlEncode(payload); return `${header}.${body}.signature`; } const VALID_USER = { id: '019d0000-0000-7000-8000-000000000001', email: 'user@example.com', fullName: 'Test User', twoFactorEnabled: false, emailVerified: true, avatarUrl: null, createdAt: '2026-05-06T00:00:00.000Z', updatedAt: '2026-05-06T00:00:00.000Z', currentCompanyId: '019d0000-0000-7000-8000-000000000002', currentCompanyName: 'Test Co', currentCompanyType: 'PERSONAL', currentCompanyRole: 'admin', isProfileComplete: true, missingProfileFields: [], }; describe('AuthenticatedUserMapper.fromTokenResponse', () => { it('seeds authOrigin = TWWIM from the JWT (regression: previously hardcoded undefined)', () => { const accessToken = synthJwt({ authId: VALID_USER.id, currentCompanyId: VALID_USER.currentCompanyId, authOrigin: AuthOrigin.TWWIM, }); const user = AuthenticatedUserMapper.fromTokenResponse({ accessToken, refreshToken: 'r', user: VALID_USER, }); expect(user.authOrigin).toBe(AuthOrigin.TWWIM); }); it('seeds authOrigin = SHOPIFY from the JWT — Shopify deep-link must keep its origin across refresh', () => { const accessToken = synthJwt({ authId: VALID_USER.id, currentCompanyId: VALID_USER.currentCompanyId, authOrigin: AuthOrigin.SHOPIFY, }); const user = AuthenticatedUserMapper.fromTokenResponse({ accessToken, refreshToken: 'r', user: VALID_USER, }); expect(user.authOrigin).toBe(AuthOrigin.SHOPIFY); }); it('returns user.authOrigin = undefined when the JWT carries no authOrigin claim (graceful fallback)', () => { const accessToken = synthJwt({ authId: VALID_USER.id, currentCompanyId: VALID_USER.currentCompanyId, // no authOrigin claim }); const user = AuthenticatedUserMapper.fromTokenResponse({ accessToken, refreshToken: 'r', user: VALID_USER, }); expect(user.authOrigin).toBeUndefined(); }); it('returns user.authOrigin = undefined when accessToken is missing (defensive)', () => { const user = AuthenticatedUserMapper.fromTokenResponse({ // no accessToken (e.g. unusual response shape) refreshToken: 'r', user: VALID_USER, }); expect(user.authOrigin).toBeUndefined(); }); it('returns user.authOrigin = undefined when accessToken is malformed', () => { const user = AuthenticatedUserMapper.fromTokenResponse({ accessToken: 'not-a-jwt', refreshToken: 'r', user: VALID_USER, }); expect(user.authOrigin).toBeUndefined(); }); it('preserves identity fields from the response body (id, email, name, companyId, role)', () => { const accessToken = synthJwt({ authId: VALID_USER.id, currentCompanyId: VALID_USER.currentCompanyId, authOrigin: AuthOrigin.TWWIM, }); const user = AuthenticatedUserMapper.fromTokenResponse({ accessToken, refreshToken: 'r', user: VALID_USER, }); expect(user.id).toBe(VALID_USER.id); expect(user.email).toBe(VALID_USER.email); expect(user.name).toBe(VALID_USER.fullName); expect(user.companyId).toBe(VALID_USER.currentCompanyId); expect(user.role).toBe(VALID_USER.currentCompanyRole); }); it('throws on missing currentCompanyId (would otherwise admit incomplete identity)', () => { const accessToken = synthJwt({ authOrigin: AuthOrigin.TWWIM }); expect(() => AuthenticatedUserMapper.fromTokenResponse({ accessToken, refreshToken: 'r', user: { ...VALID_USER, currentCompanyId: null }, })).toThrow(/currentCompanyId/); }); });