Files
shd.build/src/routes/isac/roles/authority-action.test.ts
T

159 lines
4.7 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
const mocks = vi.hoisted(() => ({
database: {
prepare: vi.fn(),
exec: vi.fn(),
},
getDatabase: vi.fn(),
getPermissions: vi.fn(),
hasRecentAal2: vi.fn(),
requirePermission: vi.fn(),
getSessionUser: vi.fn(),
recordIsacAudit: vi.fn(),
countActiveFullAdministrators: vi.fn(),
runs: [] as unknown[][],
}));
vi.mock('$lib/server/auth/session', () => ({
getSessionUser: mocks.getSessionUser,
}));
vi.mock('$lib/server/auth/authorization', () => ({
getPermissions: mocks.getPermissions,
hasRecentAal2: mocks.hasRecentAal2,
requirePermission: mocks.requirePermission,
}));
vi.mock('$lib/server/auth/bootstrap', () => ({
countActiveFullAdministrators: mocks.countActiveFullAdministrators,
}));
vi.mock('$lib/server/database', () => ({
getDatabase: mocks.getDatabase,
}));
vi.mock('$lib/server/audit', () => ({
recordIsacAudit: mocks.recordIsacAudit,
}));
import { actions } from './+page.server';
const postRole = (id = '', permissions: string[] = []) => {
const form = new FormData();
form.set('id', id);
form.set('name', 'Field Operators');
form.set('description', 'Ordinary access role');
for (const permission of permissions)
form.append('permissionIds', permission);
return new Request('http://localhost/isac/roles?/save', {
method: 'POST',
body: form,
});
};
const postDelete = () => {
const form = new FormData();
form.set('id', 'role-user-operator');
form.set('confirmation', 'Existing');
return new Request('http://localhost/isac/roles?/delete', {
method: 'POST',
body: form,
});
};
describe('ISAC role authority actions', () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.runs.length = 0;
mocks.getSessionUser.mockResolvedValue({ id: 'operator-1' });
mocks.getPermissions.mockResolvedValue(['users.roles']);
mocks.hasRecentAal2.mockReturnValue(true);
mocks.requirePermission.mockImplementation(async (user) => user);
mocks.getDatabase.mockResolvedValue(mocks.database);
mocks.countActiveFullAdministrators.mockResolvedValue(1);
mocks.database.prepare.mockImplementation((sql: string) => ({
get: vi.fn(async (...args: unknown[]) => {
if (sql.includes('SELECT id,name,description FROM roles WHERE id=?'))
return args[0]
? { id: args[0], name: 'Existing', description: null }
: null;
if (sql.includes('SELECT id FROM roles WHERE name=')) return null;
if (sql.includes('COUNT(DISTINCT users.id)')) return { count: 1 };
return null;
}),
all: vi.fn(async (...args: unknown[]) => {
if (sql.includes('SELECT id,name FROM permissions'))
return args.map((id) => ({ id, name: 'users.view' }));
if (sql.includes('SELECT permissions.name FROM role_permissions'))
return [];
return [];
}),
run: vi.fn(async (...args: unknown[]) => {
mocks.runs.push(args);
return { changes: 1 };
}),
}));
});
it('rolls back role writes when audit recording fails', async () => {
mocks.recordIsacAudit.mockRejectedValueOnce(new Error('audit failed'));
await expect(
actions.save?.({
cookies: {},
request: postRole('', ['permission-users-view']),
} as never),
).rejects.toThrow('audit failed');
expect(mocks.database.exec.mock.calls).toEqual([
['BEGIN IMMEDIATE'],
['ROLLBACK'],
]);
expect(mocks.database.exec).not.toHaveBeenCalledWith('COMMIT');
expect(mocks.recordIsacAudit).toHaveBeenCalledWith(
expect.objectContaining({ database: mocks.database }),
);
});
it('rejects a role mutation that would leave no active full administrator', async () => {
mocks.countActiveFullAdministrators.mockResolvedValue(0);
const result = await actions.save?.({
cookies: {},
request: postRole('', ['permission-users-view']),
} as never);
expect(result).toMatchObject({ status: 409 });
expect(mocks.database.exec.mock.calls).toEqual([
['BEGIN IMMEDIATE'],
['ROLLBACK'],
]);
expect(mocks.recordIsacAudit).not.toHaveBeenCalled();
});
it('keeps role deletion and its audit evidence in one transaction', async () => {
mocks.getPermissions.mockResolvedValue(['*']);
const result = await actions.delete?.({
cookies: {},
request: postDelete(),
} as never);
expect(result).toMatchObject({
roleDeleted: true,
resultToken: expect.any(String),
});
expect(mocks.database.exec.mock.calls).toEqual([
['BEGIN IMMEDIATE'],
['COMMIT'],
]);
expect(mocks.recordIsacAudit).toHaveBeenCalledWith(
expect.objectContaining({
action: 'role.deleted',
database: mocks.database,
}),
);
});
});