import { describe, it, expect } from 'vitest';
import { PlumeApiError, PlumeErrorType } from '../../src/client/errors';
describe('PlumeApiError', () => {
describe('基本的なエラー作成', () => {
it('最小限の情報でエラーを作成できる', () => {
const error = new PlumeApiError({
type: PlumeErrorType.NETWORK,
message: 'Network error occurred',
});
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(PlumeApiError);
expect(error.message).toBe('Network error occurred');
expect(error.type).toBe(PlumeErrorType.NETWORK);
expect(error.retryCount).toBe(0);
expect(error.statusCode).toBeUndefined();
expect(error.responseBody).toBeUndefined();
expect(error.endpoint).toBeUndefined();
expect(error.cause).toBeUndefined();
});
it('全てのオプションを指定してエラーを作成できる', () => {
const originalError = new Error('Original error');
const error = new PlumeApiError({
type: PlumeErrorType.API,
message: 'API error occurred',
statusCode: 404,
responseBody: { error: 'Not found' },
retryCount: 3,
endpoint: '/api/articles/123',
cause: originalError,
});
expect(error.message).toBe('API error occurred');
expect(error.type).toBe(PlumeErrorType.API);
expect(error.statusCode).toBe(404);
expect(error.responseBody).toEqual({ error: 'Not found' });
expect(error.retryCount).toBe(3);
expect(error.endpoint).toBe('/api/articles/123');
expect(error.cause).toBe(originalError);
});
});
describe('エラータイプ別', () => {
it('NETWORK エラーを作成できる', () => {
const error = new PlumeApiError({
type: PlumeErrorType.NETWORK,
message: 'Failed to fetch',
});
expect(error.type).toBe(PlumeErrorType.NETWORK);
});
it('TIMEOUT エラーを作成できる', () => {
const error = new PlumeApiError({
type: PlumeErrorType.TIMEOUT,
message: 'Request timed out',
endpoint: '/api/auth/login',
});
expect(error.type).toBe(PlumeErrorType.TIMEOUT);
expect(error.endpoint).toBe('/api/auth/login');
});
it('API エラーを作成できる (HTTPステータスコード付き)', () => {
const error = new PlumeApiError({
type: PlumeErrorType.API,
message: 'Unauthorized',
statusCode: 401,
responseBody: { error: 'Invalid token' },
});
expect(error.type).toBe(PlumeErrorType.API);
expect(error.statusCode).toBe(401);
expect(error.responseBody).toEqual({ error: 'Invalid token' });
});
it('VALIDATION エラーを作成できる', () => {
const error = new PlumeApiError({
type: PlumeErrorType.VALIDATION,
message: 'Response validation failed: Expected string, got number',
responseBody: { id: 123 },
});
expect(error.type).toBe(PlumeErrorType.VALIDATION);
});
it('UNKNOWN エラーを作成できる', () => {
const error = new PlumeApiError({
type: PlumeErrorType.UNKNOWN,
message: 'Unknown error',
});
expect(error.type).toBe(PlumeErrorType.UNKNOWN);
});
});
describe('エラー情報の取得', () => {
it('toJSON() で構造化されたエラー情報を取得できる', () => {
const error = new PlumeApiError({
type: PlumeErrorType.API,
message: 'Not found',
statusCode: 404,
responseBody: { error: 'Article not found' },
retryCount: 2,
endpoint: '/api/articles/999',
});
const json = error.toJSON();
expect(json).toEqual({
type: 'API',
message: 'Not found',
statusCode: 404,
responseBody: { error: 'Article not found' },
retryCount: 2,
endpoint: '/api/articles/999',
});
});
it('toJSON() で cause は除外される (循環参照回避)', () => {
const originalError = new Error('Original');
const error = new PlumeApiError({
type: PlumeErrorType.NETWORK,
message: 'Network error',
cause: originalError,
});
const json = error.toJSON();
expect(json).toEqual({
type: 'NETWORK',
message: 'Network error',
retryCount: 0,
});
expect(json).not.toHaveProperty('cause');
});
});
describe('ヘルパーメソッド', () => {
it('isRetryable() - リトライ可能なエラーを判定できる', () => {
const networkError = new PlumeApiError({
type: PlumeErrorType.NETWORK,
message: 'Network error',
});
const timeoutError = new PlumeApiError({
type: PlumeErrorType.TIMEOUT,
message: 'Timeout',
});
const apiError503 = new PlumeApiError({
type: PlumeErrorType.API,
message: 'Service unavailable',
statusCode: 503,
});
const apiError404 = new PlumeApiError({
type: PlumeErrorType.API,
message: 'Not found',
statusCode: 404,
});
const validationError = new PlumeApiError({
type: PlumeErrorType.VALIDATION,
message: 'Validation failed',
});
expect(networkError.isRetryable()).toBe(true);
expect(timeoutError.isRetryable()).toBe(true);
expect(apiError503.isRetryable()).toBe(true);
expect(apiError404.isRetryable()).toBe(false);
expect(validationError.isRetryable()).toBe(false);
});
});
describe('エラーメッセージフォーマット', () => {
it('toString() でフォーマットされたエラーメッセージを取得できる', () => {
const error = new PlumeApiError({
type: PlumeErrorType.API,
message: 'Unauthorized',
statusCode: 401,
endpoint: '/api/auth/login',
retryCount: 1,
});
const str = error.toString();
expect(str).toContain('PlumeApiError');
expect(str).toContain('Unauthorized');
expect(str).toContain('401');
expect(str).toContain('/api/auth/login');
});
});
});