main
John Lauer Publish 0.2.3 6e27c4b
"""Issue #73, offline (no service): the outline takes the mark's line split (--lines, else the etch's own
<etched>.mark-plan.json, which keeps a forced --lines split), records it on the SVG, and the gate fails an
outline whose text lines differ from the model's mark.
Run: python3 -m unittest -v test_outline_lines   (from tools/laser-etch)"""
import json, os, sys, tempfile, unittest

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import mark_check as MC
import mark_outline as MO

PLAN = {"text": {"lines": ["ABC-D", "1E-04B"]}}
SIDECAR = {"plan": PLAN, "etch": {"lines": ["ABC-D1E", "04B"]}}


def svg(lines=None):
    attr = f' data-lines="{lines}"' if lines is not None else ""
    return f'<svg viewBox="0 0 320 200"><polyline points="0,0 10,10"/><g id="adom-mark-name"{attr}><polyline points="1,1 2,2"/></g></svg>'


class OutlineLines(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.TemporaryDirectory()
        self.etched = os.path.join(self.tmp.name, "part-etched.step")
        open(self.etched, "w").write("ISO-10303-21;\n")

    def tearDown(self):
        self.tmp.cleanup()

    def write_sidecar(self, v=SIDECAR):
        json.dump(v, open(os.path.splitext(self.etched)[0] + ".mark-plan.json", "w"))

    def test_forced_split_is_read_from_the_etch_sidecar(self):
        self.write_sidecar()
        self.assertEqual(MO.resolve_lines(None, self.etched), (["ABC-D1E", "04B"], os.path.splitext(self.etched)[0] + ".mark-plan.json"))

    def test_lines_flag_wins_and_warns_when_it_differs(self):
        self.write_sidecar()
        logs = []
        got, src = MO.resolve_lines("ABC|D1E-04B", self.etched, log=logs.append)
        self.assertEqual((got, src), (["ABC", "D1E-04B"], "--lines"))
        self.assertTrue(logs and "differs from the etch's own lines" in logs[0])

    def test_explicit_plan_path_and_no_record(self):
        side = os.path.join(self.tmp.name, "other.mark-plan.json")
        json.dump(SIDECAR, open(side, "w"))
        self.assertEqual(MO.resolve_lines(None, None, side)[0], ["ABC-D1E", "04B"])
        self.assertEqual(MO.resolve_lines(None, self.etched), (None, None))
        # an older sidecar with no etch record falls back to its plan's lines
        self.write_sidecar({"plan": PLAN})
        self.assertEqual(MO.resolve_lines(None, self.etched)[0], ["ABC-D", "1E-04B"])

    def test_mark_lines_prefers_the_etch_record(self):
        self.assertEqual(MC.mark_lines(PLAN, SIDECAR), ["ABC-D1E", "04B"])
        self.assertEqual(MC.mark_lines(PLAN, {"plan": PLAN}), ["ABC-D", "1E-04B"])
        self.assertEqual(MC.mark_lines(PLAN, SIDECAR, ["X", "Y"]), ["X", "Y"])

    def test_gate_fails_an_outline_with_a_different_split(self):
        st, hint = MC.lines_check(svg("ABC-D|1E-04B"), ["ABC-D1E", "04B"])
        self.assertEqual(st, "FAIL")
        self.assertIn("ABC-D / 1E-04B differ from the model's mark ABC-D1E / 04B", hint)
        self.assertEqual(MC.lines_check(svg("ABC-D1E|04B"), ["ABC-D1E", "04B"]), ("PASS", None))
        self.assertEqual(MC.lines_check(svg(None), ["ABC-D1E", "04B"])[0], "WARN")

    def test_record_survives_escaping(self):
        import html
        self.assertEqual(MC.svg_lines(svg(html.escape('A&B"|C<D', quote=True))), ['A&B"', "C<D"])


if __name__ == "__main__":
    unittest.main()