const FCodeClient = require('../robot/FCodeClient'); const robot = { x: 0, y: 300, z: 0, phi: Math.PI / 2, theta: -Math.PI / 2, psi: 0, e: 0, feedrate: 1000, }; function mockOk(data) { global.fetch = jest.fn().mockResolvedValue({ ok: true, status: 200, statusText: 'OK', json: () => Promise.resolve(data), }); } function mockErr(status, code, message) { global.fetch = jest.fn().mockResolvedValue({ ok: false, status, statusText: 'Error', json: () => Promise.resolve({ code, message }), }); } afterEach(() => { delete global.fetch; }); /* ---------- isFCode ---------- */ describe('isFCode', () => { test('erkennt alle bekannten FCodes', () => { for (const f of ['FList','FShow','FLoad demo','FSave Name','FClear','FPoint','FPlus','FMinus','FFirst','FLast','FGoto 3','FPlay','FStop']) { expect(FCodeClient.isFCode(f)).toBe(true); } }); test('erkennt Nicht-FCodes nicht', () => { expect(FCodeClient.isFCode('G1 X100')).toBe(false); expect(FCodeClient.isFCode('F1000')).toBe(false); expect(FCodeClient.isFCode('M114')).toBe(false); expect(FCodeClient.isFCode('Ping')).toBe(false); expect(FCodeClient.isFCode('')).toBe(false); }); }); /* ---------- handle: Listing / Info ---------- */ test('FList → GET /api/programs, type list', async () => { mockOk([{ id: 'a', name: 'A' }]); const result = await FCodeClient.handle(robot, 'FList'); expect(result.type).toBe('list'); expect(global.fetch).toHaveBeenCalledWith( expect.stringContaining('/api/programs'), expect.objectContaining({ method: 'GET' }) ); expect(JSON.parse(result.data)).toEqual([{ id: 'a', name: 'A' }]); }); test('FShow ohne id → GET /api/active', async () => { mockOk({ programId: 'x', cursor: 0 }); const result = await FCodeClient.handle(robot, 'FShow'); expect(global.fetch).toHaveBeenCalledWith(expect.stringContaining('/api/active'), expect.anything()); expect(result.type).toBe('show'); }); test('FShow mit id → GET /api/programs/:id', async () => { mockOk({ id: 'demo', name: 'Demo' }); await FCodeClient.handle(robot, 'FShow demo'); expect(global.fetch).toHaveBeenCalledWith(expect.stringContaining('/api/programs/demo'), expect.anything()); }); /* ---------- handle: Teaching ---------- */ test('FPoint → POST /api/active/points mit Roboterpose (Radian)', async () => { mockOk({ index: 0, line: 'G90 G1 x0 y300 z0 a1.5708 b-1.5708 c0 e0 f1000' }); const result = await FCodeClient.handle(robot, 'FPoint'); expect(result.type).toBe('point'); const call = global.fetch.mock.calls[0]; expect(call[1].method).toBe('POST'); const body = JSON.parse(call[1].body); expect(body.pose.a).toBeCloseTo(Math.PI / 2, 4); expect(body.pose.b).toBeCloseTo(-Math.PI / 2, 4); expect(body.feedrate).toBe(1000); }); /* ---------- handle: Program management ---------- */ test('FLoad → PUT /api/active mit id', async () => { mockOk({ programId: 'mein_prog', cursor: 0, lineCount: 5 }); const result = await FCodeClient.handle(robot, 'FLoad mein_prog'); expect(result.type).toBe('ok'); const body = JSON.parse(global.fetch.mock.calls[0][1].body); expect(body).toEqual({ id: 'mein_prog' }); }); test('FSave → POST /api/programs mit name und fromActive:true', async () => { mockOk({ id: 'test_prog', name: 'Test Prog' }); await FCodeClient.handle(robot, 'FSave Test Prog'); const body = JSON.parse(global.fetch.mock.calls[0][1].body); expect(body.name).toBe('Test Prog'); expect(body.fromActive).toBe(true); }); test('FClear → POST /api/active/clear', async () => { mockOk({ ok: true }); await FCodeClient.handle(robot, 'FClear'); expect(global.fetch).toHaveBeenCalledWith(expect.stringContaining('/api/active/clear'), expect.objectContaining({ method: 'POST' })); }); /* ---------- handle: Stepping → type step ---------- */ test('FPlus → POST /api/active/next → type step mit Zeile', async () => { mockOk({ cursor: 1, line: 'G90 G1 x10 y300 z0 a1.5708 b-1.5708 c0 e0 f1000' }); const result = await FCodeClient.handle(robot, 'FPlus'); expect(result.type).toBe('step'); expect(result.line).toContain('G1'); }); test('FMinus → POST /api/active/prev', async () => { mockOk({ cursor: 0, line: 'G90 G1 x0 y300 z0 a0 b0 c0 e0 f1000' }); const result = await FCodeClient.handle(robot, 'FMinus'); expect(result.type).toBe('step'); }); test('FFirst / FLast', async () => { mockOk({ cursor: 0, line: 'G90 G1 x0 y0 z0 a0 b0 c0 e0 f1000' }); const r1 = await FCodeClient.handle(robot, 'FFirst'); expect(r1.type).toBe('step'); mockOk({ cursor: 4, line: 'G90 G1 x5 y0 z0 a0 b0 c0 e0 f1000' }); const r2 = await FCodeClient.handle(robot, 'FLast'); expect(r2.type).toBe('step'); }); test('FGoto N → POST /api/active/goto mit index', async () => { mockOk({ cursor: 3, line: 'G90 G1 x3 y0 z0 a0 b0 c0 e0 f1000' }); await FCodeClient.handle(robot, 'FGoto 3'); const body = JSON.parse(global.fetch.mock.calls[0][1].body); expect(body.index).toBe(3); }); /* ---------- Fehlerbehandlung ---------- */ test('Fileservice-Fehler → wirft mit code und status', async () => { mockErr(404, 'PROGRAM_NOT_FOUND', 'not found'); await expect(FCodeClient.handle(robot, 'FLoad nichtda')).rejects.toMatchObject({ code: 'PROGRAM_NOT_FOUND', status: 404, }); }); test('Unbekannter FCode → wirft', async () => { await expect(FCodeClient.handle(robot, 'FUnbekannt')).rejects.toThrow('Unbekannter FCode'); });