50c74b9007
Build & Push / Pipeline Tests (push) Failing after 1m34s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Successful in 56s
Test / API Unit Tests (push) Successful in 1m8s
Test / Homepage Unit Tests (push) Successful in 47s
Test / Carplace Unit Tests (push) Successful in 43s
Test / Admin Unit Tests (push) Successful in 46s
Test / Dashboard Unit Tests (push) Failing after 43s
Test / API Integration Tests (push) Successful in 1m8s
158 lines
6.1 KiB
TypeScript
158 lines
6.1 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/requireCompanyAuth', () => ({
|
|
requireCompanyDocumentAuth: (_req: any, _res: any, next: any) => next(),
|
|
requireCompanyAuth: (req: any, _res: any, next: any) => {
|
|
req.employee = { id: 'employee_1', role: 'MANAGER', firstName: 'Aya', lastName: 'Manager' }
|
|
next()
|
|
},
|
|
}))
|
|
vi.mock('../../middleware/requireTenant', () => ({
|
|
requireTenant: (req: any, _res: any, next: any) => {
|
|
req.companyId = 'company_1'
|
|
next()
|
|
},
|
|
}))
|
|
vi.mock('../../middleware/requireSubscription', () => {
|
|
const pass = (_req: any, _res: any, next: any) => next()
|
|
return { requireSubscription: pass, requireSubscriptionRead: pass, requireSubscriptionWrite: pass, requireSubscriptionFull: pass }
|
|
})
|
|
vi.mock('../../modules/auth/auth.employee.service', () => ({
|
|
getMe: vi.fn(),
|
|
login: vi.fn(),
|
|
forgotPassword: vi.fn(),
|
|
updateLanguage: vi.fn(),
|
|
resetPassword: vi.fn(),
|
|
}))
|
|
vi.mock('../../modules/menu/menu.service', () => ({ getEmployeeMenu: vi.fn() }))
|
|
vi.mock('../../modules/notifications/notification.service', () => ({
|
|
listCompany: vi.fn(),
|
|
countUnread: vi.fn(),
|
|
markRead: vi.fn(),
|
|
markAllRead: vi.fn(),
|
|
getPreferences: vi.fn(),
|
|
setPreferences: vi.fn(),
|
|
listCompanyHistory: vi.fn(),
|
|
listRenter: vi.fn(),
|
|
markRenterRead: vi.fn(),
|
|
markAllRenterRead: vi.fn(),
|
|
getRenterPreferences: vi.fn(),
|
|
setRenterPreferences: vi.fn(),
|
|
}))
|
|
vi.mock('../../modules/carplace/carplace.service', () => ({
|
|
getCities: vi.fn(),
|
|
getListedCompanies: vi.fn(),
|
|
searchVehicles: vi.fn(),
|
|
searchVehiclesPage: vi.fn(),
|
|
getVehicleDetail: vi.fn(),
|
|
createCarplaceReservation: vi.fn(),
|
|
getCompanyPage: vi.fn(),
|
|
getCompanyReviews: vi.fn(),
|
|
getCompanyVehicles: vi.fn(),
|
|
getVehicle: vi.fn(),
|
|
getCompanyOffers: vi.fn(),
|
|
getReviewContext: vi.fn(),
|
|
submitReview: vi.fn(),
|
|
validateOfferCode: vi.fn(),
|
|
getPublicOffers: vi.fn(),
|
|
}))
|
|
|
|
import request from 'supertest'
|
|
import { createApp } from '../../app'
|
|
import * as employeeService from '../../modules/auth/auth.employee.service'
|
|
import * as notificationService from '../../modules/notifications/notification.service'
|
|
import * as carplaceService from '../../modules/carplace/carplace.service'
|
|
|
|
const app = createApp()
|
|
|
|
describe('employee, notification, and carplace API validation contracts', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
vi.mocked(employeeService.login).mockResolvedValue({ token: 'jwt', employee: { id: 'employee_1' } } as never)
|
|
vi.mocked(employeeService.forgotPassword).mockResolvedValue({ message: 'If that email is registered, a reset link has been sent.' } as never)
|
|
vi.mocked(employeeService.updateLanguage).mockResolvedValue({ language: 'ar' } as never)
|
|
vi.mocked(employeeService.resetPassword).mockResolvedValue({ message: 'Password updated successfully. You can now sign in.' } as never)
|
|
vi.mocked(notificationService.setPreferences).mockResolvedValue({ success: true } as never)
|
|
vi.mocked(carplaceService.searchVehiclesPage).mockResolvedValue({
|
|
items: [],
|
|
pagination: { page: 1, pageSize: 20, totalItems: 0, totalPages: 0 },
|
|
facets: { categories: [], transmissions: [], makes: [], price: { min: null, max: null } },
|
|
} as never)
|
|
vi.mocked(carplaceService.submitReview).mockResolvedValue({ id: 'review_1' } as never)
|
|
})
|
|
|
|
it('normalizes employee login email before service execution', async () => {
|
|
const res = await request(app).post('/api/v1/auth/employee/login').send({
|
|
email: 'Agent@Example.TEST',
|
|
password: 'valid-password',
|
|
})
|
|
|
|
expect(res.status).toBe(200)
|
|
expect(employeeService.login).toHaveBeenCalledWith({ email: 'agent@example.test', password: 'valid-password' })
|
|
})
|
|
|
|
it('clears any admin session when employee login succeeds', async () => {
|
|
const res = await request(app).post('/api/v1/auth/employee/login').send({
|
|
email: 'agent@example.test',
|
|
password: 'valid-password',
|
|
})
|
|
|
|
expect(res.status).toBe(200)
|
|
expect(res.headers['set-cookie']).toEqual(expect.arrayContaining([
|
|
expect.stringMatching(/^admin_session=;/),
|
|
expect.stringMatching(/^employee_session=/),
|
|
]))
|
|
})
|
|
|
|
it('rejects empty employee reset tokens before service execution', async () => {
|
|
const res = await request(app).post('/api/v1/auth/employee/reset-password').send({ token: '', password: 'new-password' })
|
|
|
|
expect(res.status).toBe(400)
|
|
expect(employeeService.resetPassword).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('forwards authenticated language updates with the employee id from auth context', async () => {
|
|
const res = await request(app).patch('/api/v1/auth/employee/me/language').send({ language: 'ar' })
|
|
|
|
expect(res.status).toBe(200)
|
|
expect(employeeService.updateLanguage).toHaveBeenCalledWith('employee_1', 'ar')
|
|
})
|
|
|
|
it('rejects malformed notification preferences before persistence', async () => {
|
|
const res = await request(app).patch('/api/v1/notifications/company/preferences').send({
|
|
preferences: [{ notificationType: 'RESERVATION_CREATED', channel: 'EMAIL', enabled: true }],
|
|
})
|
|
|
|
expect(res.status).toBe(400)
|
|
expect(notificationService.setPreferences).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('defaults carplace search pagination at the public route boundary', async () => {
|
|
const res = await request(app).get('/api/v1/carplace/search?city=Casablanca')
|
|
|
|
expect(res.status).toBe(200)
|
|
expect(carplaceService.searchVehiclesPage).toHaveBeenCalledWith(expect.objectContaining({
|
|
city: 'Casablanca',
|
|
page: 1,
|
|
pageSize: 20,
|
|
}))
|
|
expect(res.body.data.pagination).toEqual({ page: 1, pageSize: 20, totalItems: 0, totalPages: 0 })
|
|
})
|
|
|
|
it('rejects out-of-range public review ratings before service execution', async () => {
|
|
const res = await request(app).post('/api/v1/carplace/review/token_123').send({
|
|
overallRating: 6,
|
|
vehicleRating: 5,
|
|
serviceRating: 5,
|
|
comment: 'Too magical',
|
|
})
|
|
|
|
expect(res.status).toBe(400)
|
|
expect(carplaceService.submitReview).not.toHaveBeenCalled()
|
|
})
|
|
})
|