125 lines
4.1 KiB
TypeScript
125 lines
4.1 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import {
|
|
clearWorksSquareAIGatewayCredential,
|
|
getFreshWorksSquareAIGatewayCredential,
|
|
getWorksSquareAIGatewaySnapshot,
|
|
markWorksSquareAIGatewayCredentialExpired,
|
|
seedWorksSquareAIGatewayCredential,
|
|
} from '@electron/services/works-square-ai-gateway';
|
|
import {
|
|
clearWorksSquareSession,
|
|
storeWorksSquareSession,
|
|
} from '@electron/services/works-square-session';
|
|
|
|
describe('works-square-ai-gateway service', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(new Date('2026-07-06T08:00:00.000Z'));
|
|
clearWorksSquareAIGatewayCredential();
|
|
clearWorksSquareSession();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
clearWorksSquareAIGatewayCredential();
|
|
clearWorksSquareSession();
|
|
});
|
|
|
|
it('returns a seeded credential while it is outside the refresh skew', async () => {
|
|
seedWorksSquareAIGatewayCredential({
|
|
accessToken: 'seeded-ai-token',
|
|
expiresIn: 3600,
|
|
oneApiBaseUrl: 'https://one-api.example.com/v1/',
|
|
});
|
|
|
|
await expect(getFreshWorksSquareAIGatewayCredential()).resolves.toEqual({
|
|
accessToken: 'seeded-ai-token',
|
|
expiresAt: Date.now() + 3_600_000,
|
|
oneApiBaseUrl: 'https://one-api.example.com/v1',
|
|
});
|
|
expect(getWorksSquareAIGatewaySnapshot()?.accessToken).toBe('seeded-ai-token');
|
|
});
|
|
|
|
it('fetches a new gateway credential when none is cached', async () => {
|
|
storeWorksSquareSession({
|
|
accessToken: 'works-access-token',
|
|
refreshToken: 'works-refresh-token',
|
|
expiresAt: Date.now() + 120_000,
|
|
});
|
|
const fetchImpl = vi.fn().mockResolvedValueOnce(
|
|
new Response(JSON.stringify({
|
|
access_token: 'fresh-ai-token',
|
|
expires_in: 1800,
|
|
one_api_base_url: 'https://one-api.example.com/v1/',
|
|
}), { status: 200 }),
|
|
);
|
|
|
|
await expect(getFreshWorksSquareAIGatewayCredential({ fetchImpl })).resolves.toEqual({
|
|
accessToken: 'fresh-ai-token',
|
|
expiresAt: Date.now() + 1_800_000,
|
|
oneApiBaseUrl: 'https://one-api.example.com/v1',
|
|
});
|
|
|
|
expect(fetchImpl).toHaveBeenCalledWith(
|
|
'https://square.nianxx.cn/api/ai-gateway/session',
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
headers: expect.objectContaining({
|
|
Authorization: 'Bearer works-access-token',
|
|
Accept: 'application/json',
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('uses a single refresh request for parallel callers', async () => {
|
|
storeWorksSquareSession({
|
|
accessToken: 'works-access-token',
|
|
refreshToken: 'works-refresh-token',
|
|
expiresAt: Date.now() + 120_000,
|
|
});
|
|
const fetchImpl = vi.fn().mockResolvedValueOnce(
|
|
new Response(JSON.stringify({
|
|
access_token: 'fresh-ai-token',
|
|
expires_in: 1800,
|
|
one_api_base_url: 'https://one-api.example.com/v1',
|
|
}), { status: 200 }),
|
|
);
|
|
|
|
const [first, second] = await Promise.all([
|
|
getFreshWorksSquareAIGatewayCredential({ fetchImpl }),
|
|
getFreshWorksSquareAIGatewayCredential({ fetchImpl }),
|
|
]);
|
|
|
|
expect(first?.accessToken).toBe('fresh-ai-token');
|
|
expect(second?.accessToken).toBe('fresh-ai-token');
|
|
expect(fetchImpl).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('mark expired forces the next call to refresh the gateway credential', async () => {
|
|
storeWorksSquareSession({
|
|
accessToken: 'works-access-token',
|
|
refreshToken: 'works-refresh-token',
|
|
expiresAt: Date.now() + 120_000,
|
|
});
|
|
seedWorksSquareAIGatewayCredential({
|
|
accessToken: 'old-ai-token',
|
|
expiresIn: 3600,
|
|
oneApiBaseUrl: 'https://one-api.example.com/v1',
|
|
});
|
|
markWorksSquareAIGatewayCredentialExpired();
|
|
const fetchImpl = vi.fn().mockResolvedValueOnce(
|
|
new Response(JSON.stringify({
|
|
access_token: 'fresh-ai-token',
|
|
expires_in: 1800,
|
|
one_api_base_url: 'https://one-api.example.com/v1',
|
|
}), { status: 200 }),
|
|
);
|
|
|
|
await expect(getFreshWorksSquareAIGatewayCredential({ fetchImpl })).resolves.toMatchObject({
|
|
accessToken: 'fresh-ai-token',
|
|
});
|
|
expect(fetchImpl).toHaveBeenCalledOnce();
|
|
});
|
|
});
|