UNPKG

@accounter/shaam-uniform-format-generator

Version:

Fully typed application that generates, parses, and validates SHAAM uniform format tax reports (INI.TXT and BKMVDATA.TXT).

257 lines (218 loc) • 9.69 kB
import { describe, expect, it } from 'vitest'; import { SHAAM_VERSION } from '../../src/constants.js'; import { encodeZ900, parseZ900, Z900Schema, type Z900 } from '../../src/generator/records/z900.js'; describe('Z900 Record', () => { const validZ900: Z900 = { code: 'Z900', recordNumber: 123, vatId: '987654321', uniqueId: 123_456_789_012_345, totalRecords: 1000, reserved: '', }; describe('Z900Schema validation', () => { it('should validate a correct Z900 record', () => { expect(() => Z900Schema.parse(validZ900)).not.toThrow(); }); it('should require code to be exactly "Z900"', () => { const invalidRecord = { ...validZ900, code: 'Z800' }; expect(() => Z900Schema.parse(invalidRecord)).toThrow(); }); it('should require recordNumber to be non-empty and max 9 characters', () => { expect(() => Z900Schema.parse({ ...validZ900, recordNumber: 0 })).toThrow(); expect(() => Z900Schema.parse({ ...validZ900, recordNumber: 1_000_000_000 })).toThrow(); expect(() => Z900Schema.parse({ ...validZ900, recordNumber: 123_456_789 })).not.toThrow(); }); it('should require vatId to be non-empty and max 9 characters', () => { expect(() => Z900Schema.parse({ ...validZ900, vatId: '' })).toThrow(); expect(() => Z900Schema.parse({ ...validZ900, vatId: '1234567890' })).toThrow(); expect(() => Z900Schema.parse({ ...validZ900, vatId: '123456789' })).not.toThrow(); }); it('should require uniqueId to be non-empty and max 15 characters', () => { expect(() => Z900Schema.parse({ ...validZ900, uniqueId: 0 })).toThrow(); expect(() => Z900Schema.parse({ ...validZ900, uniqueId: 1_000_000_000_000_000 })).toThrow(); expect(() => Z900Schema.parse({ ...validZ900, uniqueId: 123_456_789_012_345 })).not.toThrow(); }); it('should require totalRecords to be non-empty and max 15 characters', () => { expect(() => Z900Schema.parse({ ...validZ900, totalRecords: 0 })).toThrow(); expect(() => Z900Schema.parse({ ...validZ900, totalRecords: 1_000_000_000_000_000 }), ).toThrow(); expect(() => Z900Schema.parse({ ...validZ900, totalRecords: 123_456_789_012_345 }), ).not.toThrow(); }); it('should validate reserved field', () => { expect(() => Z900Schema.parse({ ...validZ900, reserved: '' })).not.toThrow(); expect(() => Z900Schema.parse({ ...validZ900, reserved: 'Reserved data' })).not.toThrow(); expect(() => Z900Schema.parse({ ...validZ900, reserved: 'A'.repeat(50) })).not.toThrow(); expect(() => Z900Schema.parse({ ...validZ900, reserved: 'A'.repeat(51) })).toThrow(); }); it('should set default empty string for reserved field when missing', () => { const { reserved: _reserved, ...recordWithoutReserved } = validZ900; const parsed = Z900Schema.parse(recordWithoutReserved); expect(parsed.reserved).toBe(''); }); }); describe('encodeZ900', () => { it('should encode a Z900 record to fixed-width format', () => { const encoded = encodeZ900(validZ900); // Should end with CRLF expect(encoded).toMatch(/\r\n$/); // Remove CRLF for length check const withoutCRLF = encoded.replace(/\r\n$/, ''); expect(withoutCRLF).toHaveLength(110); // Check field positions and padding expect(withoutCRLF.slice(0, 4)).toBe('Z900'); // code (4) expect(withoutCRLF.slice(4, 13)).toBe('000000123'); // recordNumber zero-padded (9) expect(withoutCRLF.slice(13, 22)).toBe('987654321'); // vatId zero-padded (9) expect(withoutCRLF.slice(22, 37)).toBe('123456789012345'); // uniqueId zero-padded (15) expect(withoutCRLF.slice(37, 45)).toBe(SHAAM_VERSION); // static SHAAM version (8) expect(withoutCRLF.slice(45, 60)).toBe('000000000001000'); // totalRecords zero-padded (15) expect(withoutCRLF.slice(60, 110)).toBe(' '.repeat(50)); // reserved left-aligned (50) }); it('should handle maximum length fields', () => { const maxLengthRecord: Z900 = { code: 'Z900', recordNumber: 123_456_789, // 9 digits vatId: '123456789', // 9 chars uniqueId: 123_456_789_012_345, // 15 digits totalRecords: 123_456_789_012_345, // 15 digits reserved: 'A'.repeat(50), // 50 chars }; const encoded = encodeZ900(maxLengthRecord); const withoutCRLF = encoded.replace(/\r\n$/, ''); expect(withoutCRLF).toHaveLength(110); expect(withoutCRLF.slice(0, 4)).toBe('Z900'); expect(withoutCRLF.slice(4, 13)).toBe('123456789'); expect(withoutCRLF.slice(13, 22)).toBe('123456789'); expect(withoutCRLF.slice(22, 37)).toBe('123456789012345'); expect(withoutCRLF.slice(37, 45)).toBe(SHAAM_VERSION); expect(withoutCRLF.slice(45, 60)).toBe('123456789012345'); expect(withoutCRLF.slice(60, 110)).toBe('A'.repeat(50)); }); it('should truncate fields that exceed maximum length', () => { const oversizedRecord: Z900 = { code: 'Z900', recordNumber: 999_999_999, // max 9 digits vatId: '1234567890', // 10 chars (max 9) uniqueId: 999_999_999_999_999, // max 15 digits totalRecords: 999_999_999_999_999, // max 15 digits reserved: 'A'.repeat(60), // 60 chars (max 50) }; const encoded = encodeZ900(oversizedRecord); const withoutCRLF = encoded.replace(/\r\n$/, ''); expect(withoutCRLF).toHaveLength(110); expect(withoutCRLF.slice(4, 13)).toBe('999999999'); // max 9 digits expect(withoutCRLF.slice(13, 22)).toBe('123456789'); // truncated expect(withoutCRLF.slice(22, 37)).toBe('999999999999999'); // max 15 digits expect(withoutCRLF.slice(37, 45)).toBe(SHAAM_VERSION); // always the constant expect(withoutCRLF.slice(45, 60)).toBe('999999999999999'); // max 15 digits expect(withoutCRLF.slice(60, 110)).toBe('A'.repeat(50)); // truncated }); }); describe('parseZ900', () => { it('should parse a valid Z900 record line', () => { const line = 'Z900000000123987654321123456789012345' + SHAAM_VERSION + '000000000001000' + ' '.repeat(50) + '\r\n'; const parsed = parseZ900(line); expect(parsed).toEqual({ code: 'Z900', recordNumber: 123, vatId: '987654321', uniqueId: 123_456_789_012_345, totalRecords: 1000, reserved: '', }); }); it('should parse line without CRLF', () => { const line = 'Z900000000123987654321123456789012345' + SHAAM_VERSION + '000000000001000' + ' '.repeat(50); const parsed = parseZ900(line); expect(parsed.code).toBe('Z900'); expect(parsed.recordNumber).toBe(123); }); it('should throw error for invalid line length', () => { const shortLine = 'Z900' + ' '.repeat(50); expect(() => parseZ900(shortLine)).toThrow( 'Invalid Z900 record length: expected 110 characters, got 54', ); const longLine = 'Z900' + ' '.repeat(200); expect(() => parseZ900(longLine)).toThrow( 'Invalid Z900 record length: expected 110 characters, got 204', ); }); it('should throw error for invalid record code', () => { const invalidLine = 'Z800' + ' '.repeat(106) + '\r\n'; expect(() => parseZ900(invalidLine)).toThrow( 'Invalid Z900 record code: expected "Z900", got "Z800"', ); }); it('should throw error for invalid SHAAM version', () => { const invalidLine = 'Z900000000123987654321123456789012345INVALID 000000000001000' + ' '.repeat(50) + '\r\n'; expect(() => parseZ900(invalidLine)).toThrow('Invalid SHAAM version'); }); it('should handle fields with trailing spaces correctly', () => { const line = 'Z900000000123000123456000000000123456' + SHAAM_VERSION + '000000000009999' + ' '.repeat(50) + '\r\n'; const parsed = parseZ900(line); expect(parsed.vatId).toBe('123456'); expect(parsed.uniqueId).toBe(123_456); expect(parsed.totalRecords).toBe(9999); expect(parsed.reserved).toBe(''); }); }); describe('Round-trip encoding and parsing', () => { it('should maintain data integrity through encode -> parse cycle', () => { const original = validZ900; const encoded = encodeZ900(original); const parsed = parseZ900(encoded); expect(parsed).toEqual(original); }); it('should handle round-trip with maximum length fields', () => { const original: Z900 = { code: 'Z900', recordNumber: 999_999_999, vatId: '999888777', uniqueId: 999_888_777_666_555, totalRecords: 999_888_777_666_555, reserved: 'A'.repeat(50), }; const encoded = encodeZ900(original); const parsed = parseZ900(encoded); expect(parsed).toEqual(original); }); it('should handle round-trip with minimal fields', () => { const original: Z900 = { code: 'Z900', recordNumber: 1, vatId: '1', uniqueId: 1, totalRecords: 1, reserved: '', }; const encoded = encodeZ900(original); const parsed = parseZ900(encoded); expect(parsed).toEqual(original); }); }); describe('Error handling', () => { it('should throw validation error for invalid parsed data', () => { // Create a line that parses but fails schema validation const invalidLine = 'Z900 ' + ' '.repeat(97); // empty recordNumber expect(() => parseZ900(invalidLine)).toThrow(); }); }); });