Files
computer-playbook/tests/unit/python/roles/test-e2e-playwright/test_service_gating_helper.py
T
kevinveenbirkenbachandClaude Opus 5 b0155d44d9 fix(test-e2e-playwright): resolve hyphenated service names in the gate
registeredServices() derives each slug from its env key, so
AGENT_BROKER_SERVICE_ENABLED registers agent_broker, while
assertKnownService() compared the raw name. isServiceEnabled("agent-broker")
therefore threw "Unknown service" before the spec ran. The check now
normalises the name through envKey(), the same path the registry uses;
names made of [a-z0-9_] resolve exactly as before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 17:26:57 +02:00

215 lines
7.2 KiB
Python

"""Unit tests for roles/test-e2e-playwright/files/service-gating.js.
The helper is plain CommonJS. Its only external dependency is
`@playwright/test` for the `test.skip()` control flow. We fabricate a
tiny `node_modules/@playwright/test` stub in a temp dir so the helper
loads against a predictable fake, then call it from a short Node
script and assert the observable behaviour.
"""
import json
import os
import shutil
import subprocess
import tempfile
import textwrap
import unittest
from pathlib import Path
from . import PROJECT_ROOT
HELPER_PATH = str(
PROJECT_ROOT / "roles" / "test-e2e-playwright" / "files" / "service-gating.js"
)
STUB_PKG_JSON = json.dumps({"name": "@playwright/test", "main": "index.js"})
STUB_INDEX_JS = textwrap.dedent(
"""
const skipRecords = [];
module.exports = {
test: {
skip(cond, reason) {
if (cond) {
skipRecords.push(reason || "");
console.log("SKIP:" + (reason || ""));
const e = new Error("skipped");
e.__skipped = true;
throw e;
}
},
},
expect: () => ({ toBeVisible: () => {}, toBeTruthy: () => {} }),
__skipRecords: skipRecords,
};
"""
)
def _have_node():
return shutil.which("node") is not None
@unittest.skipUnless(_have_node(), "node is not available in PATH")
class TestServiceGatingHelper(unittest.TestCase):
def setUp(self):
self.tmpdir = tempfile.mkdtemp(prefix="sg-helper-")
stub_dir = str(Path(self.tmpdir) / "node_modules" / "@playwright" / "test")
Path(stub_dir).mkdir(parents=True, exist_ok=True)
with Path(str(Path(stub_dir) / "package.json")).open("w") as f:
f.write(STUB_PKG_JSON)
with Path(str(Path(stub_dir) / "index.js")).open("w") as f:
f.write(STUB_INDEX_JS)
shutil.copy(HELPER_PATH, str(Path(self.tmpdir) / "service-gating.js"))
def tearDown(self):
shutil.rmtree(self.tmpdir, ignore_errors=True)
def _run_in_node(self, snippet, env):
script = textwrap.dedent(
f"""
const helper = require("./service-gating");
(async () => {{
try {{
const result = await (async () => {{ {snippet} }})();
process.stdout.write("RESULT:" + JSON.stringify(result ?? null) + "\\n");
}} catch (err) {{
if (err && err.__skipped) {{
process.exit(0);
}}
process.stdout.write(
"ERROR:" + (err && err.message ? err.message : String(err)) + "\\n"
);
process.exit(1);
}}
}})();
"""
)
script_path = str(Path(self.tmpdir) / "run.js")
with Path(script_path).open("w") as f:
f.write(script)
return subprocess.run(
["node", "run.js"],
capture_output=True,
text=True,
env={**os.environ, **env},
cwd=self.tmpdir,
timeout=10,
check=False,
)
# isServiceEnabled -----------------------------------------------------
def test_enabled_when_flag_true(self):
proc = self._run_in_node(
"return helper.isServiceEnabled('sso');",
env={"SSO_SERVICE_ENABLED": "true"},
)
self.assertEqual(
proc.returncode, 0, msg=f"stderr={proc.stderr}\nstdout={proc.stdout}"
)
self.assertIn("RESULT:true", proc.stdout)
def test_disabled_when_flag_false(self):
proc = self._run_in_node(
"return helper.isServiceEnabled('sso');",
env={
"SSO_SERVICE_ENABLED": "false",
"EMAIL_SERVICE_ENABLED": "true",
},
)
self.assertEqual(
proc.returncode, 0, msg=f"stderr={proc.stderr}\nstdout={proc.stdout}"
)
self.assertIn("RESULT:false", proc.stdout)
def test_strict_value_rejected(self):
proc = self._run_in_node(
"return helper.isServiceEnabled('sso');",
env={"SSO_SERVICE_ENABLED": "yes"},
)
self.assertNotEqual(proc.returncode, 0)
self.assertIn("SSO_SERVICE_ENABLED", proc.stdout)
def test_a_hyphenated_service_resolves_to_its_underscored_flag(self):
proc = self._run_in_node(
"return helper.isServiceEnabled('agent-broker');",
env={"AGENT_BROKER_SERVICE_ENABLED": "false"},
)
self.assertEqual(
proc.returncode, 0, msg=f"stderr={proc.stderr}\nstdout={proc.stdout}"
)
self.assertIn("RESULT:false", proc.stdout)
def test_unknown_service_hard_fails(self):
proc = self._run_in_node(
"return helper.isServiceEnabled('oicd');",
env={"SSO_SERVICE_ENABLED": "true"},
)
self.assertNotEqual(proc.returncode, 0)
self.assertIn("Unknown service", proc.stdout)
def test_empty_registry_treats_everything_enabled(self):
proc = self._run_in_node(
"return helper.isServiceEnabled('sso');",
env={},
)
self.assertEqual(
proc.returncode, 0, msg=f"stderr={proc.stderr}\nstdout={proc.stdout}"
)
self.assertIn("RESULT:true", proc.stdout)
# isServiceDisabledReason ---------------------------------------------
def test_disabled_reason_names_the_flag(self):
proc = self._run_in_node(
"return helper.isServiceDisabledReason('email');",
env={
"EMAIL_SERVICE_ENABLED": "false",
"SSO_SERVICE_ENABLED": "true",
},
)
self.assertEqual(
proc.returncode, 0, msg=f"stderr={proc.stderr}\nstdout={proc.stdout}"
)
self.assertIn("EMAIL_SERVICE_ENABLED=false", proc.stdout)
def test_enabled_reason_is_null(self):
proc = self._run_in_node(
"return helper.isServiceDisabledReason('email');",
env={"EMAIL_SERVICE_ENABLED": "true"},
)
self.assertEqual(
proc.returncode, 0, msg=f"stderr={proc.stderr}\nstdout={proc.stdout}"
)
self.assertIn("RESULT:null", proc.stdout)
# requireService -------------------------------------------------------
def test_require_service_runs_testfn_when_enabled(self):
proc = self._run_in_node(
"const fn = helper.requireService('sso', async () => 'HIT');return fn({});",
env={"SSO_SERVICE_ENABLED": "true"},
)
self.assertEqual(
proc.returncode, 0, msg=f"stderr={proc.stderr}\nstdout={proc.stdout}"
)
self.assertIn('RESULT:"HIT"', proc.stdout)
def test_require_service_skips_when_disabled(self):
proc = self._run_in_node(
"const fn = helper.requireService('sso', async () => 'HIT');return fn({});",
env={
"SSO_SERVICE_ENABLED": "false",
"EMAIL_SERVICE_ENABLED": "true",
},
)
self.assertEqual(
proc.returncode, 0, msg=f"stderr={proc.stderr}\nstdout={proc.stdout}"
)
self.assertIn("SKIP:", proc.stdout)
self.assertIn("SSO_SERVICE_ENABLED=false", proc.stdout)
if __name__ == "__main__":
unittest.main()