fix(gateway): split profile identity from scenario
This commit is contained in:
@@ -85,6 +85,8 @@ const buildCaller = async (
|
||||
const profile = {
|
||||
profileName: 'che:2',
|
||||
profile: 'che',
|
||||
instanceKey: '2',
|
||||
currentScenario: options.profileScenario ?? '2',
|
||||
scenario: options.profileScenario ?? '2',
|
||||
apiPort: 15003,
|
||||
status: options.initialProfileStatus ?? ('STOPPED' as const),
|
||||
@@ -98,7 +100,7 @@ const buildCaller = async (
|
||||
listProfiles: async () => [profile],
|
||||
getProfile: async () => profile,
|
||||
upsertProfile: async () => profile,
|
||||
updateScenario: async () => profile,
|
||||
updateCurrentScenario: async () => profile,
|
||||
updateStatus: async (_profileName, status) => {
|
||||
updatedStatuses.push(status);
|
||||
return { ...profile, status };
|
||||
@@ -362,6 +364,8 @@ describe('admin profile navigation API', () => {
|
||||
{
|
||||
profileName: 'che:2',
|
||||
profile: 'che',
|
||||
instanceKey: '2',
|
||||
currentScenario: '2',
|
||||
meta: {},
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -15,6 +15,8 @@ import { appRouter } from '../src/router.js';
|
||||
const profile = {
|
||||
profileName: 'che:default',
|
||||
profile: 'che',
|
||||
instanceKey: 'default',
|
||||
currentScenario: null,
|
||||
scenario: 'default',
|
||||
apiPort: 15003,
|
||||
status: 'RUNNING' as const,
|
||||
@@ -28,7 +30,7 @@ const profiles: GatewayProfileRepository = {
|
||||
listProfiles: async () => [profile],
|
||||
getProfile: async (profileName) => (profileName === profile.profileName ? profile : null),
|
||||
upsertProfile: async () => profile,
|
||||
updateScenario: async () => profile,
|
||||
updateCurrentScenario: async () => profile,
|
||||
updateStatus: async () => profile,
|
||||
updateBuildStatus: async () => profile,
|
||||
updateMeta: async () => profile,
|
||||
|
||||
@@ -92,6 +92,8 @@ const buildCaller = (
|
||||
{
|
||||
profileName: 'che:default',
|
||||
profile: 'che',
|
||||
instanceKey: 'default',
|
||||
currentScenario: null,
|
||||
scenario: 'default',
|
||||
apiPort: 15003,
|
||||
status: 'RUNNING' as const,
|
||||
@@ -103,6 +105,8 @@ const buildCaller = (
|
||||
{
|
||||
profileName: 'hwe:default',
|
||||
profile: 'hwe',
|
||||
instanceKey: 'default',
|
||||
currentScenario: null,
|
||||
scenario: 'default',
|
||||
apiPort: 15015,
|
||||
status: 'RUNNING' as const,
|
||||
@@ -119,7 +123,7 @@ const buildCaller = (
|
||||
upsertProfile: async () => {
|
||||
throw new Error('not used');
|
||||
},
|
||||
updateScenario: async () => null,
|
||||
updateCurrentScenario: async () => null,
|
||||
updateStatus: async () => null,
|
||||
updateBuildStatus: async () => null,
|
||||
updateMeta: async () => null,
|
||||
@@ -167,6 +171,8 @@ const buildCaller = (
|
||||
profileRows.map((profile) => ({
|
||||
profileName: profile.profileName,
|
||||
profile: profile.profile,
|
||||
instanceKey: profile.instanceKey,
|
||||
currentScenario: profile.currentScenario,
|
||||
scenario: profile.scenario,
|
||||
status: profile.status,
|
||||
apiPort: profile.apiPort,
|
||||
|
||||
@@ -13,6 +13,8 @@ import { GitWorkspaceManager } from '../src/orchestrator/workspaceManager.js';
|
||||
const profile: GatewayProfileRecord = {
|
||||
profileName: 'che:2',
|
||||
profile: 'che',
|
||||
instanceKey: '2',
|
||||
currentScenario: '2',
|
||||
scenario: '2',
|
||||
apiPort: 15003,
|
||||
status: 'STOPPED',
|
||||
@@ -58,7 +60,7 @@ const createHarness = (
|
||||
listProfiles: async () => [profile],
|
||||
getProfile: async () => profile,
|
||||
upsertProfile: async () => profile,
|
||||
updateScenario: async () => profile,
|
||||
updateCurrentScenario: async () => profile,
|
||||
updateStatus: async (_profileName, status) => {
|
||||
statuses.push(status);
|
||||
return { ...profile, status };
|
||||
|
||||
@@ -14,6 +14,8 @@ import type { GatewayProfileRecord } from '../src/orchestrator/profileRepository
|
||||
const buildProfile = (buildWorkspace?: string): GatewayProfileRecord => ({
|
||||
profileName: 'che:2',
|
||||
profile: 'che',
|
||||
instanceKey: '2',
|
||||
currentScenario: '2',
|
||||
scenario: '2',
|
||||
apiPort: 15003,
|
||||
status: 'RUNNING',
|
||||
@@ -172,6 +174,39 @@ describe('buildProcessDefinitions', () => {
|
||||
expect(definitions.tournament.cwd).toBe(path.join(processConfig.workspaceRoot, 'app', 'game-api'));
|
||||
});
|
||||
|
||||
it('keeps the instance identity stable while passing the mutable current scenario', () => {
|
||||
const definitions = buildProcessDefinitions(
|
||||
{
|
||||
...buildProfile(),
|
||||
profileName: 'che:default',
|
||||
instanceKey: 'default',
|
||||
currentScenario: '1010',
|
||||
scenario: '1010',
|
||||
},
|
||||
processConfig
|
||||
);
|
||||
|
||||
expect(definitions.api.name).toBe('sammo:che:default:game-api');
|
||||
expect(definitions.api.env).toMatchObject({
|
||||
GAME_PROFILE_NAME: 'che:default',
|
||||
SCENARIO: '1010',
|
||||
});
|
||||
expect(definitions.daemon.env).toMatchObject({
|
||||
TURN_PROFILE_NAME: 'che:default',
|
||||
SCENARIO: '1010',
|
||||
});
|
||||
});
|
||||
|
||||
it('uses the legacy default scenario marker only for an uninitialized instance runtime', () => {
|
||||
const definitions = buildProcessDefinitions(
|
||||
{ ...buildProfile(), currentScenario: null, scenario: 'default' },
|
||||
processConfig
|
||||
);
|
||||
|
||||
expect(definitions.api.env.SCENARIO).toBe('default');
|
||||
expect(definitions.daemon.env.SCENARIO).toBe('default');
|
||||
});
|
||||
|
||||
it('does not forward PM2 identity or parent runtime roles to profile processes', () => {
|
||||
const definitions = buildProcessDefinitions(buildProfile(), {
|
||||
...processConfig,
|
||||
|
||||
@@ -14,6 +14,8 @@ const makeProfile = (
|
||||
): GatewayProfileRecord => ({
|
||||
profileName,
|
||||
profile: profileName.split(':')[0] ?? 'che',
|
||||
instanceKey: profileName.split(':')[1] ?? 'default',
|
||||
currentScenario: null,
|
||||
scenario: profileName.split(':')[1] ?? 'default',
|
||||
apiPort: 15_003,
|
||||
status: 'RUNNING',
|
||||
|
||||
@@ -50,6 +50,8 @@ describe('profile DEPLOY operation', () => {
|
||||
const profile: GatewayProfileRecord = {
|
||||
profileName: 'che:1010',
|
||||
profile: 'che',
|
||||
instanceKey: '1010',
|
||||
currentScenario: '1010',
|
||||
scenario: '1010',
|
||||
apiPort: 15003,
|
||||
status: 'RUNNING',
|
||||
@@ -80,7 +82,7 @@ describe('profile DEPLOY operation', () => {
|
||||
listProfiles: async () => [profile],
|
||||
getProfile: async () => profile,
|
||||
upsertProfile: async () => profile,
|
||||
updateScenario: async () => profile,
|
||||
updateCurrentScenario: async () => profile,
|
||||
updateStatus: async () => profile,
|
||||
updateBuildStatus: async () => profile,
|
||||
updateMeta: async () => profile,
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
buildGatewayProfileName,
|
||||
resolveGatewayProfileIdentity,
|
||||
type GatewayProfileUpsertInput,
|
||||
} from '../src/orchestrator/profileRepository.js';
|
||||
|
||||
const resolve = (input: Partial<GatewayProfileUpsertInput>) =>
|
||||
resolveGatewayProfileIdentity({
|
||||
profile: 'che',
|
||||
apiPort: 15003,
|
||||
...input,
|
||||
});
|
||||
|
||||
describe('Gateway profile identity', () => {
|
||||
it('builds the immutable technical id from profile and instance key', () => {
|
||||
expect(buildGatewayProfileName('che', 'default')).toBe('che:default');
|
||||
});
|
||||
|
||||
it('does not treat a new default instance as an initialized scenario', () => {
|
||||
expect(resolve({ instanceKey: 'default' })).toEqual({
|
||||
instanceKey: 'default',
|
||||
currentScenario: null,
|
||||
shouldUpdateCurrentScenario: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts the old bootstrap default marker without clearing an existing scenario on upsert', () => {
|
||||
expect(resolve({ scenario: 'default' })).toEqual({
|
||||
instanceKey: 'default',
|
||||
currentScenario: null,
|
||||
shouldUpdateCurrentScenario: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('maps a legacy non-default scenario to both identity and current state', () => {
|
||||
expect(resolve({ scenario: '2' })).toEqual({
|
||||
instanceKey: '2',
|
||||
currentScenario: '2',
|
||||
shouldUpdateCurrentScenario: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps a default instance stable when its current scenario changes', () => {
|
||||
expect(resolve({ instanceKey: 'default', currentScenario: '1010' })).toEqual({
|
||||
instanceKey: 'default',
|
||||
currentScenario: '1010',
|
||||
shouldUpdateCurrentScenario: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,25 +6,25 @@ describe('orderGatewayProfiles', () => {
|
||||
it('uses the public server order instead of alphabetical profile order', () => {
|
||||
const profiles = ['hwe', 'pya', 'che', 'nya', 'twe', 'pwe', 'kwe'].map((profile) => ({
|
||||
profile,
|
||||
scenario: 'default',
|
||||
instanceKey: 'default',
|
||||
}));
|
||||
|
||||
expect(orderGatewayProfiles(profiles).map(({ profile }) => profile)).toEqual(GATEWAY_PROFILE_ORDER);
|
||||
});
|
||||
|
||||
it('orders scenarios within a profile and places unknown profiles afterward', () => {
|
||||
it('orders instance keys within a profile and places unknown profiles afterward', () => {
|
||||
const profiles = [
|
||||
{ profile: 'zeta', scenario: 'default' },
|
||||
{ profile: 'che', scenario: '20' },
|
||||
{ profile: 'alpha', scenario: 'default' },
|
||||
{ profile: 'che', scenario: '10' },
|
||||
{ profile: 'zeta', instanceKey: 'default' },
|
||||
{ profile: 'che', instanceKey: '20' },
|
||||
{ profile: 'alpha', instanceKey: 'default' },
|
||||
{ profile: 'che', instanceKey: '10' },
|
||||
];
|
||||
|
||||
expect(orderGatewayProfiles(profiles)).toEqual([
|
||||
{ profile: 'che', scenario: '10' },
|
||||
{ profile: 'che', scenario: '20' },
|
||||
{ profile: 'alpha', scenario: 'default' },
|
||||
{ profile: 'zeta', scenario: 'default' },
|
||||
{ profile: 'che', instanceKey: '10' },
|
||||
{ profile: 'che', instanceKey: '20' },
|
||||
{ profile: 'alpha', instanceKey: 'default' },
|
||||
{ profile: 'zeta', instanceKey: 'default' },
|
||||
]);
|
||||
expect(profiles[0]?.profile).toBe('zeta');
|
||||
});
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('readReleaseManifest', () => {
|
||||
|
||||
await expect(readReleaseManifest(workspaceRoot)).resolves.toMatchObject({
|
||||
controllerProtocol: RELEASE_CONTROLLER_PROTOCOL,
|
||||
gatewaySchemaHead: '20260811000000_add_gateway_operation_logs',
|
||||
gatewaySchemaHead: '20260813000000_split_gateway_profile_identity',
|
||||
gameSchemaHead: '20260803000000_add_logical_game_clock',
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user