133 lines
8.8 KiB
Python
133 lines
8.8 KiB
Python
"""Regressions for omitted business commands and false completion claims."""
|
|
import json
|
|
import contextlib
|
|
import io
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
import business_acceptance as acceptance
|
|
import run_business_acceptance as runner
|
|
import generate_web_business_completion_audit as audit
|
|
from tools.generate_web_workbench_inventory import python_gui_commands, source_gui_inventory
|
|
|
|
|
|
class InventoryTests(unittest.TestCase):
|
|
def test_module_root_registration_and_named_menu_lists(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
module = root / "src/Mod/Assembly"
|
|
module.mkdir(parents=True)
|
|
(module / "CommandJoint.py").write_text('Gui.addCommand("Assembly_CreateJointRevolute", Command())\n')
|
|
init = module / "InitGui.py"
|
|
init.write_text('commands = ["Assembly_CreateAssembly"]\nextra = ["Assembly_SolveAssembly"]\nself.appendMenu("Assembly", commands + extra)\n')
|
|
inventory = source_gui_inventory(root, module)
|
|
self.assertEqual(set(inventory["commands"]), {"Assembly_CreateJointRevolute", "Assembly_CreateAssembly", "Assembly_SolveAssembly"})
|
|
|
|
def test_cyclic_alias_does_not_hang_or_invent_command(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
path = Path(directory) / "InitGui.py"
|
|
path.write_text('first = second\nsecond = first\nself.appendToolbar("Label", first)\n')
|
|
self.assertEqual(python_gui_commands(path), [])
|
|
|
|
|
|
class AcceptanceTests(unittest.TestCase):
|
|
def tearDown(self):
|
|
acceptance.digest.cache_clear()
|
|
acceptance.module_inputs.cache_clear()
|
|
|
|
def test_empty_or_failed_assertions_cannot_complete_business(self):
|
|
self.assertFalse(acceptance.successful_result({"status": "passed", "cases": []}))
|
|
self.assertFalse(acceptance.successful_result({"status": "passed", "cases": [{"input": {}, "assertions": [{"name": "volume", "passed": False}]}]}))
|
|
self.assertFalse(acceptance.successful_result({"status": "passed", "cases": [{"assertions": [{"name": "volume", "passed": True}]}]}))
|
|
|
|
def test_runtime_issue_closure_requires_declared_success_and_unchanged_observation(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
path = root / "docs/current/runtime-issue-ledger.json"
|
|
path.parent.mkdir(parents=True)
|
|
evidence = root / "evidence.json"
|
|
evidence.write_text("verified results")
|
|
issue = {"id": "runtime:one", "capabilityId": "TechDraw:create", "status": "fix_deployed_pending_use", "occurrences": 1, "args": {"length": 10}}
|
|
result = {"capabilityId": issue["capabilityId"], "status": "passed", "cases": [{"input": issue["args"], "assertions": [{"name": "native length", "passed": True}]}]}
|
|
report = {"executedAt": "2026-09-08T00:00:00Z", "results": [result]}
|
|
before = {issue["id"]: runner.observation(issue)}
|
|
passed = {issue["capabilityId"]}
|
|
with patch.object(runner, "ROOT", root):
|
|
path.write_text(json.dumps({"issues": [issue]}))
|
|
self.assertEqual(runner.close_tested_issues(report, evidence, before, passed), 0)
|
|
result["resolvedIssueIds"] = [issue["id"]]
|
|
self.assertEqual(runner.close_tested_issues(report, evidence, before, set()), 0)
|
|
newer = {**issue, "occurrences": 2, "lastSeenAt": "later"}
|
|
path.write_text(json.dumps({"issues": [newer]}))
|
|
self.assertEqual(runner.close_tested_issues(report, evidence, before, passed), 0)
|
|
path.write_text(json.dumps({"issues": [issue]}))
|
|
self.assertEqual(runner.close_tested_issues(report, evidence, before, passed), 1)
|
|
closed = json.loads(path.read_text())["issues"][0]
|
|
self.assertEqual(closed["status"], "resolved")
|
|
self.assertEqual(closed["businessEvidenceSha256"], acceptance.digest(evidence))
|
|
self.assertEqual(closed["resolvedObservation"], before[issue["id"]])
|
|
|
|
def test_current_evidence_is_reused_but_changed_module_is_reopened(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
source = root / "web/src/part-facade.ts"
|
|
source.parent.mkdir(parents=True)
|
|
source.write_text("version1")
|
|
evidence = root / "evidence.json"
|
|
result = {"capabilityId": "Part:part.create", "status": "passed", "cases": [{"input": {"length": 2}, "assertions": [{"name": "volume", "passed": True}]}]}
|
|
evidence.write_text(json.dumps({"schema": "freecad-web-business-cases/v1", "results": [result]}))
|
|
ledger = root / "ledger.json"
|
|
row = {"id": result["capabilityId"], "module": "Part"}
|
|
with patch.object(acceptance, "ROOT", root), patch.object(acceptance, "LEDGER", ledger):
|
|
ledger.write_text(json.dumps({"records": {row["id"]: {"status": "passed", "fingerprint": acceptance.fingerprint(row), "evidencePath": "evidence.json", "evidenceSha256": acceptance.digest(evidence)}}}))
|
|
self.assertEqual(acceptance.accepted_ids([row]), {row["id"]})
|
|
source.write_text("version2")
|
|
acceptance.digest.cache_clear()
|
|
acceptance.module_inputs.cache_clear()
|
|
self.assertEqual(acceptance.accepted_ids([row]), set())
|
|
|
|
def test_runner_requires_successful_execution_and_preserves_prior_evidence(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
(root / "docs/current").mkdir(parents=True)
|
|
(root / "docs/business-acceptance").mkdir()
|
|
script = root / "case.py"
|
|
row = {"id": "Part:part.create", "module": "Part", "implementationStatus": "routed"}
|
|
result = {"schema": "freecad-web-business-cases/v1", "results": [{"capabilityId": row["id"], "status": "passed", "cases": [{"input": {"length": 2}, "assertions": [{"name": "volume", "passed": True}]}]}]}
|
|
script.write_text(f"print({json.dumps(result)!r})\n")
|
|
(root / "docs/business-acceptance/Part.json").write_text(json.dumps({"command": ["python3", "case.py"]}))
|
|
(root / "docs/current/module-batch-manifest.json").write_text(json.dumps({"batchId": "batch", "selected": {"module": "Part"}, "records": [[row["id"]]]}))
|
|
ledger = root / "docs/current/business-acceptance.json"
|
|
with patch.object(acceptance, "ROOT", root), patch.object(acceptance, "LEDGER", ledger), patch.object(runner, "ROOT", root), patch.object(runner, "LEDGER", ledger), patch.object(runner, "requirements", return_value=[row]), contextlib.redirect_stdout(io.StringIO()):
|
|
self.assertEqual(runner.main(), 0)
|
|
self.assertEqual(acceptance.accepted_ids([row]), {row["id"]})
|
|
previous = json.loads(ledger.read_text())["records"][row["id"]]["evidencePath"]
|
|
script.write_text("raise SystemExit(1)\n")
|
|
self.assertEqual(runner.main(), 1)
|
|
self.assertTrue((root / previous).is_file())
|
|
self.assertEqual(json.loads(ledger.read_text())["records"][row["id"]]["status"], "failed")
|
|
self.assertEqual(acceptance.accepted_ids([row]), set())
|
|
|
|
def test_unrouted_business_remains_in_completion_denominator(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
sources = {key: root / f"{key}.json" for key in audit.SOURCES}
|
|
rows = [{"id": "Part:one", "module": "Part", "implementationStatus": "routed"},
|
|
{"id": "Part:two", "module": "Part", "implementationStatus": "implementation_required"}]
|
|
sources["features"].write_text(json.dumps({"records": rows}))
|
|
sources["receipts"].write_text(json.dumps({"records": {row["id"]: {"status": "implementation_ready"} for row in rows}}))
|
|
sources["modules"].write_text(json.dumps({"modules": {str(i): {} for i in range(32)}}))
|
|
with patch.object(audit, "ROOT", root), patch.object(audit, "SOURCES", sources), patch.object(audit, "OUTPUT", root / "out.json"), patch.object(audit, "MARKDOWN", root / "out.md"), patch.object(audit, "requirements", return_value=rows), patch.object(audit, "accepted_ids", return_value=set()), contextlib.redirect_stdout(io.StringIO()):
|
|
self.assertEqual(audit.main(), 0)
|
|
result = json.loads((root / "out.json").read_text())
|
|
self.assertEqual(result["implementationProgress"], {"completed": 1, "total": 2, "remaining": 1})
|
|
self.assertFalse(result["businessFunctionalComplete"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|