Update Harbor client and gateway integration workflows
Build and Deploy Gateway / build-and-push (push) Failing after 14s
Build and Deploy Gateway / deploy (push) Has been skipped

This commit is contained in:
2026-08-19 18:16:10 +03:00
parent 416b2b294a
commit daec12e013
63 changed files with 5016 additions and 108 deletions
+116
View File
@@ -124,6 +124,8 @@ function createHarness(overrides = {}) {
clearInterval: (timer) => { timer.clearCalls += 1; },
},
onRefreshError: overrides.onRefreshError || (() => {}),
onEvent: overrides.onEvent,
failover: overrides.failover,
now: () => new Date('2026-08-08T12:30:00.000Z'),
});
@@ -305,6 +307,36 @@ test('inactive delete is state-only; applied delete requires one stop-and-delete
assert.equal(after.config, null);
});
test('failed active delete restores the previous reserve selector', async () => {
const state = defaultState();
state.failoverPolicy = {
enabled: true,
paused: false,
primary: { profileId: 'work', serverId: 'shared' },
reserve: { profileId: 'personal', serverId: 'shared' },
};
state.appliedFailoverPolicy = {
primary: state.failoverPolicy.primary,
reserve: state.failoverPolicy.reserve,
};
const harness = createHarness({
state,
failStateAt: 1,
failover: {
reconcile: async () => {},
restoreAppliedActivation: async (previousState) => {
assert.equal(previousState.appliedProfileId, 'personal');
harness.calls.push('selector.reserve');
},
},
});
await assert.rejects(harness.service.deleteProfile('personal', 'stop-and-delete'), /state failed/);
assert.equal(harness.snapshot().running, true);
assert.equal(harness.snapshot().config, 'old-config');
assert.equal(harness.snapshot().state.appliedProfileId, 'personal');
assert.ok(harness.calls.indexOf('selector.reserve') > harness.calls.indexOf('runtime.start'));
});
test('deleting a pending desired profile leaves the running applied route untouched', async () => {
const state = defaultState();
state.desiredProfileId = 'work';
@@ -317,6 +349,90 @@ test('deleting a pending desired profile leaves the running applied route untouc
assert.equal(harness.calls.includes('gateway.set'), false);
});
test('deleting a desired failover target disables only the pending policy', async () => {
const state = defaultState();
state.failoverPolicy = {
enabled: true,
paused: false,
primary: { profileId: 'work', serverId: 'shared' },
reserve: { profileId: 'personal', serverId: 'shared' },
};
const harness = createHarness({ state });
await harness.service.deleteProfile('work', 'delete', 3);
const after = harness.snapshot();
assert.equal(after.running, true);
assert.equal(after.config, 'old-config');
assert.equal(after.state.failoverPolicy.enabled, false);
assert.deepEqual(after.state.failoverPolicy.primary, { profileId: '', serverId: '' });
assert.deepEqual(after.state.failoverPolicy.reserve, { profileId: 'personal', serverId: 'shared' });
});
test('refresh pauses failover when a loaded channel target disappears', async () => {
const state = defaultState();
state.failoverPolicy = {
enabled: true,
paused: false,
primary: { profileId: 'personal', serverId: 'shared' },
reserve: { profileId: 'work', serverId: 'shared' },
};
state.appliedFailoverPolicy = {
primary: state.failoverPolicy.primary,
reserve: state.failoverPolicy.reserve,
primaryConfigFingerprint: 'a'.repeat(64),
reserveConfigFingerprint: 'b'.repeat(64),
};
const events = [];
const harness = createHarness({ state, onEvent: (event) => events.push(event) });
await harness.service.refreshProfile('personal');
assert.equal(harness.snapshot().state.failoverPolicy.paused, true);
assert.equal(harness.snapshot().running, true);
assert.equal(harness.snapshot().config, 'old-config');
assert.ok(events.some(({ type }) => type === 'failover.paused'));
});
test('scheduled refresh failures use one stable key for the same failure streak', async () => {
const failure = Object.assign(new Error('down'), { code: 'PROVIDER_UNAVAILABLE' });
const events = [];
const harness = createHarness({
fetchSubscription: async () => { throw failure; },
onEvent: (event) => events.push(event),
});
await assert.rejects(harness.service.refreshProfile('personal', undefined, 'scheduled'));
await assert.rejects(harness.service.refreshProfile('personal', undefined, 'scheduled'));
const keys = events.filter(({ type }) => type === 'subscription.refresh_failed').map(({ dedupeKey }) => dedupeKey);
assert.equal(keys.length, 2);
assert.equal(keys[0], keys[1]);
});
test('scheduled success logs only content changes or recovery while manual refresh always logs', async () => {
const unchanged = (id) => ({
config: { profile: id },
servers: [oldServer],
userInfo: { total: 100 },
fetchedAt: '2026-08-08T12:00:00.000Z',
});
const events = [];
const harness = createHarness({
fetchSubscription: async () => unchanged('work'),
onEvent: (event) => events.push(event),
});
await harness.service.refreshProfile('work', undefined, 'scheduled');
assert.equal(events.length, 0);
await harness.service.refreshProfile('work');
assert.deepEqual(events.map(({ type }) => type), ['subscription.refreshed']);
const recoveryState = defaultState();
recoveryState.profiles[1].lastRefreshErrorCode = 'PROVIDER_UNAVAILABLE';
const recoveryEvents = [];
const recovery = createHarness({
state: recoveryState,
fetchSubscription: async () => unchanged('work'),
onEvent: (event) => recoveryEvents.push(event),
});
await recovery.service.refreshProfile('work', undefined, 'scheduled');
assert.deepEqual(recoveryEvents.map(({ type }) => type), ['subscription.refreshed']);
});
test('auto refresh iterates profiles once, reports scoped failures, and stops idempotently', async () => {
const errors = [];
const failure = Object.assign(new Error('down'), { code: 'PROVIDER_UNAVAILABLE' });