2020from utils .cli_chat_reader import _extract_blob_refs
2121
2222
23- # ---------------------------------------------------------------------------
24- # Fixtures
25- # ---------------------------------------------------------------------------
26-
2723GOOD_COMPOSER_RAW : dict = {
2824 "name" : "Refactor api/workspaces.py" ,
2925 "createdAt" : 1_715_000_000_000 ,
@@ -48,11 +44,6 @@ def _make_blob_chain(*ref_hashes: str) -> bytes:
4844 return bytes (out )
4945
5046
51- # ---------------------------------------------------------------------------
52- # 1. Known-good schema
53- # ---------------------------------------------------------------------------
54-
55-
5647class ComposerKnownGoodSchema (unittest .TestCase ):
5748 def test_parses_required_and_optional_fields (self ) -> None :
5849 composer = Composer .from_dict (GOOD_COMPOSER_RAW , composer_id = "cid-001" )
@@ -74,8 +65,6 @@ def test_workspace_parses_with_optional_folder(self) -> None:
7465 self .assertEqual (ws_no_folder .folder , None )
7566
7667 def test_workspace_local_composer_parses (self ) -> None :
77- # Workspace-local composers (composer.composerData ItemTable rows)
78- # carry composerId + lastUpdatedAt but not the global-storage fields.
7968 c = WorkspaceLocalComposer .from_dict ({
8069 "composerId" : "cid-local-1" ,
8170 "lastUpdatedAt" : 1_715_000_500_000 ,
@@ -96,11 +85,6 @@ def test_export_entry_parses(self) -> None:
9685 self .assertEqual (entry .workspace , "ws-1" )
9786
9887
99- # ---------------------------------------------------------------------------
100- # 2. Missing-field schema → SchemaError
101- # ---------------------------------------------------------------------------
102-
103-
10488class ComposerMissingFieldSchema (unittest .TestCase ):
10589 def test_missing_full_conversation_headers_only_raises (self ) -> None :
10690 bad = {k : v for k , v in GOOD_COMPOSER_RAW .items () if k != "fullConversationHeadersOnly" }
@@ -110,9 +94,6 @@ def test_missing_full_conversation_headers_only_raises(self) -> None:
11094 self .assertEqual (cm .exception .field , "fullConversationHeadersOnly" )
11195
11296 def test_missing_created_at_raises (self ) -> None :
113- # Issue #24 lists createdAt as a required field. A live workspaceStorage
114- # check confirmed 17/17 composers carry it, so a missing value is real
115- # schema drift (not benign older-record absence).
11697 bad = {k : v for k , v in GOOD_COMPOSER_RAW .items () if k != "createdAt" }
11798 with self .assertRaises (SchemaError ) as cm :
11899 Composer .from_dict (bad , composer_id = "cid-001" )
@@ -130,16 +111,12 @@ def test_headers_wrong_type_raises(self) -> None:
130111 self .assertIn ("expected list" , str (cm .exception ))
131112
132113 def test_headers_falsy_non_list_raises (self ) -> None :
133- # Regression: CodeRabbit caught that ``raw.get(...) or []`` silently
134- # coerced None / "" / 0 / False to an empty list, bypassing the
135- # isinstance gate. Each falsy non-list value must now raise.
136114 for bad_value in (None , "" , 0 , False ):
137115 bad = dict (GOOD_COMPOSER_RAW , fullConversationHeadersOnly = bad_value )
138116 with self .assertRaises (SchemaError , msg = f"failed for { bad_value !r} " ):
139117 Composer .from_dict (bad , composer_id = "cid-001" )
140118
141119 def test_headers_empty_list_is_valid (self ) -> None :
142- # Empty list must still pass — a composer with no messages is legal.
143120 ok = dict (GOOD_COMPOSER_RAW , fullConversationHeadersOnly = [])
144121 composer = Composer .from_dict (ok , composer_id = "cid-001" )
145122 self .assertEqual (composer .full_conversation_headers_only , [])
@@ -149,9 +126,6 @@ def test_bubble_empty_id_raises(self) -> None:
149126 Bubble .from_dict ({"text" : "hi" }, bubble_id = "" )
150127
151128 def test_workspace_local_composer_missing_id_raises (self ) -> None :
152- # Regression for CodeRabbit's per-row drift comment on api/composers.py:
153- # previously a row missing composerId was silently skipped. Now drift
154- # raises SchemaError so api/composers.py can log + skip explicitly.
155129 for bad in (
156130 {"lastUpdatedAt" : 0 }, # composerId absent
157131 {"composerId" : "" }, # empty string
@@ -173,28 +147,20 @@ def test_export_entry_missing_required_raises(self) -> None:
173147 self .assertEqual (cm .exception .field , "log_id" )
174148
175149 def test_export_entry_non_string_required_raises (self ) -> None :
176- # Regression: CodeRabbit caught that ``str(raw["log_id"])`` silently
177- # coerced ints, UUIDs, lists, etc. into strings, masking schema drift.
178- # Each required field must now be an actual non-empty string.
179150 bad_values : tuple [object , ...] = (123 , None , "" , [], {"x" : 1 }, True )
180151 for bad_value in bad_values :
181152 bad : dict [str , object ] = {"log_id" : bad_value , "title" : "x" , "workspace" : "w" }
182153 with self .assertRaises (SchemaError , msg = f"failed for { bad_value !r} " ):
183154 ExportEntry .from_dict (bad )
184155
185156 def test_schema_error_inherits_value_error (self ) -> None :
186- # call sites that catch ValueError still trap SchemaError (back-compat)
187157 try :
188158 Composer .from_dict ({}, composer_id = "cid-001" )
189159 except ValueError :
190160 return
191161 self .fail ("SchemaError did not propagate as ValueError" )
192162
193163 def test_non_dict_payload_raises_schema_error (self ) -> None :
194- # Regression: CodeRabbit caught that a malformed top-level payload
195- # (list, string, None) would previously trip AttributeError on
196- # ``raw.get(...)`` and get swallowed by surrounding ``except Exception``.
197- # Each ``from_dict`` now surfaces it as SchemaError so drift stays loud.
198164 bad_payloads : tuple [object , ...] = ([], "not a dict" , 42 , None , ("a" , "b" ))
199165 for bad in bad_payloads :
200166 with self .assertRaises (SchemaError , msg = f"failed for { type (bad ).__name__ } " ):
@@ -209,11 +175,6 @@ def test_non_dict_payload_raises_schema_error(self) -> None:
209175 Bubble .from_dict (bad , bubble_id = "b-1" ) # type: ignore[arg-type]
210176
211177
212- # ---------------------------------------------------------------------------
213- # 3. _extract_blob_refs binary blob path (0x0a 0x20 marker)
214- # ---------------------------------------------------------------------------
215-
216-
217178class CliSessionMetaAndBlobChain (unittest .TestCase ):
218179 def test_meta_missing_latest_root_blob_id_raises (self ) -> None :
219180 with self .assertRaises (SchemaError ) as cm :
@@ -226,8 +187,6 @@ def test_meta_wrong_type_raises(self) -> None:
226187 CliSessionMeta .from_dict ({"latestRootBlobId" : 12345 })
227188
228189 def test_meta_parses_then_blob_chain_extracts_refs (self ) -> None :
229- # Realistic flow: the meta blob points at a root chain blob, whose
230- # 0x0a 0x20-prefixed runs are SHA-256 references to JSON message blobs.
231190 ref1 = "a" * 64
232191 ref2 = "b" * 64
233192 ref3 = "c" * 64
@@ -245,7 +204,6 @@ def test_meta_parses_then_blob_chain_extracts_refs(self) -> None:
245204 self .assertEqual (refs , [ref1 , ref2 , ref3 ])
246205
247206 def test_blob_chain_skips_non_marker_bytes (self ) -> None :
248- # Garbage prefix + valid run + garbage suffix — only the run extracts.
249207 ref = "f" * 64
250208 garbage_before = b"\x01 \x02 \x03 "
251209 garbage_after = b"\xff \xfe "
0 commit comments