140 lines
4.3 KiB
TypeScript
140 lines
4.3 KiB
TypeScript
import { beforeEach, describe, expect, it } from 'vitest';
|
|
import { InMemoryMagicLinkRepository } from './InMemoryMagicLinkRepository';
|
|
|
|
const mockLogger = {
|
|
debug: () => {},
|
|
info: () => {},
|
|
warn: () => {},
|
|
error: () => {},
|
|
};
|
|
|
|
describe('InMemoryMagicLinkRepository', () => {
|
|
let repository: InMemoryMagicLinkRepository;
|
|
|
|
beforeEach(() => {
|
|
repository = new InMemoryMagicLinkRepository(mockLogger as any);
|
|
});
|
|
|
|
describe('createPasswordResetRequest', () => {
|
|
it('should create a password reset request', async () => {
|
|
const request = {
|
|
email: 'test@example.com',
|
|
token: 'abc123',
|
|
expiresAt: new Date(Date.now() + 15 * 60 * 1000),
|
|
userId: 'user-123',
|
|
};
|
|
|
|
await repository.createPasswordResetRequest(request);
|
|
|
|
const found = await repository.findByToken('abc123');
|
|
expect(found).toEqual(request);
|
|
});
|
|
|
|
it('should enforce rate limiting', async () => {
|
|
const request = {
|
|
email: 'test@example.com',
|
|
token: 'token1',
|
|
expiresAt: new Date(Date.now() + 15 * 60 * 1000),
|
|
userId: 'user-123',
|
|
};
|
|
|
|
// Create 3 requests for same email
|
|
await repository.createPasswordResetRequest({ ...request, token: 'token1' });
|
|
await repository.createPasswordResetRequest({ ...request, token: 'token2' });
|
|
await repository.createPasswordResetRequest({ ...request, token: 'token3' });
|
|
|
|
// 4th should fail
|
|
const result = await repository.checkRateLimit('test@example.com');
|
|
expect(result.isErr()).toBe(true);
|
|
});
|
|
|
|
it('should allow requests after time window expires', async () => {
|
|
const now = Date.now();
|
|
const request = {
|
|
email: 'test@example.com',
|
|
token: 'token1',
|
|
expiresAt: new Date(now + 15 * 60 * 1000),
|
|
userId: 'user-123',
|
|
};
|
|
|
|
// Mock Date.now to return time after rate limit window
|
|
const originalNow = Date.now;
|
|
Date.now = () => now + (16 * 60 * 1000); // 16 minutes later
|
|
|
|
try {
|
|
await repository.createPasswordResetRequest(request);
|
|
const found = await repository.findByToken('token1');
|
|
expect(found).toBeDefined();
|
|
} finally {
|
|
Date.now = originalNow;
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('findByToken', () => {
|
|
it('should find existing token', async () => {
|
|
const request = {
|
|
email: 'test@example.com',
|
|
token: 'abc123',
|
|
expiresAt: new Date(Date.now() + 15 * 60 * 1000),
|
|
userId: 'user-123',
|
|
};
|
|
|
|
await repository.createPasswordResetRequest(request);
|
|
const found = await repository.findByToken('abc123');
|
|
|
|
expect(found).toEqual(request);
|
|
});
|
|
|
|
it('should return null for non-existent token', async () => {
|
|
const found = await repository.findByToken('nonexistent');
|
|
expect(found).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('markAsUsed', () => {
|
|
it('should mark token as used', async () => {
|
|
const request = {
|
|
email: 'test@example.com',
|
|
token: 'abc123',
|
|
expiresAt: new Date(Date.now() + 15 * 60 * 1000),
|
|
userId: 'user-123',
|
|
};
|
|
|
|
await repository.createPasswordResetRequest(request);
|
|
await repository.markAsUsed('abc123');
|
|
|
|
const found = await repository.findByToken('abc123');
|
|
expect(found).toBeNull();
|
|
});
|
|
|
|
it('should handle non-existent token gracefully', async () => {
|
|
await expect(repository.markAsUsed('nonexistent')).resolves.not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('checkRateLimit', () => {
|
|
it('should allow requests under limit', async () => {
|
|
const result = await repository.checkRateLimit('test@example.com');
|
|
expect(result.isOk()).toBe(true);
|
|
});
|
|
|
|
it('should reject requests over limit', async () => {
|
|
const email = 'test@example.com';
|
|
const request = {
|
|
email,
|
|
token: 'token',
|
|
expiresAt: new Date(Date.now() + 15 * 60 * 1000),
|
|
userId: 'user-123',
|
|
};
|
|
|
|
// Create 3 requests
|
|
await repository.createPasswordResetRequest({ ...request, token: 'token1' });
|
|
await repository.createPasswordResetRequest({ ...request, token: 'token2' });
|
|
await repository.createPasswordResetRequest({ ...request, token: 'token3' });
|
|
|
|
const result = await repository.checkRateLimit(email);
|
|
expect(result.isErr()).toBe(true);
|
|
});
|
|
});
|
|
}); |