Multipoint

This commit is contained in:
chk
2026-06-17 22:57:52 +02:00
parent 5f8e1a0189
commit eb403dab36
8 changed files with 667 additions and 15 deletions

215
test/assignMarkerId.test.js Normal file
View File

@@ -0,0 +1,215 @@
/**
* assignMarkerId.test.js
* ======================
* Integration-Test für server/editRobot.js → assignMarkerId().
*
* Testet insbesondere den Guard für fehlende position_mm (Marker ohne
* triangulierte Position, z.B. Einzelkamera-Marker nach Schritt 5).
*
* Technisch: editRobot.js ist ein ES-Modul — es wird über den dünnen Runner
* test/fixtures/runAssignMarkerId.mjs per spawnSync aufgerufen (gleiche
* Strategie wie yAxisRotation.test.js für das Python-Skript).
* Datei-I/O läuft gegen echte Temp-Dateien (os.tmpdir()).
*/
const { spawnSync } = require('child_process');
const os = require('os');
const fs = require('fs');
const path = require('path');
const RUNNER = path.join(__dirname, 'fixtures', 'runAssignMarkerId.mjs');
// ── Hilfsfunktionen ───────────────────────────────────────────────────────────
function callAssignMarkerId(robotPath, params) {
const proc = spawnSync('node', [RUNNER, robotPath, JSON.stringify(params)], {
encoding: 'utf-8',
});
if (proc.error) throw proc.error;
const stdout = (proc.stdout ?? '').trim();
if (!stdout) throw new Error(`Kein Output.\nstderr: ${proc.stderr}`);
return JSON.parse(stdout);
}
function makeTempRobot(content) {
const p = path.join(
os.tmpdir(),
`robot_test_${Date.now()}_${Math.random().toString(36).slice(2)}.json`
);
fs.writeFileSync(p, JSON.stringify(content, null, 2), 'utf8');
return p;
}
const ROBOT_WITH_42 = () => ({
links: {
Arm1: { markers: [{ id: 42, set: 'A0', position: [100, 200, 300] }] },
},
});
const ROBOT_EMPTY = () => ({
links: { Arm1: { markers: [] } },
});
// ── Eingabe-Validierung ───────────────────────────────────────────────────────
describe('assignMarkerId Eingabe-Validierung', () => {
test('ungültige Marker-ID → changed: false', () => {
const p = makeTempRobot(ROBOT_EMPTY());
try {
const r = callAssignMarkerId(p, { markerId: -1, link: 'Arm1' });
expect(r.changed).toBe(false);
expect(r.error).toMatch(/Ungültige Marker-ID/);
} finally { fs.unlinkSync(p); }
});
test('kein link für neuen Marker → changed: false', () => {
const p = makeTempRobot(ROBOT_EMPTY());
try {
const r = callAssignMarkerId(p, {
markerId: 99,
extraMarkers: [{ marker_id: 99, position_mm: [10, 20, 30] }],
});
expect(r.changed).toBe(false);
expect(r.error).toMatch(/Link/);
} finally { fs.unlinkSync(p); }
});
});
// ── Guard: fehlende position_mm ───────────────────────────────────────────────
describe('assignMarkerId Guard: fehlende position_mm (z.B. Einzelkamera-Marker)', () => {
test('extraMarker ohne position_mm → changed: false, kein Crash', () => {
const p = makeTempRobot(ROBOT_EMPTY());
try {
const r = callAssignMarkerId(p, {
markerId: 55,
link: 'Arm1',
extraMarkers: [{ marker_id: 55 }], // kein position_mm
});
expect(r.changed).toBe(false);
expect(r.error).toMatch(/position_mm fehlt/);
} finally { fs.unlinkSync(p); }
});
test('extraMarker mit position_mm: null → changed: false, kein Crash', () => {
const p = makeTempRobot(ROBOT_EMPTY());
try {
const r = callAssignMarkerId(p, {
markerId: 56,
link: 'Arm1',
extraMarkers: [{ marker_id: 56, position_mm: null }],
});
expect(r.changed).toBe(false);
expect(r.error).toMatch(/position_mm fehlt/);
} finally { fs.unlinkSync(p); }
});
test('extraMarker mit position_mm als String → changed: false, kein Crash', () => {
const p = makeTempRobot(ROBOT_EMPTY());
try {
const r = callAssignMarkerId(p, {
markerId: 57,
link: 'Arm1',
extraMarkers: [{ marker_id: 57, position_mm: '[1,2,3]' }],
});
expect(r.changed).toBe(false);
expect(r.error).toMatch(/position_mm fehlt/);
} finally { fs.unlinkSync(p); }
});
});
// ── Normalfall: Marker hinzufügen ─────────────────────────────────────────────
describe('assignMarkerId Normalfall: neuen Marker hinzufügen', () => {
test('gültige position_mm → changed: true, action: added, Datei geschrieben', () => {
const p = makeTempRobot(ROBOT_EMPTY());
try {
const r = callAssignMarkerId(p, {
markerId: 77,
link: 'Arm1',
extraMarkers: [{ marker_id: 77, position_mm: [10.1, 20.22, 30.333] }],
});
expect(r.changed).toBe(true);
expect(r.change.action).toBe('added');
expect(r.change.markerId).toBe(77);
expect(r.change.newLink).toBe('Arm1');
const saved = JSON.parse(fs.readFileSync(p, 'utf8'));
const added = saved.links.Arm1.markers.find(m => m.id === 77);
expect(added).toBeDefined();
expect(Array.isArray(added.position)).toBe(true);
expect(added.position).toHaveLength(3);
} finally { fs.unlinkSync(p); }
});
test('Marker nicht in extraMarkers → changed: false', () => {
const p = makeTempRobot(ROBOT_EMPTY());
try {
const r = callAssignMarkerId(p, { markerId: 99, link: 'Arm1', extraMarkers: [] });
expect(r.changed).toBe(false);
expect(r.error).toMatch(/nicht.*vorhanden/i);
} finally { fs.unlinkSync(p); }
});
test('mit set → Marker hat set-Wert in der gespeicherten Datei', () => {
const p = makeTempRobot(ROBOT_EMPTY());
try {
const r = callAssignMarkerId(p, {
markerId: 78,
link: 'Arm1',
set: 'A0',
extraMarkers: [{ marker_id: 78, position_mm: [1, 2, 3] }],
});
expect(r.changed).toBe(true);
const saved = JSON.parse(fs.readFileSync(p, 'utf8'));
const added = saved.links.Arm1.markers.find(m => m.id === 78);
expect(added.set).toBe('A0');
} finally { fs.unlinkSync(p); }
});
});
// ── Normalfall: bestehenden Marker aktualisieren ──────────────────────────────
describe('assignMarkerId Normalfall: bestehenden Marker aktualisieren', () => {
test('Set ändern → changed: true, action: updated, Datei geändert', () => {
const p = makeTempRobot(ROBOT_WITH_42());
try {
const r = callAssignMarkerId(p, { markerId: 42, set: 'B0' });
expect(r.changed).toBe(true);
expect(r.change.action).toBe('updated');
expect(r.change.oldSet).toBe('A0');
expect(r.change.newSet).toBe('B0');
const saved = JSON.parse(fs.readFileSync(p, 'utf8'));
expect(saved.links.Arm1.markers.find(m => m.id === 42).set).toBe('B0');
} finally { fs.unlinkSync(p); }
});
test('in anderen Link verschieben → oldLink / newLink korrekt, Datei geändert', () => {
const p = makeTempRobot(ROBOT_WITH_42());
try {
const r = callAssignMarkerId(p, { markerId: 42, link: 'Arm2' });
expect(r.changed).toBe(true);
expect(r.change.oldLink).toBe('Arm1');
expect(r.change.newLink).toBe('Arm2');
const saved = JSON.parse(fs.readFileSync(p, 'utf8'));
expect(saved.links.Arm1.markers.find(m => m.id === 42)).toBeUndefined();
expect(saved.links.Arm2.markers.find(m => m.id === 42)).toBeDefined();
} finally { fs.unlinkSync(p); }
});
test('bestehender Marker: fehlende position_mm in extraMarkers ist irrelevant', () => {
const p = makeTempRobot(ROBOT_WITH_42());
try {
// Marker 42 ist in robot.json → position_mm-Guard darf nicht zuschlagen
const r = callAssignMarkerId(p, {
markerId: 42,
set: 'C0',
extraMarkers: [{ marker_id: 42 }], // kein position_mm aber irrelevant
});
expect(r.changed).toBe(true);
expect(r.change.action).toBe('updated');
} finally { fs.unlinkSync(p); }
});
});

25
test/fixtures/runAssignMarkerId.mjs vendored Normal file
View File

@@ -0,0 +1,25 @@
/**
* Dünner Runner für assignMarkerId wird von assignMarkerId.test.js per spawnSync aufgerufen.
*
* Argumente:
* node runAssignMarkerId.mjs <robotPath> <paramsJson>
*
* Gibt das Ergebnis als JSON-Zeile auf stdout aus.
* Wirft der Aufruf, erscheint { __error: "<message>" } + Exit 1.
*/
import { assignMarkerId } from '../../server/editRobot.js';
const [, , robotPath, paramsJson] = process.argv;
if (!robotPath || !paramsJson) {
process.stderr.write('Usage: runAssignMarkerId.mjs <robotPath> <paramsJson>\n');
process.exit(2);
}
try {
const params = JSON.parse(paramsJson);
const result = await assignMarkerId(robotPath, params);
process.stdout.write(JSON.stringify(result) + '\n');
} catch (err) {
process.stdout.write(JSON.stringify({ __error: err.message }) + '\n');
process.exit(1);
}