#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import annotations import argparse from dataclasses import replace from pathlib import Path import sys,unittest P=argparse.ArgumentParser();P.add_argument("--root",type=Path,required=True);ROOT=P.parse_args().root;sys.path.insert(0,str(ROOT/"tools")) from phase10df_title_observer_contract import ObserverError,ObserverPlan,Outcome,PHASE,SyntheticResult,classify_synthetic,validate_candidate,validate_inactive # noqa:E402 def inactive():return ObserverPlan(PHASE,False,*([None]*8),*([False]*9)) def query():return ObserverPlan(PHASE,True,"9.60","PPSA01659","SOURCE_BOUND_QUERY","a"*64,None,None,"b"*64,4096,True,True,False,False,False,False,False,False,False) def result(**changes): value=SyntheticResult("9.60","PPSA01659","SOURCE_BOUND_QUERY","b"*64,True,64,True,False,None) return replace(value,**changes) class Tests(unittest.TestCase): def test_inactive(self):validate_inactive(inactive()) def test_source_query_candidate(self):validate_candidate(query()) def test_path_requires_provenance(self): plan=replace(query(),method="EXACT_PATH_METADATA",exact_literal_path="/fixed/path",path_provenance_sha256="c"*64);validate_candidate(plan) with self.assertRaises(ObserverError):validate_candidate(replace(plan,path_provenance_sha256=None)) def test_no_shell_or_enumeration(self): for field in ("shell_present","directory_enumeration","title_launch","app_termination","device_write","retry","reconnect"): with self.subTest(field=field),self.assertRaises(ObserverError):validate_candidate(replace(query(),**{field:True})) def test_explicit_results(self): self.assertEqual(classify_synthetic(query(),result()),Outcome.PRESENT) self.assertEqual(classify_synthetic(query(),result(explicit_present=False,explicit_absent=True)),Outcome.ABSENT) def test_error_and_incomplete_are_unknown(self): self.assertEqual(classify_synthetic(query(),result(error_code=-1)),Outcome.UNKNOWN) self.assertEqual(classify_synthetic(query(),result(complete=False)),Outcome.UNKNOWN) def test_ambiguous_and_oversize_rejected(self): with self.assertRaises(ObserverError):classify_synthetic(query(),result(explicit_absent=True)) with self.assertRaises(ObserverError):classify_synthetic(query(),result(result_bytes=4097)) def test_binding_mismatch_rejected(self): with self.assertRaises(ObserverError):classify_synthetic(query(),result(title_id="OTHER")) if __name__=="__main__":unittest.main(argv=[__file__])