Files
memind/server/portal-core-auth-routes.test.mjs

459 lines
10 KiB
JavaScript

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 });
});