Skip to content

Commit 3c2874b

Browse files
✅ test: Add unit tests for validation, errors, and client
1 parent 835846c commit 3c2874b

File tree

3 files changed

+157
-0
lines changed

3 files changed

+157
-0
lines changed

test/client.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { XposedOrNot } from '../src/client.js';
3+
import { ValidationError } from '../src/errors/index.js';
4+
5+
describe('XposedOrNot client', () => {
6+
it('creates client with default config', () => {
7+
const client = new XposedOrNot();
8+
expect(client).toBeInstanceOf(XposedOrNot);
9+
});
10+
11+
it('creates client with custom config', () => {
12+
const client = new XposedOrNot({
13+
timeout: 5000,
14+
retries: 5,
15+
});
16+
expect(client).toBeInstanceOf(XposedOrNot);
17+
});
18+
19+
it('rejects non-HTTPS baseUrl', () => {
20+
expect(() => new XposedOrNot({ baseUrl: 'http://api.example.com' })).toThrow(ValidationError);
21+
});
22+
23+
it('rejects invalid baseUrl', () => {
24+
expect(() => new XposedOrNot({ baseUrl: 'not-a-url' })).toThrow(ValidationError);
25+
});
26+
27+
it('rejects timeout out of range', () => {
28+
expect(() => new XposedOrNot({ timeout: 100 })).toThrow(ValidationError);
29+
expect(() => new XposedOrNot({ timeout: 999999 })).toThrow(ValidationError);
30+
});
31+
32+
it('rejects retries out of range', () => {
33+
expect(() => new XposedOrNot({ retries: -1 })).toThrow(ValidationError);
34+
expect(() => new XposedOrNot({ retries: 20 })).toThrow(ValidationError);
35+
});
36+
});

test/errors.test.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { describe, it, expect } from 'vitest';
2+
import {
3+
XposedOrNotError,
4+
ValidationError,
5+
RateLimitError,
6+
NotFoundError,
7+
NetworkError,
8+
TimeoutError,
9+
ApiError,
10+
} from '../src/errors/index.js';
11+
12+
describe('XposedOrNotError', () => {
13+
it('has correct properties', () => {
14+
const error = new XposedOrNotError('Test error', 'TEST_CODE', 500);
15+
expect(error.message).toBe('Test error');
16+
expect(error.code).toBe('TEST_CODE');
17+
expect(error.statusCode).toBe(500);
18+
expect(error.name).toBe('XposedOrNotError');
19+
});
20+
});
21+
22+
describe('ValidationError', () => {
23+
it('has correct properties', () => {
24+
const error = new ValidationError('Invalid email', 'email');
25+
expect(error.message).toBe('Invalid email');
26+
expect(error.field).toBe('email');
27+
expect(error.code).toBe('VALIDATION_ERROR');
28+
expect(error.name).toBe('ValidationError');
29+
});
30+
});
31+
32+
describe('RateLimitError', () => {
33+
it('has correct defaults', () => {
34+
const error = new RateLimitError();
35+
expect(error.statusCode).toBe(429);
36+
expect(error.code).toBe('RATE_LIMIT_EXCEEDED');
37+
});
38+
39+
it('includes retryAfter', () => {
40+
const error = new RateLimitError('Too many requests', 60);
41+
expect(error.retryAfter).toBe(60);
42+
});
43+
});
44+
45+
describe('NotFoundError', () => {
46+
it('has correct defaults', () => {
47+
const error = new NotFoundError();
48+
expect(error.statusCode).toBe(404);
49+
expect(error.code).toBe('NOT_FOUND');
50+
});
51+
});
52+
53+
describe('NetworkError', () => {
54+
it('has correct properties', () => {
55+
const cause = new Error('Connection refused');
56+
const error = new NetworkError('Network failed', cause);
57+
expect(error.code).toBe('NETWORK_ERROR');
58+
expect(error.cause).toBe(cause);
59+
});
60+
});
61+
62+
describe('TimeoutError', () => {
63+
it('has correct properties', () => {
64+
const error = new TimeoutError();
65+
expect(error.code).toBe('TIMEOUT');
66+
});
67+
});
68+
69+
describe('ApiError', () => {
70+
it('has correct properties', () => {
71+
const error = new ApiError('Server error', 500, { detail: 'Internal error' });
72+
expect(error.statusCode).toBe(500);
73+
expect(error.response).toEqual({ detail: 'Internal error' });
74+
});
75+
});

test/validation.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { validateEmail, validateDomain, normalizeEmail } from '../src/utils/validation.js';
3+
import { ValidationError } from '../src/errors/index.js';
4+
5+
describe('validateEmail', () => {
6+
it('accepts valid emails', () => {
7+
expect(() => validateEmail('test@example.com')).not.toThrow();
8+
expect(() => validateEmail('user.name@domain.org')).not.toThrow();
9+
expect(() => validateEmail('user+tag@example.co.uk')).not.toThrow();
10+
});
11+
12+
it('rejects non-string input', () => {
13+
expect(() => validateEmail(null)).toThrow(ValidationError);
14+
expect(() => validateEmail(undefined)).toThrow(ValidationError);
15+
expect(() => validateEmail(123)).toThrow(ValidationError);
16+
});
17+
18+
it('rejects empty string', () => {
19+
expect(() => validateEmail('')).toThrow(ValidationError);
20+
expect(() => validateEmail(' ')).toThrow(ValidationError);
21+
});
22+
23+
it('rejects invalid email format', () => {
24+
expect(() => validateEmail('notanemail')).toThrow(ValidationError);
25+
expect(() => validateEmail('missing@domain')).toThrow(ValidationError);
26+
expect(() => validateEmail('@nodomain.com')).toThrow(ValidationError);
27+
});
28+
});
29+
30+
describe('validateDomain', () => {
31+
it('accepts valid domains', () => {
32+
expect(() => validateDomain('example.com')).not.toThrow();
33+
expect(() => validateDomain('sub.domain.org')).not.toThrow();
34+
});
35+
36+
it('rejects invalid domains', () => {
37+
expect(() => validateDomain('')).toThrow(ValidationError);
38+
expect(() => validateDomain('invalid')).toThrow(ValidationError);
39+
});
40+
});
41+
42+
describe('normalizeEmail', () => {
43+
it('trims whitespace and lowercases', () => {
44+
expect(normalizeEmail(' Test@Example.COM ')).toBe('test@example.com');
45+
});
46+
});

0 commit comments

Comments
 (0)