You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/arts/active-app/test/presets.test.ts

311 lines
8.9 KiB

/**
* End-to-end tests for the orca-based presets in
* `arts/active-app/presets/`. Validate that:
* - Publishing a SESSION_EVENT_* on the bus triggers the registered
* orca action.
* - The action calls the imperative API on the relevant service.
* - Detachers actually unregister the action.
* - The aggregator only registers presets whose services are present.
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
applyCacheClearOnIdentityChange,
applyCacheClearOnRevoke,
applyConnectionsCloseOnRevoke,
applyConnectionsReauthOnIdentityChange,
applyPermInvalidateOnIdentityChange,
applyStandardOrca
} from '../presets/index.ts';
import { createSvelteEngineBus } from '$bus';
import { createEngineLogger } from '$logger/engine-logger';
import { createEngineOrca, type EngineOrca } from '$orca';
import { createActiveTimers } from '$timer/active-timers.svelte';
import { SESSION_EVENT_IDENTITY_CHANGED, SESSION_EVENT_REVOKED } from '$session';
import type { EngineLogger } from '$logger';
import type { EngineBus } from '$bus';
import type { ActiveTimers } from '$timer';
import type { ActiveCache } from '$cache/types';
import type { ActiveConnections } from '$connection/types';
import type { ActivePerms } from '$perm/types';
interface CoreState {
logger: EngineLogger;
bus: EngineBus<Record<string, unknown>>;
timers: ActiveTimers;
orca: EngineOrca;
}
function buildCore(): CoreState {
const logger = createEngineLogger({});
const timers = createActiveTimers({ logger });
const bus = createSvelteEngineBus<Record<string, unknown>>({ logger, clock: timers.clock });
const orca = createEngineOrca({ bus, timers, logger });
return { logger, bus, timers, orca };
}
function disposeCore(core: CoreState): void {
core.orca.dispose();
core.bus.dispose();
core.timers.dispose();
core.logger.dispose();
}
const samplePayload = {
event: 'session.lifecycle.adopted',
generation: 1,
identity: { from: 'anon', to: 'user-42' },
previousActorId: null,
nextActorId: 'user-42'
};
async function flush(rounds = 4) {
for (let i = 0; i < rounds; i++) {
await new Promise((r) => queueMicrotask(() => r(undefined)));
await new Promise((r) => setTimeout(r, 0));
}
}
describe('applyCacheClearOnIdentityChange', () => {
let core: CoreState;
beforeEach(() => {
core = buildCore();
});
afterEach(() => {
disposeCore(core);
});
it('clears the cache when SESSION_EVENT_IDENTITY_CHANGED is published', async () => {
const clear = vi.fn(() => Promise.resolve());
const cache = { clear } as unknown as ActiveCache;
applyCacheClearOnIdentityChange({ orca: core.orca, cache });
core.bus.publish(SESSION_EVENT_IDENTITY_CHANGED, samplePayload);
await flush();
expect(clear).toHaveBeenCalledTimes(1);
});
it('detacher unregisters the action', async () => {
const clear = vi.fn(() => Promise.resolve());
const cache = { clear } as unknown as ActiveCache;
const detach = applyCacheClearOnIdentityChange({ orca: core.orca, cache });
detach();
core.bus.publish(SESSION_EVENT_IDENTITY_CHANGED, samplePayload);
await flush();
expect(clear).not.toHaveBeenCalled();
});
it('records action failure in run trace when clear() rejects', async () => {
const error = new Error('clear failed');
const cache = { clear: vi.fn(() => Promise.reject(error)) } as unknown as ActiveCache;
applyCacheClearOnIdentityChange({ orca: core.orca, cache });
core.bus.publish(SESSION_EVENT_IDENTITY_CHANGED, samplePayload);
await flush();
const runs = core.orca.recentRuns();
expect(runs).toHaveLength(1);
expect(runs[0].actions[0].status).toBe('error');
expect(runs[0].actions[0].error).toBe(error);
});
});
describe('applyPermInvalidateOnIdentityChange', () => {
let core: CoreState;
beforeEach(() => {
core = buildCore();
});
afterEach(() => {
disposeCore(core);
});
it('invalidates perms when SESSION_EVENT_IDENTITY_CHANGED is published', async () => {
const invalidate = vi.fn();
const perm = { invalidate } as unknown as ActivePerms;
applyPermInvalidateOnIdentityChange({ orca: core.orca, perm });
core.bus.publish(SESSION_EVENT_IDENTITY_CHANGED, samplePayload);
await flush();
expect(invalidate).toHaveBeenCalledTimes(1);
});
});
describe('applyCacheClearOnRevoke', () => {
let core: CoreState;
beforeEach(() => {
core = buildCore();
});
afterEach(() => {
disposeCore(core);
});
it('clears the cache when SESSION_EVENT_REVOKED is published', async () => {
const clear = vi.fn(() => Promise.resolve());
const cache = { clear } as unknown as ActiveCache;
applyCacheClearOnRevoke({ orca: core.orca, cache });
const revokePayload = {
...samplePayload,
event: 'session.lifecycle.revoked'
};
core.bus.publish(SESSION_EVENT_REVOKED, revokePayload);
await flush();
expect(clear).toHaveBeenCalledTimes(1);
});
});
describe('applyConnectionsReauthOnIdentityChange', () => {
let core: CoreState;
beforeEach(() => {
core = buildCore();
});
afterEach(() => {
disposeCore(core);
});
it('reauthenticates connections when SESSION_EVENT_IDENTITY_CHANGED is published', async () => {
const reauthenticateAll = vi.fn(() => Promise.resolve([]));
const connections = { reauthenticateAll } as unknown as ActiveConnections;
applyConnectionsReauthOnIdentityChange({ orca: core.orca, connections });
core.bus.publish(SESSION_EVENT_IDENTITY_CHANGED, samplePayload);
await flush();
expect(reauthenticateAll).toHaveBeenCalledTimes(1);
});
it('records action failure in run trace when reauthenticateAll() rejects', async () => {
const error = new Error('reauth failed');
const connections = {
reauthenticateAll: vi.fn(() => Promise.reject(error))
} as unknown as ActiveConnections;
applyConnectionsReauthOnIdentityChange({ orca: core.orca, connections });
core.bus.publish(SESSION_EVENT_IDENTITY_CHANGED, samplePayload);
await flush();
const runs = core.orca.recentRuns();
expect(runs).toHaveLength(1);
expect(runs[0].actions[0].status).toBe('error');
expect(runs[0].actions[0].error).toBe(error);
});
});
describe('applyConnectionsCloseOnRevoke', () => {
let core: CoreState;
beforeEach(() => {
core = buildCore();
});
afterEach(() => {
disposeCore(core);
});
it('closes connections when SESSION_EVENT_REVOKED is published', async () => {
const closeAll = vi.fn();
const connections = { closeAll } as unknown as ActiveConnections;
applyConnectionsCloseOnRevoke({ orca: core.orca, connections });
const revokePayload = {
...samplePayload,
event: 'session.lifecycle.revoked'
};
core.bus.publish(SESSION_EVENT_REVOKED, revokePayload);
await flush();
expect(closeAll).toHaveBeenCalledTimes(1);
expect(closeAll).toHaveBeenCalledWith('session-revoked');
});
});
describe('applyStandardOrca', () => {
let core: CoreState;
beforeEach(() => {
core = buildCore();
});
afterEach(() => {
disposeCore(core);
});
it('registers cache + perm reactions when both services are present', async () => {
const cacheClear = vi.fn(() => Promise.resolve());
const permInvalidate = vi.fn();
const cache = { clear: cacheClear } as unknown as ActiveCache;
const perm = { invalidate: permInvalidate } as unknown as ActivePerms;
applyStandardOrca({ orca: core.orca, cache, perm });
core.bus.publish(SESSION_EVENT_IDENTITY_CHANGED, samplePayload);
await flush();
expect(cacheClear).toHaveBeenCalledTimes(1);
expect(permInvalidate).toHaveBeenCalledTimes(1);
});
it('registers connection reauth + close when connections is present', async () => {
const reauthenticateAll = vi.fn(() => Promise.resolve([]));
const closeAll = vi.fn();
const connections = { reauthenticateAll, closeAll } as unknown as ActiveConnections;
applyStandardOrca({ orca: core.orca, connections });
core.bus.publish(SESSION_EVENT_IDENTITY_CHANGED, samplePayload);
await flush();
expect(reauthenticateAll).toHaveBeenCalledTimes(1);
core.bus.publish(SESSION_EVENT_REVOKED, {
...samplePayload,
event: 'session.lifecycle.revoked'
});
await flush();
expect(closeAll).toHaveBeenCalledTimes(1);
});
it('skips cache reactions when cache is absent', async () => {
const permInvalidate = vi.fn();
const perm = { invalidate: permInvalidate } as unknown as ActivePerms;
applyStandardOrca({ orca: core.orca, perm });
// No cache action registered -> no error from publish
core.bus.publish(SESSION_EVENT_IDENTITY_CHANGED, samplePayload);
await flush();
expect(permInvalidate).toHaveBeenCalledTimes(1);
});
it('detach removes every registered preset', async () => {
const cacheClear = vi.fn(() => Promise.resolve());
const permInvalidate = vi.fn();
const cache = { clear: cacheClear } as unknown as ActiveCache;
const perm = { invalidate: permInvalidate } as unknown as ActivePerms;
const detach = applyStandardOrca({ orca: core.orca, cache, perm });
detach();
core.bus.publish(SESSION_EVENT_IDENTITY_CHANGED, samplePayload);
await flush();
expect(cacheClear).not.toHaveBeenCalled();
expect(permInvalidate).not.toHaveBeenCalled();
});
});

Powered by TurnKey Linux.