Files
spotlightcam/backend/src/__tests__/wsdc.test.js
Radosław Gierwiało 7a2f6d07ec feat: add email verification, password reset, and WSDC integration (Phase 1.5)
Backend features:
- AWS SES email service with HTML templates
- Email verification with dual method (link + 6-digit PIN code)
- Password reset workflow with secure tokens
- WSDC API proxy for dancer lookup and auto-fill registration
- Extended User model with verification and WSDC fields
- Email verification middleware for protected routes

Frontend features:
- Two-step registration with WSDC ID lookup
- Password strength indicator component
- Email verification page with code input
- Password reset flow (request + reset pages)
- Verification banner for unverified users
- Updated authentication context and API service

Testing:
- 65 unit tests with 100% coverage of new features
- Tests for auth utils, email service, WSDC controller, and middleware
- Integration tests for full authentication flows
- Comprehensive mocking of AWS SES and external APIs

Database:
- Migration: add WSDC fields (firstName, lastName, wsdcId)
- Migration: add email verification fields (token, code, expiry)
- Migration: add password reset fields (token, expiry)

Documentation:
- Complete Phase 1.5 documentation
- Test suite documentation and best practices
- Updated session context with new features
2025-11-13 15:47:54 +01:00

213 lines
5.7 KiB
JavaScript

/**
* WSDC Controller Tests (Phase 1.5)
* Tests for WSDC API proxy functionality
*/
const request = require('supertest');
const app = require('../app');
// Mock fetch globally
global.fetch = jest.fn();
describe('WSDC Controller Tests (Phase 1.5)', () => {
beforeEach(() => {
fetch.mockClear();
});
describe('GET /api/wsdc/lookup', () => {
it('should lookup dancer by WSDC ID successfully', async () => {
const mockDancerData = {
dancer_wsdcid: 26997,
dancer_first: 'Radoslaw',
dancer_last: 'Gierwialo',
recent_year: 2025
};
fetch.mockResolvedValue({
ok: true,
json: async () => mockDancerData
});
const response = await request(app)
.get('/api/wsdc/lookup?id=26997');
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.dancer).toMatchObject({
wsdcId: 26997,
firstName: 'Radoslaw',
lastName: 'Gierwialo'
});
});
it('should return 400 if WSDC ID is missing', async () => {
const response = await request(app)
.get('/api/wsdc/lookup');
expect(response.status).toBe(400);
expect(response.body.error).toBe('Bad Request');
expect(response.body.message).toContain('WSDC ID is required');
});
it('should return 400 for invalid WSDC ID format', async () => {
const response = await request(app)
.get('/api/wsdc/lookup?id=abc123');
expect(response.status).toBe(400);
expect(response.body.error).toBe('Bad Request');
expect(response.body.message).toContain('Invalid WSDC ID format');
});
it('should return 400 for WSDC ID too long', async () => {
const response = await request(app)
.get('/api/wsdc/lookup?id=12345678901'); // 11 digits
expect(response.status).toBe(400);
expect(response.body.error).toBe('Bad Request');
});
it('should return 404 if dancer not found', async () => {
fetch.mockResolvedValue({
ok: true,
json: async () => ({}) // Empty response
});
const response = await request(app)
.get('/api/wsdc/lookup?id=99999');
expect(response.status).toBe(404);
expect(response.body.error).toBe('Not Found');
expect(response.body.message).toContain('not found');
});
it('should return 502 if WSDC API fails', async () => {
fetch.mockResolvedValue({
ok: false,
status: 500,
statusText: 'Internal Server Error'
});
const response = await request(app)
.get('/api/wsdc/lookup?id=26997');
expect(response.status).toBe(502);
expect(response.body.error).toBe('Bad Gateway');
});
it('should handle network errors', async () => {
fetch.mockRejectedValue(new Error('Network error'));
const response = await request(app)
.get('/api/wsdc/lookup?id=26997');
expect(response.status).toBe(500);
expect(response.body.error).toBe('Internal Server Error');
});
it('should call WSDC API with correct URL', async () => {
fetch.mockResolvedValue({
ok: true,
json: async () => ({
dancer_wsdcid: 26997,
dancer_first: 'Test',
dancer_last: 'User'
})
});
await request(app)
.get('/api/wsdc/lookup?id=26997');
expect(fetch).toHaveBeenCalledWith(
'https://points.worldsdc.com/lookup2020/find?q=26997'
);
});
it('should validate numeric WSDC IDs only', async () => {
const invalidIds = ['abc', '123abc', 'test', '!@#$'];
for (const id of invalidIds) {
const response = await request(app)
.get(`/api/wsdc/lookup?id=${id}`);
expect(response.status).toBe(400);
}
});
it('should accept valid numeric WSDC IDs', async () => {
fetch.mockResolvedValue({
ok: true,
json: async () => ({
dancer_wsdcid: 123,
dancer_first: 'Test',
dancer_last: 'User'
})
});
const validIds = ['1', '123', '12345', '1234567890'];
for (const id of validIds) {
const response = await request(app)
.get(`/api/wsdc/lookup?id=${id}`);
expect(response.status).toBe(200);
}
});
it('should include optional fields if available', async () => {
const mockDancerData = {
dancer_wsdcid: 26997,
dancer_first: 'Radoslaw',
dancer_last: 'Gierwialo',
recent_year: 2025,
dominate_data: {
short_dominate_role: 'Leader'
}
};
fetch.mockResolvedValue({
ok: true,
json: async () => mockDancerData
});
const response = await request(app)
.get('/api/wsdc/lookup?id=26997');
expect(response.status).toBe(200);
expect(response.body.dancer.recentYear).toBe(2025);
expect(response.body.dancer.dominateRole).toBe('Leader');
});
it('should handle missing optional fields gracefully', async () => {
const mockDancerData = {
dancer_wsdcid: 26997,
dancer_first: 'Test',
dancer_last: 'User'
// No recent_year or dominate_data
};
fetch.mockResolvedValue({
ok: true,
json: async () => mockDancerData
});
const response = await request(app)
.get('/api/wsdc/lookup?id=26997');
expect(response.status).toBe(200);
expect(response.body.dancer.dominateRole).toBeNull();
});
it('should log errors for debugging', async () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
fetch.mockRejectedValue(new Error('Test error'));
await request(app)
.get('/api/wsdc/lookup?id=26997');
expect(consoleSpy).toHaveBeenCalled();
consoleSpy.mockRestore();
});
});
});