Wire expected_revision, blob PDF framing, mustChangePassword session rewrite, client_key project create, no-project send disable, and right-rail persistence. Co-authored-by: Cursor <cursoragent@cursor.com>
161 lines
5.1 KiB
TypeScript
161 lines
5.1 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mapLoginResult, mapMeResult, mapUserAccount, mapUserAccountList } from './authUserMap';
|
|
import { AuthClient } from './auth';
|
|
|
|
const snakeAdminUser = {
|
|
id: 1,
|
|
username: 'admin',
|
|
name: '系统管理员',
|
|
status: 'active',
|
|
must_change_password: false,
|
|
role_id: 'role-admin',
|
|
position: '系统管理员',
|
|
employee_id: 'SYS-000',
|
|
department: '系统运维部',
|
|
created_at: '2026-09-13T00:00:00Z',
|
|
updated_at: '2026-09-13T00:00:00Z',
|
|
};
|
|
|
|
test('mapUserAccount: snake_case Go contract → camelCase', () => {
|
|
const u = mapUserAccount(snakeAdminUser);
|
|
assert.equal(u.roleId, 'role-admin');
|
|
assert.equal(u.employeeId, 'SYS-000');
|
|
assert.equal(u.mustChangePassword, false);
|
|
assert.equal(u.createdAt, '2026-09-13T00:00:00Z');
|
|
assert.equal(u.updatedAt, '2026-09-13T00:00:00Z');
|
|
assert.equal(u.username, 'admin');
|
|
});
|
|
|
|
test('mapUserAccount: prefers snake_case when both present', () => {
|
|
const u = mapUserAccount({
|
|
...snakeAdminUser,
|
|
roleId: 'role-guest',
|
|
role_id: 'role-admin',
|
|
employeeId: 'WRONG',
|
|
employee_id: 'SYS-000',
|
|
});
|
|
assert.equal(u.roleId, 'role-admin');
|
|
assert.equal(u.employeeId, 'SYS-000');
|
|
});
|
|
|
|
test('mapLoginResult/mapMeResult use top-level must_change_password as sole authority', () => {
|
|
// Nested true must not revive when top-level is false (post password-change cache).
|
|
const me = mapMeResult({
|
|
user: { ...snakeAdminUser, must_change_password: true },
|
|
must_change_password: false,
|
|
env: 'dev',
|
|
capabilities: [],
|
|
});
|
|
assert.equal(me.mustChangePassword, false);
|
|
assert.equal(me.user.mustChangePassword, false);
|
|
|
|
const login = mapLoginResult({
|
|
token: 'tok-2',
|
|
user: { ...snakeAdminUser, must_change_password: true },
|
|
must_change_password: false,
|
|
env: 'dev',
|
|
});
|
|
assert.equal(login.mustChangePassword, false);
|
|
assert.equal(login.user.mustChangePassword, false);
|
|
});
|
|
|
|
test('mapUserAccountList maps admin users list', () => {
|
|
const list = mapUserAccountList([
|
|
snakeAdminUser,
|
|
{
|
|
id: 2,
|
|
username: 'eng',
|
|
name: '工程师',
|
|
status: 'active',
|
|
must_change_password: true,
|
|
role_id: 'role-engineer',
|
|
position: '助理工程师',
|
|
employee_id: 'RD-010',
|
|
department: '研发',
|
|
created_at: '2026-09-13T01:00:00Z',
|
|
updated_at: '2026-09-13T01:00:00Z',
|
|
},
|
|
]);
|
|
assert.equal(list.length, 2);
|
|
assert.equal(list[0].roleId, 'role-admin');
|
|
assert.equal(list[1].roleId, 'role-engineer');
|
|
assert.equal(list[1].mustChangePassword, true);
|
|
assert.equal(list[1].employeeId, 'RD-010');
|
|
});
|
|
|
|
test('AuthClient.login + fetchMe map real snake_case wire payloads', async () => {
|
|
const originalFetch = globalThis.fetch;
|
|
const originalWindow = (globalThis as { window?: unknown }).window;
|
|
const store: Record<string, string> = {};
|
|
const localStorage = {
|
|
getItem: (k: string) => store[k] ?? null,
|
|
setItem: (k: string, v: string) => {
|
|
store[k] = v;
|
|
},
|
|
removeItem: (k: string) => {
|
|
delete store[k];
|
|
},
|
|
};
|
|
const mockFetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
const url = String(input);
|
|
if (url.includes('/api/v1/auth/login')) {
|
|
return new Response(
|
|
JSON.stringify({
|
|
token: 'wire-token',
|
|
user: snakeAdminUser,
|
|
must_change_password: false,
|
|
env: 'dev',
|
|
}),
|
|
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
);
|
|
}
|
|
if (url.includes('/api/v1/auth/me')) {
|
|
return new Response(
|
|
JSON.stringify({
|
|
user: snakeAdminUser,
|
|
must_change_password: false,
|
|
env: 'dev',
|
|
capabilities: ['admin:manage_users'],
|
|
}),
|
|
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
);
|
|
}
|
|
if (url.includes('/api/v1/admin/users') && (!init?.method || init.method === 'GET')) {
|
|
return new Response(JSON.stringify({ users: [snakeAdminUser] }), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
return new Response('not found', { status: 404 });
|
|
}) as typeof fetch;
|
|
|
|
globalThis.fetch = mockFetch;
|
|
(globalThis as { window: unknown }).window = { fetch: mockFetch, localStorage };
|
|
|
|
try {
|
|
const client = new AuthClient();
|
|
const session = await client.login({ username: 'admin', password: 'x' });
|
|
assert.equal(session.user.roleId, 'role-admin');
|
|
assert.equal(session.user.employeeId, 'SYS-000');
|
|
assert.equal(session.mustChangePassword, false);
|
|
assert.ok(session.capabilities?.includes('admin:manage_users'));
|
|
|
|
const me = await client.fetchMe();
|
|
assert.equal(me.user.roleId, 'role-admin');
|
|
assert.equal(me.user.employeeId, 'SYS-000');
|
|
assert.equal(me.mustChangePassword, false);
|
|
|
|
const users = await client.getAdminUsers();
|
|
assert.equal(users[0].roleId, 'role-admin');
|
|
assert.equal(users[0].employeeId, 'SYS-000');
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
if (originalWindow === undefined) {
|
|
delete (globalThis as { window?: unknown }).window;
|
|
} else {
|
|
(globalThis as { window: unknown }).window = originalWindow;
|
|
}
|
|
}
|
|
});
|