Add Super Viewer read-only administrator role
CI / checks (push) Successful in 5m40s

This commit is contained in:
2026-10-11 23:30:40 -04:00
parent 253d47300c
commit 4ea291d305
48 changed files with 1684 additions and 933 deletions
+63 -2
View File
@@ -1,11 +1,27 @@
import { expect, it, vi } from 'vitest';
import { beforeEach, expect, it, vi } from 'vitest';
import { render } from 'svelte/server';
import { writable } from 'svelte/store';
import { defaultPlan, requirement, type BetaTestView } from '../../src/lib/beta-plan.js';
import { betaStatus, type BetaDeploymentServer } from '../../src/lib/ui/betas.js';
import BetaTestProgress from '../../src/lib/ui/components/BetaTestProgress.svelte';
import BetaList from '../../src/routes/betas/+page.svelte';
import NewBeta from '../../src/routes/betas/new/+page.svelte';
import type { UserRole } from '../../src/lib/auth.js';
vi.mock('$app/stores', () => ({ page: writable({ url: new URL('http://localhost/betas/75') }) }));
const currentPage = writable({
url: new URL('http://localhost/betas/75'),
data: { user: { role: 'admin' as UserRole } }
});
vi.mock('$app/stores', () => ({
page: {
subscribe: (run: Parameters<typeof currentPage.subscribe>[0]) => currentPage.subscribe(run)
}
}));
vi.mock('$app/navigation', () => ({ goto: vi.fn(), beforeNavigate: vi.fn() }));
function asRole(role: UserRole) {
currentPage.update((value) => ({ ...value, data: { user: { role } } }));
}
beforeEach(() => asRole('admin'));
it('prioritizes lifecycle, failures and pauses over readiness without treating stage text as failure', () => {
expect(betaStatus({ state: 'promoted', testOutcome: 'paused', servers: [] })).toMatchObject({
@@ -153,3 +169,48 @@ it('shows missing historical evidence as not recorded, without claiming zero ser
expect(body).not.toContain('Awaiting test run');
expect(body).not.toContain('0 / 0');
});
it.each(['super_viewer', 'viewer'] as const)(
'keeps beta evidence available to %s without pause, retry or sign-off controls',
(role) => {
asRole(role);
const test: BetaTestView = {
plan: { ...defaultPlan(), requirements: [requirement('manual', 'health')] },
outcome: 'active',
reason: null,
servers: [testRun('manual', false)],
evidence: []
};
for (const outcome of ['active', 'paused', 'failed'] as const) {
const body = render(BetaTestProgress, {
props: {
test: { ...test, outcome },
betaId: 75,
closed: false,
onUpdate: async () => {}
}
}).body;
expect(body).toContain('Test results');
expect(body).toContain('Test policy');
expect(body).toContain('View relevant evidence');
expect(body).not.toContain('Tester name and observation');
expect(body).not.toContain('Sign off</button>');
expect(body).not.toContain('Pause testing');
expect(body).not.toContain('Resume testing');
expect(body).not.toContain('Start fresh attempt');
}
}
);
it('hides new-beta entry points from Super Viewers and explains directly opened creation pages', () => {
asRole('super_viewer');
expect(render(BetaList).body).not.toContain('href="/betas/new"');
const creation = render(NewBeta).body;
expect(creation).toContain('Beta creation requires operator access');
expect(creation).toContain('View betas');
expect(creation).not.toContain('<form');
asRole('operator');
expect(render(BetaList).body).toContain('href="/betas/new"');
expect(render(NewBeta).body).not.toContain('Beta creation requires operator access');
});