test(email): add internal controller spec
Add unit tests for the internal email controller. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2cd36ec961
commit
64333c9115
1 changed files with 234 additions and 0 deletions
234
features/email/backend/src/internal/internal.controller.spec.ts
Normal file
234
features/email/backend/src/internal/internal.controller.spec.ts
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing'
|
||||
import { UnauthorizedException } from '@nestjs/common'
|
||||
import { ConfigService } from '@nestjs/config'
|
||||
import { InternalController } from './internal.controller'
|
||||
import { UsersEmailService } from '../users/users-email.service'
|
||||
import { EmailQueueService } from '../core/email-queue.service'
|
||||
import { EmailCategory } from '../core/entities/email-log.entity'
|
||||
|
||||
describe('InternalController', () => {
|
||||
let controller: InternalController
|
||||
let usersEmailService: jest.Mocked<UsersEmailService>
|
||||
let emailQueueService: jest.Mocked<EmailQueueService>
|
||||
|
||||
const validApiKey = 'test-internal-api-key'
|
||||
const mockUserData = {
|
||||
userId: 'user-123',
|
||||
email: 'test@example.com',
|
||||
name: 'Test User',
|
||||
}
|
||||
|
||||
describe('when API key is configured', () => {
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [InternalController],
|
||||
providers: [
|
||||
{
|
||||
provide: UsersEmailService,
|
||||
useValue: {
|
||||
sendWelcomeEmail: jest.fn().mockResolvedValue('job-welcome'),
|
||||
sendEmailVerification: jest.fn().mockResolvedValue('job-verify'),
|
||||
sendPasswordReset: jest.fn().mockResolvedValue('job-reset'),
|
||||
sendPasswordChanged: jest.fn().mockResolvedValue('job-changed'),
|
||||
sendAccountLocked: jest.fn().mockResolvedValue('job-locked'),
|
||||
sendLoginAlert: jest.fn().mockResolvedValue('job-alert'),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: EmailQueueService,
|
||||
useValue: {
|
||||
queueEmail: jest.fn().mockResolvedValue('job-otp'),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: {
|
||||
get: jest.fn((key: string, defaultValue?: string) => {
|
||||
if (key === 'INTERNAL_API_KEY') return validApiKey
|
||||
return defaultValue
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile()
|
||||
|
||||
controller = module.get<InternalController>(InternalController)
|
||||
usersEmailService = module.get(UsersEmailService)
|
||||
emailQueueService = module.get(EmailQueueService)
|
||||
})
|
||||
|
||||
describe('API key validation', () => {
|
||||
it('should reject requests without API key', async () => {
|
||||
await expect(controller.sendWelcome(undefined as any, mockUserData)).rejects.toThrow(
|
||||
UnauthorizedException
|
||||
)
|
||||
})
|
||||
|
||||
it('should reject requests with invalid API key', async () => {
|
||||
await expect(controller.sendWelcome('wrong-key', mockUserData)).rejects.toThrow(
|
||||
UnauthorizedException
|
||||
)
|
||||
})
|
||||
|
||||
it('should accept requests with valid API key', async () => {
|
||||
const result = await controller.sendWelcome(validApiKey, mockUserData)
|
||||
expect(result.jobId).toBe('job-welcome')
|
||||
})
|
||||
})
|
||||
|
||||
describe('sendWelcome', () => {
|
||||
it('should send welcome email and return job ID', async () => {
|
||||
const result = await controller.sendWelcome(validApiKey, mockUserData)
|
||||
|
||||
expect(result).toEqual({ jobId: 'job-welcome' })
|
||||
expect(usersEmailService.sendWelcomeEmail).toHaveBeenCalledWith(mockUserData)
|
||||
})
|
||||
})
|
||||
|
||||
describe('sendVerification', () => {
|
||||
it('should send verification email', async () => {
|
||||
const body = { ...mockUserData, verificationToken: 'token-123' }
|
||||
|
||||
const result = await controller.sendVerification(validApiKey, body)
|
||||
|
||||
expect(result).toEqual({ jobId: 'job-verify' })
|
||||
expect(usersEmailService.sendEmailVerification).toHaveBeenCalledWith(body)
|
||||
})
|
||||
})
|
||||
|
||||
describe('sendPasswordReset', () => {
|
||||
it('should send password reset email', async () => {
|
||||
const body = { ...mockUserData, resetToken: 'reset-123' }
|
||||
|
||||
const result = await controller.sendPasswordReset(validApiKey, body)
|
||||
|
||||
expect(result).toEqual({ jobId: 'job-reset' })
|
||||
expect(usersEmailService.sendPasswordReset).toHaveBeenCalledWith(body)
|
||||
})
|
||||
})
|
||||
|
||||
describe('sendPasswordChanged', () => {
|
||||
it('should send password changed notification', async () => {
|
||||
const result = await controller.sendPasswordChanged(validApiKey, mockUserData)
|
||||
|
||||
expect(result).toEqual({ jobId: 'job-changed' })
|
||||
expect(usersEmailService.sendPasswordChanged).toHaveBeenCalledWith(mockUserData)
|
||||
})
|
||||
})
|
||||
|
||||
describe('sendAccountLocked', () => {
|
||||
it('should send account locked notification', async () => {
|
||||
const body = {
|
||||
...mockUserData,
|
||||
reason: 'Too many failed attempts',
|
||||
unlockUrl: 'https://example.com/unlock',
|
||||
}
|
||||
|
||||
const result = await controller.sendAccountLocked(validApiKey, body)
|
||||
|
||||
expect(result).toEqual({ jobId: 'job-locked' })
|
||||
expect(usersEmailService.sendAccountLocked).toHaveBeenCalledWith(body)
|
||||
})
|
||||
})
|
||||
|
||||
describe('sendLoginAlert', () => {
|
||||
it('should send login alert', async () => {
|
||||
const body = {
|
||||
...mockUserData,
|
||||
device: 'Chrome on Windows',
|
||||
location: 'Reykjavik, Iceland',
|
||||
ipAddress: '192.168.1.1',
|
||||
}
|
||||
|
||||
const result = await controller.sendLoginAlert(validApiKey, body)
|
||||
|
||||
expect(result).toEqual({ jobId: 'job-alert' })
|
||||
expect(usersEmailService.sendLoginAlert).toHaveBeenCalledWith(body)
|
||||
})
|
||||
})
|
||||
|
||||
describe('sendOtp', () => {
|
||||
it('should queue OTP email with correct template', async () => {
|
||||
const body = {
|
||||
...mockUserData,
|
||||
code: '123456',
|
||||
expiresInMinutes: 5,
|
||||
}
|
||||
|
||||
const result = await controller.sendOtp(validApiKey, body)
|
||||
|
||||
expect(result).toEqual({ jobId: 'job-otp' })
|
||||
expect(emailQueueService.queueEmail).toHaveBeenCalledWith({
|
||||
to: mockUserData.email,
|
||||
templateName: 'otp-code',
|
||||
variables: {
|
||||
name: mockUserData.name,
|
||||
code: '123456',
|
||||
expiresIn: '5 minutes',
|
||||
},
|
||||
category: EmailCategory.USERS,
|
||||
userId: mockUserData.userId,
|
||||
priority: 'high',
|
||||
})
|
||||
})
|
||||
|
||||
it('should use default values when optional fields missing', async () => {
|
||||
const body = {
|
||||
userId: 'user-123',
|
||||
email: 'test@example.com',
|
||||
code: '654321',
|
||||
}
|
||||
|
||||
await controller.sendOtp(validApiKey, body)
|
||||
|
||||
expect(emailQueueService.queueEmail).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: expect.objectContaining({
|
||||
name: 'there',
|
||||
expiresIn: '10 minutes',
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when API key is not configured', () => {
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [InternalController],
|
||||
providers: [
|
||||
{
|
||||
provide: UsersEmailService,
|
||||
useValue: {
|
||||
sendWelcomeEmail: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: EmailQueueService,
|
||||
useValue: {
|
||||
queueEmail: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: {
|
||||
get: jest.fn((key: string, defaultValue?: string) => {
|
||||
if (key === 'INTERNAL_API_KEY') return '' // Not configured
|
||||
return defaultValue
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile()
|
||||
|
||||
controller = module.get<InternalController>(InternalController)
|
||||
})
|
||||
|
||||
it('should reject all requests when not configured', async () => {
|
||||
await expect(controller.sendWelcome(validApiKey, mockUserData)).rejects.toThrow(
|
||||
'Internal API not configured'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue