mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-10-16 14:57:05 +08:00
- 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
61 lines
2.4 KiB
TypeScript
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');
|
|
}
|
|
});
|
|
}
|
|
});
|