# Copyright 2026 514 LLC d/b/a OpenGlow # Written by Scott Wiederhold # https://community.openglow.org # SPDX-License-Identifier: MIT """The setup.* suite on the host: the registration (ids, kinds, the takeover tests, the operator tests' hands), the record builders, the cookie parsing, and the settle rule for a gated supervisor.""" import json import os import shutil import sys import tempfile import unittest sys.path.insert(0, os.path.dirname(__file__)) import helpers # noqa: E402 from forgetest import baseline, catalog # noqa: E402 from forgetest.suite import setup # noqa: E402 IDS = ("setup.account-login",) class RegistrationTests(unittest.TestCase): def setUp(self): self.reg = catalog.load_suite() def test_every_test_is_registered_with_its_kind(self): kinds = {tid: self.reg[tid].kind for tid in IDS if tid in self.reg} self.assertEqual(sorted(kinds), sorted(IDS)) for tid in IDS: self.assertEqual(kinds[tid], "auto", tid) def test_recommended_reads_the_status_document(self): w = {"recommended": [{"id": "laser.floor", "reason": "the tube was replaced"}, {"id": "laser.focus", "reason": "the tray was replaced"}, {"id": "airflow", "reason": "not run yet"}, "stray"]} self.assertEqual(setup.recommended(w), {"laser.floor": "the tube was replaced", "laser.focus": "the tray was replaced", "airflow": "not run yet"}) def test_the_form_wizards_and_the_required_steps(self): self.assertEqual(setup.FORM_WIZARDS, ("advisories", "account", "preferences", "machine", "cloud")) # in the order the gate's reason names them self.assertEqual(setup.REQUIRED_FORM_STEPS, ("preferences", "machine", "cloud")) self.assertEqual(setup.REQUIRED_STEPS, ("preferences", "machine", "cloud", "cooling.flow")) self.assertIn("cooling.flow", setup.REQUIRED_STEPS) def test_the_intake_tests_start_from_a_count(self): reg = self.reg from forgetest.suite import setup_dark self.assertIs(reg["setup.check-airflow"].precheck, setup_dark.intakes_counted) def test_the_only_attended_tests_need_a_workstation_or_a_scrap(self): # With the bench actuator up, two setup tests keep a person: the # print from the Glowforge app, and the focus burn on a fresh # scrap. Everything else runs from the queue. attended = sorted(tid for tid, t in self.reg.items() if tid.startswith("setup.") and t.kind != "auto" and not t.fixture_runnable(("button", "lid", "interlock"))) self.assertEqual(attended, ["setup.cloud-header-capture", "setup.focus"]) def test_the_login_test_makes_its_own_account(self): # No bench credentials, no precheck: the test installs a temporary # account under a takeover and restores the real one. t = self.reg["setup.account-login"] self.assertIsNone(getattr(t, "precheck", None)) self.assertEqual(t.hardware, "takeover") with open(setup.__file__) as f: src = f.read() for k in ("FORGETEST_LOGIN_NAME", "FORGETEST_LOGIN_PASSWORD"): self.assertNotIn(k, src) class RecordTests(unittest.TestCase): STATUS = {"sheet_id": "ABCDE-FGHIJ", "documents": [{"id": "safety-and-risk", "hash": "a" * 64, "consent": "typed"}, {"id": "licenses", "hash": "b" * 64, "consent": "check"}], "wizards": [{"id": "advisories", "version": 1}, {"id": "account", "version": 1}, {"id": "machine", "version": 2}]} def test_complete_record_counts_as_set_up(self): rec = setup.complete_record(self.STATUS) self.assertEqual(rec["schema"], 1) self.assertEqual(rec["advisories"]["safety-and-risk"]["hash"], "a" * 64) self.assertEqual(rec["advisories"]["safety-and-risk"]["method"], "typed") self.assertIn("pressed_at", rec["acceptance"]) self.assertEqual(rec["account"]["name"], "bench") self.assertEqual(rec["wizards"]["machine"]["version"], 2) self.assertEqual(rec["sheet_id"], "ABCDE-FGHIJ") self.assertTrue(rec["completed"]) json.dumps(rec) def test_complete_record_keeps_the_base_account_and_machine(self): base = {"account": {"name": "owner", "uid": 1000, "created": "x"}, "machine": {"model": "pro"}, "sheet_id": "KKKKK-LLLLL", "created": "then"} rec = setup.complete_record(self.STATUS, base) self.assertEqual(rec["account"]["name"], "owner") self.assertEqual(rec["machine"], {"model": "pro"}) self.assertEqual(rec["sheet_id"], "KKKKK-LLLLL") self.assertEqual(rec["created"], "then") def test_without_wizards_keeps_consent_and_account(self): rec = setup.complete_record(self.STATUS) rec["flags"] = {"machine": {"reason": "x"}} out = setup.record_without_wizards(rec) self.assertEqual(out["wizards"], {}) self.assertEqual(out["flags"], {}) self.assertEqual(out["advisories"], rec["advisories"]) self.assertEqual(out["account"], rec["account"]) self.assertEqual(rec["flags"], {"machine": {"reason": "x"}}) # a copy def test_with_required_steps_keeps_them_and_nothing_else(self): rec = setup.complete_record(self.STATUS) rec["wizards"]["machine"]["result"] = {"model": "basic"} rec["wizards"]["switches"] = {"version": 1} rec["flags"] = {"machine": {"reason": "the head was replaced"}} out = setup.record_with_required_steps(rec) self.assertEqual(sorted(out["wizards"]), ["cloud", "cooling.flow", "machine", "preferences"]) self.assertEqual(out["wizards"]["machine"]["result"], {"model": "basic"}) # kept from the record self.assertEqual(out["wizards"]["preferences"]["version"], 1) # written at version 1 self.assertEqual(out["wizards"]["cooling.flow"]["version"], 1) self.assertEqual(out["flags"], {}) self.assertEqual(out["account"], rec["account"]) self.assertIn("switches", rec["wizards"]) # a copy def test_without_consent_keeps_the_account_and_the_wizards(self): rec = setup.complete_record(self.STATUS) out = setup.record_without_consent(rec) self.assertEqual(out["advisories"], {}) self.assertNotIn("acceptance", out) self.assertEqual(out["account"], rec["account"]) self.assertEqual(out["wizards"], rec["wizards"]) self.assertIn("pressed_at", rec["acceptance"]) # a copy def test_without_account_keeps_the_consent(self): rec = setup.complete_record(self.STATUS) out = setup.record_without_account(rec) self.assertNotIn("account", out) self.assertEqual(out["advisories"], rec["advisories"]) self.assertEqual(out["acceptance"], rec["acceptance"]) self.assertEqual(rec["account"]["name"], "bench") # a copy def test_write_file_and_remove(self): tmp = tempfile.mkdtemp(prefix="forgetest-comm-") try: p = os.path.join(tmp, "sub", "setup.json") setup.write_file(p, b"{}\n") self.assertEqual(setup.read_file(p), b"{}\n") if os.name == "posix": # a mode means nothing on a Windows host self.assertEqual(oct(os.stat(p).st_mode & 0o777), oct(0o600)) setup.write_file(p, None) self.assertIsNone(setup.read_file(p)) setup.write_file(p, None) # a second remove is silent finally: shutil.rmtree(tmp, ignore_errors=True) def test_paths_follow_the_daemon_environment(self): os.environ["FORGECTRL_DATA_DIR"] = "/tmp/ffdata" os.environ["GF_RUN_DIR"] = "/tmp/ffrun" try: self.assertEqual(setup.record_path(), "/tmp/ffdata/setup.json") self.assertEqual(setup.users_path(), "/tmp/ffdata/users") self.assertEqual(setup.ssh_flag_path(), "/tmp/ffrun/ssh-enabled") finally: os.environ.pop("FORGECTRL_DATA_DIR", None) os.environ.pop("GF_RUN_DIR", None) class SmallHelpersTests(unittest.TestCase): def test_cookie_parsing(self): sid = "ab" * 32 value = "ffsid=%s; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=43200" % sid self.assertEqual(setup.session_from_cookie(value), sid) self.assertEqual(setup.cookie_flags(value), {"path", "httponly", "secure", "samesite", "max-age"}) self.assertIsNone(setup.session_from_cookie("ffsid=; Path=/; Max-Age=0")) self.assertIsNone(setup.session_from_cookie("")) def test_decode(self): self.assertEqual(setup.decode(b'{"a": 1}'), {"a": 1}) self.assertEqual(setup.decode(b"cloud mode is not enabled"), "cloud mode is not enabled") class GatedSettleTests(unittest.TestCase): """A supervisor that reports the setup gate closed is settled: it spawns nothing until the gate opens, so a takeover that installs a gating record must not wait the whole settle timeout for it.""" def setUp(self): self.fake = helpers.FakeForgectrl().start() baseline.Baseline._unreachable_until = 0.0 def tearDown(self): self.fake.stop() def test_gated_returns_at_once(self): self.fake.state["mode"] = {"mode": "grbl", "controller": "gated", "pid": 0, "motion": "unverified", "gated": True, "why": "the intake fans are not counted"} lines = [] body = baseline.Baseline(lines.append).wait_settled(timeout=8) self.assertEqual(body["controller"], "gated") self.assertTrue(any("controller=gated" in ln for ln in lines)) if __name__ == "__main__": unittest.main()