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
189 lines
4.7 KiB
JavaScript
189 lines
4.7 KiB
JavaScript
/**
|
|
* Auth Middleware Tests (Phase 1.5)
|
|
* Tests for authentication and email verification middleware
|
|
*/
|
|
|
|
const { requireEmailVerification } = require('../../middleware/auth');
|
|
|
|
describe('Auth Middleware Tests (Phase 1.5)', () => {
|
|
let req, res, next;
|
|
|
|
beforeEach(() => {
|
|
req = {
|
|
user: null
|
|
};
|
|
res = {
|
|
status: jest.fn().mockReturnThis(),
|
|
json: jest.fn()
|
|
};
|
|
next = jest.fn();
|
|
});
|
|
|
|
describe('requireEmailVerification', () => {
|
|
it('should pass through if email is verified', async () => {
|
|
req.user = {
|
|
id: 1,
|
|
email: 'user@example.com',
|
|
emailVerified: true
|
|
};
|
|
|
|
await requireEmailVerification(req, res, next);
|
|
|
|
expect(next).toHaveBeenCalled();
|
|
expect(res.status).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should return 401 if user is not attached to request', async () => {
|
|
req.user = null;
|
|
|
|
await requireEmailVerification(req, res, next);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(401);
|
|
expect(res.json).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
success: false,
|
|
error: 'Unauthorized'
|
|
})
|
|
);
|
|
expect(next).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should return 403 if email is not verified', async () => {
|
|
req.user = {
|
|
id: 1,
|
|
email: 'user@example.com',
|
|
emailVerified: false
|
|
};
|
|
|
|
await requireEmailVerification(req, res, next);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(403);
|
|
expect(res.json).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
success: false,
|
|
error: 'Email Not Verified',
|
|
requiresVerification: true
|
|
})
|
|
);
|
|
expect(next).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should include helpful message for unverified email', async () => {
|
|
req.user = {
|
|
id: 1,
|
|
email: 'user@example.com',
|
|
emailVerified: false
|
|
};
|
|
|
|
await requireEmailVerification(req, res, next);
|
|
|
|
expect(res.json).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
message: expect.stringContaining('verify your email')
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should handle undefined emailVerified as false', async () => {
|
|
req.user = {
|
|
id: 1,
|
|
email: 'user@example.com'
|
|
// emailVerified is undefined
|
|
};
|
|
|
|
await requireEmailVerification(req, res, next);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(403);
|
|
expect(next).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should handle errors gracefully', async () => {
|
|
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
|
|
// Force an error by making req.user a getter that throws
|
|
Object.defineProperty(req, 'user', {
|
|
get: () => {
|
|
throw new Error('Test error');
|
|
}
|
|
});
|
|
|
|
await requireEmailVerification(req, res, next);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(500);
|
|
expect(res.json).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
success: false,
|
|
error: 'Internal Server Error'
|
|
})
|
|
);
|
|
|
|
consoleSpy.mockRestore();
|
|
});
|
|
|
|
it('should not call next if verification fails', async () => {
|
|
req.user = {
|
|
id: 1,
|
|
email: 'user@example.com',
|
|
emailVerified: false
|
|
};
|
|
|
|
await requireEmailVerification(req, res, next);
|
|
|
|
expect(next).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should work with verified users multiple times', async () => {
|
|
req.user = {
|
|
id: 1,
|
|
email: 'user@example.com',
|
|
emailVerified: true
|
|
};
|
|
|
|
await requireEmailVerification(req, res, next);
|
|
await requireEmailVerification(req, res, next);
|
|
await requireEmailVerification(req, res, next);
|
|
|
|
expect(next).toHaveBeenCalledTimes(3);
|
|
});
|
|
|
|
it('should handle boolean true emailVerified', async () => {
|
|
req.user = {
|
|
id: 1,
|
|
email: 'user@example.com',
|
|
emailVerified: true
|
|
};
|
|
|
|
await requireEmailVerification(req, res, next);
|
|
|
|
expect(next).toHaveBeenCalled();
|
|
expect(res.status).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should handle boolean false emailVerified', async () => {
|
|
req.user = {
|
|
id: 1,
|
|
email: 'user@example.com',
|
|
emailVerified: false
|
|
};
|
|
|
|
await requireEmailVerification(req, res, next);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(403);
|
|
expect(next).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should include requiresVerification flag in response', async () => {
|
|
req.user = {
|
|
id: 1,
|
|
email: 'user@example.com',
|
|
emailVerified: false
|
|
};
|
|
|
|
await requireEmailVerification(req, res, next);
|
|
|
|
const jsonCall = res.json.mock.calls[0][0];
|
|
expect(jsonCall.requiresVerification).toBe(true);
|
|
});
|
|
});
|
|
});
|