Skip to content

Commit aa8f04d

Browse files
committed
test: add verify code endpoint tests
1 parent b59de98 commit aa8f04d

File tree

1 file changed

+290
-0
lines changed

1 file changed

+290
-0
lines changed
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
4+
import 'package:dart_frog/dart_frog.dart';
5+
import 'package:ht_api/src/services/auth_service.dart';
6+
import 'package:ht_shared/ht_shared.dart';
7+
import 'package:mocktail/mocktail.dart';
8+
import 'package:test/test.dart';
9+
10+
import '../../../../helpers/create_mock_request_context.dart';
11+
import '../../../../helpers/mock_classes.dart';
12+
// Import the actual route handler
13+
import '../../../../../routes/api/v1/auth/verify-code.dart' as route;
14+
15+
void main() {
16+
group('POST /api/v1/auth/verify-code', () {
17+
late MockAuthService mockAuthService;
18+
late MockRequest mockRequest;
19+
const validEmail = '[email protected]';
20+
const validCode = '123456';
21+
final validRequestBody = jsonEncode({
22+
'email': validEmail,
23+
'code': validCode,
24+
});
25+
26+
// Define a sample user and token for success cases
27+
final testUser = User(
28+
id: 'user-456',
29+
email: validEmail,
30+
isAnonymous: false,
31+
);
32+
const testToken = 'verified-auth-token';
33+
final authResult = (user: testUser, token: testToken);
34+
35+
// Expected success response payload
36+
final successPayload = SuccessApiResponse<AuthSuccessResponse>(
37+
data: AuthSuccessResponse(user: testUser, token: testToken),
38+
);
39+
final expectedSuccessBody = jsonEncode(
40+
successPayload.toJson((auth) => auth.toJson()),
41+
);
42+
43+
44+
setUp(() {
45+
mockAuthService = MockAuthService();
46+
mockRequest = MockRequest();
47+
48+
// Default stub for POST method
49+
when(() => mockRequest.method).thenReturn(HttpMethod.post);
50+
// Default stub for headers
51+
when(() => mockRequest.headers).thenReturn(
52+
{HttpHeaders.contentTypeHeader: ContentType.json.mimeType},
53+
);
54+
// Default stub for valid body
55+
when(() => mockRequest.body()).thenAnswer((_) async => validRequestBody);
56+
when(() => mockRequest.json()).thenAnswer(
57+
(_) async => jsonDecode(validRequestBody) as Map<String, dynamic>,
58+
);
59+
});
60+
61+
test('returns 200 OK with user and token on successful verification',
62+
() async {
63+
// Arrange
64+
when(() => mockAuthService.completeEmailSignIn(validEmail, validCode))
65+
.thenAnswer((_) async => authResult);
66+
67+
final context = createMockRequestContext(
68+
request: mockRequest,
69+
dependencies: {AuthService: mockAuthService},
70+
);
71+
72+
// Act
73+
final response = await route.onRequest(context);
74+
75+
// Assert
76+
expect(response.statusCode, equals(HttpStatus.ok));
77+
expect(
78+
await response.body(),
79+
equals(expectedSuccessBody),
80+
);
81+
expect(
82+
response.headers[HttpHeaders.contentTypeHeader],
83+
equals('application/json'),
84+
);
85+
verify(() => mockAuthService.completeEmailSignIn(validEmail, validCode))
86+
.called(1);
87+
});
88+
89+
test('returns 405 Method Not Allowed for non-POST requests', () async {
90+
// Arrange
91+
when(() => mockRequest.method).thenReturn(HttpMethod.put); // Test PUT
92+
final context = createMockRequestContext(
93+
request: mockRequest,
94+
dependencies: {AuthService: mockAuthService},
95+
);
96+
97+
// Act
98+
final response = await route.onRequest(context);
99+
100+
// Assert
101+
expect(response.statusCode, equals(HttpStatus.methodNotAllowed));
102+
verifyNever(() => mockAuthService.completeEmailSignIn(any(), any()));
103+
});
104+
105+
test('throws InvalidInputException for invalid JSON body', () async {
106+
// Arrange
107+
when(() => mockRequest.json()).thenThrow(FormatException('Invalid JSON'));
108+
final context = createMockRequestContext(
109+
request: mockRequest,
110+
dependencies: {AuthService: mockAuthService},
111+
);
112+
113+
// Act & Assert
114+
expect(
115+
() => route.onRequest(context),
116+
throwsA(isA<InvalidInputException>().having(
117+
(e) => e.message,
118+
'message',
119+
'Invalid JSON format in request body.',
120+
)),
121+
);
122+
verifyNever(() => mockAuthService.completeEmailSignIn(any(), any()));
123+
});
124+
125+
test('throws InvalidInputException for non-object JSON body', () async {
126+
// Arrange
127+
when(() => mockRequest.json()).thenAnswer((_) async => []); // Array body
128+
final context = createMockRequestContext(
129+
request: mockRequest,
130+
dependencies: {AuthService: mockAuthService},
131+
);
132+
133+
// Act & Assert
134+
expect(
135+
() => route.onRequest(context),
136+
throwsA(isA<InvalidInputException>().having(
137+
(e) => e.message,
138+
'message',
139+
'Request body must be a JSON object.',
140+
)),
141+
);
142+
verifyNever(() => mockAuthService.completeEmailSignIn(any(), any()));
143+
});
144+
145+
test('throws InvalidInputException for missing email field', () async {
146+
// Arrange
147+
when(() => mockRequest.json()).thenAnswer((_) async => {'code': validCode});
148+
final context = createMockRequestContext(
149+
request: mockRequest,
150+
dependencies: {AuthService: mockAuthService},
151+
);
152+
153+
// Act & Assert
154+
expect(
155+
() => route.onRequest(context),
156+
throwsA(isA<InvalidInputException>().having(
157+
(e) => e.message,
158+
'message',
159+
'Missing or empty "email" field in request body.',
160+
)),
161+
);
162+
verifyNever(() => mockAuthService.completeEmailSignIn(any(), any()));
163+
});
164+
165+
test('throws InvalidInputException for invalid email format', () async {
166+
// Arrange
167+
const invalidEmail = 'not-an-email';
168+
when(() => mockRequest.json()).thenAnswer((_) async => {'email': invalidEmail, 'code': validCode});
169+
final context = createMockRequestContext(
170+
request: mockRequest,
171+
dependencies: {AuthService: mockAuthService},
172+
);
173+
174+
// Act & Assert
175+
expect(
176+
() => route.onRequest(context),
177+
throwsA(isA<InvalidInputException>().having(
178+
(e) => e.message,
179+
'message',
180+
'Invalid email format provided.',
181+
)),
182+
);
183+
verifyNever(() => mockAuthService.completeEmailSignIn(any(), any()));
184+
});
185+
186+
test('throws InvalidInputException for missing code field', () async {
187+
// Arrange
188+
when(() => mockRequest.json()).thenAnswer((_) async => {'email': validEmail});
189+
final context = createMockRequestContext(
190+
request: mockRequest,
191+
dependencies: {AuthService: mockAuthService},
192+
);
193+
194+
// Act & Assert
195+
expect(
196+
() => route.onRequest(context),
197+
throwsA(isA<InvalidInputException>().having(
198+
(e) => e.message,
199+
'message',
200+
'Missing or empty "code" field in request body.',
201+
)),
202+
);
203+
verifyNever(() => mockAuthService.completeEmailSignIn(any(), any()));
204+
});
205+
206+
test('throws InvalidInputException for invalid code format (not 6 digits)', () async {
207+
// Arrange
208+
const invalidCode = '123';
209+
when(() => mockRequest.json()).thenAnswer((_) async => {'email': validEmail, 'code': invalidCode});
210+
final context = createMockRequestContext(
211+
request: mockRequest,
212+
dependencies: {AuthService: mockAuthService},
213+
);
214+
215+
// Act & Assert
216+
expect(
217+
() => route.onRequest(context),
218+
throwsA(isA<InvalidInputException>().having(
219+
(e) => e.message,
220+
'message',
221+
'Invalid code format. Code must be 6 digits.',
222+
)),
223+
);
224+
verifyNever(() => mockAuthService.completeEmailSignIn(any(), any()));
225+
});
226+
227+
228+
test('rethrows InvalidInputException from AuthService (e.g., wrong code)',
229+
() async {
230+
// Arrange
231+
const exception = InvalidInputException('Invalid or expired code.');
232+
when(() => mockAuthService.completeEmailSignIn(validEmail, validCode))
233+
.thenThrow(exception);
234+
final context = createMockRequestContext(
235+
request: mockRequest,
236+
dependencies: {AuthService: mockAuthService},
237+
);
238+
239+
// Act & Assert
240+
expect(
241+
() => route.onRequest(context),
242+
throwsA(isA<InvalidInputException>()),
243+
);
244+
verify(() => mockAuthService.completeEmailSignIn(validEmail, validCode))
245+
.called(1);
246+
});
247+
248+
test('rethrows other HtHttpException from AuthService', () async {
249+
// Arrange
250+
const exception = OperationFailedException('User creation failed');
251+
when(() => mockAuthService.completeEmailSignIn(validEmail, validCode))
252+
.thenThrow(exception);
253+
final context = createMockRequestContext(
254+
request: mockRequest,
255+
dependencies: {AuthService: mockAuthService},
256+
);
257+
258+
// Act & Assert
259+
expect(
260+
() => route.onRequest(context),
261+
throwsA(isA<OperationFailedException>()),
262+
);
263+
verify(() => mockAuthService.completeEmailSignIn(validEmail, validCode))
264+
.called(1);
265+
});
266+
267+
test('throws OperationFailedException for unexpected errors', () async {
268+
// Arrange
269+
final exception = Exception('Unexpected');
270+
when(() => mockAuthService.completeEmailSignIn(validEmail, validCode))
271+
.thenThrow(exception);
272+
final context = createMockRequestContext(
273+
request: mockRequest,
274+
dependencies: {AuthService: mockAuthService},
275+
);
276+
277+
// Act & Assert
278+
expect(
279+
() => route.onRequest(context),
280+
throwsA(isA<OperationFailedException>().having(
281+
(e) => e.message,
282+
'message',
283+
'An unexpected error occurred while verifying the sign-in code.',
284+
)),
285+
);
286+
verify(() => mockAuthService.completeEmailSignIn(validEmail, validCode))
287+
.called(1);
288+
});
289+
});
290+
}

0 commit comments

Comments
 (0)