381287bacc
- Codec 8 parser (1-byte IO IDs, no NX/Generation Type) - Codec 8 Extended parser (2-byte IO IDs + variable-length NX section) - Codec 16 parser (mixed widths + Generation Type, supports IO IDs > 255) - Shared GPS element / timestamp helpers in gps-element.ts - Fixture loader with bigint/Buffer sentinel encoding and auto-discovery - 12 fixture pairs across codec8/8E/16 (canonical doc + synthetic edge cases) - Cross-checked Codec 8 against Traccar's TeltonikaProtocolDecoder (no discrepancies) 26 new tests. Total 62 passing across 10 test files. typecheck/lint/test/build all clean.
119 lines
4.3 KiB
TypeScript
119 lines
4.3 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
import type { Position } from '../src/core/types.js';
|
|
import type { CodecHandlerContext } from '../src/adapters/teltonika/codec/registry.js';
|
|
import { codec16Handler } from '../src/adapters/teltonika/codec/data/codec16.js';
|
|
import { loadFixturesFromDir, compareToExpected } from './fixtures/_loader.js';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test context factory
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function makeTestCtx(positions: Position[]): CodecHandlerContext {
|
|
return {
|
|
imei: 'FIXTURE',
|
|
publish: async (pos: Position) => {
|
|
positions.push(pos);
|
|
},
|
|
logger: {
|
|
debug: vi.fn(),
|
|
info: vi.fn(),
|
|
warn: vi.fn(),
|
|
error: vi.fn(),
|
|
child: vi.fn().mockReturnThis(),
|
|
} as unknown as CodecHandlerContext['logger'],
|
|
};
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Auto-discovered fixture tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const fixtureDir = path.join(__dirname, 'fixtures/teltonika/codec16');
|
|
const fixtures = loadFixturesFromDir(fixtureDir);
|
|
|
|
describe('Codec 16 parser — fixture tests', () => {
|
|
for (const fixture of fixtures) {
|
|
it(`parses ${fixture.name}`, async () => {
|
|
const positions: Position[] = [];
|
|
const ctx = makeTestCtx(positions);
|
|
|
|
const result = await codec16Handler.handle(fixture.body, ctx);
|
|
|
|
expect(result.recordCount).toBe(fixture.expected.ack_record_count);
|
|
|
|
const mismatch = compareToExpected(positions, fixture.expected.positions);
|
|
expect(mismatch, mismatch ?? '').toBeNull();
|
|
});
|
|
}
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Unit tests — Codec 16-specific behaviours
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('Codec 16 parser — unit tests', () => {
|
|
it('codec_id is 0x10', () => {
|
|
expect(codec16Handler.codec_id).toBe(0x10);
|
|
});
|
|
|
|
it('sets __generation_type on every record', async () => {
|
|
const fixtureBody = Buffer.from(
|
|
'10010000016B40D8EA300100000000000000000000000000000004000501010400FF00000001',
|
|
'hex',
|
|
);
|
|
const positions: Position[] = [];
|
|
await codec16Handler.handle(fixtureBody, makeTestCtx(positions));
|
|
|
|
expect(positions[0]?.attributes['__generation_type']).toBe(5);
|
|
});
|
|
|
|
it('does NOT set __generation_type to undefined — key must be present', async () => {
|
|
// Both records in the canonical fixture have __generation_type=5
|
|
const fixtureBody = Buffer.from(
|
|
'10020000016BDBC7833000000000000000000000000000000000000B05040200010000030002000B00270042563A00000000016BDBC7871800000000000000000000000000000000000B05040200010000030002000B00260042563A000002',
|
|
'hex',
|
|
);
|
|
const positions: Position[] = [];
|
|
await codec16Handler.handle(fixtureBody, makeTestCtx(positions));
|
|
|
|
for (const pos of positions) {
|
|
expect('__generation_type' in pos.attributes).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('reads IO IDs greater than 255 correctly', async () => {
|
|
const fixtureBody = Buffer.from(
|
|
'10010000016B40D8EA300100000000000000000000000000000004000501010400FF00000001',
|
|
'hex',
|
|
);
|
|
const positions: Position[] = [];
|
|
await codec16Handler.handle(fixtureBody, makeTestCtx(positions));
|
|
|
|
// IO ID 0x0400 = 1024 must appear in attributes
|
|
expect(positions[0]?.attributes['1024']).toBe(255);
|
|
// Event IO ID 0x0400 = 1024 must appear under __event
|
|
expect(positions[0]?.attributes['__event']).toBe(1024);
|
|
});
|
|
|
|
it('logs a debug message for reserved Generation Type 3', async () => {
|
|
// Build a body with Generation Type = 3
|
|
const body = Buffer.from(
|
|
'10010000016B40D8EA300100000000000000000000000000000000000301010100FF00000001',
|
|
'hex',
|
|
);
|
|
const positions: Position[] = [];
|
|
const ctx = makeTestCtx(positions);
|
|
const debugFn = ctx.logger.debug as ReturnType<typeof vi.fn>;
|
|
|
|
await codec16Handler.handle(body, ctx);
|
|
|
|
expect(positions[0]?.attributes['__generation_type']).toBe(3);
|
|
// Debug should have been called at least once
|
|
expect(debugFn).toHaveBeenCalled();
|
|
});
|
|
});
|