#!/usr/bin/env python3 """Programs V1 desktop projection export regressions.""" import sqlite3 import sys import tempfile import unittest from contextlib import closing from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "tools")) import export_programs import export_programs_v2 SCHEMA = """ CREATE TABLE programs(program_id TEXT PRIMARY KEY,title TEXT,note TEXT,state TEXT, start_date TEXT,end_date TEXT,created_at TEXT,updated_at TEXT,revision_id TEXT, source_format TEXT,source_version INTEGER,source_payload_sha256 TEXT,deleted_at TEXT); CREATE TABLE program_sessions(program_session_id TEXT PRIMARY KEY,program_id TEXT, position INTEGER,title TEXT,session_type TEXT,planned_for TEXT,note TEXT); CREATE TABLE program_session_entries(program_session_id TEXT,entry_id TEXT,position INTEGER, exercise_id TEXT,equipment_id TEXT,recording_mode TEXT,tracking_mode TEXT,data_fields INTEGER, load_mode TEXT,rest_seconds INTEGER,target_sets INTEGER,target_reps INTEGER, target_duration_seconds INTEGER,target_weight_kg REAL,notes TEXT); CREATE TABLE program_deletions(program_id TEXT PRIMARY KEY,request_id TEXT, expected_revision TEXT,deleted_revision TEXT,deleted_at TEXT,response_json TEXT, generation_id TEXT,acknowledged_at TEXT); PRAGMA user_version=26; """ class ProgramsExchangeTest(unittest.TestCase): def setUp(self): self.temporary = tempfile.TemporaryDirectory() self.database = Path(self.temporary.name) / "trainlog.sqlite" with closing(sqlite3.connect(self.database)) as db: db.executescript(SCHEMA) db.execute("INSERT INTO programs VALUES(?,?,?,?,?,?,?,?,?,?,?,?,NULL)", ( "pg_live", "Cycle", None, "active", "2026-09-21", None, "2026-09-18T10:00:00Z", "2026-09-18T11:00:00Z", "pgr_live", "trainlog-program", 1, "a" * 64, )) db.execute("INSERT INTO program_sessions VALUES(?,?,?,?,?,?,?)", ("pgs_live", "pg_live", 0, "A", "training", None, None)) db.execute("INSERT INTO program_session_entries VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", ("pgs_live", "pge_live", 0, "ex_unknown", None, "sets", "reps", 0, "none", 60, 3, 8, None, None, None)) db.execute("INSERT INTO programs VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)", ( "pg_deleted", "Deleted", None, "active", None, None, "2026-09-18T10:00:00Z", "2026-09-18T11:00:00Z", "pgr_old", "trainlog-program", 1, "b" * 64, "2026-09-18T12:00:00Z", )) db.execute("INSERT INTO program_deletions VALUES(?,?,?,?,?,?,NULL,NULL)", ("pg_deleted", "pgd_delete", "pgr_old", "pgr_deleted", "2026-09-18T12:00:00Z", "{}")) db.execute("INSERT INTO program_deletions VALUES(?,?,?,?,?,?,?,?)", ("pg_ack", "pgd_ack", "pgr_ack_old", "pgr_ack_deleted", "2026-09-18T12:00:00Z", "{}", "gen_old", "2026-09-18T13:00:00Z")) db.commit() def tearDown(self): self.temporary.cleanup() def test_full_live_snapshot_and_only_unacknowledged_deletions(self): payload = export_programs.export_programs(self.database) self.assertEqual("trainlog-programs", payload["format"]) self.assertEqual(["pg_live"], [program["program_id"] for program in payload["programs"]]) occurrence = payload["programs"][0]["sessions"][0]["occurrences"][0] self.assertEqual((3, 8, None), ( occurrence["target_sets"], occurrence["target_reps"], occurrence["target_duration_seconds"], )) self.assertEqual([{ "deletion_id": "pgd_delete", "program_id": "pg_deleted", "predecessor_revision_id": "pgr_old", "revision_id": "pgr_deleted", "requested_at": "2026-09-18T12:00:00Z", }], payload["deletions"]) def test_v2_carries_current_date_and_ceded_state_without_changing_v1(self): with closing(sqlite3.connect(self.database)) as db: db.executescript(""" CREATE TABLE program_session_planning( program_session_id TEXT PRIMARY KEY,current_for TEXT, state TEXT,operation_id TEXT,updated_at TEXT); CREATE TABLE program_revision_sequences( program_id TEXT PRIMARY KEY,revision_sequence INTEGER NOT NULL); INSERT INTO program_revision_sequences VALUES('pg_live',1); INSERT INTO program_session_planning VALUES( 'pgs_live','2026-10-01','active','op_fixture','2026-09-30T12:00:00Z'); PRAGMA user_version=37; """) db.commit() initial = export_programs.export_programs(self.database) current = export_programs_v2.export_programs_v2(self.database) self.assertEqual(1, initial["version"]) self.assertEqual(2, current["version"]) original = initial["programs"][0]["sessions"][0] planned = current["programs"][0]["sessions"][0] self.assertIsNone(original["planned_for"]) self.assertEqual(set(original), { "program_session_id", "title", "session_type", "planned_for", "note", "occurrences" }) self.assertEqual("2026-10-01", planned["current_for"]) self.assertEqual(1, current["programs"][0]["revision_sequence"]) self.assertEqual("active", planned["planning_state"]) self.assertEqual(0, planned["position"]) with closing(sqlite3.connect(self.database)) as db: db.execute("UPDATE program_session_planning SET current_for=NULL,state='ceded'") db.commit() ceded = export_programs_v2.export_programs_v2(self.database) self.assertIsNone(ceded["programs"][0]["sessions"][0]["current_for"]) self.assertEqual("ceded", ceded["programs"][0]["sessions"][0]["planning_state"]) if __name__ == "__main__": unittest.main()