@accounter/shaam-uniform-format-generator
Version:
Fully typed application that generates, parses, and validates SHAAM uniform format tax reports (INI.TXT and BKMVDATA.TXT).
279 lines (222 loc) • 9.38 kB
text/typescript
import { describe, expect, it } from 'vitest';
import {
A000SumSchema,
encodeA000Sum,
parseA000Sum,
type A000Sum,
type A000SumInput,
} from '../../src/generator/records/a000-sum.js';
describe('A000Sum Record', () => {
const validA000Sum: A000Sum = {
code: 'A100',
recordCount: '12345',
};
const validA000SumInput: A000SumInput = {
code: 'B100',
recordCount: '67890',
};
describe('Schema Validation', () => {
it('should validate a valid A000Sum record', () => {
expect(() => A000SumSchema.parse(validA000Sum)).not.toThrow();
});
it('should reject empty code', () => {
const invalidRecord = { ...validA000Sum, code: '' };
expect(() => A000SumSchema.parse(invalidRecord)).toThrow();
});
it('should reject code longer than 4 characters', () => {
const invalidRecord = { ...validA000Sum, code: 'TOOLONG' };
expect(() => A000SumSchema.parse(invalidRecord)).toThrow();
});
it('should reject empty record count', () => {
const invalidRecord = { ...validA000Sum, recordCount: '' };
expect(() => A000SumSchema.parse(invalidRecord)).toThrow();
});
it('should reject record count longer than 15 characters', () => {
const invalidRecord = { ...validA000Sum, recordCount: '1234567890123456' };
expect(() => A000SumSchema.parse(invalidRecord)).toThrow();
});
it('should reject non-numeric record count', () => {
const invalidRecord = { ...validA000Sum, recordCount: 'abc123' };
expect(() => A000SumSchema.parse(invalidRecord)).toThrow();
});
it('should accept valid record codes', () => {
const validCodes = ['A100', 'B100', 'C100', 'D110', 'M100', 'Z900'];
for (const code of validCodes) {
const record = { ...validA000Sum, code };
expect(() => A000SumSchema.parse(record)).not.toThrow();
}
});
it('should accept record count with leading zeros when parsed as string', () => {
const record = { ...validA000Sum, recordCount: '000123' };
expect(() => A000SumSchema.parse(record)).not.toThrow();
});
});
describe('encodeA000Sum', () => {
it('should encode a valid A000Sum record to fixed-width string', () => {
const encoded = encodeA000Sum(validA000SumInput);
// Should end with CRLF
expect(encoded).toMatch(/\r\n$/);
// Should have correct length (19 + 2 for CRLF)
expect(encoded.length).toBe(21);
// Should start with the record code
expect(encoded.slice(0, 4)).toBe('B100');
});
it('should pad numeric fields with leading zeros', () => {
const shortCountInput: A000SumInput = {
code: 'A100',
recordCount: '123',
};
const encoded = encodeA000Sum(shortCountInput);
// Record count should be zero-padded to 15 digits (position 4-18)
const recordCountField = encoded.slice(4, 19);
expect(recordCountField).toBe('000000000000123');
});
it('should handle maximum length fields', () => {
const maxLengthInput: A000SumInput = {
code: 'ABCD',
recordCount: '123456789012345',
};
const encoded = encodeA000Sum(maxLengthInput);
expect(encoded.length).toBe(21); // Should still have correct total length
// Check that fields are not truncated
expect(encoded.slice(0, 4)).toBe('ABCD');
expect(encoded.slice(4, 19)).toBe('123456789012345');
});
it('should handle single digit record count', () => {
const singleDigitInput: A000SumInput = {
code: 'Z900',
recordCount: '1',
};
const encoded = encodeA000Sum(singleDigitInput);
const recordCountField = encoded.slice(4, 19);
expect(recordCountField).toBe('000000000000001');
});
});
describe('parseA000Sum', () => {
it('should parse a valid fixed-width A000Sum record string', () => {
const encoded = encodeA000Sum(validA000SumInput);
const parsed = parseA000Sum(encoded);
expect(parsed.code).toBe('B100');
expect(parsed.recordCount).toBe('67890');
});
it('should handle lines with and without CRLF', () => {
const encoded = encodeA000Sum(validA000SumInput);
const withoutCRLF = encoded.replace(/\r\n$/, '');
expect(() => parseA000Sum(encoded)).not.toThrow();
expect(() => parseA000Sum(withoutCRLF)).not.toThrow();
const parsedWithCRLF = parseA000Sum(encoded);
const parsedWithoutCRLF = parseA000Sum(withoutCRLF);
expect(parsedWithCRLF).toEqual(parsedWithoutCRLF);
});
it('should throw error for invalid record length', () => {
const shortLine = 'A100' + ' '.repeat(10);
const longLine = 'A100' + ' '.repeat(50);
expect(() => parseA000Sum(shortLine)).toThrow(
'Invalid A000Sum record length: expected 19 characters, got 14',
);
expect(() => parseA000Sum(longLine)).toThrow(
'Invalid A000Sum record length: expected 19 characters, got 54',
);
});
it('should properly strip leading zeros from numeric fields', () => {
const paddedInput: A000SumInput = {
code: 'M100',
recordCount: '00000000000042',
};
const encoded = encodeA000Sum(paddedInput);
const parsed = parseA000Sum(encoded);
// Should strip leading zeros
expect(parsed.recordCount).toBe('42');
});
it('should handle zero record count correctly', () => {
const zeroInput: A000SumInput = {
code: 'C100',
recordCount: '0',
};
const encoded = encodeA000Sum(zeroInput);
const parsed = parseA000Sum(encoded);
expect(parsed.recordCount).toBe('0');
});
it('should preserve code field correctly', () => {
const testCodes = ['A100', 'B100', 'C100', 'D110', 'M100', 'Z900'];
for (const code of testCodes) {
const input: A000SumInput = { code, recordCount: '100' };
const encoded = encodeA000Sum(input);
const parsed = parseA000Sum(encoded);
expect(parsed.code).toBe(code);
}
});
});
describe('Round-trip Tests', () => {
it('should maintain data integrity through encode-parse round trip', () => {
const encoded = encodeA000Sum(validA000SumInput);
const parsed = parseA000Sum(encoded);
const reEncoded = encodeA000Sum(parsed);
expect(reEncoded).toBe(encoded);
});
it('should preserve all field values in round trip', () => {
const encoded = encodeA000Sum(validA000SumInput);
const parsed = parseA000Sum(encoded);
expect(parsed.code).toBe(validA000SumInput.code);
expect(parsed.recordCount).toBe(validA000SumInput.recordCount);
});
it('should handle edge cases in round trip', () => {
const edgeCases: A000SumInput[] = [
{ code: 'A', recordCount: '1' }, // Minimum lengths
{ code: 'ABCD', recordCount: '123456789012345' }, // Maximum lengths
{ code: 'Z900', recordCount: '0' }, // Zero count
{ code: 'TEST', recordCount: '999999999999999' }, // Large number
];
for (const testCase of edgeCases) {
const encoded = encodeA000Sum(testCase);
const parsed = parseA000Sum(encoded);
const reEncoded = encodeA000Sum(parsed);
expect(reEncoded).toBe(encoded);
expect(parsed.code).toBe(testCase.code);
expect(parsed.recordCount).toBe(testCase.recordCount);
}
});
it('should handle record count with various leading zero patterns', () => {
const testCases = [
{ input: '1', expected: '1' },
{ input: '01', expected: '1' },
{ input: '001', expected: '1' },
{ input: '0001234', expected: '1234' },
{ input: '000000000000001', expected: '1' },
];
for (const { input, expected } of testCases) {
const testInput: A000SumInput = { code: 'TEST', recordCount: input };
const encoded = encodeA000Sum(testInput);
const parsed = parseA000Sum(encoded);
expect(parsed.recordCount).toBe(expected);
}
});
});
describe('Error Handling', () => {
it('should throw error when feeding invalid line to parseA000Sum', () => {
const invalidLines = [
'', // Empty line
'A100', // Too short
'A100' + ' '.repeat(100), // Too long
'XY', // Way too short
];
for (const line of invalidLines) {
expect(() => parseA000Sum(line)).toThrow();
}
});
it('should validate schema after parsing', () => {
// Create a line with valid length but invalid content that should fail schema validation
const validEncoded = encodeA000Sum(validA000SumInput);
// This should pass since the content is valid
expect(() => parseA000Sum(validEncoded)).not.toThrow();
});
it('should handle malformed input gracefully', () => {
// Test with various malformed inputs that should fail schema validation
// Test first input separately to debug - all spaces should fail because code becomes empty
expect(() => parseA000Sum(' '.repeat(19)), 'All spaces input should fail').toThrow();
// Test second input - this creates "A" + 18 spaces = "A " which is valid
// Let's use something that should actually fail
expect(() => parseA000Sum('ABCDE' + ' '.repeat(14)), 'Code too long should fail').toThrow();
});
});
});