import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { VergeApiClient } from '../../src/client/api';
describe('VergeApiClient - タイムアウト処理', () => {
let client: VergeApiClient;
let fetchMock: ReturnType<typeof vi.fn>;
beforeEach(() => {
fetchMock = vi.fn();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('デフォルトタイムアウト', () => {
it('タイムアウトが設定されていない場合、タイムアウトしない', async () => {
const mockResponse = {
token: 'mock-token',
user: {
id: 1,
email: 'test@example.com',
name: 'Test User',
role: 'admin',
password_change_required: false,
created_at: '2024-01-01T00:00:00.000Z',
},
};
client = new VergeApiClient({
baseUrl: 'https://api.example.com',
fetchFn: fetchMock,
// timeout未設定
});
client.setToken('mock-token');
fetchMock.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => mockResponse,
});
const result = await client.login('test@example.com', 'password123');
expect(result).toEqual(mockResponse);
expect(fetchMock).toHaveBeenCalledWith(
'https://api.example.com/api/auth/login',
expect.objectContaining({
signal: expect.any(AbortSignal),
})
);
});
});
describe('カスタムタイムアウト設定', () => {
it(
'タイムアウト時間を過ぎるとエラーを投げる',
async () => {
client = new VergeApiClient({
baseUrl: 'https://api.example.com',
fetchFn: fetchMock,
timeout: 100, // 100ms
});
client.setToken('mock-token');
// AbortSignalを尊重する fetchMock
fetchMock.mockImplementation(
(url: string, options?: RequestInit) =>
new Promise((resolve, reject) => {
const signal = options?.signal;
if (signal) {
signal.addEventListener('abort', () => {
reject(new DOMException('The operation was aborted', 'AbortError'));
});
}
// 永遠に resolve しない
})
);
await expect(client.login('test@example.com', 'password123')).rejects.toThrow(
/timeout|timed out/i
);
},
2000
); // テストタイムアウト: 2秒
it('タイムアウト前にレスポンスが返れば正常に完了する', async () => {
const mockResponse = {
token: 'new-mock-token',
user: {
id: 1,
email: 'test@example.com',
name: 'Test User',
role: 'admin',
password_change_required: false,
created_at: '2024-01-01T00:00:00.000Z',
},
};
client = new VergeApiClient({
baseUrl: 'https://api.example.com',
fetchFn: fetchMock,
timeout: 5000, // 5秒
});
client.setToken('mock-token');
fetchMock.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => mockResponse,
});
const result = await client.login('test@example.com', 'password123');
expect(result).toEqual(mockResponse);
});
it('異なるタイムアウト値でカスタマイズできる', async () => {
const mockResponse = {
token: 'mock-token',
user: {
id: 1,
email: 'test@example.com',
name: 'Test User',
role: 'admin',
password_change_required: false,
created_at: '2024-01-01T00:00:00.000Z',
},
};
client = new VergeApiClient({
baseUrl: 'https://api.example.com',
fetchFn: fetchMock,
timeout: 10000, // 10秒
});
client.setToken('mock-token');
fetchMock.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => mockResponse,
});
const result = await client.login('test@example.com', 'password123');
expect(result).toEqual(mockResponse);
});
});
describe('AbortController', () => {
it('AbortSignalがfetchに渡される', async () => {
let capturedSignal: AbortSignal | undefined;
const customFetchMock = vi.fn((url: string, options?: RequestInit) => {
capturedSignal = options?.signal as AbortSignal | undefined;
return Promise.resolve({
ok: true,
status: 200,
json: async () => ({
token: 'mock-token',
user: {
id: 1,
email: 'test@example.com',
name: 'Test User',
role: 'admin',
password_change_required: false,
created_at: '2024-01-01T00:00:00.000Z',
},
}),
});
});
client = new VergeApiClient({
baseUrl: 'https://api.example.com',
fetchFn: customFetchMock,
timeout: 3000, // 3秒
});
client.setToken('mock-token');
await client.login('test@example.com', 'password123');
// AbortSignalが渡されたことを確認
expect(capturedSignal).toBeDefined();
expect(capturedSignal).toBeInstanceOf(AbortSignal);
});
});
});