Files
carmanagement/apps/api/src/tests/api/auth-boundaries.api.test.ts
T
root 149806a773
Build & Push / Pipeline Tests (push) Successful in 1m53s
Test / Type Check (all packages) (push) Successful in 56s
Build & Push / Build & Push Docker Image (push) Failing after 4m53s
Test / API Unit Tests (push) Successful in 1m15s
Test / Homepage Unit Tests (push) Successful in 46s
Test / Carplace Unit Tests (push) Successful in 41s
Test / Admin Unit Tests (push) Successful in 40s
Test / Dashboard Unit Tests (push) Successful in 42s
Test / API Integration Tests (push) Successful in 1m6s
fix bug 16 and 17
2026-08-04 00:52:58 -04:00

215 lines
7.7 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('../../lib/prisma', () => ({ prisma: {} }))
vi.mock('../../lib/redis', () => ({
redis: { on: vi.fn(), get: vi.fn(), set: vi.fn(), del: vi.fn(), quit: vi.fn() },
}))
vi.mock('../../middleware/requireRenterAuth', () => ({
requireRenterAuth: (req: any, _res: any, next: any) => {
req.renterId = 'renter_1'
next()
},
optionalRenterAuth: (_req: any, _res: any, next: any) => next(),
}))
vi.mock('../../modules/auth/auth.company.service', () => ({
signup: vi.fn(),
completeSignupDisabled: vi.fn(() => {
const err = new Error('gone') as Error & { statusCode?: number; code?: string }
err.statusCode = 410
err.code = 'disabled'
throw err
}),
verifyEmailDisabled: vi.fn(() => {
const err = new Error('gone') as Error & { statusCode?: number; code?: string }
err.statusCode = 410
err.code = 'disabled'
throw err
}),
}))
vi.mock('../../modules/auth/auth.account.service', () => ({
startAccount: vi.fn(),
}))
vi.mock('../../modules/auth/auth.renter.service', () => ({
signupDisabled: vi.fn(() => {
const err = new Error('renter disabled') as Error & { statusCode?: number; code?: string }
err.statusCode = 403
err.code = 'renter_signup_disabled'
throw err
}),
loginDisabled: vi.fn(() => {
const err = new Error('login disabled') as Error & { statusCode?: number; code?: string }
err.statusCode = 403
err.code = 'renter_login_disabled'
throw err
}),
getMe: vi.fn(),
updateMe: vi.fn(),
updateFcmToken: vi.fn(),
}))
import request from 'supertest'
import { createApp } from '../../app'
import * as accountService from '../../modules/auth/auth.account.service'
import * as companyService from '../../modules/auth/auth.company.service'
import * as renterService from '../../modules/auth/auth.renter.service'
const app = createApp()
const signupPayload = {
firstName: 'Aya',
lastName: 'Benali',
email: 'owner@example.com',
password: 'super-secret',
companyName: 'Atlas Cars',
legalName: 'Atlas Cars SARL',
legalForm: 'SARL',
registrationNumber: 'REG123',
iceNumber: 'ICE123',
taxId: 'TAX123',
operatingLicenseNumber: 'LIC123',
operatingLicenseIssuedAt: '2026-01-01',
operatingLicenseIssuedBy: 'Transport Authority',
streetAddress: '1 Fleet Street',
city: 'Casablanca',
country: 'Morocco',
zipCode: '20000',
companyPhone: '+212600000000',
companyEmail: 'contact@example.com',
yearsActive: '5',
responsibleName: 'Omar Alaoui',
responsibleRole: 'Manager',
responsibleIdentityNumber: 'ID123',
responsiblePhone: '+212611111111',
responsibleEmail: 'responsible@example.com',
preferredLanguage: 'fr',
plan: 'STARTER',
billingPeriod: 'MONTHLY',
currency: 'MAD',
paymentProvider: 'PAYPAL',
}
describe('auth API boundaries', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(accountService.startAccount).mockResolvedValue({
message: 'Account created. Please check your email to verify your address before signing in.',
email: 'owner@example.com',
} as never)
vi.mocked(companyService.signup).mockResolvedValue({ companyId: 'company_1', nextStep: 'workspace_created' } as never)
vi.mocked(renterService.getMe).mockResolvedValue({ id: 'renter_1', savedCompanies: [] } as never)
vi.mocked(renterService.updateMe).mockResolvedValue({ id: 'renter_1', firstName: 'Nora' } as never)
vi.mocked(renterService.updateFcmToken).mockResolvedValue({ success: true } as never)
})
it('POST /auth/company/signup validates the full onboarding contract and returns 201', async () => {
const res = await request(app).post('/api/v1/auth/company/signup').send(signupPayload)
expect(res.status).toBe(201)
expect(res.body).toEqual({ data: { companyId: 'company_1', nextStep: 'workspace_created' } })
expect(companyService.signup).toHaveBeenCalledWith(expect.objectContaining({
companyName: 'Atlas Cars',
currency: 'MAD',
preferredLanguage: 'fr',
paymentProvider: 'PAYPAL',
}))
})
it('POST /auth/company/signup rejects malformed onboarding payloads before service execution', async () => {
const res = await request(app).post('/api/v1/auth/company/signup').send({ ...signupPayload, companyEmail: 'not-email' })
expect(res.status).toBe(400)
expect(res.body.error).toBe('validation_error')
expect(companyService.signup).not.toHaveBeenCalled()
})
it('POST /auth/account/start validates minimal signup and returns 201', async () => {
const res = await request(app).post('/api/v1/auth/account/start').send({
firstName: 'Aya',
lastName: 'Benali',
companyName: 'Atlas Cars',
email: 'owner@example.com',
password: 'super-secret',
preferredLanguage: 'fr',
subscriptionPlan: 'GROWTH',
})
expect(res.status).toBe(201)
expect(res.body).toEqual({
data: {
message: 'Account created. Please check your email to verify your address before signing in.',
email: 'owner@example.com',
},
})
expect(accountService.startAccount).toHaveBeenCalledWith({
firstName: 'Aya',
lastName: 'Benali',
companyName: 'Atlas Cars',
email: 'owner@example.com',
password: 'super-secret',
preferredLanguage: 'fr',
subscriptionPlan: 'GROWTH',
})
})
it('POST /auth/account/start rejects malformed minimal signup payloads before service execution', async () => {
const res = await request(app).post('/api/v1/auth/account/start').send({
firstName: 'Aya',
lastName: 'Benali',
companyName: 'A',
email: 'not-email',
password: 'short',
})
expect(res.status).toBe(400)
expect(res.body.error).toBe('validation_error')
expect(accountService.startAccount).not.toHaveBeenCalled()
})
it('preserves removed company auth compatibility endpoints as explicit 410 responses', async () => {
const complete = await request(app).post('/api/v1/auth/company/complete-signup').send({})
const verify = await request(app).post('/api/v1/auth/company/verify-email').send({})
expect(complete.status).toBe(410)
expect(complete.body.error).toBe('disabled')
expect(verify.status).toBe(410)
expect(verify.body.error).toBe('disabled')
})
it('keeps public renter signup and login disabled at the router boundary', async () => {
const signup = await request(app).post('/api/v1/auth/renter/signup').send({})
const login = await request(app).post('/api/v1/auth/renter/login').send({})
expect(signup.status).toBe(403)
expect(signup.body.error).toBe('renter_signup_disabled')
expect(login.status).toBe(403)
expect(login.body.error).toBe('renter_login_disabled')
})
it('GET /auth/renter/me uses authenticated renter identity', async () => {
const res = await request(app).get('/api/v1/auth/renter/me')
expect(res.status).toBe(200)
expect(res.body).toEqual({ data: { id: 'renter_1', savedCompanies: [] } })
expect(renterService.getMe).toHaveBeenCalledWith('renter_1')
})
it('PATCH /auth/renter/me validates renter profile updates', async () => {
const res = await request(app).patch('/api/v1/auth/renter/me').send({ firstName: ' Nora ', preferredCurrency: 'MAD' })
expect(res.status).toBe(200)
expect(renterService.updateMe).toHaveBeenCalledWith('renter_1', { firstName: 'Nora', preferredCurrency: 'MAD' })
})
it('POST /auth/renter/me/fcm-token updates only the token value for the authenticated renter', async () => {
const res = await request(app).post('/api/v1/auth/renter/me/fcm-token').send({ fcmToken: 'token_123' })
expect(res.status).toBe(200)
expect(res.body).toEqual({ data: { success: true } })
expect(renterService.updateFcmToken).toHaveBeenCalledWith('renter_1', 'token_123')
})
})