sms-verification-api 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +20 -0
- package/DEPLOYMENT.md +151 -0
- package/README.md +475 -0
- package/docs/app/(home)/layout.tsx +7 -0
- package/docs/app/(home)/page.tsx +38 -0
- package/docs/app/docs/[[...slug]]/page.tsx +59 -0
- package/docs/app/docs/layout.tsx +12 -0
- package/docs/app/docs-og/[...slug]/route.ts +24 -0
- package/docs/app/globals.css +587 -0
- package/docs/app/layout.config.tsx +13 -0
- package/docs/app/layout.tsx +27 -0
- package/docs/app/logo.tsx +35 -0
- package/docs/content/docs/API_AUTHENTICATION.md +91 -0
- package/docs/content/docs/DEPLOYMENT.md +181 -0
- package/docs/content/docs/api/post.mdx +35 -0
- package/docs/content/docs/api/verify.mdx +34 -0
- package/docs/content/docs/meta.json +8 -0
- package/docs/content/docs/verify-legal-name.md +339 -0
- package/docs/lib/source.ts +14 -0
- package/docs/mdx-components.tsx +12 -0
- package/docs/next.config.mjs +51 -0
- package/docs/openapi.json +329 -0
- package/docs/package.json +37 -0
- package/docs/postcss.config.mjs +5 -0
- package/docs/scripts/generate-docs.mjs +23 -0
- package/docs/source.config.ts +5 -0
- package/docs/tsconfig.json +29 -0
- package/docs/worker.js +35 -0
- package/docs/wrangler.toml +26 -0
- package/examples/client.js +119 -0
- package/examples/demo.html +325 -0
- package/examples/libphonenumber-example.js +120 -0
- package/openapi.json +329 -0
- package/package.json +71 -0
- package/scripts/deploy.sh +63 -0
- package/src/identity-verification-server.ts +553 -0
- package/src/index.js +8 -0
- package/src/sns.js +236 -0
- package/src/verify-phone-server.js +448 -0
- package/src/verify-phone.ts +551 -0
- package/test/api.test.js +201 -0
- package/test/integration.test.js +152 -0
- package/test/metadata-test.js +73 -0
- package/test/server.test.js +143 -0
- package/test/setup.js +32 -0
- package/test/utils.test.js +186 -0
- package/test/verify.test.js +23 -0
- package/test/voip.test.js +112 -0
- package/vitest.config.js +10 -0
- package/wrangler.toml +27 -0
package/test/api.test.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { createApp } from '../src/verify-phone-server.js';
|
|
3
|
+
|
|
4
|
+
describe('SMS Verification API', () => {
|
|
5
|
+
let app;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
// Create a fresh app instance for each test
|
|
9
|
+
app = createApp(globalThis.env);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('Health Check', () => {
|
|
13
|
+
it('should return health status', async () => {
|
|
14
|
+
const req = new Request('http://localhost/health');
|
|
15
|
+
const res = await app.request(req, globalThis.env);
|
|
16
|
+
const data = await res.json();
|
|
17
|
+
|
|
18
|
+
expect(res.status).toBe(200);
|
|
19
|
+
expect(data).toMatchObject({
|
|
20
|
+
status: 'ok'
|
|
21
|
+
});
|
|
22
|
+
expect(data.timestamp).toBeDefined();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('Send Verification Code', () => {
|
|
27
|
+
it('should require API key authentication', async () => {
|
|
28
|
+
const req = new Request('http://localhost/api/send-verification?phoneNumber=%2B1234567890');
|
|
29
|
+
const res = await app.request(req, globalThis.env);
|
|
30
|
+
const data = await res.json();
|
|
31
|
+
|
|
32
|
+
expect(res.status).toBe(401);
|
|
33
|
+
expect(data).toMatchObject({
|
|
34
|
+
error: 'API key required'
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should reject invalid API key', async () => {
|
|
39
|
+
const req = new Request('http://localhost/api/send-verification?phoneNumber=%2B1234567890', {
|
|
40
|
+
headers: {
|
|
41
|
+
'X-API-Key': 'invalid-key'
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
const res = await app.request(req, globalThis.env);
|
|
45
|
+
const data = await res.json();
|
|
46
|
+
|
|
47
|
+
expect(res.status).toBe(401);
|
|
48
|
+
expect(data).toMatchObject({
|
|
49
|
+
error: 'Invalid API key'
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should accept Bearer token authentication', async () => {
|
|
54
|
+
const req = new Request('http://localhost/api/send-verification?phoneNumber=%2B1234567890', {
|
|
55
|
+
headers: {
|
|
56
|
+
'Authorization': 'Bearer test-api-key'
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
const res = await app.request(req, globalThis.env);
|
|
60
|
+
const data = await res.json();
|
|
61
|
+
|
|
62
|
+
expect(res.status).toBe(200);
|
|
63
|
+
expect(data.success).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should reject invalid phone number format', async () => {
|
|
67
|
+
const req = new Request('http://localhost/api/send-verification?phoneNumber=invalid-phone', {
|
|
68
|
+
headers: {
|
|
69
|
+
'X-API-Key': 'test-api-key'
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
const res = await app.request(req, globalThis.env);
|
|
73
|
+
const data = await res.json();
|
|
74
|
+
|
|
75
|
+
expect(res.status).toBe(400);
|
|
76
|
+
expect(data).toMatchObject({
|
|
77
|
+
error: 'Invalid phone number format'
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should format phone number correctly', async () => {
|
|
82
|
+
const req = new Request('http://localhost/api/send-verification?phoneNumber=1234567890', {
|
|
83
|
+
headers: {
|
|
84
|
+
'X-API-Key': 'test-api-key'
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
const res = await app.request(req, globalThis.env);
|
|
88
|
+
const data = await res.json();
|
|
89
|
+
|
|
90
|
+
expect(res.status).toBe(200);
|
|
91
|
+
expect(data.success).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should accept blockVoip parameter', async () => {
|
|
95
|
+
const req = new Request('http://localhost/api/send-verification?phoneNumber=%2B1234567890&blockVoip=true', {
|
|
96
|
+
headers: {
|
|
97
|
+
'X-API-Key': 'test-api-key'
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
const res = await app.request(req, globalThis.env);
|
|
101
|
+
const data = await res.json();
|
|
102
|
+
|
|
103
|
+
expect(res.status).toBe(200);
|
|
104
|
+
expect(data.success).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should handle blockVoip=false parameter', async () => {
|
|
108
|
+
const req = new Request('http://localhost/api/send-verification?phoneNumber=%2B1234567890&blockVoip=false', {
|
|
109
|
+
headers: {
|
|
110
|
+
'X-API-Key': 'test-api-key'
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
const res = await app.request(req, globalThis.env);
|
|
114
|
+
const data = await res.json();
|
|
115
|
+
|
|
116
|
+
expect(res.status).toBe(200);
|
|
117
|
+
expect(data.success).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('Verify Code', () => {
|
|
122
|
+
it('should require API key authentication', async () => {
|
|
123
|
+
const req = new Request('http://localhost/api/verify-code?phoneNumber=%2B1234567890&code=123456');
|
|
124
|
+
const res = await app.request(req, globalThis.env);
|
|
125
|
+
const data = await res.json();
|
|
126
|
+
|
|
127
|
+
expect(res.status).toBe(401);
|
|
128
|
+
expect(data).toMatchObject({
|
|
129
|
+
error: 'API key required'
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should reject invalid API key', async () => {
|
|
134
|
+
const req = new Request('http://localhost/api/verify-code?phoneNumber=%2B1234567890&code=123456', {
|
|
135
|
+
headers: {
|
|
136
|
+
'X-API-Key': 'invalid-key'
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
const res = await app.request(req, globalThis.env);
|
|
140
|
+
const data = await res.json();
|
|
141
|
+
|
|
142
|
+
expect(res.status).toBe(401);
|
|
143
|
+
expect(data).toMatchObject({
|
|
144
|
+
error: 'Invalid API key'
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should reject invalid phone number format', async () => {
|
|
149
|
+
const req = new Request('http://localhost/api/verify-code?phoneNumber=invalid-phone&code=123456', {
|
|
150
|
+
headers: {
|
|
151
|
+
'X-API-Key': 'test-api-key'
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
const res = await app.request(req, globalThis.env);
|
|
155
|
+
const data = await res.json();
|
|
156
|
+
|
|
157
|
+
expect(res.status).toBe(400);
|
|
158
|
+
expect(data).toMatchObject({
|
|
159
|
+
error: 'Invalid phone number format'
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should accept Bearer token authentication', async () => {
|
|
164
|
+
const req = new Request('http://localhost/api/verify-code?phoneNumber=%2B1234567890&code=123456', {
|
|
165
|
+
headers: {
|
|
166
|
+
'Authorization': 'Bearer test-api-key'
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
const res = await app.request(req, globalThis.env);
|
|
170
|
+
const data = await res.json();
|
|
171
|
+
|
|
172
|
+
expect(res.status).toBe(400); // Will fail because no code was sent, but auth works
|
|
173
|
+
expect(data.error).toBeDefined();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('Documentation Endpoints', () => {
|
|
178
|
+
it('should serve OpenAPI documentation', async () => {
|
|
179
|
+
const req = new Request('http://localhost/openapi.json');
|
|
180
|
+
const res = await app.request(req, globalThis.env);
|
|
181
|
+
const data = await res.json();
|
|
182
|
+
|
|
183
|
+
expect(res.status).toBe(200);
|
|
184
|
+
expect(data).toMatchObject({
|
|
185
|
+
openapi: '3.0.0',
|
|
186
|
+
info: {
|
|
187
|
+
title: 'SMS API',
|
|
188
|
+
version: '1.0.0'
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should serve Swagger UI', async () => {
|
|
194
|
+
const req = new Request('http://localhost/');
|
|
195
|
+
const res = await app.request(req, globalThis.env);
|
|
196
|
+
|
|
197
|
+
expect(res.status).toBe(200);
|
|
198
|
+
expect(res.headers.get('content-type')).toContain('text/html');
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { createApp } from '../src/verify-phone-server.js';
|
|
3
|
+
|
|
4
|
+
describe('SMS Verification API Integration Tests', () => {
|
|
5
|
+
let app;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
app = createApp(globalThis.env);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('Authentication Methods', () => {
|
|
12
|
+
it('should accept both X-API-Key and Bearer token headers', async () => {
|
|
13
|
+
const phoneNumber = '+1234567890';
|
|
14
|
+
|
|
15
|
+
// Test X-API-Key header
|
|
16
|
+
const req1 = new Request(`http://localhost/api/send-verification?phoneNumber=${encodeURIComponent(phoneNumber)}`, {
|
|
17
|
+
headers: {
|
|
18
|
+
'X-API-Key': 'test-api-key'
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
const res1 = await app.request(req1, globalThis.env);
|
|
22
|
+
const data1 = await res1.json();
|
|
23
|
+
|
|
24
|
+
expect(res1.status).toBe(200);
|
|
25
|
+
expect(data1.success).toBe(true);
|
|
26
|
+
|
|
27
|
+
// Test Bearer token header
|
|
28
|
+
const req2 = new Request(`http://localhost/api/send-verification?phoneNumber=${encodeURIComponent(phoneNumber)}`, {
|
|
29
|
+
headers: {
|
|
30
|
+
'Authorization': 'Bearer test-api-key'
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
const res2 = await app.request(req2, globalThis.env);
|
|
34
|
+
const data2 = await res2.json();
|
|
35
|
+
|
|
36
|
+
expect(res2.status).toBe(200);
|
|
37
|
+
expect(data2.success).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should reject requests without authentication', async () => {
|
|
41
|
+
const phoneNumber = '+1234567890';
|
|
42
|
+
|
|
43
|
+
const req = new Request(`http://localhost/api/send-verification?phoneNumber=${encodeURIComponent(phoneNumber)}`);
|
|
44
|
+
const res = await app.request(req, globalThis.env);
|
|
45
|
+
const data = await res.json();
|
|
46
|
+
|
|
47
|
+
expect(res.status).toBe(401);
|
|
48
|
+
expect(data.error).toBe('API key required');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should reject requests with invalid authentication', async () => {
|
|
52
|
+
const phoneNumber = '+1234567890';
|
|
53
|
+
|
|
54
|
+
const req = new Request(`http://localhost/api/send-verification?phoneNumber=${encodeURIComponent(phoneNumber)}`, {
|
|
55
|
+
headers: {
|
|
56
|
+
'X-API-Key': 'invalid-key'
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
const res = await app.request(req, globalThis.env);
|
|
60
|
+
const data = await res.json();
|
|
61
|
+
|
|
62
|
+
expect(res.status).toBe(401);
|
|
63
|
+
expect(data.error).toBe('Invalid API key');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('Phone Number Formatting', () => {
|
|
68
|
+
it('should format various phone number formats correctly', async () => {
|
|
69
|
+
const testCases = [
|
|
70
|
+
'1234567890',
|
|
71
|
+
'11234567890',
|
|
72
|
+
'+1234567890',
|
|
73
|
+
'(123) 456-7890',
|
|
74
|
+
'123-456-7890',
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
for (const phoneNumber of testCases) {
|
|
78
|
+
const req = new Request(`http://localhost/api/send-verification?phoneNumber=${encodeURIComponent(phoneNumber)}`, {
|
|
79
|
+
headers: {
|
|
80
|
+
'X-API-Key': 'test-api-key'
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const res = await app.request(req, globalThis.env);
|
|
84
|
+
const data = await res.json();
|
|
85
|
+
|
|
86
|
+
expect(res.status).toBe(200);
|
|
87
|
+
expect(data.success).toBe(true);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should reject clearly invalid phone numbers', async () => {
|
|
92
|
+
const invalidNumbers = [
|
|
93
|
+
'not-a-number',
|
|
94
|
+
'123',
|
|
95
|
+
'12345678901234567890', // Too long
|
|
96
|
+
'+', // Just plus sign
|
|
97
|
+
'abc123def',
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
for (const invalidNumber of invalidNumbers) {
|
|
101
|
+
const req = new Request(`http://localhost/api/send-verification?phoneNumber=${encodeURIComponent(invalidNumber)}`, {
|
|
102
|
+
headers: {
|
|
103
|
+
'X-API-Key': 'test-api-key'
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
const res = await app.request(req, globalThis.env);
|
|
107
|
+
const data = await res.json();
|
|
108
|
+
|
|
109
|
+
expect(res.status).toBe(400);
|
|
110
|
+
expect(data.error).toBe('Invalid phone number format');
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('API Endpoints', () => {
|
|
116
|
+
it('should handle send verification endpoint', async () => {
|
|
117
|
+
const phoneNumber = '+1234567890';
|
|
118
|
+
|
|
119
|
+
const req = new Request(`http://localhost/api/send-verification?phoneNumber=${encodeURIComponent(phoneNumber)}`, {
|
|
120
|
+
headers: {
|
|
121
|
+
'X-API-Key': 'test-api-key'
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
const res = await app.request(req, globalThis.env);
|
|
125
|
+
const data = await res.json();
|
|
126
|
+
|
|
127
|
+
expect(res.status).toBe(200);
|
|
128
|
+
expect(data).toMatchObject({
|
|
129
|
+
success: true,
|
|
130
|
+
message: 'Verification code sent successfully',
|
|
131
|
+
expiresIn: 600
|
|
132
|
+
});
|
|
133
|
+
expect(data.messageId).toBeDefined();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should handle verify code endpoint', async () => {
|
|
137
|
+
const phoneNumber = '+1234567890';
|
|
138
|
+
const code = '123456';
|
|
139
|
+
|
|
140
|
+
const req = new Request(`http://localhost/api/verify-code?phoneNumber=${encodeURIComponent(phoneNumber)}&code=${code}`, {
|
|
141
|
+
headers: {
|
|
142
|
+
'X-API-Key': 'test-api-key'
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
const res = await app.request(req, globalThis.env);
|
|
146
|
+
const data = await res.json();
|
|
147
|
+
|
|
148
|
+
expect(res.status).toBe(400); // Will fail because no code was sent, but endpoint works
|
|
149
|
+
expect(data.error).toBeDefined();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { parsePhoneNumber, getNumberType } from 'libphonenumber-js';
|
|
2
|
+
|
|
3
|
+
// Test the metadata functionality
|
|
4
|
+
console.log('=== Testing libphonenumber-js Metadata Functionality ===\n');
|
|
5
|
+
|
|
6
|
+
// Test 1: US toll-free number
|
|
7
|
+
console.log('Test 1: US Toll-free number (+1-800-555-0123)');
|
|
8
|
+
try {
|
|
9
|
+
const phone1 = parsePhoneNumber('+1-800-555-0123');
|
|
10
|
+
console.log(' Parsed:', phone1 ? 'Success' : 'Failed');
|
|
11
|
+
console.log(' Valid:', phone1?.isValid());
|
|
12
|
+
console.log(' Country:', phone1?.country);
|
|
13
|
+
console.log(' National Number:', phone1?.nationalNumber);
|
|
14
|
+
console.log(' Non-geographic:', phone1?.isNonGeographic());
|
|
15
|
+
|
|
16
|
+
// Test getNumberType (requires full metadata)
|
|
17
|
+
try {
|
|
18
|
+
const type1 = getNumberType('+1-800-555-0123');
|
|
19
|
+
console.log(' Number Type:', type1);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.log(' Number Type: Error -', error.message);
|
|
22
|
+
}
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.log(' Parse Error:', error.message);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log('\n---\n');
|
|
28
|
+
|
|
29
|
+
// Test 2: US mobile number
|
|
30
|
+
console.log('Test 2: US Mobile number (+1-555-123-4567)');
|
|
31
|
+
try {
|
|
32
|
+
const phone2 = parsePhoneNumber('+1-555-123-4567');
|
|
33
|
+
console.log(' Parsed:', phone2 ? 'Success' : 'Failed');
|
|
34
|
+
console.log(' Valid:', phone2?.isValid());
|
|
35
|
+
console.log(' Country:', phone2?.country);
|
|
36
|
+
console.log(' National Number:', phone2?.nationalNumber);
|
|
37
|
+
console.log(' Non-geographic:', phone2?.isNonGeographic());
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const type2 = getNumberType('+1-555-123-4567');
|
|
41
|
+
console.log(' Number Type:', type2);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.log(' Number Type: Error -', error.message);
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.log(' Parse Error:', error.message);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log('\n---\n');
|
|
50
|
+
|
|
51
|
+
// Test 3: UK number
|
|
52
|
+
console.log('Test 3: UK number (+44 20 7946 0958)');
|
|
53
|
+
try {
|
|
54
|
+
const phone3 = parsePhoneNumber('+44 20 7946 0958');
|
|
55
|
+
console.log(' Parsed:', phone3 ? 'Success' : 'Failed');
|
|
56
|
+
console.log(' Valid:', phone3?.isValid());
|
|
57
|
+
console.log(' Country:', phone3?.country);
|
|
58
|
+
console.log(' National Number:', phone3?.nationalNumber);
|
|
59
|
+
console.log(' Non-geographic:', phone3?.isNonGeographic());
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const type3 = getNumberType('+44 20 7946 0958');
|
|
63
|
+
console.log(' Number Type:', type3);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.log(' Number Type: Error -', error.message);
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.log(' Parse Error:', error.message);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log('\n=== Metadata Test Complete ===');
|
|
72
|
+
console.log('\nNote: Number type detection may return undefined with minimal metadata.');
|
|
73
|
+
console.log('Use metadataType: "full" for better phone number type detection.');
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test file for the SMS Verification API server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
6
|
+
import { Hono } from 'hono';
|
|
7
|
+
import app from '../src/verify-phone-server.js';
|
|
8
|
+
|
|
9
|
+
describe('SMS Verification API Server', () => {
|
|
10
|
+
let server;
|
|
11
|
+
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
server = new Hono();
|
|
14
|
+
server.route('/', app);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterAll(() => {
|
|
18
|
+
// Cleanup if needed
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('Health Check Endpoints', () => {
|
|
22
|
+
it('should return API info on root endpoint', async () => {
|
|
23
|
+
const res = await server.request('/');
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
|
|
26
|
+
expect(res.status).toBe(200);
|
|
27
|
+
expect(data.success).toBe(true);
|
|
28
|
+
expect(data.message).toBe('SMS Verification API');
|
|
29
|
+
expect(data.endpoints).toBeDefined();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should return health status', async () => {
|
|
33
|
+
const res = await server.request('/health');
|
|
34
|
+
const data = await res.json();
|
|
35
|
+
|
|
36
|
+
expect(res.status).toBe(200);
|
|
37
|
+
expect(data.success).toBe(true);
|
|
38
|
+
expect(data.status).toBe('healthy');
|
|
39
|
+
expect(data.timestamp).toBeDefined();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('API Authentication', () => {
|
|
44
|
+
it('should require API key for protected endpoints', async () => {
|
|
45
|
+
const res = await server.request('/api/send', {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: {
|
|
48
|
+
'Content-Type': 'application/json',
|
|
49
|
+
},
|
|
50
|
+
body: JSON.stringify({
|
|
51
|
+
phoneNumber: '+1234567890'
|
|
52
|
+
})
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(res.status).toBe(401);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should accept valid API key', async () => {
|
|
59
|
+
const res = await server.request('/api/send', {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: {
|
|
62
|
+
'Content-Type': 'application/json',
|
|
63
|
+
'X-API-Key': 'sms_1234567890abcdef1234567890abcdef'
|
|
64
|
+
},
|
|
65
|
+
body: JSON.stringify({
|
|
66
|
+
phoneNumber: '+1234567890'
|
|
67
|
+
})
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Should not be 401 (unauthorized) - might be 500 due to missing AWS credentials
|
|
71
|
+
expect(res.status).not.toBe(401);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('Documentation', () => {
|
|
76
|
+
it('should serve OpenAPI documentation', async () => {
|
|
77
|
+
const res = await server.request('/docs');
|
|
78
|
+
expect(res.status).toBe(200);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('Error Handling', () => {
|
|
83
|
+
it('should return 404 for non-existent endpoints', async () => {
|
|
84
|
+
const res = await server.request('/nonexistent');
|
|
85
|
+
const data = await res.json();
|
|
86
|
+
|
|
87
|
+
expect(res.status).toBe(404);
|
|
88
|
+
expect(data.success).toBe(false);
|
|
89
|
+
expect(data.error).toBe('Not found');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Example usage functions
|
|
95
|
+
export async function sendVerificationCode(phoneNumber, apiKey) {
|
|
96
|
+
const response = await fetch('http://localhost:8787/api/send', {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: {
|
|
99
|
+
'Content-Type': 'application/json',
|
|
100
|
+
'X-API-Key': apiKey
|
|
101
|
+
},
|
|
102
|
+
body: JSON.stringify({
|
|
103
|
+
phoneNumber,
|
|
104
|
+
blockVoip: true,
|
|
105
|
+
senderId: 'MyApp'
|
|
106
|
+
})
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return response.json();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function verifyCode(phoneNumber, code, apiKey) {
|
|
113
|
+
const response = await fetch('http://localhost:8787/api/verify', {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: {
|
|
116
|
+
'Content-Type': 'application/json',
|
|
117
|
+
'X-API-Key': apiKey
|
|
118
|
+
},
|
|
119
|
+
body: JSON.stringify({
|
|
120
|
+
phoneNumber,
|
|
121
|
+
code
|
|
122
|
+
})
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return response.json();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function sendGeneralSMS(phoneNumber, message, apiKey) {
|
|
129
|
+
const response = await fetch('http://localhost:8787/api/sms', {
|
|
130
|
+
method: 'POST',
|
|
131
|
+
headers: {
|
|
132
|
+
'Content-Type': 'application/json',
|
|
133
|
+
'X-API-Key': apiKey
|
|
134
|
+
},
|
|
135
|
+
body: JSON.stringify({
|
|
136
|
+
phoneNumber,
|
|
137
|
+
message,
|
|
138
|
+
senderId: 'MyApp'
|
|
139
|
+
})
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return response.json();
|
|
143
|
+
}
|
package/test/setup.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Test setup file
|
|
2
|
+
import { beforeAll, afterAll } from 'vitest';
|
|
3
|
+
|
|
4
|
+
// Set up global environment variables for testing
|
|
5
|
+
globalThis.env = {
|
|
6
|
+
API_KEY: 'test-api-key',
|
|
7
|
+
AWS_ACCESS_KEY_ID: 'test-access-key',
|
|
8
|
+
AWS_SECRET_ACCESS_KEY: 'test-secret-key',
|
|
9
|
+
AWS_REGION: 'us-east-1',
|
|
10
|
+
SMS_SENDER_ID: 'TestVerify',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
console.log('Test setup - globalThis.env set to:', globalThis.env);
|
|
14
|
+
|
|
15
|
+
// Global test setup
|
|
16
|
+
beforeAll(() => {
|
|
17
|
+
// Ensure environment is set
|
|
18
|
+
if (!globalThis.env) {
|
|
19
|
+
globalThis.env = {
|
|
20
|
+
API_KEY: 'test-api-key',
|
|
21
|
+
AWS_ACCESS_KEY_ID: 'test-access-key',
|
|
22
|
+
AWS_SECRET_ACCESS_KEY: 'test-secret-key',
|
|
23
|
+
AWS_REGION: 'us-east-1',
|
|
24
|
+
SMS_SENDER_ID: 'TestVerify',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
console.log('beforeAll - globalThis.env:', globalThis.env);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterAll(() => {
|
|
31
|
+
// Any global cleanup needed
|
|
32
|
+
});
|