fixed integration tests

This commit is contained in:
jackiettran
2026-01-18 17:44:26 -05:00
parent d570f607d3
commit e6c56ae90f
2 changed files with 105 additions and 12 deletions

View File

@@ -1,13 +1,30 @@
// Integration test setup // Integration test setup
// Integration tests use a real database, so we don't mock DATABASE_URL
process.env.NODE_ENV = 'test'; const path = require("path");
require("dotenv").config({ path: path.join(__dirname, "..", ".env.test") });
// Ensure JWT secrets are set for integration tests process.env.NODE_ENV = "test";
process.env.JWT_ACCESS_SECRET = process.env.JWT_ACCESS_SECRET || 'test-access-secret';
process.env.JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET || 'test-refresh-secret';
process.env.JWT_SECRET = process.env.JWT_SECRET || 'test-secret';
// Set other required env vars if not already set // Required environment variables - fail fast if missing
process.env.GOOGLE_MAPS_API_KEY = process.env.GOOGLE_MAPS_API_KEY || 'test-key'; const requiredEnvVars = [
process.env.STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY || 'sk_test_key'; "JWT_ACCESS_SECRET",
"JWT_REFRESH_SECRET",
"CSRF_SECRET",
"TOTP_ENCRYPTION_KEY",
];
const missingVars = requiredEnvVars.filter((v) => !process.env[v]);
if (missingVars.length > 0) {
throw new Error(
`Missing required environment variables for integration tests: ${missingVars.join(", ")}\n` +
`Please ensure these are set in your .env.test file.`,
);
}
// Optional variables with safe defaults
process.env.JWT_SECRET =
process.env.JWT_SECRET || process.env.JWT_ACCESS_SECRET;
process.env.EMAIL_ENABLED = "false";
process.env.FRONTEND_URL = process.env.FRONTEND_URL || "http://localhost:3000";
process.env.GOOGLE_MAPS_API_KEY = process.env.GOOGLE_MAPS_API_KEY || "test-key";
process.env.STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY || "sk_test_key";

View File

@@ -6,6 +6,17 @@
* and password reset functionality. * 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 request = require('supertest');
const express = require('express'); const express = require('express');
const cookieParser = require('cookie-parser'); const cookieParser = require('cookie-parser');
@@ -32,6 +43,63 @@ jest.mock('../../middleware/csrf', () => ({
}, },
})); }));
// 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 { sequelize, User, AlphaInvitation } = require('../../models');
const authRoutes = require('../../routes/auth'); const authRoutes = require('../../routes/auth');
@@ -48,6 +116,14 @@ const createTestApp = () => {
}); });
app.use('/auth', authRoutes); 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; return app;
}; };
@@ -98,9 +174,9 @@ describe('Auth Integration Tests', () => {
}); });
beforeEach(async () => { beforeEach(async () => {
// Clean up users before each test // Use destroy without truncate for safer cleanup with foreign keys
await User.destroy({ where: {}, truncate: true, cascade: true }); await User.destroy({ where: {}, force: true });
await AlphaInvitation.destroy({ where: {}, truncate: true, cascade: true }); await AlphaInvitation.destroy({ where: {}, force: true });
}); });
describe('POST /auth/register', () => { describe('POST /auth/register', () => {