fix test failure
Test / Type Check (all packages) (push) Successful in 3m30s
Test / API Unit Tests (push) Successful in 3m5s
Test / Homepage Unit Tests (push) Successful in 3m0s
Test / Storefront Unit Tests (push) Successful in 3m36s
Test / Admin Unit Tests (push) Successful in 4m0s
Test / Dashboard Unit Tests (push) Successful in 3m11s
Test / API Integration Tests (push) Successful in 3m6s
Build & Deploy / Build & Push Docker Image (push) Failing after 14s
Build & Deploy / Deploy to VPS (push) Has been skipped

This commit is contained in:
root
2026-06-30 00:23:31 -04:00
parent f22e0d45e1
commit 9ad92765c1
3 changed files with 107 additions and 25 deletions
@@ -1,6 +1,9 @@
import { beforeEach, describe, expect, it, vi } from 'vitest' import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('../../lib/storage', () => ({ uploadImage: vi.fn().mockResolvedValue('/storage/companies/company_1/brand/logo.jpg') })) vi.mock('../../lib/storage', () => ({ uploadImage: vi.fn().mockResolvedValue('/storage/companies/company_1/brand/logo.jpg') }))
vi.mock('./settingsEntitlements', () => ({
resolveSettingsEntitlements: vi.fn(),
}))
vi.mock('./company.repo', () => ({ vi.mock('./company.repo', () => ({
findCompany: vi.fn(), findCompany: vi.fn(),
updateCompany: vi.fn(), updateCompany: vi.fn(),
@@ -28,8 +31,43 @@ vi.mock('./company.repo', () => ({
})) }))
const repo = await import('./company.repo') const repo = await import('./company.repo')
const entitlements = await import('./settingsEntitlements')
const service = await import('./company.service') const service = await import('./company.service')
const editableSettingsFeatures = [
'settings.company_profile',
'settings.public_contact',
'settings.locale_currency',
'settings.storefront_listing',
'settings.branding_basic',
'settings.branding_custom',
'settings.branding_hero',
'settings.renter_payments',
'settings.rental_policies_basic',
'settings.additional_driver_fees',
'settings.insurance_policies',
'settings.pricing_rules',
'settings.accounting_defaults',
'settings.accounting_scheduled_delivery',
'settings.accounting_advanced_formats',
] as const
function buildEditableEntitlements() {
return {
plan: 'PRO',
subscriptionStatus: 'ACTIVE',
currentPeriodEnd: null,
trialEndAt: null,
accessLevel: 'full',
features: Object.fromEntries(editableSettingsFeatures.map((featureKey) => [featureKey, {
available: true,
editable: true,
requiredPlan: null,
reason: null,
}])),
}
}
const currentBrand = { const currentBrand = {
id: 'brand_1', id: 'brand_1',
companyId: 'company_1', companyId: 'company_1',
@@ -44,6 +82,7 @@ const currentBrand = {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks() vi.clearAllMocks()
vi.mocked(entitlements.resolveSettingsEntitlements).mockResolvedValue(buildEditableEntitlements() as any)
delete process.env.NEXT_PUBLIC_CUSTOM_DOMAIN_TARGET delete process.env.NEXT_PUBLIC_CUSTOM_DOMAIN_TARGET
}) })
+42 -2
View File
@@ -1,12 +1,51 @@
import { describe, it, expect, vi, beforeEach } from 'vitest' import { describe, it, expect, vi, beforeEach } from 'vitest'
import * as repo from './company.repo'
import * as service from './company.service'
vi.mock('./company.repo') vi.mock('./company.repo')
vi.mock('./settingsEntitlements', () => ({
resolveSettingsEntitlements: vi.fn(),
}))
vi.mock('../../lib/storage', () => ({ vi.mock('../../lib/storage', () => ({
uploadImage: vi.fn().mockResolvedValue('http://localhost:4000/storage/companies/comp_1/brand/logo.jpg'), uploadImage: vi.fn().mockResolvedValue('http://localhost:4000/storage/companies/comp_1/brand/logo.jpg'),
})) }))
const repo = await import('./company.repo')
const entitlements = await import('./settingsEntitlements')
const service = await import('./company.service')
const editableSettingsFeatures = [
'settings.company_profile',
'settings.public_contact',
'settings.locale_currency',
'settings.storefront_listing',
'settings.branding_basic',
'settings.branding_custom',
'settings.branding_hero',
'settings.renter_payments',
'settings.rental_policies_basic',
'settings.additional_driver_fees',
'settings.insurance_policies',
'settings.pricing_rules',
'settings.accounting_defaults',
'settings.accounting_scheduled_delivery',
'settings.accounting_advanced_formats',
] as const
function buildEditableEntitlements() {
return {
plan: 'PRO',
subscriptionStatus: 'ACTIVE',
currentPeriodEnd: null,
trialEndAt: null,
accessLevel: 'full',
features: Object.fromEntries(editableSettingsFeatures.map((featureKey) => [featureKey, {
available: true,
editable: true,
requiredPlan: null,
reason: null,
}])),
}
}
const mockCompany = { const mockCompany = {
id: 'comp_1', id: 'comp_1',
name: 'Test Rentals', name: 'Test Rentals',
@@ -33,6 +72,7 @@ const mockBrand = {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks() vi.clearAllMocks()
vi.mocked(entitlements.resolveSettingsEntitlements).mockResolvedValue(buildEditableEntitlements() as any)
}) })
describe('company.service', () => { describe('company.service', () => {
@@ -12,6 +12,17 @@ export function listByReservation(reservationId: string, companyId: string) {
return repo.findByReservation(reservationId, companyId) return repo.findByReservation(reservationId, companyId)
} }
function sumSucceededPayments(payments: any[], type: 'CHARGE' | 'DEPOSIT') {
return payments.reduce((total: number, payment: any) => {
if (payment.type !== type || payment.status !== 'SUCCEEDED') return total
return total + payment.amount
}, 0)
}
function getInvoicePaid(reservation: any, rentalPayments: any[]) {
return Math.max(sumSucceededPayments(rentalPayments, 'CHARGE'), reservation.paidAmount ?? 0)
}
async function applyAmanpayWebhook(event: any) { async function applyAmanpayWebhook(event: any) {
const transactionId = event.transaction_id ?? event.id const transactionId = event.transaction_id ?? event.id
const status = event.status?.toUpperCase() const status = event.status?.toUpperCase()
@@ -70,22 +81,17 @@ export async function initCharge(reservationId: string, companyId: string, body:
}) { }) {
const reservation = await repo.findReservationOrThrow(reservationId, companyId) const reservation = await repo.findReservationOrThrow(reservationId, companyId)
const rentalPayments = (reservation as any).rentalPayments ?? [] const rentalPayments = (reservation as any).rentalPayments ?? []
const invoicePaid = rentalPayments.reduce((total: number, payment: any) => { const invoicePaid = getInvoicePaid(reservation, rentalPayments)
if (payment.type !== 'CHARGE' || payment.status !== 'SUCCEEDED') return total const depositCollected = sumSucceededPayments(rentalPayments, 'DEPOSIT')
return total + payment.amount const balanceDue = body.type === 'DEPOSIT'
}, 0) ? reservation.depositAmount - depositCollected
const depositCollected = rentalPayments.reduce((total: number, payment: any) => { : reservation.totalAmount - invoicePaid
if (payment.type !== 'DEPOSIT' || payment.status !== 'SUCCEEDED') return total
return total + payment.amount
}, 0)
const chargeRemaining = Math.max(reservation.totalAmount - invoicePaid, 0)
const depositRemaining = Math.max(reservation.depositAmount - depositCollected, 0)
const amount = body.type === 'DEPOSIT' ? depositRemaining : chargeRemaining
if (amount <= 0) { if (balanceDue <= 0) {
throw new ConflictError(body.type === 'DEPOSIT' ? 'Security deposit is already fully collected' : 'Reservation is already fully paid') throw new ConflictError(body.type === 'DEPOSIT' ? 'Security deposit is already fully collected' : 'Reservation is already fully paid')
} }
const amount = balanceDue
const description = `${body.type === 'DEPOSIT' ? 'Deposit' : 'Rental'}: ${reservation.vehicle.make} ${reservation.vehicle.model}` const description = `${body.type === 'DEPOSIT' ? 'Deposit' : 'Rental'}: ${reservation.vehicle.make} ${reservation.vehicle.model}`
const orderId = `${reservationId}-${body.type}-${Date.now()}` const orderId = `${reservationId}-${body.type}-${Date.now()}`
const webhookBase = process.env.API_URL ?? 'http://localhost:4000' const webhookBase = process.env.API_URL ?? 'http://localhost:4000'
@@ -132,21 +138,18 @@ export async function recordManualPayment(reservationId: string, companyId: stri
}) { }) {
const reservation = await repo.findReservation(reservationId, companyId) const reservation = await repo.findReservation(reservationId, companyId)
const rentalPayments = (reservation as any).rentalPayments ?? [] const rentalPayments = (reservation as any).rentalPayments ?? []
const invoicePaid = rentalPayments.reduce((total: number, payment: any) => { const invoicePaid = getInvoicePaid(reservation, rentalPayments)
if (payment.type !== 'CHARGE' || payment.status !== 'SUCCEEDED') return total const depositCollected = sumSucceededPayments(rentalPayments, 'DEPOSIT')
return total + payment.amount const remaining = body.type === 'DEPOSIT'
}, 0) ? reservation.depositAmount - depositCollected
const depositCollected = rentalPayments.reduce((total: number, payment: any) => { : reservation.totalAmount - invoicePaid
if (payment.type !== 'DEPOSIT' || payment.status !== 'SUCCEEDED') return total
return total + payment.amount
}, 0)
const chargeRemaining = Math.max(reservation.totalAmount - invoicePaid, 0)
const depositRemaining = Math.max(reservation.depositAmount - depositCollected, 0)
const remaining = body.type === 'DEPOSIT' ? depositRemaining : chargeRemaining
if (remaining <= 0) { if (remaining <= 0) {
throw new ConflictError(body.type === 'DEPOSIT' ? 'Security deposit is already fully collected' : 'Reservation is already fully paid') throw new ConflictError(body.type === 'DEPOSIT' ? 'Security deposit is already fully collected' : 'Reservation is already fully paid')
} }
if (body.amount <= 0) {
throw new ValidationError('Payment amount must be greater than zero')
}
if (body.amount > remaining) { if (body.amount > remaining) {
throw new ValidationError(body.type === 'DEPOSIT' ? 'Payment amount exceeds deposit outstanding' : 'Payment amount exceeds remaining balance') throw new ValidationError(body.type === 'DEPOSIT' ? 'Payment amount exceeds deposit outstanding' : 'Payment amount exceeds remaining balance')
} }