722 lines
19 KiB
JavaScript
722 lines
19 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { attachPortalSessionRoutes } from './portal-session-routes.mjs';
|
|
|
|
function createRouterRecorder() {
|
|
const routes = new Map();
|
|
return {
|
|
routes,
|
|
get(path, handler) {
|
|
routes.set(`GET ${path}`, handler);
|
|
},
|
|
delete(path, handler) {
|
|
routes.set(`DELETE ${path}`, handler);
|
|
},
|
|
};
|
|
}
|
|
|
|
function createResponseRecorder() {
|
|
return {
|
|
statusCode: 200,
|
|
body: undefined,
|
|
ended: false,
|
|
status(code) {
|
|
this.statusCode = code;
|
|
return this;
|
|
},
|
|
json(body) {
|
|
this.body = body;
|
|
return this;
|
|
},
|
|
send(body) {
|
|
this.body = body;
|
|
return this;
|
|
},
|
|
end() {
|
|
this.ended = true;
|
|
return this;
|
|
},
|
|
};
|
|
}
|
|
|
|
function createRequest(overrides = {}) {
|
|
return {
|
|
params: { sessionId: 'session-1' },
|
|
query: {},
|
|
currentUser: {
|
|
id: 'user-1',
|
|
username: 'alice',
|
|
},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function createSnapshot(overrides = {}) {
|
|
return {
|
|
meta: {
|
|
synced_msg_count: 2,
|
|
source_updated_at: 'updated-1',
|
|
},
|
|
session: {
|
|
id: 'session-1',
|
|
name: 'Session',
|
|
},
|
|
messages: [
|
|
{
|
|
id: 'message-1',
|
|
role: 'assistant',
|
|
content: 'Hello',
|
|
metadata: { userVisible: true },
|
|
},
|
|
],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function createUpstream({
|
|
ok = true,
|
|
status = 200,
|
|
body = { id: 'session-1', conversation: [] },
|
|
text = '',
|
|
} = {}) {
|
|
return {
|
|
ok,
|
|
status,
|
|
async json() {
|
|
return body;
|
|
},
|
|
async text() {
|
|
return text;
|
|
},
|
|
};
|
|
}
|
|
|
|
function createDependencies(overrides = {}) {
|
|
const calls = {
|
|
waits: 0,
|
|
owns: [],
|
|
unregister: [],
|
|
begin: [],
|
|
end: [],
|
|
syncPages: [],
|
|
artifacts: [],
|
|
};
|
|
const proxy = {
|
|
async resolveTarget(sessionId) {
|
|
return `target:${sessionId}`;
|
|
},
|
|
async apiFetchTo() {
|
|
return createUpstream();
|
|
},
|
|
proxySessionEvents(_req, _res, sessionId, hooks) {
|
|
return { sessionId, hooks };
|
|
},
|
|
};
|
|
return {
|
|
calls,
|
|
proxy,
|
|
dependencies: {
|
|
h5Root: '/workspace',
|
|
env: {},
|
|
async waitForUserAuthReady() {
|
|
calls.waits += 1;
|
|
},
|
|
getUserAuth: () => ({}),
|
|
getTkmindProxy: () => proxy,
|
|
getSessionSnapshotService: () => null,
|
|
getAuthPool: () => null,
|
|
getMindSpaceAssets: () => null,
|
|
getMemoryV2: () => null,
|
|
getMindSpaceAnalyticsConfig: () => ({}),
|
|
isWorkspaceMaintenanceEnabled: () => false,
|
|
async ownsAgentSession(userId, sessionId) {
|
|
calls.owns.push({ userId, sessionId });
|
|
return true;
|
|
},
|
|
async unregisterAgentSessionForUser(userId, sessionId) {
|
|
calls.unregister.push({ userId, sessionId });
|
|
},
|
|
beginSessionPageDelivery(sessionId) {
|
|
calls.begin.push(sessionId);
|
|
},
|
|
endSessionPageDelivery(sessionId) {
|
|
calls.end.push(sessionId);
|
|
},
|
|
async syncUserGeneratedPages(userId, options) {
|
|
calls.syncPages.push({ userId, options });
|
|
},
|
|
async registerPublicHtmlArtifactsForConversation(input) {
|
|
calls.artifacts.push(input);
|
|
},
|
|
isDirectChatSessionIdFn: () => false,
|
|
isPortalDirectChatSnapshotFn: () => false,
|
|
shouldExpirePortalDirectChatSnapshotFn: async () => false,
|
|
sanitizeSessionConversationFn: (messages) => messages,
|
|
repairSessionConversationFromDbFn: async (
|
|
_pool,
|
|
session,
|
|
) => session,
|
|
filterNonemptyUserVisibleMessagesFn: (messages) => messages,
|
|
resolveMindSpaceUserPublishDirFn: () =>
|
|
'/workspace/user-1',
|
|
collectPublicHtmlWritePathsFn: () => [],
|
|
preparePageDeliveryContractFn: async () => ({}),
|
|
materializePublicHtmlWritesFn: () => {},
|
|
sendMindSpaceAnalyticsEventFn: async () => {},
|
|
resolveAnalyticsOwnerSegmentFn: () => 'segment',
|
|
resolveAnalyticsOwnerLabelFn: () => 'label',
|
|
resolveAnalyticsPlanFn: () => 'pro',
|
|
syncPublicHtmlAfterFinishFn: async () => ({
|
|
publicHtmlRelativePaths: [],
|
|
}),
|
|
resolveMindSpaceRuntimeConfigFn: () => ({
|
|
storageRoot: '/storage',
|
|
}),
|
|
maybeRepairH5HtmlAfterFinishFn: async () => ({
|
|
skipped: 'ok',
|
|
}),
|
|
maybeRepairPageDataAfterFinishFn: async () => ({
|
|
skipped: 'not_page_data',
|
|
}),
|
|
markPageDeliveryContractReadyFn: async () => true,
|
|
fileExists: () => true,
|
|
resolvePath: (...parts) => parts.join('/'),
|
|
publishRootDir: 'MindSpace',
|
|
logger: {
|
|
warn() {},
|
|
},
|
|
...overrides,
|
|
},
|
|
};
|
|
}
|
|
|
|
test('session module preserves route inventory and order', () => {
|
|
const api = createRouterRecorder();
|
|
attachPortalSessionRoutes(api, createDependencies().dependencies);
|
|
assert.deepEqual([...api.routes.keys()], [
|
|
'GET /sessions/:sessionId',
|
|
'DELETE /sessions/:sessionId',
|
|
'GET /sessions/:sessionId/events',
|
|
]);
|
|
});
|
|
|
|
test('session detail uses ordinary snapshot only when both hints match', async () => {
|
|
const snapshot = createSnapshot();
|
|
let upstreamCalls = 0;
|
|
const snapshotService = {
|
|
isEnabled: () => true,
|
|
get: async () => snapshot,
|
|
save: async () => {},
|
|
};
|
|
const setup = createDependencies({
|
|
getSessionSnapshotService: () => snapshotService,
|
|
getTkmindProxy: () => ({
|
|
async resolveTarget() {
|
|
return 'goose';
|
|
},
|
|
async apiFetchTo() {
|
|
upstreamCalls += 1;
|
|
return createUpstream({
|
|
body: {
|
|
id: 'session-1',
|
|
source: 'goose',
|
|
conversation: [],
|
|
},
|
|
});
|
|
},
|
|
}),
|
|
});
|
|
const api = createRouterRecorder();
|
|
attachPortalSessionRoutes(api, setup.dependencies);
|
|
const handler = api.routes.get('GET /sessions/:sessionId');
|
|
|
|
const noHintsRes = createResponseRecorder();
|
|
await handler(createRequest(), noHintsRes, () => {});
|
|
assert.equal(noHintsRes.body.source, 'goose');
|
|
assert.equal(upstreamCalls, 1);
|
|
|
|
const matchedRes = createResponseRecorder();
|
|
await handler(
|
|
createRequest({
|
|
query: {
|
|
hint_mc: '2',
|
|
hint_ua: 'updated-1',
|
|
},
|
|
}),
|
|
matchedRes,
|
|
() => {},
|
|
);
|
|
assert.equal(matchedRes.body.name, 'Session');
|
|
assert.equal(matchedRes.body.conversation.length, 1);
|
|
assert.equal(upstreamCalls, 1);
|
|
});
|
|
|
|
test('direct and portal-direct snapshots bypass ordinary cache hints', async () => {
|
|
for (const directMode of ['session-id', 'snapshot']) {
|
|
const api = createRouterRecorder();
|
|
let upstreamCalls = 0;
|
|
const setup = createDependencies({
|
|
getSessionSnapshotService: () => ({
|
|
isEnabled: () => true,
|
|
get: async () => createSnapshot(),
|
|
}),
|
|
isDirectChatSessionIdFn: () => directMode === 'session-id',
|
|
isPortalDirectChatSnapshotFn: () =>
|
|
directMode === 'snapshot',
|
|
getTkmindProxy: () => ({
|
|
async resolveTarget() {
|
|
return 'goose';
|
|
},
|
|
async apiFetchTo() {
|
|
upstreamCalls += 1;
|
|
return createUpstream();
|
|
},
|
|
}),
|
|
});
|
|
attachPortalSessionRoutes(api, setup.dependencies);
|
|
const res = createResponseRecorder();
|
|
await api.routes.get('GET /sessions/:sessionId')(
|
|
createRequest(),
|
|
res,
|
|
() => {},
|
|
);
|
|
assert.equal(res.body.name, 'Session');
|
|
assert.equal(upstreamCalls, 0);
|
|
}
|
|
});
|
|
|
|
test('session detail expires stale portal snapshot and writes through Goose result', async () => {
|
|
const calls = [];
|
|
const snapshotService = {
|
|
isEnabled: () => true,
|
|
get: async () => createSnapshot(),
|
|
async remove(sessionId) {
|
|
calls.push({ kind: 'remove', sessionId });
|
|
},
|
|
async save(sessionId, userId, session, messages) {
|
|
calls.push({
|
|
kind: 'save',
|
|
sessionId,
|
|
userId,
|
|
session,
|
|
messages,
|
|
});
|
|
},
|
|
};
|
|
const api = createRouterRecorder();
|
|
const setup = createDependencies({
|
|
getAuthPool: () => ({ id: 'pool' }),
|
|
getSessionSnapshotService: () => snapshotService,
|
|
shouldExpirePortalDirectChatSnapshotFn: async () => true,
|
|
sanitizeSessionConversationFn(messages) {
|
|
calls.push({ kind: 'sanitize', messages });
|
|
return messages.map((message) => ({
|
|
...message,
|
|
sanitized: true,
|
|
}));
|
|
},
|
|
async repairSessionConversationFromDbFn(
|
|
pool,
|
|
session,
|
|
sessionId,
|
|
userId,
|
|
) {
|
|
calls.push({
|
|
kind: 'repair',
|
|
pool,
|
|
sessionId,
|
|
userId,
|
|
});
|
|
return { ...session, repaired: true };
|
|
},
|
|
getTkmindProxy: () => ({
|
|
async resolveTarget() {
|
|
return 'goose';
|
|
},
|
|
async apiFetchTo() {
|
|
return createUpstream({
|
|
body: {
|
|
id: 'session-1',
|
|
conversation: [
|
|
{
|
|
id: 'message-1',
|
|
metadata: { userVisible: true },
|
|
},
|
|
],
|
|
},
|
|
});
|
|
},
|
|
}),
|
|
});
|
|
attachPortalSessionRoutes(api, setup.dependencies);
|
|
const res = createResponseRecorder();
|
|
await api.routes.get('GET /sessions/:sessionId')(
|
|
createRequest(),
|
|
res,
|
|
() => {},
|
|
);
|
|
await Promise.resolve();
|
|
assert.equal(res.body.repaired, true);
|
|
assert.deepEqual(calls.map((call) => call.kind), [
|
|
'remove',
|
|
'sanitize',
|
|
'repair',
|
|
'save',
|
|
]);
|
|
});
|
|
|
|
test('session routes preserve ownership and unavailable fallthrough', async () => {
|
|
const unavailableApi = createRouterRecorder();
|
|
let nextCalls = 0;
|
|
const unavailable = createDependencies({
|
|
getUserAuth: () => null,
|
|
});
|
|
attachPortalSessionRoutes(
|
|
unavailableApi,
|
|
unavailable.dependencies,
|
|
);
|
|
await unavailableApi.routes.get('GET /sessions/:sessionId')(
|
|
createRequest(),
|
|
createResponseRecorder(),
|
|
() => {
|
|
nextCalls += 1;
|
|
},
|
|
);
|
|
assert.equal(nextCalls, 1);
|
|
|
|
const deniedApi = createRouterRecorder();
|
|
const denied = createDependencies({
|
|
ownsAgentSession: async () => false,
|
|
});
|
|
attachPortalSessionRoutes(deniedApi, denied.dependencies);
|
|
const deniedRes = createResponseRecorder();
|
|
await deniedApi.routes.get('GET /sessions/:sessionId')(
|
|
createRequest(),
|
|
deniedRes,
|
|
() => {},
|
|
);
|
|
assert.equal(deniedRes.statusCode, 403);
|
|
});
|
|
|
|
test('session delete preserves direct and Goose deletion behavior', async () => {
|
|
for (const direct of [true, false]) {
|
|
const removed = [];
|
|
const api = createRouterRecorder();
|
|
const setup = createDependencies({
|
|
isDirectChatSessionIdFn: () => direct,
|
|
getSessionSnapshotService: () => ({
|
|
async remove(sessionId) {
|
|
removed.push(sessionId);
|
|
},
|
|
}),
|
|
getTkmindProxy: () => ({
|
|
async resolveTarget(sessionId) {
|
|
assert.equal(direct, false);
|
|
return `target:${sessionId}`;
|
|
},
|
|
async apiFetchTo(target, pathname, init) {
|
|
assert.equal(target, 'target:session-1');
|
|
assert.equal(pathname, '/sessions/session-1');
|
|
assert.equal(init.method, 'DELETE');
|
|
return createUpstream({ ok: false, status: 404 });
|
|
},
|
|
}),
|
|
});
|
|
attachPortalSessionRoutes(api, setup.dependencies);
|
|
const res = createResponseRecorder();
|
|
await api.routes.get('DELETE /sessions/:sessionId')(
|
|
createRequest(),
|
|
res,
|
|
() => {},
|
|
);
|
|
await Promise.resolve();
|
|
assert.equal(res.statusCode, 204);
|
|
assert.equal(res.ended, true);
|
|
assert.deepEqual(setup.calls.unregister, [
|
|
{ userId: 'user-1', sessionId: 'session-1' },
|
|
]);
|
|
assert.deepEqual(removed, ['session-1']);
|
|
}
|
|
});
|
|
|
|
test('direct session events preserve snapshot and 404 behavior', async () => {
|
|
const snapshot = createSnapshot();
|
|
const api = createRouterRecorder();
|
|
const setup = createDependencies({
|
|
isDirectChatSessionIdFn: () => true,
|
|
getSessionSnapshotService: () => ({
|
|
get: async () => snapshot,
|
|
}),
|
|
sendDirectChatSessionEventsFn(req, res, received) {
|
|
assert.equal(received, snapshot);
|
|
return res.status(209).json({ direct: req.params.sessionId });
|
|
},
|
|
});
|
|
attachPortalSessionRoutes(api, setup.dependencies);
|
|
const res = createResponseRecorder();
|
|
await api.routes.get('GET /sessions/:sessionId/events')(
|
|
createRequest(),
|
|
res,
|
|
() => {},
|
|
);
|
|
assert.equal(res.statusCode, 209);
|
|
assert.equal(res.body.direct, 'session-1');
|
|
|
|
const missingApi = createRouterRecorder();
|
|
const missing = createDependencies({
|
|
isDirectChatSessionIdFn: () => true,
|
|
getSessionSnapshotService: () => ({
|
|
get: async () => null,
|
|
}),
|
|
});
|
|
attachPortalSessionRoutes(missingApi, missing.dependencies);
|
|
const missingRes = createResponseRecorder();
|
|
await missingApi.routes.get('GET /sessions/:sessionId/events')(
|
|
createRequest(),
|
|
missingRes,
|
|
() => {},
|
|
);
|
|
assert.equal(missingRes.statusCode, 404);
|
|
});
|
|
|
|
test('stream hook uses current session id for contracts and analytics', async () => {
|
|
const calls = [];
|
|
let captured = null;
|
|
const api = createRouterRecorder();
|
|
const setup = createDependencies({
|
|
getAuthPool: () => ({ id: 'pool' }),
|
|
collectPublicHtmlWritePathsFn: () => [
|
|
'public/page.html',
|
|
],
|
|
preparePageDeliveryContractFn(input) {
|
|
calls.push({ kind: 'contract', input });
|
|
return Promise.resolve({});
|
|
},
|
|
materializePublicHtmlWritesFn(event, options) {
|
|
calls.push({ kind: 'materialize', event, options });
|
|
},
|
|
sendMindSpaceAnalyticsEventFn(input) {
|
|
calls.push({ kind: 'analytics', input });
|
|
return Promise.resolve();
|
|
},
|
|
getTkmindProxy: () => ({
|
|
proxySessionEvents(_req, _res, sessionId, hooks) {
|
|
captured = { sessionId, hooks };
|
|
return captured;
|
|
},
|
|
}),
|
|
});
|
|
attachPortalSessionRoutes(api, setup.dependencies);
|
|
await api.routes.get('GET /sessions/:sessionId/events')(
|
|
createRequest(),
|
|
createResponseRecorder(),
|
|
() => {},
|
|
);
|
|
captured.hooks.onEvent({
|
|
type: 'Message',
|
|
message: {
|
|
role: 'user',
|
|
metadata: { memindRun: { pgRequired: true } },
|
|
},
|
|
});
|
|
await Promise.resolve();
|
|
assert.equal(calls[0].input.requestId, 'session-1');
|
|
assert.equal(calls[0].input.pgRequired, true);
|
|
assert.equal(calls[1].kind, 'materialize');
|
|
assert.equal(calls[2].input.publicationId, 'session-1');
|
|
assert.equal(calls[2].input.agentRunId, 'session-1');
|
|
assert.equal(calls[2].input.planType, 'pro');
|
|
assert.match(
|
|
calls[2].input.generatedAt,
|
|
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/,
|
|
);
|
|
});
|
|
|
|
test('Finish hook preserves refresh, sync, delivery readiness, memory, and lock lifecycle', async () => {
|
|
const calls = [];
|
|
let hooks = null;
|
|
const userMessage = {
|
|
id: 'user-message',
|
|
role: 'user',
|
|
content: [{ type: 'text', text: ' build page ' }],
|
|
metadata: {
|
|
userVisible: true,
|
|
memindRun: { pgRequired: true },
|
|
},
|
|
};
|
|
const snapshotService = {
|
|
isEnabled: () => true,
|
|
async get() {
|
|
return { messages: [userMessage] };
|
|
},
|
|
async refresh(sessionId, userId, apiFetchFn) {
|
|
calls.push({ kind: 'refresh', sessionId, userId });
|
|
assert.equal(typeof apiFetchFn, 'function');
|
|
},
|
|
};
|
|
const setup = createDependencies({
|
|
getAuthPool: () => ({ id: 'pool' }),
|
|
getSessionSnapshotService: () => snapshotService,
|
|
getMemoryV2: () => ({
|
|
async observePersonalMemory(input) {
|
|
calls.push({ kind: 'memory', input });
|
|
},
|
|
}),
|
|
getTkmindProxy: () => ({
|
|
async resolveTarget(sessionId) {
|
|
return `target:${sessionId}`;
|
|
},
|
|
async apiFetchTo() {
|
|
return createUpstream();
|
|
},
|
|
proxySessionEvents(_req, _res, _sessionId, receivedHooks) {
|
|
hooks = receivedHooks;
|
|
},
|
|
}),
|
|
beginSessionPageDelivery(sessionId) {
|
|
calls.push({ kind: 'begin', sessionId });
|
|
},
|
|
endSessionPageDelivery(sessionId) {
|
|
calls.push({ kind: 'end', sessionId });
|
|
},
|
|
async syncPublicHtmlAfterFinishFn(input) {
|
|
calls.push({ kind: 'sync-html', input });
|
|
await input.registerPublicHtmlArtifacts('user-1', {
|
|
sessionId: 'session-1',
|
|
relativePaths: ['public/page.html'],
|
|
});
|
|
return {
|
|
publicHtmlRelativePaths: ['public/page.html'],
|
|
docxSync: { missing: [] },
|
|
};
|
|
},
|
|
async maybeRepairH5HtmlAfterFinishFn(input) {
|
|
calls.push({ kind: 'repair-html', input });
|
|
return { skipped: 'ok' };
|
|
},
|
|
async syncUserGeneratedPages(userId, options) {
|
|
calls.push({ kind: 'sync-pages', userId, options });
|
|
},
|
|
async maybeRepairPageDataAfterFinishFn(input) {
|
|
calls.push({ kind: 'repair-page-data', input });
|
|
return { skipped: 'not_page_data' };
|
|
},
|
|
async preparePageDeliveryContractFn(input) {
|
|
calls.push({ kind: 'prepare-contract', input });
|
|
return {};
|
|
},
|
|
async markPageDeliveryContractReadyFn(input) {
|
|
calls.push({ kind: 'ready', input });
|
|
return true;
|
|
},
|
|
async registerPublicHtmlArtifactsForConversation(input) {
|
|
calls.push({ kind: 'artifacts', input });
|
|
},
|
|
});
|
|
const api = createRouterRecorder();
|
|
attachPortalSessionRoutes(api, setup.dependencies);
|
|
await api.routes.get('GET /sessions/:sessionId/events')(
|
|
createRequest(),
|
|
createResponseRecorder(),
|
|
() => {},
|
|
);
|
|
await hooks.onAfterFinish('session-1', 'user-1');
|
|
|
|
assert.equal(calls[0].kind, 'begin');
|
|
assert.equal(calls.at(-1).kind, 'end');
|
|
assert.deepEqual(
|
|
calls
|
|
.filter((call) =>
|
|
[
|
|
'refresh',
|
|
'sync-html',
|
|
'artifacts',
|
|
'repair-html',
|
|
'sync-pages',
|
|
'repair-page-data',
|
|
'prepare-contract',
|
|
'ready',
|
|
'memory',
|
|
].includes(call.kind),
|
|
)
|
|
.map((call) => call.kind),
|
|
[
|
|
'refresh',
|
|
'sync-html',
|
|
'artifacts',
|
|
'repair-html',
|
|
'sync-pages',
|
|
'repair-page-data',
|
|
'prepare-contract',
|
|
'ready',
|
|
'memory',
|
|
],
|
|
);
|
|
assert.equal(
|
|
calls.find((call) => call.kind === 'repair-page-data').input
|
|
.userText,
|
|
'build page',
|
|
);
|
|
});
|
|
|
|
test('Finish marks streamed HTML contracts ready when final sync omits one path', async () => {
|
|
let hooks = null;
|
|
const readyPaths = [];
|
|
const preparedPaths = [];
|
|
const setup = createDependencies({
|
|
getAuthPool: () => ({ id: 'pool' }),
|
|
getTkmindProxy: () => ({
|
|
async resolveTarget(sessionId) {
|
|
return `target:${sessionId}`;
|
|
},
|
|
async apiFetchTo() {
|
|
return createUpstream();
|
|
},
|
|
proxySessionEvents(_req, _res, _sessionId, receivedHooks) {
|
|
hooks = receivedHooks;
|
|
},
|
|
}),
|
|
collectPublicHtmlWritePathsFn: () => [
|
|
'public/shop-admin.html',
|
|
],
|
|
async preparePageDeliveryContractFn(input) {
|
|
preparedPaths.push(input.relativePath);
|
|
return {};
|
|
},
|
|
async syncPublicHtmlAfterFinishFn() {
|
|
return {
|
|
publicHtmlRelativePaths: ['public/shop.html'],
|
|
docxSync: { missing: [] },
|
|
};
|
|
},
|
|
async markPageDeliveryContractReadyFn(input) {
|
|
readyPaths.push(input.relativePath);
|
|
return true;
|
|
},
|
|
});
|
|
const api = createRouterRecorder();
|
|
attachPortalSessionRoutes(api, setup.dependencies);
|
|
await api.routes.get('GET /sessions/:sessionId/events')(
|
|
createRequest(),
|
|
createResponseRecorder(),
|
|
() => {},
|
|
);
|
|
|
|
hooks.onEvent({
|
|
type: 'Message',
|
|
message: {
|
|
role: 'assistant',
|
|
content: [],
|
|
},
|
|
});
|
|
await hooks.onAfterFinish('session-1', 'user-1');
|
|
|
|
assert.ok(
|
|
preparedPaths.includes('public/shop-admin.html'),
|
|
);
|
|
assert.deepEqual(readyPaths, [
|
|
'public/shop-admin.html',
|
|
'public/shop.html',
|
|
]);
|
|
});
|