PSA/packages/billing/tests/quote/quotePdfGenerationService.test.ts
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

165 lines
5.7 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
const TENANT_ID = '22222222-2222-4222-8222-222222222222';
const QUOTE_ID = '33333333-3333-4333-8333-333333333333';
const USER_ID = '11111111-1111-4111-8111-111111111111';
const createTenantKnex = vi.fn();
const mapDbQuoteToViewModel = vi.fn();
const resolveQuoteTemplateAst = vi.fn();
const quoteGetById = vi.fn();
const uploadMock = vi.fn();
const createFileStoreMock = vi.fn();
const getBrowserMock = vi.fn();
const releaseBrowserMock = vi.fn();
vi.mock('@alga-psa/db', () => ({
createTenantKnex: (...args: any[]) => createTenantKnex(...args),
runWithTenant: async (_tenant: string, fn: () => Promise<unknown>) => fn(),
}));
vi.mock('../../src/lib/adapters/quoteAdapters', () => ({
mapDbQuoteToViewModel: (...args: any[]) => mapDbQuoteToViewModel(...args),
}));
vi.mock('../../src/lib/quote-template-ast/templateSelection', () => ({
resolveQuoteTemplateAst: (...args: any[]) => resolveQuoteTemplateAst(...args),
}));
vi.mock('../../src/models/quote', () => ({
default: {
getById: (...args: any[]) => quoteGetById(...args),
},
}));
vi.mock('@alga-psa/storage', () => ({
StorageProviderFactory: {
createProvider: async () => ({
upload: (...args: any[]) => uploadMock(...args),
}),
},
generateStoragePath: (...parts: string[]) => parts.join('/'),
FileStoreModel: {
create: (...args: any[]) => createFileStoreMock(...args),
findById: vi.fn().mockResolvedValue(null),
},
}));
vi.mock('../../src/services/browserPoolService', () => ({
browserPoolService: {
getBrowser: (...args: any[]) => getBrowserMock(...args),
releaseBrowser: (...args: any[]) => releaseBrowserMock(...args),
},
}));
import { createPDFGenerationService } from '../../src/services/pdfGenerationService';
import { getStandardQuoteTemplateAstByCode } from '../../src/lib/quote-template-ast/standardTemplates';
describe('quotePdfGenerationService', () => {
beforeEach(() => {
vi.clearAllMocks();
const pageMock = {
setContent: vi.fn().mockResolvedValue(undefined),
pdf: vi.fn().mockResolvedValue(Buffer.from('%PDF-quote-test')),
close: vi.fn().mockResolvedValue(undefined),
};
const browserMock = {
newPage: vi.fn().mockResolvedValue(pageMock),
};
getBrowserMock.mockResolvedValue(browserMock);
releaseBrowserMock.mockResolvedValue(undefined);
createTenantKnex.mockResolvedValue({ knex: { scope: 'knex' }, tenant: TENANT_ID });
mapDbQuoteToViewModel.mockResolvedValue({
quote_id: QUOTE_ID,
quote_number: 'Q-0042',
title: 'Proposal',
description: 'Managed services',
scope_of_work: 'Managed services',
quote_date: '2026-03-13T00:00:00.000Z',
valid_until: '2026-03-20T00:00:00.000Z',
status: 'draft',
version: 1,
po_number: null,
currency_code: 'USD',
subtotal: 1000,
discount_total: 0,
tax: 0,
total_amount: 1000,
terms_and_conditions: 'Net 30',
client_notes: null,
client_id: 'client-1',
contact_id: null,
client: { name: 'Client', address: null, email: null, phone: null, logo_url: null },
contact: null,
tenant: { name: 'Tenant', address: null, email: null, phone: null, logo_url: null },
line_items: [
{
quote_item_id: 'item-1',
service_id: null,
service_name: null,
service_sku: null,
billing_method: 'fixed',
description: 'Managed services',
quantity: 1,
unit_price: 1000,
total_price: 1000,
tax_amount: 0,
net_amount: 1000,
unit_of_measure: null,
phase: null,
is_optional: false,
is_selected: true,
is_recurring: false,
billing_frequency: null,
is_discount: false,
discount_type: null,
discount_percentage: null,
applies_to_item_id: null,
applies_to_service_id: null,
tax_region: null,
tax_rate: null,
},
],
phases: [],
});
resolveQuoteTemplateAst.mockResolvedValue({
templateAst: getStandardQuoteTemplateAstByCode('standard-quote-default'),
source: 'standard-fallback',
standardCode: 'standard-quote-default',
});
quoteGetById.mockResolvedValue({ quote_id: QUOTE_ID, quote_number: 'Q-0042' });
uploadMock.mockResolvedValue({ path: 'stored/pdfs/Q-0042.pdf' });
createFileStoreMock.mockResolvedValue({ file_id: 'file-1', storage_path: 'stored/pdfs/Q-0042.pdf' });
});
it('T083: generates a valid PDF buffer from quote data', async () => {
const service = createPDFGenerationService(TENANT_ID);
const pdf = await service.generatePDF({ quoteId: QUOTE_ID, userId: USER_ID });
expect(Buffer.isBuffer(pdf)).toBe(true);
expect(pdf.toString('utf8')).toContain('%PDF-quote-test');
const browser = await getBrowserMock.mock.results[0].value;
const page = await browser.newPage.mock.results[0].value;
expect(page.setContent).toHaveBeenCalledWith(expect.stringContaining('<!doctype html>'), { waitUntil: 'load' });
});
it('T084: stores generated file in file storage and returns file_id', async () => {
const service = createPDFGenerationService(TENANT_ID);
const result = await service.generateAndStore({ quoteId: QUOTE_ID, quoteNumber: 'Q-0042', userId: USER_ID });
expect(uploadMock).toHaveBeenCalledWith(expect.any(Buffer), '22222222-2222-4222-8222-222222222222/pdfs/Q-0042.pdf', {
mime_type: 'application/pdf',
});
expect(createFileStoreMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
original_name: 'Q-0042.pdf',
uploaded_by_id: USER_ID,
})
);
expect(result).toMatchObject({ file_id: 'file-1' });
});
});