CI (SIAX Cloud) / integration (push) Skipped
CI (SIAX Cloud) / sonar (push) Skipped
masterplan-lock / check (push) Successful in 10s
CI (SIAX Cloud) / sonar (pull_request) Skipped
Security Scan (SIAX Cloud) / security (push) Successful in 13s
masterplan-lock / check (pull_request) Successful in 7s
CI (SIAX Cloud) / security (push) Successful in 26s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 17s
CI (SIAX Cloud) / security (pull_request) Successful in 19s
CI (SIAX Cloud) / quality (push) Successful in 1m9s
CI (SIAX Cloud) / quality (pull_request) Successful in 2m26s
CI (SIAX Cloud) / integration (pull_request) Successful in 2m33s
- POST /api/0pt/documents/upload: real multipart bytes into DOC0 through the durable attempt journal and the existing coordinator; verified tenant/actor only, project authorization fail-closed. - POST /api/0pt/documents/structure: DOC0 segments -> INF0 -> evidence-bound validation -> optional JSON/CSV export, with credit reserve/settle. - Credits: balance, server-owned price book, Stripe Checkout and idempotent webhook reconciliation via a Stripe re-read of session + payment intent. - /portal/dokument and /portal/krediter: SIAX-styled workspace. - @siax/access: universal, framework-neutral OpenFGA project relationship primitive reusable by DOC0/N0D/other products. - Readiness: real Redis PING probe instead of presence-only; queue is now a live dependency when configured. - Dockerfile: copy every workspace manifest so --frozen-lockfile works. - Format/build hygiene: .prettierignore, .worktrees ignored, valid manifest YAML.
50 lines
1.9 KiB
TypeScript
50 lines
1.9 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
creditsForUsage,
|
|
findPack,
|
|
parseCreditPriceBook,
|
|
readCreditPriceBook,
|
|
} from '../../src/document-workbench/price-book';
|
|
|
|
const valid = {
|
|
version: '2026-11-01',
|
|
currency: 'SEK',
|
|
packs: [
|
|
{ id: 'starter', credits: 100, amountMinor: 9900 },
|
|
{ id: 'pro', credits: 2000, amountMinor: 129900 },
|
|
],
|
|
metering: { pagesPerCredit: 5, inputTokensPerCredit: 20000, outputTokensPerCredit: 4000 },
|
|
};
|
|
|
|
describe('credit price book', () => {
|
|
it('parses a valid server-owned price book', () => {
|
|
const book = parseCreditPriceBook(valid);
|
|
expect(book.packs).toHaveLength(2);
|
|
expect(findPack(book, 'pro')?.credits).toBe(2000);
|
|
});
|
|
|
|
it('fails closed when the env price book is absent or invalid', () => {
|
|
expect(readCreditPriceBook({} as unknown as NodeJS.ProcessEnv)).toBeUndefined();
|
|
expect(readCreditPriceBook({ OPT_CREDIT_PRICE_BOOK: '{not json' } as unknown as NodeJS.ProcessEnv)).toBeUndefined();
|
|
expect(
|
|
readCreditPriceBook({
|
|
OPT_CREDIT_PRICE_BOOK: JSON.stringify({ ...valid, currency: 'sek' }),
|
|
} as unknown as NodeJS.ProcessEnv),
|
|
).toBeUndefined();
|
|
});
|
|
|
|
it('rejects duplicate pack ids, unknown currencies and non-integer money', () => {
|
|
expect(() => parseCreditPriceBook({ ...valid, packs: [valid.packs[0], valid.packs[0]] })).toThrow();
|
|
expect(() =>
|
|
parseCreditPriceBook({ ...valid, packs: [{ id: 'x', credits: 1, amountMinor: 1.5 }] }),
|
|
).toThrow();
|
|
});
|
|
|
|
it('rounds every metered component up to its billing block', () => {
|
|
const book = parseCreditPriceBook(valid);
|
|
expect(creditsForUsage(book, { pages: 0, inputTokens: 0, outputTokens: 0 })).toBe(0);
|
|
expect(creditsForUsage(book, { pages: 1, inputTokens: 1, outputTokens: 1 })).toBe(3);
|
|
expect(creditsForUsage(book, { pages: 6, inputTokens: 40001, outputTokens: 8001 })).toBe(8);
|
|
});
|
|
});
|