7ff2dbb139
Build & Deploy / Build & Push Docker Image (push) Successful in 2m55s
Test / Type Check (all packages) (push) Successful in 54s
Build & Deploy / Deploy to VPS (push) Successful in 4s
Test / API Unit Tests (push) Failing after 48s
Test / Homepage Unit Tests (push) Successful in 43s
Test / Storefront Unit Tests (push) Failing after 40s
Test / Admin Unit Tests (push) Successful in 40s
Test / Dashboard Unit Tests (push) Failing after 42s
Test / API Integration Tests (push) Successful in 1m0s
172 lines
6.4 KiB
TypeScript
172 lines
6.4 KiB
TypeScript
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('./settingsEntitlements', () => ({
|
|
resolveSettingsEntitlements: vi.fn(),
|
|
}))
|
|
vi.mock('./company.repo', () => ({
|
|
findCompany: vi.fn(),
|
|
updateCompany: vi.fn(),
|
|
findBrand: vi.fn(),
|
|
upsertBrand: vi.fn(),
|
|
findBrandBySubdomain: vi.fn(),
|
|
findBrandByCustomDomain: vi.fn(),
|
|
clearCustomDomain: vi.fn(),
|
|
findContractSettings: vi.fn(),
|
|
upsertContractSettings: vi.fn(),
|
|
findInsurancePolicies: vi.fn(),
|
|
createInsurancePolicy: vi.fn(),
|
|
updateInsurancePolicy: vi.fn(),
|
|
findInsurancePolicyOrThrow: vi.fn(),
|
|
deleteInsurancePolicy: vi.fn(),
|
|
findPricingRules: vi.fn(),
|
|
createPricingRule: vi.fn(),
|
|
updatePricingRule: vi.fn(),
|
|
findPricingRuleOrThrow: vi.fn(),
|
|
deletePricingRule: vi.fn(),
|
|
findAccountingSettings: vi.fn(),
|
|
upsertAccountingSettings: vi.fn(),
|
|
findApiKey: vi.fn(),
|
|
regenerateApiKey: vi.fn(),
|
|
}))
|
|
|
|
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.carplace_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 = {
|
|
id: 'brand_1',
|
|
companyId: 'company_1',
|
|
displayName: 'Atlas Cars',
|
|
subdomain: 'atlas',
|
|
amanpayMerchantId: 'merchant_1',
|
|
amanpaySecretKey: 'secret_1',
|
|
paypalEmail: null,
|
|
paypalMerchantId: null,
|
|
paymentMethodsEnabled: ['AMANPAY'],
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
vi.mocked(entitlements.resolveSettingsEntitlements).mockResolvedValue(buildEditableEntitlements() as any)
|
|
delete process.env.NEXT_PUBLIC_CUSTOM_DOMAIN_TARGET
|
|
})
|
|
|
|
describe('company.service edge behavior', () => {
|
|
it('preserves existing AmanPay credentials when recomputing enabled payment methods', async () => {
|
|
vi.mocked(repo.findBrand).mockResolvedValue(currentBrand as any)
|
|
vi.mocked(repo.upsertBrand).mockResolvedValue({ ...currentBrand, paypalEmail: 'billing@example.test' } as any)
|
|
|
|
const result = await service.updateBrand('company_1', { paypalEmail: 'billing@example.test' }, 'Atlas Cars', 'atlas')
|
|
|
|
expect(repo.upsertBrand).toHaveBeenCalledWith(
|
|
'company_1',
|
|
expect.objectContaining({
|
|
paypalEmail: 'billing@example.test',
|
|
paymentMethodsEnabled: ['AMANPAY', 'PAYPAL'],
|
|
}),
|
|
expect.objectContaining({
|
|
displayName: 'Atlas Cars',
|
|
subdomain: 'atlas',
|
|
paypalEmail: 'billing@example.test',
|
|
paymentMethodsEnabled: ['AMANPAY', 'PAYPAL'],
|
|
}),
|
|
)
|
|
expect(result).not.toHaveProperty('paypalEmail')
|
|
expect(result.paypalConfigured).toBe(true)
|
|
})
|
|
|
|
it('does not report AmanPay enabled when only one credential is available', async () => {
|
|
vi.mocked(repo.findBrand).mockResolvedValue({ ...currentBrand, amanpaySecretKey: null } as any)
|
|
vi.mocked(repo.upsertBrand).mockResolvedValue({ ...currentBrand, amanpaySecretKey: null } as any)
|
|
|
|
await service.updateBrand('company_1', {}, 'Atlas Cars', 'atlas')
|
|
|
|
expect(repo.upsertBrand).toHaveBeenCalledWith(
|
|
'company_1',
|
|
expect.objectContaining({ paymentMethodsEnabled: [] }),
|
|
expect.objectContaining({ paymentMethodsEnabled: [] }),
|
|
)
|
|
})
|
|
|
|
it('normalizes custom domains and marks them pending verification', async () => {
|
|
vi.mocked(repo.findBrandByCustomDomain).mockResolvedValue(null as any)
|
|
vi.mocked(repo.upsertBrand).mockResolvedValue({ id: 'brand_1', customDomain: 'cars.example.com' } as any)
|
|
|
|
await service.setCustomDomain('company_1', 'Atlas Cars', 'atlas', ' CARS.Example.COM ')
|
|
|
|
expect(repo.findBrandByCustomDomain).toHaveBeenCalledWith('cars.example.com', 'company_1')
|
|
expect(repo.upsertBrand).toHaveBeenCalledWith(
|
|
'company_1',
|
|
expect.objectContaining({ customDomain: 'cars.example.com', customDomainVerified: false, customDomainAddedAt: expect.any(Date) }),
|
|
expect.objectContaining({ displayName: 'Atlas Cars', subdomain: 'atlas', customDomain: 'cars.example.com', customDomainVerified: false, customDomainAddedAt: expect.any(Date) }),
|
|
)
|
|
})
|
|
|
|
it('rejects custom domains already owned by another company', async () => {
|
|
vi.mocked(repo.findBrandByCustomDomain).mockResolvedValue({ id: 'other_brand' } as any)
|
|
|
|
await expect(service.setCustomDomain('company_1', 'Atlas Cars', 'atlas', 'cars.example.com')).rejects.toMatchObject({ statusCode: 409 })
|
|
expect(repo.upsertBrand).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('returns deterministic custom-domain status and honors configured DNS target', async () => {
|
|
process.env.NEXT_PUBLIC_CUSTOM_DOMAIN_TARGET = 'tenant-target.example.net'
|
|
vi.mocked(repo.findBrand).mockResolvedValue({ customDomain: 'cars.example.com', customDomainVerified: false } as any)
|
|
|
|
await expect(service.getCustomDomainStatus('company_1')).resolves.toEqual({
|
|
customDomain: 'cars.example.com',
|
|
verified: false,
|
|
status: 'pending_dns',
|
|
dnsTarget: 'tenant-target.example.net',
|
|
})
|
|
})
|
|
|
|
it('raises not found when updating a missing insurance policy', async () => {
|
|
vi.mocked(repo.updateInsurancePolicy).mockResolvedValue({ count: 0 } as any)
|
|
|
|
await expect(service.updateInsurancePolicy('policy_1', 'company_1', { name: 'CDW' })).rejects.toMatchObject({ statusCode: 404 })
|
|
expect(repo.findInsurancePolicyOrThrow).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('raises not found when deleting a missing pricing rule', async () => {
|
|
vi.mocked(repo.deletePricingRule).mockResolvedValue({ count: 0 } as any)
|
|
|
|
await expect(service.deletePricingRule('rule_1', 'company_1')).rejects.toMatchObject({ statusCode: 404 })
|
|
})
|
|
})
|