import copy import json import tempfile import threading import io import time from types import SimpleNamespace import unittest from concurrent.futures import ThreadPoolExecutor from unittest.mock import patch import compression_engine as engine import compression_settings as cs import gecis_context as gc class Compression(unittest.TestCase): def setUp(self): self.temp = tempfile.TemporaryDirectory() self.store = cs.Checkpoints(self.temp.name) self.settings = dict(cs.DEFAULTS, keepTurns=2, primaryModel='ide/primary', fallbackModel='ide/backup') self.req = {'model': 'ide/target', 'prompt_cache_key': 'same-chat', 'input': [ {'role': 'user', 'content': 'DECISION-4827 ' + 'x' * 60000}, {'role': 'assistant', 'content': 'Done'}, {'role': 'user', 'content': 'Recent one'}, {'role': 'assistant', 'content': 'Latest answer'}, {'role': 'user', 'content': 'Recent two'}]} self.summary = 'Amaç: DECISION-4827. Kararlar: preserve. Kısıtlar: same ID. Tamamlanan işler: done. Kalan adımlar: test. ' def tearDown(self): self.temp.cleanup() def test_strict_retention_never_shrinks_ten_turns_to_one(self): req = copy.deepcopy(self.req) settings = dict(self.settings, keepTurns=10) with self.assertRaises(gc.TransferError): gc.Preparer().prepare(req, 32768, lambda *_: self.fail('Must not summarize protected exchanges'), settings=settings) self.assertEqual(req, self.req) def test_percent_token_and_output_reserve_budgets(self): for mode, expected in [('percent', 70000), ('tokens', 64000)]: self.assertEqual(gc.input_budget({}, 100000, dict(self.settings, budgetMode=mode)), expected) self.assertEqual(gc.input_budget({}, 100000, self.settings, automatic=True), 50000) self.assertEqual(gc.input_budget({'max_output_tokens': 80000}, 100000, self.settings), 20000) def test_boundary_equal_does_not_summarize(self): size = gc.tokens(self.req) settings = dict(self.settings, budgetMode='tokens', maxTokens=size) self.assertEqual(gc.Preparer().prepare(self.req, 100000, lambda *_: self.fail('At boundary'), settings=settings), self.req) def test_checkpoint_survives_restart_and_appended_message(self): first = gc.Preparer().prepare(self.req, 32768, lambda *_: self.summary, 'account-chat', settings=self.settings, checkpoints=self.store) later = copy.deepcopy(self.req) later['input'].extend([{'role':'assistant','content':'new answer'}, {'role':'user','content':'continue'}]) second = gc.Preparer().prepare(later, 32768, lambda *_: self.fail('Should reuse saved prefix'), 'account-chat', settings=self.settings, checkpoints=cs.Checkpoints(self.temp.name)) self.assertEqual(second['prompt_cache_key'], 'same-chat') self.assertIn('DECISION-4827', json.dumps(second)) self.assertEqual(second['input'][-1], later['input'][-1]) self.assertLessEqual(gc.tokens(first), gc.input_budget(self.req, 32768, self.settings)) def test_failed_or_oversized_summary_never_commits(self): for text in ['', 'x' * 60000]: with self.assertRaises(gc.TransferError): gc.Preparer().prepare(self.req, 32768, lambda *_: text, 'scope', settings=self.settings, checkpoints=self.store) old = gc.split_history(self.req['input'], 2)[1] self.assertIsNone(self.store.get('scope', old, self.settings)) def test_scopes_and_changed_sources_never_share_summary(self): old = gc.split_history(self.req['input'], 2)[1] self.store.save('a', old, self.settings, self.summary) self.assertIsNone(self.store.get('b', old, self.settings)) changed = copy.deepcopy(old); changed[0]['content'] = 'different' self.assertIsNone(self.store.get('a', changed, self.settings)) def test_restore_checks_scope_and_preserves_all_versions(self): old = [{'role':'user','content':'decision'}] self.store.save('a', old, self.settings, 'version one') self.store.save('a', old, self.settings, 'version two') self.store.restore('a', 1) self.assertEqual(self.store.get('a', old, self.settings)[1], 'version one') with self.assertRaises(ValueError): self.store.restore('b', 1) def test_duplicate_requests_coalesce(self): entered, release = threading.Event(), threading.Event() count = [] def run(): count.append(1); entered.set(); release.wait(5) return {'value':'same'} with ThreadPoolExecutor(2) as pool: one = pool.submit(self.store.coalesce, 'same-key', run) self.assertTrue(entered.wait(2)) two = pool.submit(self.store.coalesce, 'same-key', run) # Synchronize entry under the store lock before releasing owner. threading.Event().wait(.05) release.set() self.assertEqual(one.result(), two.result()) self.assertEqual(len(count), 1) def test_fallback_sequence_and_unverified_million_rejected(self): class Handler: def _transfer_window(self, slug): return 1000000 if slug == 'ide/backup' else 128000 calls = [] def mock_call(_h,_m,slug,*args): calls.append(slug) if slug == 'ide/primary': raise TimeoutError() return self.summary with patch.object(engine, 'call', side_effect=mock_call): self.assertEqual(engine.summarize(Handler(), None, '[]', 500, self.settings, 'ide/current'), self.summary) self.assertEqual(calls, ['ide/primary', 'ide/backup']) calls.clear() with patch.object(engine, 'call', side_effect=mock_call): engine.summarize(Handler(), None, '[]', 500, dict(self.settings, fallbackModel='ide/unverified'), 'ide/current') self.assertEqual(calls, ['ide/primary', 'ide/current']) def test_tool_pairs_cannot_be_split_between_chunks(self): items = [{'type':'function_call','call_id':'a','arguments':'x'*500}, {'role':'user','content':'interleaved'}, {'type':'function_call_output','call_id':'a','output':'result'}] grouped = engine.chunks(gc.encoded(items), 2000) self.assertEqual(len(grouped), 1) with self.assertRaises(gc.TransferError): engine.chunks(gc.encoded(items), 100) def test_invalid_settings_fail_without_defaults(self): for patch_value in [{'keepTurns':0}, {'targetPercent':70}, {'timeoutSeconds':0}, {'autoEnabled':'yes'}]: with self.assertRaises(ValueError): cs.validate(dict(self.settings, **patch_value)) def test_structured_unicode_messages_and_reduce_text_split_without_loss(self): content = 'Karar 🧭 ' * 3000 item = {'role':'user', 'content':[{'type':'input_text', 'text':content}]} parts = [x for chunk in engine.chunks(gc.encoded([item]), 2000) for x in json.loads(chunk)] self.assertEqual(''.join(x['content'][0]['text'] for x in parts), content) self.assertTrue(all(gc.tokens([x]) <= 2000 for x in parts)) reduced = [x for chunk in engine.chunks(gc.encoded([content]), 2000) for x in json.loads(chunk)] self.assertEqual(''.join(reduced), content) def test_late_older_checkpoint_cannot_replace_newer_prefix(self): old = [{'role':'user', 'content':'old'}] newer = old + [{'role':'assistant', 'content':'new'}] self.store.save('scope', newer, self.settings, 'new snapshot') self.store.save('scope', old, self.settings, 'late old snapshot') self.assertEqual(self.store.get('scope', newer, self.settings), (2, 'new snapshot')) def test_late_summary_cannot_replace_edited_history_of_same_length(self): old = [{'role':'user', 'content':'old'}] edited = [{'role':'user', 'content':'edited'}] self.store.begin('scope', old, self.settings) self.store.begin('scope', edited, self.settings) self.store.save('scope', edited, self.settings, 'edited snapshot') self.store.save('scope', old, self.settings, 'late old snapshot') self.assertEqual(self.store.get('scope', edited, self.settings), (1, 'edited snapshot')) def test_tool_free_high_summary_reserves_thinking_and_requires_completion(self): module = SimpleNamespace(ek_model_mu=lambda _: True, MODEL_MAP={'ide/primary':('route', 'gemini-3.8-flash-high')}, ROUTES={'route':{'url':'https://example.invalid/responses', 'key':'synthetic'}}, UA='test') handler = SimpleNamespace(_transfer_window=lambda _: 1000000) event = {'type':'response.completed', 'response':{'output':[{'type':'message','content':[{'type':'output_text','text':self.summary}]}]}} bodies = [] def response(request, **_kwargs): bodies.append(json.loads(request.data)) return io.BytesIO(('data: '+json.dumps(event)+'\n\n').encode()) with patch.object(engine.urllib.request, 'urlopen', side_effect=response): result = engine.call(handler,module,'ide/primary','["history"]',1024,self.settings,time.monotonic()+5) self.assertEqual(result.strip(), self.summary.strip()) self.assertEqual(bodies[0]['tools'], []) self.assertEqual(bodies[0]['tool_choice'], 'none') self.assertEqual(bodies[0]['max_output_tokens'], 25600) event['type'] = 'response.incomplete' with patch.object(engine.urllib.request, 'urlopen', side_effect=response), self.assertRaises(gc.TransferError): engine.call(handler,module,'ide/primary','["history"]',1024,self.settings,time.monotonic()+5) if __name__ == '__main__': unittest.main()