import unittest import sys from pathlib import Path import psycopg # Add core directory to path code_path = Path(__file__).parent.parent / "ca_core" sys.path.insert(0, str(code_path)) import entity import property DBNAME = "ca" def get_last_log(cursor): cursor.execute("SELECT entry FROM log ORDER BY id DESC LIMIT 1") row = cursor.fetchone() return row["entry"] if row else "" class TestPropertyFunctions(unittest.TestCase): @classmethod def setUpClass(cls): cls.conn = psycopg.connect(f"dbname={DBNAME}") cls.cur = cls.conn.cursor(row_factory=psycopg.rows.dict_row) @classmethod def tearDownClass(cls): cls.cur.close() cls.conn.close() def setUp(self): self.conn.rollback() def tearDown(self): self.conn.rollback() # ------------------------------------------------------------ # Basic property set/get # ------------------------------------------------------------ def test_set_and_get_property(self): creator_id = entity.insert_creator(self.cur, "Creator1", "pubkey1") person_id = entity.enroll_person( self.cur, "Person1", "pubkey_person", creator_id ) property.set_property(self.cur, person_id, "prop1") props = property.get_properties(self.cur, person_id) self.assertIn("prop1", props) details = property.get_property(self.cur, person_id, "prop1") self.assertIsNotNone(details) # CHAR(19) is space-padded in PostgreSQL; strip for comparison. self.assertEqual(details["validation_policy"].strip(), "default") self.assertIsNone(details["source"]) log_entry = get_last_log(self.cur).lower() self.assertIn("set property", log_entry) self.assertIn("prop1", log_entry) # ------------------------------------------------------------ # Delete property # ------------------------------------------------------------ def test_delete_property(self): creator_id = entity.insert_creator(self.cur, "Creator2", "pubkey2") person_id = entity.enroll_person( self.cur, "Person2", "pubkey_person", creator_id ) property.set_property(self.cur, person_id, "prop2") property.delete_property(self.cur, person_id, "prop2") props = property.get_properties(self.cur, person_id) self.assertNotIn("prop2", props) log_entry = get_last_log(self.cur).lower() self.assertIn("deleted property", log_entry) self.assertIn("prop2", log_entry) def test_set_property_with_policy_and_source(self): creator_id = entity.insert_creator(self.cur, "CreatorPolicy", "pubkey_policy") person_id = entity.enroll_person( self.cur, "PersonPolicy", "pubkey_person", creator_id ) property.set_property( self.cur, person_id, "prop_policy", validation_policy="strict", source="unit-test", ) details = property.get_property(self.cur, person_id, "prop_policy") self.assertIsNotNone(details) self.assertEqual(details["validation_policy"].strip(), "strict") self.assertEqual(details["source"], "unit-test") log_entry = get_last_log(self.cur).lower() self.assertIn("set property", log_entry) self.assertIn("prop_policy", log_entry) self.assertIn("strict", log_entry) # ------------------------------------------------------------ # Immutability: revoked entity cannot mutate properties # ------------------------------------------------------------ def test_revoked_entity_has_no_properties(self): creator_id = entity.insert_creator(self.cur, "Creator3", "pubkey3") person_id = entity.enroll_person( self.cur, "Person3", "pubkey_person", creator_id ) entity.set_entity_status(self.cur, person_id, "revoked", creator_id) with self.assertRaises(ValueError): property.set_property(self.cur, person_id, "prop3") with self.assertRaises(ValueError): property.delete_property(self.cur, person_id, "prop3")