import assert from 'node:assert/strict'; import test from 'node:test'; import { attachPortalCoreAuthRoutes } from './portal-core-auth-routes.mjs'; function createResponse() { return { statusCode: 200, headers: {}, body: undefined, status(code) { this.statusCode = code; return this; }, set(name, value) { this.headers[name] = value; return this; }, json(body) { this.body = body; return this; }, }; } function createSetup(overrides = {}) { const routes = []; const calls = []; let userAuth = { async getMe(token) { calls.push(['get-me', token]); return { id: 'user-1', username: 'john' }; }, async getUserById(userId) { calls.push(['get-user', userId]); return { id: userId }; }, async resolveUserCapabilities(row) { calls.push(['capabilities', row]); return { capabilities: ['chat'], grantedSkills: ['web'], unrestricted: false, }; }, async login(options) { calls.push(['login', options]); return { ok: true, token: 'user-token', user: { id: 'user-1' }, }; }, async loginByWechatMiniProgram(options) { calls.push(['miniapp-login', options]); return { ok: true, token: 'mini-token', user: { id: 'user-2' }, isNewUser: true, }; }, async register(options) { calls.push(['register', options]); return { ok: true, user: { id: 'user-3' }, }; }, async resetPassword(options) { calls.push(['reset-password', options]); return { ok: true }; }, }; let legacyAuth = null; const jsonBody = (_req, _res, next) => next(); const app = { get(path, ...handlers) { routes.push({ method: 'get', path, handlers }); }, post(path, ...handlers) { routes.push({ method: 'post', path, handlers }); }, }; const plazaSeo = { recordAttribution(payload, ip) { calls.push(['attribution', payload, ip]); return Promise.resolve(); }, }; const options = { app, jsonBody, userAuthReady: Promise.resolve(), getUserAuth: () => userAuth, getLegacyAuth: () => legacyAuth, userToken: () => 'request-user-token', legacySessionToken: () => 'legacy-token', setUserLoginCookies(_res, _req, token) { calls.push(['set-user-cookies', token]); }, isSecureRequest: () => true, async resolveSkillRuntimeForClient() { calls.push(['skill-runtime']); return { enabled: true }; }, async resolveAgentCodeRunForClient(userId) { calls.push(['agent-code-run', userId]); return { enabled: true, userId }; }, getPlazaSeo: () => plazaSeo, plazaClientIp: () => '203.0.113.5', logger: { error(...args) { calls.push(['error', ...args]); }, }, isDatabaseConfiguredFn: () => false, sessionCookieFn(token, secure) { calls.push(['session-cookie', token, secure]); return `session=${token}`; }, loadWechatMiniappConfigFn: () => ({ enabled: true, appId: 'mini-app', appSecret: 'mini-secret', }), async exchangeMiniProgramCodeFn(options) { calls.push(['exchange-code', options]); return { openid: 'openid-1', unionid: 'unionid-1', }; }, ...overrides, }; attachPortalCoreAuthRoutes(options); return { calls, routes, options, jsonBody, setUserAuth(value) { userAuth = value; }, setLegacyAuth(value) { legacyAuth = value; }, route(method, path) { return routes.find( (route) => route.method === method && route.path === path, ); }, }; } async function invoke(setup, method, path, req = {}) { const route = setup.route(method, path); assert.ok(route, `${method.toUpperCase()} ${path}`); const res = createResponse(); await route.handlers.at(-1)( { body: {}, ip: '127.0.0.1', get: () => '', ...req, }, res, ); return res; } test('registers the core auth route inventory and middleware order', () => { const setup = createSetup(); assert.deepEqual( setup.routes.map(({ method, path }) => [ method, path, ]), [ ['get', '/auth/status'], ['post', '/auth/login'], ['post', '/auth/wechat-miniapp/login'], ['post', '/auth/register'], ['post', '/auth/reset-password'], ], ); for (const route of setup.routes.filter( ({ method }) => method === 'post', )) { assert.equal(route.handlers[0], setup.jsonBody); } }); test('returns multi-user status and preserves capability projection', async () => { const setup = createSetup(); const res = await invoke( setup, 'get', '/auth/status', ); assert.equal(res.statusCode, 200); assert.deepEqual(res.body, { authenticated: true, user: { id: 'user-1', username: 'john' }, mode: 'user', capabilities: ['chat'], grantedSkills: ['web'], unrestricted: false, skillRuntime: { enabled: true }, agentCodeRun: { enabled: true, userId: 'user-1' }, }); }); test('preserves unavailable, legacy, and disabled status modes', async () => { const unavailable = createSetup({ isDatabaseConfiguredFn: () => true, }); unavailable.setUserAuth(null); assert.equal( ( await invoke( unavailable, 'get', '/auth/status', ) ).statusCode, 503, ); const legacy = createSetup(); legacy.setUserAuth(null); legacy.setLegacyAuth({ verify: (token) => token === 'legacy-token', }); assert.deepEqual( ( await invoke(legacy, 'get', '/auth/status') ).body, { authenticated: true, mode: 'legacy' }, ); const disabled = createSetup(); disabled.setUserAuth(null); assert.deepEqual( ( await invoke(disabled, 'get', '/auth/status') ).body, { authenticated: false, mode: 'none' }, ); }); test('preserves multi-user and legacy login flows', async () => { const setup = createSetup(); const userRes = await invoke( setup, 'post', '/auth/login', { body: { username: 'john', password: 'secret', }, ip: '203.0.113.10', }, ); assert.deepEqual(userRes.body, { authenticated: true, user: { id: 'user-1' }, mode: 'user', sessionToken: 'user-token', }); assert.ok( setup.calls.some( ([name, token]) => name === 'set-user-cookies' && token === 'user-token', ), ); const legacy = createSetup(); legacy.setUserAuth(null); legacy.setLegacyAuth({ login(password, ip) { assert.equal(password, 'legacy-secret'); assert.equal(ip, '127.0.0.1'); return { ok: true, token: 'legacy-session' }; }, }); const legacyRes = await invoke( legacy, 'post', '/auth/login', { body: { password: 'legacy-secret' } }, ); assert.deepEqual(legacyRes.body, { authenticated: true, mode: 'legacy', }); assert.equal( legacyRes.headers['Set-Cookie'], 'session=legacy-session', ); }); test('preserves login validation and retry limits', async () => { const missing = createSetup(); assert.equal( ( await invoke( missing, 'post', '/auth/login', ) ).statusCode, 400, ); const limited = createSetup(); limited.setUserAuth({ async login() { return { ok: false, retryAfterMs: 1500, message: 'limited', }; }, }); const limitedRes = await invoke( limited, 'post', '/auth/login', { body: { username: 'john', password: 'bad' }, }, ); assert.equal(limitedRes.statusCode, 429); assert.equal(limitedRes.headers['Retry-After'], '2'); }); test('preserves mini-program exchange and login mapping', async () => { const setup = createSetup(); const res = await invoke( setup, 'post', '/auth/wechat-miniapp/login', { body: { code: ' code-1 ' } }, ); assert.deepEqual(res.body, { authenticated: true, user: { id: 'user-2' }, mode: 'user', isNewUser: true, sessionToken: 'mini-token', }); assert.ok( setup.calls.some( ([name, options]) => name === 'exchange-code' && options.code === 'code-1', ), ); assert.ok( setup.calls.some( ([name, options]) => name === 'miniapp-login' && options.openid === 'openid-1', ), ); }); test('preserves mini-program configuration and exchange failures', async () => { const disabled = createSetup({ loadWechatMiniappConfigFn: () => ({ enabled: false, }), }); assert.equal( ( await invoke( disabled, 'post', '/auth/wechat-miniapp/login', { body: { code: 'code-1' } }, ) ).statusCode, 503, ); const failed = createSetup({ async exchangeMiniProgramCodeFn() { const error = new Error('code rejected'); error.code = 'wechat_miniapp_code_failed'; throw error; }, }); const failedRes = await invoke( failed, 'post', '/auth/wechat-miniapp/login', { body: { code: 'code-1' } }, ); assert.equal(failedRes.statusCode, 401); assert.deepEqual(failedRes.body, { message: 'code rejected', }); }); test('preserves registration attribution and password reset', async () => { const setup = createSetup(); const registerRes = await invoke( setup, 'post', '/auth/register', { body: { username: 'john', password: 'secret', displayName: 'John', email: 'john@example.com', utm_source: 'campaign', utm_medium: 'wechat', ref: 'ref-1', }, }, ); assert.deepEqual(registerRes.body, { ok: true, user: { id: 'user-3' }, }); assert.ok( setup.calls.some( ([name, payload, ip]) => name === 'attribution' && payload.user_id === 'user-3' && payload.utm_source === 'campaign' && ip === '203.0.113.5', ), ); const resetRes = await invoke( setup, 'post', '/auth/reset-password', { body: { username: 'john', email: 'john@example.com', password: 'new-secret', }, }, ); assert.deepEqual(resetRes.body, { ok: true }); });