710 lines
22 KiB
JavaScript
710 lines
22 KiB
JavaScript
/**
|
|
* Authentication Integration Tests
|
|
*
|
|
* These tests use a real database connection to verify the complete
|
|
* authentication flow including user registration, login, token management,
|
|
* and password reset functionality.
|
|
*/
|
|
|
|
// Mock email services before importing routes
|
|
jest.mock('../../services/email', () => ({
|
|
auth: {
|
|
sendVerificationEmail: jest.fn().mockResolvedValue({ success: true }),
|
|
sendPasswordResetEmail: jest.fn().mockResolvedValue({ success: true }),
|
|
sendPasswordChangedEmail: jest.fn().mockResolvedValue({ success: true }),
|
|
},
|
|
initialize: jest.fn().mockResolvedValue(),
|
|
initialized: true,
|
|
}));
|
|
|
|
const request = require('supertest');
|
|
const express = require('express');
|
|
const cookieParser = require('cookie-parser');
|
|
const jwt = require('jsonwebtoken');
|
|
|
|
// Mock rate limiters before importing routes
|
|
jest.mock('../../middleware/rateLimiter', () => ({
|
|
registerLimiter: (req, res, next) => next(),
|
|
loginLimiter: (req, res, next) => next(),
|
|
refreshLimiter: (req, res, next) => next(),
|
|
passwordResetLimiter: (req, res, next) => next(),
|
|
passwordResetRequestLimiter: (req, res, next) => next(),
|
|
verifyEmailLimiter: (req, res, next) => next(),
|
|
resendVerificationLimiter: (req, res, next) => next(),
|
|
emailVerificationLimiter: (req, res, next) => next(),
|
|
}));
|
|
|
|
// Mock CSRF protection for tests
|
|
jest.mock('../../middleware/csrf', () => ({
|
|
csrfProtection: (req, res, next) => next(),
|
|
getCSRFToken: (req, res) => {
|
|
res.set('x-csrf-token', 'test-csrf-token');
|
|
res.json({ csrfToken: 'test-csrf-token' });
|
|
},
|
|
}));
|
|
|
|
// Mock sanitizeInput to avoid req.query setter issue in supertest
|
|
// Keep the actual validation rules but skip DOMPurify sanitization
|
|
jest.mock('../../middleware/validation', () => {
|
|
const { body, validationResult } = require('express-validator');
|
|
|
|
// Validation error handler
|
|
const handleValidationErrors = (req, res, next) => {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({
|
|
error: 'Validation failed',
|
|
details: errors.array().map((err) => ({
|
|
field: err.path,
|
|
message: err.msg,
|
|
})),
|
|
});
|
|
}
|
|
next();
|
|
};
|
|
|
|
// Password strength validation
|
|
const passwordStrengthRegex = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z])(?=.*[-@$!%*?&#^]).{8,}$/;
|
|
|
|
return {
|
|
sanitizeInput: (req, res, next) => next(), // Skip sanitization in tests
|
|
validateRegistration: [
|
|
body('email').isEmail().normalizeEmail().withMessage('Please provide a valid email address'),
|
|
body('password').isLength({ min: 8, max: 128 }).matches(passwordStrengthRegex).withMessage('Password does not meet requirements'),
|
|
body('firstName').trim().isLength({ min: 1, max: 50 }).withMessage('First name is required'),
|
|
body('lastName').trim().isLength({ min: 1, max: 50 }).withMessage('Last name is required'),
|
|
handleValidationErrors,
|
|
],
|
|
validateLogin: [
|
|
body('email').isEmail().normalizeEmail().withMessage('Please provide a valid email address'),
|
|
body('password').notEmpty().withMessage('Password is required'),
|
|
handleValidationErrors,
|
|
],
|
|
validateGoogleAuth: [
|
|
body('code').notEmpty().withMessage('Authorization code is required'),
|
|
handleValidationErrors,
|
|
],
|
|
validateForgotPassword: [
|
|
body('email').isEmail().normalizeEmail().withMessage('Please provide a valid email address'),
|
|
handleValidationErrors,
|
|
],
|
|
validateResetPassword: [
|
|
body('token').notEmpty().withMessage('Token is required').isLength({ min: 64, max: 64 }).withMessage('Invalid token format'),
|
|
body('newPassword').isLength({ min: 8, max: 128 }).matches(passwordStrengthRegex).withMessage('Password does not meet requirements'),
|
|
handleValidationErrors,
|
|
],
|
|
validateVerifyResetToken: [
|
|
body('token').notEmpty().withMessage('Token is required'),
|
|
handleValidationErrors,
|
|
],
|
|
};
|
|
});
|
|
|
|
const { sequelize, User, AlphaInvitation } = require('../../models');
|
|
const authRoutes = require('../../routes/auth');
|
|
|
|
// Test app setup
|
|
const createTestApp = () => {
|
|
const app = express();
|
|
app.use(express.json());
|
|
app.use(cookieParser());
|
|
|
|
// Add request ID middleware
|
|
app.use((req, res, next) => {
|
|
req.id = 'test-request-id';
|
|
next();
|
|
});
|
|
|
|
app.use('/auth', authRoutes);
|
|
|
|
// Error handler for tests
|
|
app.use((err, req, res, next) => {
|
|
res.status(err.status || 500).json({
|
|
error: err.message || 'Internal Server Error',
|
|
});
|
|
});
|
|
|
|
return app;
|
|
};
|
|
|
|
// Test data factory
|
|
const createTestUser = async (overrides = {}) => {
|
|
const defaultData = {
|
|
email: `test-${Date.now()}@example.com`,
|
|
password: 'TestPassword123!',
|
|
firstName: 'Test',
|
|
lastName: 'User',
|
|
isVerified: false,
|
|
authProvider: 'local',
|
|
};
|
|
|
|
return User.create({ ...defaultData, ...overrides });
|
|
};
|
|
|
|
const createAlphaInvitation = async (overrides = {}) => {
|
|
// Generate a valid code matching pattern /^ALPHA-[A-Z0-9]{8}$/i
|
|
const randomCode = Math.random().toString(36).substring(2, 10).toUpperCase().padEnd(8, 'X');
|
|
const defaultData = {
|
|
code: `ALPHA-${randomCode.substring(0, 8)}`,
|
|
email: `alpha-${Date.now()}@example.com`, // Email is required
|
|
status: 'pending', // Valid values: pending, active, revoked
|
|
};
|
|
|
|
return AlphaInvitation.create({ ...defaultData, ...overrides });
|
|
};
|
|
|
|
describe('Auth Integration Tests', () => {
|
|
let app;
|
|
|
|
beforeAll(async () => {
|
|
// Set test environment variables
|
|
process.env.NODE_ENV = 'test';
|
|
process.env.JWT_ACCESS_SECRET = 'test-access-secret';
|
|
process.env.JWT_REFRESH_SECRET = 'test-refresh-secret';
|
|
process.env.ALPHA_TESTING_ENABLED = 'false';
|
|
|
|
// Sync database
|
|
await sequelize.sync({ force: true });
|
|
|
|
app = createTestApp();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await sequelize.close();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
// Use destroy without truncate for safer cleanup with foreign keys
|
|
await User.destroy({ where: {}, force: true });
|
|
await AlphaInvitation.destroy({ where: {}, force: true });
|
|
});
|
|
|
|
describe('POST /auth/register', () => {
|
|
it('should register a new user successfully', async () => {
|
|
const userData = {
|
|
email: 'newuser@example.com',
|
|
password: 'SecurePassword123!',
|
|
firstName: 'New',
|
|
lastName: 'User',
|
|
};
|
|
|
|
const response = await request(app)
|
|
.post('/auth/register')
|
|
.send(userData)
|
|
.expect(201);
|
|
|
|
expect(response.body.user).toBeDefined();
|
|
expect(response.body.user.email).toBe(userData.email);
|
|
expect(response.body.user.firstName).toBe(userData.firstName);
|
|
expect(response.body.user.isVerified).toBe(false);
|
|
|
|
// Verify user was created in database
|
|
const user = await User.findOne({ where: { email: userData.email } });
|
|
expect(user).not.toBeNull();
|
|
expect(user.firstName).toBe(userData.firstName);
|
|
|
|
// Verify password was hashed
|
|
expect(user.password).not.toBe(userData.password);
|
|
|
|
// Verify cookies were set
|
|
expect(response.headers['set-cookie']).toBeDefined();
|
|
const cookies = response.headers['set-cookie'];
|
|
expect(cookies.some(c => c.startsWith('accessToken='))).toBe(true);
|
|
expect(cookies.some(c => c.startsWith('refreshToken='))).toBe(true);
|
|
});
|
|
|
|
it('should reject registration with existing email', async () => {
|
|
await createTestUser({ email: 'existing@example.com' });
|
|
|
|
const response = await request(app)
|
|
.post('/auth/register')
|
|
.send({
|
|
email: 'existing@example.com',
|
|
password: 'SecurePassword123!',
|
|
firstName: 'Another',
|
|
lastName: 'User',
|
|
})
|
|
.expect(400);
|
|
|
|
expect(response.body.error).toBe('Registration failed');
|
|
expect(response.body.details[0].field).toBe('email');
|
|
});
|
|
|
|
it('should reject registration with invalid email format', async () => {
|
|
const response = await request(app)
|
|
.post('/auth/register')
|
|
.send({
|
|
email: 'not-an-email',
|
|
password: 'SecurePassword123!',
|
|
firstName: 'Test',
|
|
lastName: 'User',
|
|
})
|
|
.expect(400);
|
|
|
|
// Response should contain errors or error message
|
|
expect(response.body.errors || response.body.error).toBeDefined();
|
|
});
|
|
|
|
it('should generate verification token on registration', async () => {
|
|
const userData = {
|
|
email: 'verify@example.com',
|
|
password: 'SecurePassword123!',
|
|
firstName: 'Verify',
|
|
lastName: 'User',
|
|
};
|
|
|
|
await request(app)
|
|
.post('/auth/register')
|
|
.send(userData)
|
|
.expect(201);
|
|
|
|
const user = await User.findOne({ where: { email: userData.email } });
|
|
expect(user.verificationToken).toBeDefined();
|
|
expect(user.verificationTokenExpiry).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('POST /auth/login', () => {
|
|
let testUser;
|
|
|
|
beforeEach(async () => {
|
|
testUser = await createTestUser({
|
|
email: 'login@example.com',
|
|
password: 'TestPassword123!',
|
|
isVerified: true,
|
|
});
|
|
});
|
|
|
|
it('should login with valid credentials', async () => {
|
|
const response = await request(app)
|
|
.post('/auth/login')
|
|
.send({
|
|
email: 'login@example.com',
|
|
password: 'TestPassword123!',
|
|
})
|
|
.expect(200);
|
|
|
|
expect(response.body.user).toBeDefined();
|
|
expect(response.body.user.email).toBe('login@example.com');
|
|
|
|
// Verify cookies were set
|
|
const cookies = response.headers['set-cookie'];
|
|
expect(cookies.some(c => c.startsWith('accessToken='))).toBe(true);
|
|
expect(cookies.some(c => c.startsWith('refreshToken='))).toBe(true);
|
|
});
|
|
|
|
it('should reject login with wrong password', async () => {
|
|
const response = await request(app)
|
|
.post('/auth/login')
|
|
.send({
|
|
email: 'login@example.com',
|
|
password: 'WrongPassword!',
|
|
})
|
|
.expect(401);
|
|
|
|
expect(response.body.error).toBe('Please check your email and password, or create an account.');
|
|
});
|
|
|
|
it('should reject login with non-existent email', async () => {
|
|
const response = await request(app)
|
|
.post('/auth/login')
|
|
.send({
|
|
email: 'nonexistent@example.com',
|
|
password: 'SomePassword123!',
|
|
})
|
|
.expect(401);
|
|
|
|
expect(response.body.error).toBe('Please check your email and password, or create an account.');
|
|
});
|
|
|
|
it('should increment login attempts on failed login', async () => {
|
|
await request(app)
|
|
.post('/auth/login')
|
|
.send({
|
|
email: 'login@example.com',
|
|
password: 'WrongPassword!',
|
|
})
|
|
.expect(401);
|
|
|
|
const user = await User.findOne({ where: { email: 'login@example.com' } });
|
|
expect(user.loginAttempts).toBe(1);
|
|
});
|
|
|
|
it('should lock account after too many failed attempts', async () => {
|
|
// Make 10 failed login attempts (MAX_LOGIN_ATTEMPTS is 10)
|
|
for (let i = 0; i < 10; i++) {
|
|
await request(app)
|
|
.post('/auth/login')
|
|
.send({
|
|
email: 'login@example.com',
|
|
password: 'WrongPassword!',
|
|
});
|
|
}
|
|
|
|
// 11th attempt should return locked error
|
|
const response = await request(app)
|
|
.post('/auth/login')
|
|
.send({
|
|
email: 'login@example.com',
|
|
password: 'TestPassword123!', // Correct password
|
|
})
|
|
.expect(423);
|
|
|
|
expect(response.body.error).toContain('Account is temporarily locked');
|
|
});
|
|
|
|
it('should reset login attempts on successful login', async () => {
|
|
// First fail a login
|
|
await request(app)
|
|
.post('/auth/login')
|
|
.send({
|
|
email: 'login@example.com',
|
|
password: 'WrongPassword!',
|
|
});
|
|
|
|
// Verify attempts incremented
|
|
let user = await User.findOne({ where: { email: 'login@example.com' } });
|
|
expect(user.loginAttempts).toBe(1);
|
|
|
|
// Now login successfully
|
|
await request(app)
|
|
.post('/auth/login')
|
|
.send({
|
|
email: 'login@example.com',
|
|
password: 'TestPassword123!',
|
|
})
|
|
.expect(200);
|
|
|
|
// Verify attempts reset
|
|
user = await User.findOne({ where: { email: 'login@example.com' } });
|
|
expect(user.loginAttempts).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('POST /auth/logout', () => {
|
|
it('should clear cookies on logout', async () => {
|
|
const response = await request(app)
|
|
.post('/auth/logout')
|
|
.expect(200);
|
|
|
|
expect(response.body.message).toBe('Logged out successfully');
|
|
|
|
// Verify cookies are cleared
|
|
const cookies = response.headers['set-cookie'];
|
|
expect(cookies.some(c => c.includes('accessToken=;'))).toBe(true);
|
|
expect(cookies.some(c => c.includes('refreshToken=;'))).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('POST /auth/refresh', () => {
|
|
let testUser;
|
|
|
|
beforeEach(async () => {
|
|
testUser = await createTestUser({
|
|
email: 'refresh@example.com',
|
|
isVerified: true,
|
|
});
|
|
});
|
|
|
|
it('should refresh access token with valid refresh token', async () => {
|
|
// Create a valid refresh token
|
|
const refreshToken = jwt.sign(
|
|
{ id: testUser.id, jwtVersion: testUser.jwtVersion, type: 'refresh' },
|
|
process.env.JWT_REFRESH_SECRET,
|
|
{ expiresIn: '7d' }
|
|
);
|
|
|
|
const response = await request(app)
|
|
.post('/auth/refresh')
|
|
.set('Cookie', [`refreshToken=${refreshToken}`])
|
|
.expect(200);
|
|
|
|
expect(response.body.user).toBeDefined();
|
|
expect(response.body.user.email).toBe('refresh@example.com');
|
|
|
|
// Verify new access token cookie was set
|
|
const cookies = response.headers['set-cookie'];
|
|
expect(cookies.some(c => c.startsWith('accessToken='))).toBe(true);
|
|
});
|
|
|
|
it('should reject refresh without token', async () => {
|
|
const response = await request(app)
|
|
.post('/auth/refresh')
|
|
.expect(401);
|
|
|
|
expect(response.body.error).toBe('Refresh token required');
|
|
});
|
|
|
|
it('should reject refresh with invalid token', async () => {
|
|
const response = await request(app)
|
|
.post('/auth/refresh')
|
|
.set('Cookie', ['refreshToken=invalid-token'])
|
|
.expect(401);
|
|
|
|
expect(response.body.error).toBe('Invalid or expired refresh token');
|
|
});
|
|
|
|
it('should reject refresh with outdated JWT version', async () => {
|
|
// Create refresh token with old JWT version
|
|
const refreshToken = jwt.sign(
|
|
{ id: testUser.id, jwtVersion: testUser.jwtVersion - 1, type: 'refresh' },
|
|
process.env.JWT_REFRESH_SECRET,
|
|
{ expiresIn: '7d' }
|
|
);
|
|
|
|
const response = await request(app)
|
|
.post('/auth/refresh')
|
|
.set('Cookie', [`refreshToken=${refreshToken}`])
|
|
.expect(401);
|
|
|
|
expect(response.body.code).toBe('JWT_VERSION_MISMATCH');
|
|
});
|
|
});
|
|
|
|
describe('GET /auth/status', () => {
|
|
let testUser;
|
|
|
|
beforeEach(async () => {
|
|
testUser = await createTestUser({
|
|
email: 'status@example.com',
|
|
isVerified: true,
|
|
});
|
|
});
|
|
|
|
it('should return authenticated status with valid token', async () => {
|
|
const accessToken = jwt.sign(
|
|
{ id: testUser.id, jwtVersion: testUser.jwtVersion },
|
|
process.env.JWT_ACCESS_SECRET,
|
|
{ expiresIn: '15m' }
|
|
);
|
|
|
|
const response = await request(app)
|
|
.get('/auth/status')
|
|
.set('Cookie', [`accessToken=${accessToken}`])
|
|
.expect(200);
|
|
|
|
expect(response.body.authenticated).toBe(true);
|
|
expect(response.body.user.email).toBe('status@example.com');
|
|
});
|
|
|
|
it('should return unauthenticated status without token', async () => {
|
|
const response = await request(app)
|
|
.get('/auth/status')
|
|
.expect(200);
|
|
|
|
expect(response.body.authenticated).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('POST /auth/verify-email', () => {
|
|
let testUser;
|
|
let verificationCode;
|
|
let accessToken;
|
|
|
|
beforeEach(async () => {
|
|
testUser = await createTestUser({
|
|
email: 'unverified@example.com',
|
|
isVerified: false,
|
|
});
|
|
await testUser.generateVerificationToken();
|
|
await testUser.reload();
|
|
verificationCode = testUser.verificationToken; // Now a 6-digit code
|
|
|
|
// Generate access token for authentication
|
|
accessToken = jwt.sign(
|
|
{ id: testUser.id, email: testUser.email, jwtVersion: testUser.jwtVersion || 0 },
|
|
process.env.JWT_ACCESS_SECRET || 'test-access-secret',
|
|
{ expiresIn: '15m' }
|
|
);
|
|
});
|
|
|
|
it('should verify email with valid code', async () => {
|
|
const response = await request(app)
|
|
.post('/auth/verify-email')
|
|
.set('Cookie', `accessToken=${accessToken}`)
|
|
.send({ code: verificationCode })
|
|
.expect(200);
|
|
|
|
expect(response.body.message).toBe('Email verified successfully');
|
|
expect(response.body.user.isVerified).toBe(true);
|
|
|
|
// Verify in database
|
|
await testUser.reload();
|
|
expect(testUser.isVerified).toBe(true);
|
|
expect(testUser.verificationToken).toBeNull();
|
|
});
|
|
|
|
it('should reject verification with invalid code', async () => {
|
|
const response = await request(app)
|
|
.post('/auth/verify-email')
|
|
.set('Cookie', `accessToken=${accessToken}`)
|
|
.send({ code: '000000' })
|
|
.expect(400);
|
|
|
|
expect(response.body.code).toBe('VERIFICATION_INVALID');
|
|
});
|
|
|
|
it('should reject verification for already verified user', async () => {
|
|
// First verify the user
|
|
await testUser.verifyEmail();
|
|
|
|
const response = await request(app)
|
|
.post('/auth/verify-email')
|
|
.set('Cookie', `accessToken=${accessToken}`)
|
|
.send({ code: verificationCode })
|
|
.expect(400);
|
|
|
|
expect(response.body.code).toBe('ALREADY_VERIFIED');
|
|
});
|
|
});
|
|
|
|
describe('Password Reset Flow', () => {
|
|
let testUser;
|
|
|
|
beforeEach(async () => {
|
|
testUser = await createTestUser({
|
|
email: 'reset@example.com',
|
|
isVerified: true,
|
|
authProvider: 'local',
|
|
});
|
|
});
|
|
|
|
describe('POST /auth/forgot-password', () => {
|
|
it('should accept valid email and generate reset token', async () => {
|
|
const response = await request(app)
|
|
.post('/auth/forgot-password')
|
|
.send({ email: 'reset@example.com' })
|
|
.expect(200);
|
|
|
|
expect(response.body.message).toContain('If an account exists');
|
|
|
|
// Verify token was generated in database
|
|
await testUser.reload();
|
|
expect(testUser.passwordResetToken).toBeDefined();
|
|
expect(testUser.passwordResetTokenExpiry).toBeDefined();
|
|
});
|
|
|
|
it('should return success even for non-existent email (security)', async () => {
|
|
const response = await request(app)
|
|
.post('/auth/forgot-password')
|
|
.send({ email: 'nonexistent@example.com' })
|
|
.expect(200);
|
|
|
|
expect(response.body.message).toContain('If an account exists');
|
|
});
|
|
});
|
|
|
|
describe('POST /auth/reset-password', () => {
|
|
let resetToken;
|
|
|
|
beforeEach(async () => {
|
|
resetToken = await testUser.generatePasswordResetToken();
|
|
});
|
|
|
|
it('should reset password with valid token', async () => {
|
|
const newPassword = 'NewSecurePassword123!';
|
|
|
|
const response = await request(app)
|
|
.post('/auth/reset-password')
|
|
.send({ token: resetToken, newPassword })
|
|
.expect(200);
|
|
|
|
expect(response.body.message).toContain('Password has been reset');
|
|
|
|
// Verify password was changed
|
|
await testUser.reload();
|
|
const isValid = await testUser.comparePassword(newPassword);
|
|
expect(isValid).toBe(true);
|
|
|
|
// Verify token was cleared
|
|
expect(testUser.passwordResetToken).toBeNull();
|
|
});
|
|
|
|
it('should reject reset with invalid token', async () => {
|
|
const response = await request(app)
|
|
.post('/auth/reset-password')
|
|
.send({ token: 'invalid-token', newPassword: 'NewPassword123!' })
|
|
.expect(400);
|
|
|
|
// Response should contain error (format may vary based on validation)
|
|
expect(response.body.error || response.body.errors).toBeDefined();
|
|
});
|
|
|
|
it('should increment JWT version after password reset', async () => {
|
|
const oldJwtVersion = testUser.jwtVersion;
|
|
|
|
await request(app)
|
|
.post('/auth/reset-password')
|
|
.send({ token: resetToken, newPassword: 'NewPassword123!' })
|
|
.expect(200);
|
|
|
|
await testUser.reload();
|
|
expect(testUser.jwtVersion).toBe(oldJwtVersion + 1);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('CSRF Token', () => {
|
|
it('should return CSRF token', async () => {
|
|
const response = await request(app)
|
|
.get('/auth/csrf-token')
|
|
.expect(200);
|
|
|
|
expect(response.headers['x-csrf-token']).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('Alpha Testing Mode', () => {
|
|
beforeEach(() => {
|
|
process.env.ALPHA_TESTING_ENABLED = 'true';
|
|
});
|
|
|
|
afterEach(() => {
|
|
process.env.ALPHA_TESTING_ENABLED = 'false';
|
|
});
|
|
|
|
it('should reject registration without alpha code when enabled', async () => {
|
|
const response = await request(app)
|
|
.post('/auth/register')
|
|
.send({
|
|
email: 'alpha@example.com',
|
|
password: 'SecurePassword123!',
|
|
firstName: 'Alpha',
|
|
lastName: 'User',
|
|
})
|
|
.expect(403);
|
|
|
|
expect(response.body.error).toContain('Alpha access required');
|
|
});
|
|
|
|
it('should allow registration with valid alpha code', async () => {
|
|
const validCode = 'ALPHA-TEST1234';
|
|
const invitation = await createAlphaInvitation({
|
|
code: validCode,
|
|
email: 'invited@example.com', // Required field
|
|
});
|
|
|
|
// Cookie-parser parses JSON cookies that start with 'j:'
|
|
const cookieValue = `j:${JSON.stringify({ code: validCode })}`;
|
|
|
|
const response = await request(app)
|
|
.post('/auth/register')
|
|
.set('Cookie', [`alphaAccessCode=${cookieValue}`])
|
|
.send({
|
|
email: 'alphauser@example.com',
|
|
password: 'SecurePassword123!',
|
|
firstName: 'Alpha',
|
|
lastName: 'User',
|
|
})
|
|
.expect(201);
|
|
|
|
expect(response.body.user.email).toBe('alphauser@example.com');
|
|
|
|
// Verify invitation was linked
|
|
await invitation.reload();
|
|
expect(invitation.usedBy).toBeDefined();
|
|
expect(invitation.status).toBe('active');
|
|
});
|
|
});
|
|
});
|