Implement Phase 1 tasks 1.1-1.4 (scaffold + core shell + Teltonika framing)
- Project scaffold (Node 22 + TS 5 + pnpm + vitest + ESLint flat config) - Core shell: TCP server, session loop, adapter registry, types - Configuration (zod-validated env) and pino logger - Teltonika adapter: IMEI handshake, frame envelope, CRC-16/IBM, codec dispatch registry, DeviceAuthority seam (AllowAllAuthority default) Codec data parsers (1.5-1.7), Redis publisher (1.8), and downstream tasks remain. 36 tests covering CRC, framing, handshake, device authority, config, and core server. typecheck/lint/test/build all clean.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest';
|
||||
import * as net from 'node:net';
|
||||
import type { Logger } from 'pino';
|
||||
import type { Adapter, AdapterContext, Metrics, Position } from '../../src/core/types.js';
|
||||
import { startServer } from '../../src/core/server.js';
|
||||
|
||||
function makeMockContext(): AdapterContext {
|
||||
const logger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
child: vi.fn().mockReturnThis(),
|
||||
} as unknown as Logger;
|
||||
|
||||
const metrics: Metrics = {
|
||||
inc: vi.fn(),
|
||||
observe: vi.fn(),
|
||||
};
|
||||
|
||||
return {
|
||||
publish: vi.fn(async (_p: Position) => {}),
|
||||
logger,
|
||||
metrics,
|
||||
};
|
||||
}
|
||||
|
||||
describe('startServer', () => {
|
||||
const servers: net.Server[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
for (const server of servers) {
|
||||
server.close();
|
||||
}
|
||||
servers.length = 0;
|
||||
});
|
||||
|
||||
it('invokes handleSession with a real socket when a client connects', async () => {
|
||||
const handleSession = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
const adapter: Adapter = {
|
||||
name: 'test-adapter',
|
||||
ports: [0], // port 0 = OS-assigned ephemeral port
|
||||
handleSession,
|
||||
};
|
||||
|
||||
const ctx = makeMockContext();
|
||||
const server = startServer(0, adapter, ctx);
|
||||
servers.push(server);
|
||||
|
||||
// Wait for the server to start listening
|
||||
const port = await new Promise<number>((resolve) => {
|
||||
server.on('listening', () => {
|
||||
const addr = server.address();
|
||||
resolve(typeof addr === 'object' && addr !== null ? addr.port : 0);
|
||||
});
|
||||
});
|
||||
|
||||
// Connect a client
|
||||
const client = new net.Socket();
|
||||
client.connect(port, '127.0.0.1');
|
||||
|
||||
// Wait for handleSession to be called
|
||||
await vi.waitFor(() => expect(handleSession).toHaveBeenCalledOnce(), { timeout: 2000 });
|
||||
|
||||
const [socketArg, ctxArg] = handleSession.mock.calls[0] as [net.Socket, AdapterContext];
|
||||
expect(socketArg).toBeInstanceOf(net.Socket);
|
||||
expect(ctxArg).toBeDefined();
|
||||
expect(typeof ctxArg.publish).toBe('function');
|
||||
expect(ctxArg.logger).toBeDefined();
|
||||
|
||||
client.destroy();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user