Files
binect-js/tests/helpers.test.ts
tegwick b9aebb42f1 Add Binect SDK implementation, Explorer, and test suite
SDK (@binect/js):
- BinectClient with domain sub-clients (documents, sendings, accounts,
  attachments, invoices)
- HTTP Basic Auth, native fetch only (no runtime dependencies)
- TypeScript types matching Binect API vocabulary
- Status predicates and polling helpers in helpers.ts
- Structured error handling (BinectApiError, BinectAuthError)

Explorer:
- Standalone browser-based API explorer (explorer/index.html)
- Interactive testing without code

Tests:
- Unit tests for client, types, errors, helpers, http
- E2E tests for upload/delete and send/cancel workflows

Also includes:
- Architecture Decision Records (ADRs)
- Example DIN 5008 letter PDFs for testing
- API specification research notes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 23:10:34 +01:00

282 lines
8.9 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import {
isShippable,
isErroneous,
isInPreparation,
isInProductionQueue,
isPrinting,
isSent,
isCanceled,
isTerminal,
isCancelable,
getErrors,
getWarnings,
getInfoMessages,
hasErrors,
hasWarnings,
getStatusDescription,
pollUntil,
} from '../src/helpers.js';
import { DocumentStatus } from '../src/types.js';
import type { Document, ValidationMessage } from '../src/types.js';
// Helper to create mock documents with specific status
function createMockDocument(status: DocumentStatus, validationMessages?: ValidationMessage[]): Document {
return {
id: 12345,
filename: 'test-doc.pdf',
numberOfPages: 1,
status: {
code: status,
text: 'Test status',
},
documentType: 'LETTER',
letter: {
letterType: 'LETTERDATA',
letterData: {
options: {
simplex: false,
color: false,
franking: 'STANDARD_FRANKING',
productionCountry: 'DE',
envelope: 'DINLANG',
},
},
errors: validationMessages ?? [],
},
};
}
describe('Status Predicates', () => {
describe('isShippable', () => {
it('returns true for status SHIPPABLE', () => {
const doc = createMockDocument(DocumentStatus.SHIPPABLE);
expect(isShippable(doc)).toBe(true);
});
it('returns false for other statuses', () => {
expect(isShippable(createMockDocument(DocumentStatus.IN_PREPARATION))).toBe(false);
expect(isShippable(createMockDocument(DocumentStatus.ERRONEOUS))).toBe(false);
expect(isShippable(createMockDocument(DocumentStatus.SENT))).toBe(false);
});
});
describe('isErroneous', () => {
it('returns true for status ERRONEOUS', () => {
const doc = createMockDocument(DocumentStatus.ERRONEOUS);
expect(isErroneous(doc)).toBe(true);
});
it('returns false for other statuses', () => {
expect(isErroneous(createMockDocument(DocumentStatus.SHIPPABLE))).toBe(false);
});
});
describe('isInPreparation', () => {
it('returns true for status IN_PREPARATION', () => {
const doc = createMockDocument(DocumentStatus.IN_PREPARATION);
expect(isInPreparation(doc)).toBe(true);
});
});
describe('isInProductionQueue', () => {
it('returns true for status PRODUCTION_QUEUE', () => {
const doc = createMockDocument(DocumentStatus.PRODUCTION_QUEUE);
expect(isInProductionQueue(doc)).toBe(true);
});
});
describe('isPrinting', () => {
it('returns true for status PRINTING', () => {
const doc = createMockDocument(DocumentStatus.PRINTING);
expect(isPrinting(doc)).toBe(true);
});
});
describe('isSent', () => {
it('returns true for status SENT', () => {
const doc = createMockDocument(DocumentStatus.SENT);
expect(isSent(doc)).toBe(true);
});
});
describe('isCanceled', () => {
it('returns true for status CANCELED', () => {
const doc = createMockDocument(DocumentStatus.CANCELED);
expect(isCanceled(doc)).toBe(true);
});
});
describe('isTerminal', () => {
it('returns true for SENT, CANCELED, and ERRONEOUS', () => {
expect(isTerminal(createMockDocument(DocumentStatus.SENT))).toBe(true);
expect(isTerminal(createMockDocument(DocumentStatus.CANCELED))).toBe(true);
expect(isTerminal(createMockDocument(DocumentStatus.ERRONEOUS))).toBe(true);
});
it('returns false for non-terminal statuses', () => {
expect(isTerminal(createMockDocument(DocumentStatus.IN_PREPARATION))).toBe(false);
expect(isTerminal(createMockDocument(DocumentStatus.SHIPPABLE))).toBe(false);
expect(isTerminal(createMockDocument(DocumentStatus.PRODUCTION_QUEUE))).toBe(false);
expect(isTerminal(createMockDocument(DocumentStatus.PRINTING))).toBe(false);
});
});
describe('isCancelable', () => {
it('returns true for PRODUCTION_QUEUE and PRINTING', () => {
expect(isCancelable(createMockDocument(DocumentStatus.PRODUCTION_QUEUE))).toBe(true);
expect(isCancelable(createMockDocument(DocumentStatus.PRINTING))).toBe(true);
});
it('returns false for non-cancelable statuses', () => {
expect(isCancelable(createMockDocument(DocumentStatus.IN_PREPARATION))).toBe(false);
expect(isCancelable(createMockDocument(DocumentStatus.SENT))).toBe(false);
expect(isCancelable(createMockDocument(DocumentStatus.CANCELED))).toBe(false);
});
});
});
describe('Validation Helpers', () => {
const mockMessages: ValidationMessage[] = [
{ type: 'ERROR', code: 'ERR001', message: 'Error 1' },
{ type: 'ERROR', code: 'ERR002', message: 'Error 2' },
{ type: 'WARNING', code: 'WARN001', message: 'Warning 1' },
{ type: 'INFO', code: 'INFO001', message: 'Info 1' },
];
describe('getErrors', () => {
it('returns only error messages', () => {
const doc = createMockDocument(DocumentStatus.ERRONEOUS, mockMessages);
const errors = getErrors(doc);
expect(errors).toHaveLength(2);
expect(errors.every(m => m.type === 'ERROR')).toBe(true);
});
it('returns empty array when no validation messages', () => {
const doc = createMockDocument(DocumentStatus.SHIPPABLE);
expect(getErrors(doc)).toEqual([]);
});
});
describe('getWarnings', () => {
it('returns only warning messages', () => {
const doc = createMockDocument(DocumentStatus.SHIPPABLE, mockMessages);
const warnings = getWarnings(doc);
expect(warnings).toHaveLength(1);
expect(warnings[0]?.type).toBe('WARNING');
});
});
describe('getInfoMessages', () => {
it('returns only info messages', () => {
const doc = createMockDocument(DocumentStatus.SHIPPABLE, mockMessages);
const info = getInfoMessages(doc);
expect(info).toHaveLength(1);
expect(info[0]?.type).toBe('INFO');
});
});
describe('hasErrors', () => {
it('returns true when document has errors', () => {
const doc = createMockDocument(DocumentStatus.ERRONEOUS, mockMessages);
expect(hasErrors(doc)).toBe(true);
});
it('returns false when document has no errors', () => {
const doc = createMockDocument(DocumentStatus.SHIPPABLE, [
{ type: 'WARNING', code: 'WARN001', message: 'Warning' },
]);
expect(hasErrors(doc)).toBe(false);
});
});
describe('hasWarnings', () => {
it('returns true when document has warnings', () => {
const doc = createMockDocument(DocumentStatus.SHIPPABLE, mockMessages);
expect(hasWarnings(doc)).toBe(true);
});
it('returns false when document has no warnings', () => {
const doc = createMockDocument(DocumentStatus.SHIPPABLE, [
{ type: 'INFO', code: 'INFO001', message: 'Info' },
]);
expect(hasWarnings(doc)).toBe(false);
});
});
});
describe('getStatusDescription', () => {
it('returns correct descriptions for all statuses', () => {
expect(getStatusDescription(DocumentStatus.IN_PREPARATION)).toBe('In preparation');
expect(getStatusDescription(DocumentStatus.SHIPPABLE)).toBe('Ready to ship');
expect(getStatusDescription(DocumentStatus.PRODUCTION_QUEUE)).toBe('In production queue');
expect(getStatusDescription(DocumentStatus.PRINTING)).toBe('Printing');
expect(getStatusDescription(DocumentStatus.SENT)).toBe('Sent');
expect(getStatusDescription(DocumentStatus.CANCELED)).toBe('Canceled');
expect(getStatusDescription(DocumentStatus.ERRONEOUS)).toBe('Has errors');
});
it('returns "Unknown status" for invalid status', () => {
expect(getStatusDescription(999 as DocumentStatus)).toBe('Unknown status');
});
});
describe('Polling Utilities', () => {
describe('pollUntil', () => {
it('returns immediately when condition is met', async () => {
let callCount = 0;
const result = await pollUntil(
async () => {
callCount++;
return { value: 42 };
},
(r) => r.value === 42,
{ intervalMs: 10 }
);
expect(result.value).toBe(42);
expect(callCount).toBe(1);
});
it('polls until condition is met', async () => {
let callCount = 0;
const result = await pollUntil(
async () => {
callCount++;
return { value: callCount };
},
(r) => r.value >= 3,
{ intervalMs: 10 }
);
expect(result.value).toBe(3);
expect(callCount).toBe(3);
});
it('throws when max attempts exceeded', async () => {
await expect(
pollUntil(
async () => ({ value: 1 }),
(r) => r.value > 100,
{ intervalMs: 10, maxAttempts: 3 }
)
).rejects.toThrow('Polling exceeded maximum attempts (3)');
});
it('respects abort signal', async () => {
const controller = new AbortController();
// Abort immediately
controller.abort();
await expect(
pollUntil(
async () => ({ value: 1 }),
() => false,
{ intervalMs: 10, signal: controller.signal }
)
).rejects.toThrow('Polling aborted');
});
});
});