feat: 게이트웨이 시계 생명주기 권위 연결
This commit is contained in:
@@ -87,6 +87,7 @@ const buildCaller = async (
|
||||
const updatedStatuses: GatewayProfileRecord['status'][] = [];
|
||||
const updatedMetas: Record<string, unknown>[] = [];
|
||||
const auditEvents: AdminAuditEventRecord[] = [];
|
||||
const lifecycle: string[] = [];
|
||||
let reconcileCount = 0;
|
||||
let runtimeStateListCount = 0;
|
||||
let storedNotice = options.initialNotice ?? '';
|
||||
@@ -111,6 +112,7 @@ const buildCaller = async (
|
||||
updateCurrentScenario: async () => profile,
|
||||
updateStatus: async (_profileName, status) => {
|
||||
updatedStatuses.push(status);
|
||||
lifecycle.push(`status:${status}`);
|
||||
return { ...profile, status };
|
||||
},
|
||||
updateBuildStatus: async () => profile,
|
||||
@@ -288,6 +290,7 @@ const buildCaller = async (
|
||||
stop: async () => {},
|
||||
reconcileNow: async () => {
|
||||
reconcileCount += 1;
|
||||
lifecycle.push('runtime:reconcile');
|
||||
},
|
||||
runScheduleNow: async () => {},
|
||||
runBuildQueueNow: async () => {},
|
||||
@@ -297,6 +300,13 @@ const buildCaller = async (
|
||||
}
|
||||
},
|
||||
cleanupStaleWorkspaces: async () => ({ removed: [], skipped: [] }),
|
||||
transitionProfileClock: async (_profileName, action) => {
|
||||
lifecycle.push(`clock:${action}`);
|
||||
return {
|
||||
phase: action === 'SUSPEND' ? 'SUSPENDED' : 'RUNNING',
|
||||
revision: 1,
|
||||
};
|
||||
},
|
||||
listRuntimeSettings: async () => [
|
||||
{
|
||||
profileName: 'che:2',
|
||||
@@ -363,6 +373,7 @@ const buildCaller = async (
|
||||
updatedStatuses,
|
||||
updatedMetas,
|
||||
auditEvents,
|
||||
lifecycle,
|
||||
getReconcileCount: () => reconcileCount,
|
||||
getRuntimeStateListCount: () => runtimeStateListCount,
|
||||
getStoredNotice: () => storedNotice,
|
||||
@@ -1384,6 +1395,7 @@ describe('admin runtime clock action API', () => {
|
||||
})
|
||||
).resolves.toMatchObject({ ok: true });
|
||||
expect(harness.updatedStatuses).toEqual(['RUNNING']);
|
||||
expect(harness.lifecycle).toEqual(['clock:RESUME', 'status:RUNNING', 'runtime:reconcile']);
|
||||
expect(harness.getReconcileCount()).toBe(1);
|
||||
expect(harness.updatedMetas).toHaveLength(2);
|
||||
expect(harness.updatedMetas.at(-1)).toMatchObject({
|
||||
@@ -1410,6 +1422,11 @@ describe('admin runtime clock action API', () => {
|
||||
});
|
||||
|
||||
expect(harness.updatedStatuses).toEqual([expectedStatus]);
|
||||
expect(harness.lifecycle).toEqual(
|
||||
action === 'STOP'
|
||||
? ['clock:SUSPEND', `status:${expectedStatus}`, 'runtime:reconcile']
|
||||
: [`status:${expectedStatus}`, 'runtime:reconcile']
|
||||
);
|
||||
expect(harness.getReconcileCount()).toBe(1);
|
||||
expect(harness.updatedMetas).toHaveLength(2);
|
||||
expect(harness.updatedMetas[0]).toMatchObject({
|
||||
|
||||
@@ -123,6 +123,10 @@ const createHarness = async (adminRoles = ['user', 'admin.users.manage', 'admin.
|
||||
runBuildQueueNow: async () => {},
|
||||
runOperationsNow: async () => {},
|
||||
cleanupStaleWorkspaces: async () => ({ removed: [], skipped: [] }),
|
||||
transitionProfileClock: async (_profileName, action) => ({
|
||||
phase: action === 'SUSPEND' ? 'SUSPENDED' : 'RUNNING',
|
||||
revision: 1,
|
||||
}),
|
||||
listRuntimeStates: async () => [],
|
||||
},
|
||||
profileStatus: new InMemoryProfileStatusService(),
|
||||
@@ -183,9 +187,7 @@ describe('admin security over HTTP transport', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const mutationInput = encodeURIComponent(
|
||||
JSON.stringify({ json: { sessionToken: harness.adminSessionToken } })
|
||||
);
|
||||
const mutationInput = encodeURIComponent(JSON.stringify({ json: { sessionToken: harness.adminSessionToken } }));
|
||||
const mutation = await fetch(`${harness.baseUrl}/trpc/auth.logout?input=${mutationInput}`);
|
||||
expect(mutation.status).toBe(405);
|
||||
expect(await mutation.json()).toMatchObject({
|
||||
|
||||
@@ -168,6 +168,10 @@ const buildCaller = (
|
||||
removed: [],
|
||||
skipped: [],
|
||||
}),
|
||||
transitionProfileClock: async (_profileName: string, action: 'SUSPEND' | 'RESUME') => ({
|
||||
phase: action === 'SUSPEND' ? 'SUSPENDED' : 'RUNNING',
|
||||
revision: 1,
|
||||
}),
|
||||
listRuntimeStates: async () => [],
|
||||
};
|
||||
const profileStatus = new InMemoryProfileStatusService(
|
||||
|
||||
@@ -65,6 +65,7 @@ const createHarness = (
|
||||
frontendServeMode?: 'static';
|
||||
frontendArtifactRoot?: string;
|
||||
activeOperationProfileNames?: string[];
|
||||
promoteProfileOpening?: GatewayOrchestratorOptions['promoteProfileOpening'];
|
||||
} = {}
|
||||
) => {
|
||||
const harnessProfile = options.profile ?? profile;
|
||||
@@ -77,6 +78,7 @@ const createHarness = (
|
||||
const deleted: string[] = [];
|
||||
const buildStatuses: string[] = [];
|
||||
const logs: Array<{ phase: string; message: string; level: string }> = [];
|
||||
const lifecycle: string[] = [];
|
||||
|
||||
const repository: GatewayProfileRepository = {
|
||||
listProfiles: async () => options.profiles ?? [harnessProfile],
|
||||
@@ -85,6 +87,7 @@ const createHarness = (
|
||||
updateCurrentScenario: async () => harnessProfile,
|
||||
updateStatus: async (_profileName, status) => {
|
||||
statuses.push(status);
|
||||
lifecycle.push(`status:${status}`);
|
||||
return { ...harnessProfile, status };
|
||||
},
|
||||
updateBuildStatus: async (_profileName, status) => {
|
||||
@@ -192,9 +195,26 @@ const createHarness = (
|
||||
adminActionIntervalMs: 60_000,
|
||||
now: options.now,
|
||||
cancelGame: options.cancelGame,
|
||||
promoteProfileOpening: options.promoteProfileOpening
|
||||
? async (openingProfile) => {
|
||||
lifecycle.push(`clock:${openingProfile.profileName}`);
|
||||
await options.promoteProfileOpening?.(openingProfile);
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
|
||||
return { orchestrator, statuses, buildStatuses, completions, completionFields, started, stopped, deleted, logs };
|
||||
return {
|
||||
orchestrator,
|
||||
statuses,
|
||||
buildStatuses,
|
||||
completions,
|
||||
completionFields,
|
||||
started,
|
||||
stopped,
|
||||
deleted,
|
||||
logs,
|
||||
lifecycle,
|
||||
};
|
||||
};
|
||||
|
||||
describe('GatewayOrchestrator first-class operations', () => {
|
||||
@@ -333,12 +353,14 @@ describe('GatewayOrchestrator first-class operations', () => {
|
||||
profiles: [],
|
||||
reservedToStart: [reservedProfile],
|
||||
now: () => now,
|
||||
promoteProfileOpening: async () => {},
|
||||
});
|
||||
|
||||
await harness.orchestrator.runScheduleNow();
|
||||
|
||||
expect(harness.statuses).toEqual(['RUNNING']);
|
||||
expect(harness.buildStatuses).toEqual([]);
|
||||
expect(harness.lifecycle).toEqual([`clock:${reservedProfile.profileName}`, 'status:RUNNING']);
|
||||
});
|
||||
|
||||
it('retains the legacy build queue for an unprepared reserved profile', async () => {
|
||||
@@ -366,6 +388,26 @@ describe('GatewayOrchestrator first-class operations', () => {
|
||||
expect(harness.buildStatuses).toEqual(['QUEUED']);
|
||||
});
|
||||
|
||||
it('promotes the durable clock before a prepared preopen profile becomes running', async () => {
|
||||
const now = new Date('2030-01-01T02:00:00.000Z');
|
||||
const preopenProfile: GatewayProfileRecord = {
|
||||
...profile,
|
||||
status: 'PREOPEN',
|
||||
openAt: now.toISOString(),
|
||||
preopenAt: '2030-01-01T01:00:00.000Z',
|
||||
};
|
||||
const harness = createHarness(buildOperation('START'), false, false, false, false, undefined, undefined, {
|
||||
profile: preopenProfile,
|
||||
profiles: [preopenProfile],
|
||||
now: () => now,
|
||||
promoteProfileOpening: async () => {},
|
||||
});
|
||||
|
||||
await harness.orchestrator.runScheduleNow();
|
||||
|
||||
expect(harness.lifecycle).toEqual([`clock:${preopenProfile.profileName}`, 'status:RUNNING']);
|
||||
});
|
||||
|
||||
it('starts every profile process and records success', async () => {
|
||||
const harness = createHarness(buildOperation('START'));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user