Files
Cli-Proxy-API-Management-Ce…/tests/oauthRequestCancellation.test.ts
Supra4E8C 1f5fd846cb feat(oauth): redesign login page as a provider gallery with a focused flow dialog
- Replace the full-width card list with a compact tile grid; tiles show live
  login status (waiting / added / failed) while the dialog is closed
- Clicking a tile starts the login and opens a step-based dialog: auth link
  with highlighted host and inline copy feedback, device code, optional
  manual callback with inline validation, and a live status bar
- Keep success visible until the dialog closes; skip duplicate toasts while
  the provider's dialog already shows the result
- Move Vertex JSON import into the same tile + dialog pattern with drag-and-drop
- Move the page into src/features/oauth and extract the flow state machine
  into useOAuthFlows without changing attempt/cancellation semantics
- Fix xAI callback parsing treating "code: xyz" as an absolute URL
- Add the new keys to all six locales, fill missing zh-TW keys, and drop
  keys orphaned by the redesign
2026-10-09 19:52:23 +08:00

61 lines
2.4 KiB
TypeScript

import { describe, expect, test } from 'bun:test';
import { AxiosError, type AxiosResponse } from 'axios';
import { apiClient } from '@/services/api/client';
import { createOAuthAttempts } from '@/features/oauth/oauthAttempts';
// Use the real Axios interceptor chain with a deferred transport response.
// Abandoned requests must not emit old server metadata or log out a new session.
describe('OAuth transport cancellation', () => {
for (const status of [200, 401]) {
test(`suppresses stale response side effects (${status})`, async () => {
const originalWindow = Object.getOwnPropertyDescriptor(globalThis, 'window');
const events: string[] = [];
Object.defineProperty(globalThis, 'window', {
configurable: true,
value: { dispatchEvent: (event: Event) => events.push(event.type) },
});
let dispatched!: () => void;
const started = new Promise<void>((resolve) => {
dispatched = resolve;
});
let deliver!: () => void;
const attempts = createOAuthAttempts({ setTimeout: () => 0, clearTimeout: () => {} });
const attempt = attempts.begin('devin');
try {
const request = apiClient.get('/oauth/status', {
signal: attempt.signal,
adapter: (config) =>
new Promise<AxiosResponse>((resolve, reject) => {
deliver = () => {
const response: AxiosResponse = {
config,
data: { status: 'ok' },
status,
statusText: String(status),
headers: { 'X-CPA-Version': 'old-server', 'X-CPA-Support-Plugin': 'true' },
};
if (status === 401)
reject(
new AxiosError('Unauthorized', 'ERR_BAD_REQUEST', config, undefined, response)
);
else resolve(response);
};
dispatched();
}),
});
await started;
attempts.invalidateAll();
const current = attempts.begin('devin');
deliver();
await expect(request).rejects.toHaveProperty('code', 'ERR_CANCELED');
expect(events).toEqual([]);
expect(current.signal.aborted).toBe(false);
} finally {
attempts.invalidateAll();
if (originalWindow) Object.defineProperty(globalThis, 'window', originalWindow);
else Reflect.deleteProperty(globalThis, 'window');
}
});
}
});