Skip to content

Commit 7db55dd

Browse files
committed
test: add aggregate schema migration coverage
1 parent f74a93b commit 7db55dd

5 files changed

Lines changed: 332 additions & 9 deletions

File tree

docs/one-dot-oh-readiness.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,17 @@ Not guaranteed:
2929
- [x] Verify installed package resources in Linux Docker: `python scripts/smoke_installed_package.py --docker`.
3030
- [x] Verify public PyPI package in Docker: `python scripts/smoke_installed_package.py --docker --from-pypi --version <version>`.
3131
- [ ] Verify PyPI metadata names remain unchanged: `python scripts/check_release.py`.
32+
- [ ] Add Python 3.14 as an official support target only after CI, package classifiers, docs, and installed-package smoke coverage pass. Track this in issue #12.
3233

3334
## 2. Upgrade And Migration
3435

35-
- [ ] Add synthetic v0.2-style SQLite fixture test proving `init_db` upgrades without data loss: `python -m pytest tests/test_store_migrations.py`.
36+
- [x] Add synthetic legacy SQLite fixture test proving `init_db` upgrades without data loss: `python -m pytest tests/test_store_migrations.py`.
3637
- [ ] Add synthetic v0.3-style SQLite fixture if schema drift requires it: `python -m pytest tests/test_store_migrations.py`.
37-
- [ ] Verify `schema_state` reports expected version and checksum after migration: `python -m pytest tests/test_store_migrations.py`.
38-
- [ ] Verify `rebuild-index` clears only tracker-owned aggregate tables: `python -m pytest tests/test_cli_lifecycle.py`.
39-
- [ ] Verify `reset-db` does not touch raw Codex logs: `python -m pytest tests/test_cli_lifecycle.py`.
40-
- [ ] Verify refresh metadata and parser diagnostics survive migration: `python -m pytest tests/test_store_migrations.py tests/test_parser.py`.
41-
- [ ] Verify CSV export columns after migration: `python -m pytest tests/test_cli_lifecycle.py`.
38+
- [x] Verify `schema_state` reports expected version and checksum after migration: `python -m pytest tests/test_store_migrations.py`.
39+
- [x] Verify `rebuild-index` clears only tracker-owned aggregate tables: `python -m pytest tests/test_store_dashboard_mcp.py`.
40+
- [x] Verify `reset-db` does not touch raw Codex logs: `python -m pytest tests/test_cli_lifecycle.py`.
41+
- [x] Verify refresh metadata and parser diagnostics survive migration: `python -m pytest tests/test_store_migrations.py tests/test_parser.py`.
42+
- [x] Verify CSV export columns after migration: `python -m pytest tests/test_store_migrations.py`.
4243

4344
## 3. CLI Compatibility
4445

@@ -127,3 +128,4 @@ Not guaranteed:
127128
- [ ] Document that live account allowance cannot be read automatically by this local tracker.
128129
- [ ] Document that cost and credit estimates are not guaranteed to match exact billing.
129130
- [ ] Document platform/plugin discovery limitations separately from the core Python CLI/dashboard support.
131+
- [ ] Document that Python 3.14 may install because package metadata allows `>=3.10`, but it is not officially supported until issue #12 is complete.

src/codex_usage_tracker/diagnostics.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
DEFAULT_PRICING_PATH,
2121
)
2222
from codex_usage_tracker.pricing import load_pricing_config
23-
from codex_usage_tracker.store import refresh_metadata, schema_state
23+
from codex_usage_tracker.store import SchemaMigrationError, refresh_metadata, schema_state
2424

2525
PLUGIN_NAME = "codex-usage-tracker"
2626

@@ -165,7 +165,15 @@ def _check_database(db_path: Path) -> DoctorCheck:
165165

166166

167167
def _check_database_schema(db_path: Path) -> DoctorCheck:
168-
state = schema_state(db_path)
168+
try:
169+
state = schema_state(db_path)
170+
except SchemaMigrationError as exc:
171+
return DoctorCheck(
172+
"Database schema",
173+
"fail",
174+
str(exc),
175+
"Run: codex-usage-tracker rebuild-index after confirming your local aggregate index can be regenerated.",
176+
)
169177
if not state["exists"]:
170178
return DoctorCheck(
171179
"Database schema",
@@ -197,7 +205,15 @@ def _check_database_schema(db_path: Path) -> DoctorCheck:
197205

198206

199207
def _check_parser_diagnostics(db_path: Path) -> DoctorCheck:
200-
metadata = refresh_metadata(db_path)
208+
try:
209+
metadata = refresh_metadata(db_path)
210+
except SchemaMigrationError as exc:
211+
return DoctorCheck(
212+
"Parser diagnostics",
213+
"fail",
214+
f"Parser diagnostics are unavailable because database migration failed: {exc}",
215+
"Run: codex-usage-tracker rebuild-index after resolving the database schema warning.",
216+
)
201217
if not metadata:
202218
return DoctorCheck(
203219
"Parser diagnostics",

src/codex_usage_tracker/store.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@
4242
)
4343

4444

45+
class SchemaMigrationError(RuntimeError):
46+
"""Raised when a persisted aggregate schema cannot be repaired safely."""
47+
48+
4549
def refresh_usage_index(
4650
codex_home: Path = DEFAULT_CODEX_HOME,
4751
db_path: Path = DEFAULT_DB_PATH,
@@ -137,6 +141,7 @@ def init_db(conn: sqlite3.Connection) -> None:
137141
else:
138142
_migrate_v2(conn)
139143
_record_migration_if_missing(conn, 2)
144+
_validate_usage_events_schema(conn)
140145
conn.execute(f"PRAGMA user_version = {SCHEMA_VERSION}")
141146

142147

@@ -307,6 +312,22 @@ def _ensure_columns(conn: sqlite3.Connection, columns: dict[str, str]) -> None:
307312
raise
308313

309314

315+
def _validate_usage_events_schema(conn: sqlite3.Connection) -> None:
316+
existing = {
317+
str(row["name"])
318+
for row in conn.execute("PRAGMA table_info(usage_events)").fetchall()
319+
}
320+
missing = [column for column in EVENT_COLUMNS if column not in existing]
321+
if missing:
322+
missing_text = ", ".join(missing)
323+
raise SchemaMigrationError(
324+
"usage_events schema is missing required columns: "
325+
f"{missing_text}. Run codex-usage-tracker rebuild-index after confirming your "
326+
"local aggregate index can be regenerated; raw Codex logs are not touched by "
327+
"rebuild-index."
328+
)
329+
330+
310331
def upsert_usage_events(
311332
events: Iterable[UsageEvent], db_path: Path = DEFAULT_DB_PATH
312333
) -> int:

tests/test_cli_lifecycle.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,13 @@ def test_setup_support_bundle_and_reset_db_cli(tmp_path: Path) -> None:
8484
"reset-db",
8585
"--yes",
8686
)
87+
raw_log_path = next((codex_home / "sessions").glob("**/*.jsonl"))
8788

8889
assert reset_without_confirm.returncode == 1
8990
assert "Re-run with --yes" in reset_without_confirm.stderr
9091
assert reset.returncode == 0
9192
assert "Raw Codex logs were not touched" in reset.stdout
93+
assert "SECRET RAW PROMPT" in raw_log_path.read_text(encoding="utf-8")
9294

9395

9496
def test_rate_card_allowance_and_pricing_snapshot_cli(tmp_path: Path) -> None:

0 commit comments

Comments
 (0)