feat(mvp): phase 8 - integration, testing & deployment
Fix all build/type/lint errors (zod 3.25 compat wrapper, Svelte 5 fixes), write 115 unit tests across 10 test files, expand seed script with demo data, update Docker config with migration on startup.
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
// Mock prisma before importing authService
|
||||
vi.mock('../../prisma.js', () => ({
|
||||
prisma: {
|
||||
user: {
|
||||
update: vi.fn(),
|
||||
findUnique: vi.fn()
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Set JWT_SECRET for tests
|
||||
process.env.JWT_SECRET = 'test-secret-key-for-unit-tests';
|
||||
|
||||
import {
|
||||
hashPassword,
|
||||
verifyPassword,
|
||||
signAccessToken,
|
||||
verifyAccessToken,
|
||||
generateRefreshToken,
|
||||
getRefreshTokenExpiry,
|
||||
rotateTokens
|
||||
} from '../authService.js';
|
||||
import { prisma } from '../../prisma.js';
|
||||
|
||||
describe('authService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('hashPassword / verifyPassword', () => {
|
||||
it('hashes a password and verifies it correctly', async () => {
|
||||
const password = 'mySecurePassword123';
|
||||
const hash = await hashPassword(password);
|
||||
|
||||
expect(hash).not.toBe(password);
|
||||
expect(hash.length).toBeGreaterThan(0);
|
||||
|
||||
const isValid = await verifyPassword(password, hash);
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects wrong password', async () => {
|
||||
const hash = await hashPassword('correct-password');
|
||||
const isValid = await verifyPassword('wrong-password', hash);
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('signAccessToken / verifyAccessToken', () => {
|
||||
it('signs and verifies a token', () => {
|
||||
const payload = { userId: 'usr-1', email: 'test@test.com', role: 'user' };
|
||||
const token = signAccessToken(payload);
|
||||
|
||||
expect(typeof token).toBe('string');
|
||||
expect(token.split('.')).toHaveLength(3);
|
||||
|
||||
const decoded = verifyAccessToken(token);
|
||||
expect(decoded.userId).toBe('usr-1');
|
||||
expect(decoded.email).toBe('test@test.com');
|
||||
expect(decoded.role).toBe('user');
|
||||
});
|
||||
|
||||
it('throws for invalid token', () => {
|
||||
expect(() => verifyAccessToken('invalid.token.value')).toThrow(
|
||||
'Invalid or expired access token'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateRefreshToken', () => {
|
||||
it('generates a hex string', () => {
|
||||
const token = generateRefreshToken();
|
||||
expect(typeof token).toBe('string');
|
||||
expect(token.length).toBe(96); // 48 bytes * 2 hex chars
|
||||
expect(/^[0-9a-f]+$/.test(token)).toBe(true);
|
||||
});
|
||||
|
||||
it('generates unique tokens', () => {
|
||||
const token1 = generateRefreshToken();
|
||||
const token2 = generateRefreshToken();
|
||||
expect(token1).not.toBe(token2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRefreshTokenExpiry', () => {
|
||||
it('returns a future date', () => {
|
||||
const expiry = getRefreshTokenExpiry();
|
||||
expect(expiry.getTime()).toBeGreaterThan(Date.now());
|
||||
});
|
||||
|
||||
it('defaults to 7 days from now', () => {
|
||||
const expiry = getRefreshTokenExpiry();
|
||||
const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;
|
||||
const diff = expiry.getTime() - Date.now();
|
||||
// Allow 10 seconds tolerance
|
||||
expect(diff).toBeGreaterThan(sevenDaysMs - 10000);
|
||||
expect(diff).toBeLessThan(sevenDaysMs + 10000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rotateTokens', () => {
|
||||
it('generates new token pair and saves refresh token', async () => {
|
||||
vi.mocked(prisma.user.update).mockResolvedValue({} as never);
|
||||
|
||||
const result = await rotateTokens('usr-1', 'test@test.com', 'user');
|
||||
|
||||
expect(result.accessToken).toBeTruthy();
|
||||
expect(result.refreshToken).toBeTruthy();
|
||||
expect(prisma.user.update).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user