Files
picpeak/backend/__tests__/integration/discountLineItems.test.js
T
Paul Nothaft 40eb03f0d8 fix(tests): raise jest timeouts to survive the growing migration chain (#860)
The 3.94.0-beta.0 release PR (#859) failed its backend job on
workflowEngine.test.js: bootCrmDb() runs every core migration in
beforeAll, and with migrations 163-165 merged the setup crossed the
suite's jest.setTimeout(30000) on CI runners — the log shows migration
099 still seeding after the hook timed out. Same pass is green locally
and passed on #857's rebase minutes earlier: borderline-slow, not
deterministic.

- jest.config.js: testTimeout 120000 as the default, so bootCrmDb
  suites without an explicit pin stop being time bombs as the chain
  grows
- every suite-level jest.setTimeout below 120s raised to 120s — local
  pins OVERRIDE the config default, so the 30s/60s ones would keep
  flaking regardless of the global bump

No test logic changed anywhere.

Co-authored-by: Paul Nothaft <paul@MacStudio-von-Paul.local>
2026-07-22 21:27:12 +02:00

80 lines
3.0 KiB
JavaScript

/**
* Negative line items (Rabatt / manual discount lines) are accepted
* end-to-end as long as the resulting total stays ≥ 0. When the
* discount would drive the total negative, the service rejects with
* a clear, code-tagged error so the admin is steered to Storno for
* credit-note workflows.
*
* Touches the actual createInvoice / createQuote service paths so a
* future change to either computeTotals or the guard fires this test.
*/
const { bootCrmDb, seedMinimal } = require('./helpers/crmDb');
// Service-level CRM calls cold-require heavy modules (pdfService,
// nodemailer, etc.) on first use; the global 5 s per-test budget is
// too tight for that. Bump it for this file only.
jest.setTimeout(120000);
describe('discount line items (negative unit_price_minor)', () => {
let db;
let cleanup;
let adminId;
let customerId;
let invoiceService;
beforeAll(async () => {
({ db, cleanup } = await bootCrmDb());
({ adminId, customerId } = await seedMinimal(db));
invoiceService = require('../../src/services/invoiceService');
}, 120000);
afterAll(async () => {
if (cleanup) await cleanup();
});
// Quote-side coverage of the symmetric validator + guard is
// deliberately omitted: createQuote's init path takes ~30 s under
// this harness (something in pdfService / emailProcessor cold-
// require), which would push the suite well past CI's per-test
// budget. The shape of the guard is identical to the invoice one
// covered below; a future change to extract the slow init or to
// stub it for tests should re-enable a parallel quote test.
describe('invoices', () => {
it('accepts a negative-price line and computes the net correctly', async () => {
const { invoiceIds } = await invoiceService.createInvoice({
customerAccountId: customerId,
currency: 'CHF',
vatRate: 0,
lineItems: [
{ position: 1, quantity: 1, description: 'Photo service', unit_price_minor: 20000, discount_percent: 0 },
{ position: 2, quantity: 1, description: 'Treuerabatt', unit_price_minor: -5000, discount_percent: 0 },
],
}, adminId);
expect(Array.isArray(invoiceIds)).toBe(true);
expect(invoiceIds.length).toBe(1);
const row = await db('invoices').where({ id: invoiceIds[0] }).first();
expect(row.net_amount_minor).toBe(15000);
expect(row.total_amount_minor).toBe(15000);
});
it('rejects when the discount drives the total negative', async () => {
await expect(invoiceService.createInvoice({
customerAccountId: customerId,
currency: 'CHF',
vatRate: 0,
lineItems: [
{ position: 1, quantity: 1, description: 'Photo service', unit_price_minor: 10000, discount_percent: 0 },
{ position: 2, quantity: 1, description: 'Übergroßer Rabatt', unit_price_minor: -50000, discount_percent: 0 },
],
}, adminId)).rejects.toMatchObject({
code: 'INVOICE_TOTAL_NEGATIVE',
statusCode: 400,
});
});
});
});