fix(gateway): ignore expected release restart polling errors
This commit is contained in:
@@ -38,6 +38,9 @@ type FixtureState = {
|
|||||||
profileLogsEmpty?: boolean;
|
profileLogsEmpty?: boolean;
|
||||||
gatewayLogPollCount?: number;
|
gatewayLogPollCount?: number;
|
||||||
gatewayLogsEmpty?: boolean;
|
gatewayLogsEmpty?: boolean;
|
||||||
|
gatewayStateFailuresAfterRequest?: number;
|
||||||
|
gatewayStateFailuresRemaining?: number;
|
||||||
|
gatewayStateFailureCount?: number;
|
||||||
capabilities?: Array<{ permission: string; scope: 'GLOBAL' | 'PROFILE'; scopes: string[] }>;
|
capabilities?: Array<{ permission: string; scope: 'GLOBAL' | 'PROFILE'; scopes: string[] }>;
|
||||||
profileListDelayMs?: number;
|
profileListDelayMs?: number;
|
||||||
profileNavigationDelayMs?: number;
|
profileNavigationDelayMs?: number;
|
||||||
@@ -139,6 +142,19 @@ const installFixture = async (page: Page, state: FixtureState) => {
|
|||||||
await route.abort('failed');
|
await route.abort('failed');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
names.includes('admin.releases.gatewayState') &&
|
||||||
|
(state.gatewayStateFailuresRemaining ?? 0) > 0
|
||||||
|
) {
|
||||||
|
state.gatewayStateFailuresRemaining = (state.gatewayStateFailuresRemaining ?? 0) - 1;
|
||||||
|
state.gatewayStateFailureCount = (state.gatewayStateFailureCount ?? 0) + 1;
|
||||||
|
await route.fulfill({
|
||||||
|
status: 502,
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: '',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (names.includes('admin.operations.logs') && !state.profileLogProgress) {
|
if (names.includes('admin.operations.logs') && !state.profileLogProgress) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||||
}
|
}
|
||||||
@@ -337,6 +353,7 @@ const installFixture = async (page: Page, state: FixtureState) => {
|
|||||||
updatedAt: '2026-08-01T02:00:00.000Z',
|
updatedAt: '2026-08-01T02:00:00.000Z',
|
||||||
};
|
};
|
||||||
state.gatewayOperations = [releaseOperation];
|
state.gatewayOperations = [releaseOperation];
|
||||||
|
state.gatewayStateFailuresRemaining = state.gatewayStateFailuresAfterRequest;
|
||||||
return response(releaseOperation);
|
return response(releaseOperation);
|
||||||
}
|
}
|
||||||
if (name === 'admin.operations.requestRuntime') {
|
if (name === 'admin.operations.requestRuntime') {
|
||||||
@@ -813,7 +830,13 @@ test('scenario-only operator resets the current version without Git or Gateway c
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('controls gateway deployment and rollback through the external controller queue', async ({ page }, testInfo) => {
|
test('controls gateway deployment and rollback through the external controller queue', async ({ page }, testInfo) => {
|
||||||
const state: FixtureState = { operations: [], gatewayOperations: [], runtimeRunning: true, requestBodies: [] };
|
const state: FixtureState = {
|
||||||
|
operations: [],
|
||||||
|
gatewayOperations: [],
|
||||||
|
runtimeRunning: true,
|
||||||
|
requestBodies: [],
|
||||||
|
gatewayStateFailuresAfterRequest: 1,
|
||||||
|
};
|
||||||
await installFixture(page, state);
|
await installFixture(page, state);
|
||||||
page.on('dialog', (dialog) => dialog.accept());
|
page.on('dialog', (dialog) => dialog.accept());
|
||||||
|
|
||||||
@@ -826,6 +849,9 @@ test('controls gateway deployment and rollback through the external controller q
|
|||||||
await page.getByTestId('request-gateway-deploy').click();
|
await page.getByTestId('request-gateway-deploy').click();
|
||||||
|
|
||||||
await expect(page.getByText(/Gateway 배포 작업을 등록했습니다/).first()).toBeVisible();
|
await expect(page.getByText(/Gateway 배포 작업을 등록했습니다/).first()).toBeVisible();
|
||||||
|
expect(state.gatewayStateFailureCount).toBe(1);
|
||||||
|
await expect(page.getByTestId('server-operations-page')).not.toContainText('Unexpected end of JSON input');
|
||||||
|
await expect(page.getByTestId('action-toast').filter({ hasText: 'Unexpected end of JSON input' })).toHaveCount(0);
|
||||||
await expect(page.getByTestId('gateway-release-table')).toContainText('DEPLOY');
|
await expect(page.getByTestId('gateway-release-table')).toContainText('DEPLOY');
|
||||||
await expect(page.getByTestId('gateway-release-log-panel')).toBeVisible();
|
await expect(page.getByTestId('gateway-release-log-panel')).toBeVisible();
|
||||||
await expect(page.getByTestId('gateway-release-log')).toContainText('Gateway 구성 요소를 빌드합니다.');
|
await expect(page.getByTestId('gateway-release-log')).toContainText('Gateway 구성 요소를 빌드합니다.');
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ let stateRequestInFlight = false;
|
|||||||
let releaseLogLoopGeneration = 0;
|
let releaseLogLoopGeneration = 0;
|
||||||
let profileLogLoopGeneration = 0;
|
let profileLogLoopGeneration = 0;
|
||||||
let componentMounted = false;
|
let componentMounted = false;
|
||||||
|
let gatewayReleaseTransitionActive = false;
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
sourceMode: (props.mode === 'scenario' ? 'CURRENT' : 'BRANCH') as 'CURRENT' | 'BRANCH' | 'COMMIT',
|
sourceMode: (props.mode === 'scenario' ? 'CURRENT' : 'BRANCH') as 'CURRENT' | 'BRANCH' | 'COMMIT',
|
||||||
@@ -295,6 +296,7 @@ const loadState = async (quiet = false) => {
|
|||||||
const active = gatewayReleaseOperations.value.find((operation) =>
|
const active = gatewayReleaseOperations.value.find((operation) =>
|
||||||
['QUEUED', 'RUNNING'].includes(operation.status)
|
['QUEUED', 'RUNNING'].includes(operation.status)
|
||||||
);
|
);
|
||||||
|
gatewayReleaseTransitionActive = Boolean(active);
|
||||||
if (active && selectedGatewayOperationId.value !== active.id) {
|
if (active && selectedGatewayOperationId.value !== active.id) {
|
||||||
selectedGatewayOperationId.value = active.id;
|
selectedGatewayOperationId.value = active.id;
|
||||||
} else if (
|
} else if (
|
||||||
@@ -321,7 +323,11 @@ const loadState = async (quiet = false) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorMessage.value = error instanceof Error ? error.message : '운영 상태를 불러오지 못했습니다.';
|
// Gateway release가 process를 교체하는 동안에는 background poll의 HTTP 연결이
|
||||||
|
// 일시적으로 끊길 수 있다. 이 경우는 기존 상태를 유지하고 다음 poll에서 복구한다.
|
||||||
|
if (!quiet || props.mode !== 'gateway' || !gatewayReleaseTransitionActive) {
|
||||||
|
errorMessage.value = error instanceof Error ? error.message : '운영 상태를 불러오지 못했습니다.';
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
stateRequestInFlight = false;
|
stateRequestInFlight = false;
|
||||||
@@ -484,6 +490,7 @@ const requestGatewayDeploy = async () => {
|
|||||||
sourceRef: gatewayForm.sourceRef.trim(),
|
sourceRef: gatewayForm.sourceRef.trim(),
|
||||||
reason: gatewayForm.reason.trim() || undefined,
|
reason: gatewayForm.reason.trim() || undefined,
|
||||||
});
|
});
|
||||||
|
gatewayReleaseTransitionActive = true;
|
||||||
selectedGatewayOperationId.value = operation.id;
|
selectedGatewayOperationId.value = operation.id;
|
||||||
message.value = 'Gateway 배포 작업을 등록했습니다. 외부 release-controller가 처리합니다.';
|
message.value = 'Gateway 배포 작업을 등록했습니다. 외부 release-controller가 처리합니다.';
|
||||||
await loadState(true);
|
await loadState(true);
|
||||||
@@ -508,6 +515,7 @@ const requestGatewayRollback = async () => {
|
|||||||
const operation = await adminClient.releases.requestGatewayRollback.mutate({
|
const operation = await adminClient.releases.requestGatewayRollback.mutate({
|
||||||
reason: gatewayForm.reason.trim() || undefined,
|
reason: gatewayForm.reason.trim() || undefined,
|
||||||
});
|
});
|
||||||
|
gatewayReleaseTransitionActive = true;
|
||||||
selectedGatewayOperationId.value = operation.id;
|
selectedGatewayOperationId.value = operation.id;
|
||||||
message.value = 'Gateway rollback 작업을 등록했습니다.';
|
message.value = 'Gateway rollback 작업을 등록했습니다.';
|
||||||
await loadState(true);
|
await loadState(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user