refractor code,
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { AppError, NotFoundError } from '../../http/errors'
|
||||
|
||||
vi.mock('./site.repo', () => ({
|
||||
findCompanyBySlug: vi.fn(),
|
||||
findPublishedVehicles: vi.fn(),
|
||||
findVehicleById: vi.fn(),
|
||||
findActiveOffers: vi.fn(),
|
||||
findInsurancePolicies: vi.fn(),
|
||||
findOfferByPromoCode: vi.fn(),
|
||||
upsertCustomer: vi.fn(),
|
||||
createReservation: vi.fn(),
|
||||
findReservationWithDetails: vi.fn(),
|
||||
findBooking: vi.fn(),
|
||||
findReservationForPayment: vi.fn(),
|
||||
createRentalPayment: vi.fn(),
|
||||
findPaymentByPaypalOrderId: vi.fn(),
|
||||
capturePaypalPayment: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('../../services/vehicleAvailabilityService', () => ({
|
||||
getVehicleAvailabilitySummary: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('../../services/insuranceService', () => ({
|
||||
applyInsurancesToReservation: vi.fn().mockResolvedValue(undefined),
|
||||
}))
|
||||
|
||||
vi.mock('../../services/additionalDriverService', () => ({
|
||||
applyAdditionalDriversToReservation: vi.fn().mockResolvedValue(undefined),
|
||||
}))
|
||||
|
||||
vi.mock('../../services/pricingRuleService', () => ({
|
||||
applyPricingRules: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('../../services/licenseValidationService', () => ({
|
||||
validateLicense: vi.fn(),
|
||||
validateAndFlagLicense: vi.fn().mockResolvedValue(undefined),
|
||||
}))
|
||||
|
||||
vi.mock('../../services/amanpayService', () => ({
|
||||
isConfigured: vi.fn(),
|
||||
createCheckout: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('../../services/paypalService', () => ({
|
||||
isConfigured: vi.fn(),
|
||||
createOrder: vi.fn(),
|
||||
captureOrder: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('../../services/platformContentService', () => ({
|
||||
getMarketplaceHomepageContent: vi.fn(),
|
||||
}))
|
||||
|
||||
import * as repo from './site.repo'
|
||||
import { getVehicleAvailabilitySummary } from '../../services/vehicleAvailabilityService'
|
||||
import { applyPricingRules } from '../../services/pricingRuleService'
|
||||
import { validateLicense } from '../../services/licenseValidationService'
|
||||
import * as amanpay from '../../services/amanpayService'
|
||||
import * as paypalSvc from '../../services/paypalService'
|
||||
import {
|
||||
getBrand, getPublicVehicles, checkAvailability, validatePromoCode,
|
||||
createBooking, initPayment,
|
||||
} from './site.service'
|
||||
|
||||
const SLUG = 'test-company'
|
||||
|
||||
function makeCompany(overrides: object = {}) {
|
||||
return {
|
||||
id: 'co-1', slug: SLUG, name: 'Test Co', phone: null, email: 'co@test.com',
|
||||
brand: { publicEmail: null }, contractSettings: null,
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
function makeVehicle(overrides: object = {}) {
|
||||
return {
|
||||
id: 'v-1', dailyRate: 10000, year: 2022, make: 'Toyota', model: 'Camry',
|
||||
isPublished: true, status: 'AVAILABLE', companyId: 'co-1', category: 'SEDAN',
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => { vi.clearAllMocks() })
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
describe('getBrand', () => {
|
||||
it('returns company and public brand data only', async () => {
|
||||
vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany({
|
||||
brand: {
|
||||
displayName: 'Test Co',
|
||||
paymentMethodsEnabled: ['AMANPAY', 'PAYPAL'],
|
||||
amanpayMerchantId: 'merchant-id',
|
||||
amanpaySecretKey: 'top-secret',
|
||||
paypalMerchantId: 'paypal-merchant-id',
|
||||
},
|
||||
}) as any)
|
||||
const result = await getBrand(SLUG)
|
||||
|
||||
expect(result.company.id).toBe('co-1')
|
||||
expect(result.brand).toMatchObject({
|
||||
displayName: 'Test Co',
|
||||
paymentMethodsEnabled: ['AMANPAY', 'PAYPAL'],
|
||||
paypalMerchantId: 'paypal-merchant-id',
|
||||
})
|
||||
expect(result.brand).not.toHaveProperty('amanpayMerchantId')
|
||||
expect(result.brand).not.toHaveProperty('amanpaySecretKey')
|
||||
})
|
||||
})
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
describe('getPublicVehicles', () => {
|
||||
it('returns vehicles enriched with availability', async () => {
|
||||
vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
|
||||
vi.mocked(repo.findPublishedVehicles).mockResolvedValue([makeVehicle()] as any)
|
||||
vi.mocked(getVehicleAvailabilitySummary).mockResolvedValue({ available: true, status: 'AVAILABLE', nextAvailableAt: null, blockingReason: null })
|
||||
|
||||
const result = await getPublicVehicles(SLUG)
|
||||
expect(result[0].availability).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
describe('checkAvailability', () => {
|
||||
it('returns availability result for given date range', async () => {
|
||||
vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
|
||||
vi.mocked(getVehicleAvailabilitySummary).mockResolvedValue({ available: true, status: 'AVAILABLE', nextAvailableAt: null, blockingReason: null })
|
||||
|
||||
const result = await checkAvailability(SLUG, 'v-1', '2025-06-01T00:00:00.000Z', '2025-06-04T00:00:00.000Z')
|
||||
expect(result.available).toBe(true)
|
||||
expect(getVehicleAvailabilitySummary).toHaveBeenCalledWith('v-1', { range: { startDate: new Date('2025-06-01T00:00:00.000Z'), endDate: new Date('2025-06-04T00:00:00.000Z') } })
|
||||
})
|
||||
})
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
describe('validatePromoCode', () => {
|
||||
it('returns offer when code is valid', async () => {
|
||||
vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
|
||||
vi.mocked(repo.findOfferByPromoCode).mockResolvedValue({ id: 'o-1' } as any)
|
||||
const result = await validatePromoCode(SLUG, 'SUMMER10')
|
||||
expect((result as any).id).toBe('o-1')
|
||||
})
|
||||
|
||||
it('throws NotFoundError for unknown promo code', async () => {
|
||||
vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
|
||||
vi.mocked(repo.findOfferByPromoCode).mockResolvedValue(null)
|
||||
await expect(validatePromoCode(SLUG, 'BADCODE')).rejects.toBeInstanceOf(NotFoundError)
|
||||
})
|
||||
})
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
describe('createBooking', () => {
|
||||
const baseBody = {
|
||||
vehicleId: 'v-1',
|
||||
startDate: '2025-06-01T00:00:00.000Z',
|
||||
endDate: '2025-06-04T00:00:00.000Z',
|
||||
firstName: 'Ali', lastName: 'Ben', email: 'ali@test.com',
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
|
||||
vi.mocked(repo.findVehicleById).mockResolvedValue(makeVehicle() as any)
|
||||
vi.mocked(getVehicleAvailabilitySummary).mockResolvedValue({ available: true, status: 'AVAILABLE', nextAvailableAt: null, blockingReason: null })
|
||||
vi.mocked(repo.upsertCustomer).mockResolvedValue({ id: 'c-1' } as any)
|
||||
vi.mocked(applyPricingRules).mockResolvedValue({ applied: [], total: 0 })
|
||||
vi.mocked(validateLicense).mockReturnValue({ status: 'VALID', requiresApproval: false } as any)
|
||||
vi.mocked(repo.createReservation).mockResolvedValue({ id: 'r-1' } as any)
|
||||
vi.mocked(repo.findReservationWithDetails).mockResolvedValue({ id: 'r-1', additionalDrivers: [], insurances: [] } as any)
|
||||
})
|
||||
|
||||
it('creates a booking and returns reservation with requiresManualApproval flag', async () => {
|
||||
const result = await createBooking(SLUG, baseBody)
|
||||
expect(repo.createReservation).toHaveBeenCalledOnce()
|
||||
expect((result as any).requiresManualApproval).toBe(false)
|
||||
})
|
||||
|
||||
it('throws AppError for invalid dates', async () => {
|
||||
await expect(
|
||||
createBooking(SLUG, { ...baseBody, startDate: '2025-06-04T00:00:00.000Z', endDate: '2025-06-01T00:00:00.000Z' }),
|
||||
).rejects.toMatchObject({ error: 'invalid_dates' })
|
||||
})
|
||||
|
||||
it('throws AppError when vehicle unavailable', async () => {
|
||||
vi.mocked(getVehicleAvailabilitySummary).mockResolvedValue({ available: false, status: 'RESERVED', nextAvailableAt: null, blockingReason: 'RESERVATION' })
|
||||
await expect(createBooking(SLUG, baseBody)).rejects.toMatchObject({ error: 'unavailable' })
|
||||
})
|
||||
|
||||
it('throws AppError when primary driver license is expired', async () => {
|
||||
vi.mocked(validateLicense).mockReturnValue({ status: 'EXPIRED', requiresApproval: false } as any)
|
||||
await expect(createBooking(SLUG, { ...baseBody, licenseExpiry: '2020-01-01T00:00:00.000Z' })).rejects.toMatchObject({ error: 'license_expired' })
|
||||
})
|
||||
})
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
describe('initPayment — payment guard paths', () => {
|
||||
const payBody = { provider: 'PAYPAL' as const, successUrl: 'http://ok', failureUrl: 'http://fail' }
|
||||
|
||||
it('throws AppError when reservation is already paid', async () => {
|
||||
vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
|
||||
vi.mocked(repo.findReservationForPayment).mockResolvedValue({
|
||||
paymentStatus: 'PAID', totalAmount: 100,
|
||||
customer: { licenseExpiry: null, licenseValidationStatus: 'APPROVED', email: 'a@b.com', firstName: 'A', lastName: 'B' },
|
||||
additionalDrivers: [],
|
||||
vehicle: { make: 'Toyota', model: 'Camry' },
|
||||
} as any)
|
||||
vi.mocked(validateLicense).mockReturnValue({ status: 'VALID', requiresApproval: false } as any)
|
||||
|
||||
await expect(initPayment(SLUG, 'r-1', payBody)).rejects.toMatchObject({ error: 'already_paid' })
|
||||
})
|
||||
|
||||
it('throws AppError when license review is required', async () => {
|
||||
vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
|
||||
vi.mocked(repo.findReservationForPayment).mockResolvedValue({
|
||||
paymentStatus: 'UNPAID', totalAmount: 100,
|
||||
customer: { licenseExpiry: null, licenseValidationStatus: 'DENIED', email: 'a@b.com', firstName: 'A', lastName: 'B' },
|
||||
additionalDrivers: [],
|
||||
vehicle: { make: 'Toyota', model: 'Camry' },
|
||||
} as any)
|
||||
vi.mocked(validateLicense).mockReturnValue({ status: 'VALID', requiresApproval: false } as any)
|
||||
|
||||
await expect(initPayment(SLUG, 'r-1', payBody)).rejects.toMatchObject({ error: 'license_review_required' })
|
||||
})
|
||||
|
||||
it('throws AppError when PayPal is not configured', async () => {
|
||||
vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
|
||||
vi.mocked(repo.findReservationForPayment).mockResolvedValue({
|
||||
paymentStatus: 'UNPAID', totalAmount: 30000,
|
||||
customer: { licenseExpiry: null, licenseValidationStatus: 'APPROVED', email: 'a@b.com', firstName: 'Ali', lastName: 'Ben' },
|
||||
additionalDrivers: [],
|
||||
vehicle: { make: 'Toyota', model: 'Camry' },
|
||||
} as any)
|
||||
vi.mocked(validateLicense).mockReturnValue({ status: 'VALID', requiresApproval: false } as any)
|
||||
vi.mocked(paypalSvc.isConfigured).mockReturnValue(false)
|
||||
|
||||
await expect(initPayment(SLUG, 'r-1', payBody)).rejects.toMatchObject({ error: 'provider_not_configured' })
|
||||
})
|
||||
|
||||
it('returns checkoutUrl when PayPal is configured', async () => {
|
||||
vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
|
||||
vi.mocked(repo.findReservationForPayment).mockResolvedValue({
|
||||
id: 'r-1', paymentStatus: 'UNPAID', totalAmount: 30000, companyId: 'co-1',
|
||||
customer: { licenseExpiry: null, licenseValidationStatus: 'APPROVED', email: 'a@b.com', firstName: 'Ali', lastName: 'Ben' },
|
||||
additionalDrivers: [],
|
||||
vehicle: { make: 'Toyota', model: 'Camry' },
|
||||
} as any)
|
||||
vi.mocked(validateLicense).mockReturnValue({ status: 'VALID', requiresApproval: false } as any)
|
||||
vi.mocked(paypalSvc.isConfigured).mockReturnValue(true)
|
||||
vi.mocked(paypalSvc.createOrder).mockResolvedValue({ approveUrl: 'https://paypal.com/approve', orderId: 'pp-1' } as any)
|
||||
vi.mocked(repo.createRentalPayment).mockResolvedValue(undefined as any)
|
||||
|
||||
const result = await initPayment(SLUG, 'r-1', payBody)
|
||||
expect(result.checkoutUrl).toBe('https://paypal.com/approve')
|
||||
expect(repo.createRentalPayment).toHaveBeenCalledOnce()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user