@databricks-solutions/lakebase-scm-utils 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/scripts/index.cjs +2 -2
  2. package/dist/scripts/index.js +2 -2
  3. package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2 -2
  4. package/dist/scripts/lakebase/ci-resolve-branch.cli.js +2 -2
  5. package/dist/scripts/lakebase/collapse-heads.cli.cjs +2 -2
  6. package/dist/scripts/lakebase/collapse-heads.cli.js +2 -2
  7. package/dist/scripts/lakebase/detect-language.cli.cjs +2 -2
  8. package/dist/scripts/lakebase/detect-language.cli.js +2 -2
  9. package/dist/scripts/lakebase/doctor.cli.cjs +2 -2
  10. package/dist/scripts/lakebase/doctor.cli.js +2 -2
  11. package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
  12. package/dist/scripts/lakebase/get-connection.cli.js +2 -2
  13. package/dist/scripts/lakebase/index.cjs +2 -2
  14. package/dist/scripts/lakebase/index.js +2 -2
  15. package/dist/scripts/lakebase/infra-runner.cli.cjs +2 -2
  16. package/dist/scripts/lakebase/infra-runner.cli.js +2 -2
  17. package/dist/scripts/lakebase/new-migration.cli.cjs +2 -2
  18. package/dist/scripts/lakebase/new-migration.cli.js +2 -2
  19. package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
  20. package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
  21. package/dist/scripts/lakebase/schema-migrate.cli.cjs +2 -2
  22. package/dist/scripts/lakebase/schema-migrate.cli.js +2 -2
  23. package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2 -2
  24. package/dist/scripts/lakebase/scm-claim-feature.cli.js +2 -2
  25. package/dist/scripts/lakebase/scm-doctor.cli.cjs +2 -2
  26. package/dist/scripts/lakebase/scm-doctor.cli.js +2 -2
  27. package/dist/scripts/lakebase/scm-merge.cli.cjs +2 -2
  28. package/dist/scripts/lakebase/scm-merge.cli.js +2 -2
  29. package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2 -2
  30. package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +2 -2
  31. package/dist/scripts/util/index.cjs +2 -2
  32. package/dist/scripts/util/index.js +2 -2
  33. package/package.json +1 -1
  34. package/templates/project/client/consort-vitest-reporter.ts +96 -0
  35. package/templates/project/client/package.json +1 -0
  36. package/templates/project/client/playwright.config.ts +18 -3
  37. package/templates/project/client/tests/e2e/about.spec.ts +1 -1
  38. package/templates/project/client/tests/e2e/consort-fixtures.ts +23 -0
  39. package/templates/project/client/tests/e2e/home.spec.ts +1 -1
  40. package/templates/project/client/tests/setup.ts +79 -4
  41. package/templates/project/client/tsconfig.json +1 -1
  42. package/templates/project/client/vite.config.ts +8 -0
  43. package/templates/project/client/vitest.globalSetup.ts +121 -0
  44. package/templates/project/common/consort-capture.ts +87 -0
  45. package/templates/project/common/consort-test-reporter.ts +45 -0
  46. package/templates/project/common/playwright.config.ts +6 -1
  47. package/templates/project/python/app/consort_capture.py +130 -0
  48. package/templates/project/python/app/database.py +6 -6
  49. package/templates/project/python/app/main.py +52 -0
  50. package/templates/project/python/app/repositories/__init__.py +8 -0
  51. package/templates/project/python/app/repositories/session.py +28 -0
  52. package/templates/project/python/pyproject.toml +1 -0
  53. package/templates/project/python/tests/conftest.py +132 -3
@@ -8,9 +8,19 @@ from app.main import app
8
8
 
9
9
 
10
10
  @pytest.fixture()
11
- def client():
12
- """FastAPI TestClient for making HTTP requests."""
13
- return TestClient(app)
11
+ def client(request):
12
+ """FastAPI TestClient for making HTTP requests.
13
+
14
+ Tagged with the test's ``@pytest.mark.testid`` via the ``X-Consort-Test-Id`` header, so the
15
+ backend's per-test capture (``app/consort_capture.py``) attributes the rows each request
16
+ RETRIEVES/STORES to this test — exactly as the Vitest fetch shim and the Playwright fixture do.
17
+ (Direct ``db_session`` setup/asserts bypass the app and are not captured, by design: the capture
18
+ is what the test did THROUGH the app's ``/api``.)"""
19
+ c = TestClient(app)
20
+ test_id = _consort_testid(request.node)
21
+ if test_id:
22
+ c.headers["X-Consort-Test-Id"] = test_id
23
+ return c
14
24
 
15
25
 
16
26
  @pytest.fixture()
@@ -54,3 +64,122 @@ def _restore_migration_head_after_each_test():
54
64
  # Everything else (a real schema error) propagates and fails loudly, which
55
65
  # is the correct behavior for a genuinely broken restore.
56
66
  print(f"WARNING: migration-head restore failed during teardown: {exc}")
67
+
68
+
69
+ # --- Per-test "green as it runs" signal for the dashboard -------------------------------------
70
+ # Each test tagged `@pytest.mark.testid("T37")` streams its lifecycle to `.consort/test-run.jsonl`:
71
+ # `running` when it STARTS (pytest_runtest_setup), then `green`/`red`/`skipped` when it FINISHES
72
+ # (pytest_runtest_makereport, the CALL phase). A dashboard tails that file and lights each bubble
73
+ # the moment it changes, so even a single whole-suite run (story cadence) shows per-test progress —
74
+ # pending -> running -> green — not an all-at-once batch flip. The `file` disambiguates a reused id
75
+ # across features (F1 and F2 may both have a T37); the reader keys by (scenario_file + id) and takes
76
+ # the LAST write per key, so running -> green composes. Observability only.
77
+
78
+
79
+ def _consort_testid(item):
80
+ """The test's ``@pytest.mark.testid(...)`` id, or ``None`` when it carries no marker."""
81
+ return next((m.args[0] for m in item.iter_markers("testid") if m.args), None)
82
+
83
+
84
+ def _consort_emit_test_run(test_id, status, item):
85
+ """Append one per-test line to ``.consort/test-run.jsonl``. Best-effort with a SCOPED except
86
+ (the only errors this body can raise: file I/O, JSON serialization) — never a blanket
87
+ ``except Exception``, which the kit's deterministic broad-integrity-except test-smell gate
88
+ correctly flags even in a conftest. A reporter write must never break the suite."""
89
+ try:
90
+ import json
91
+ from datetime import datetime, timezone
92
+ from pathlib import Path
93
+
94
+ root = Path(__file__).resolve().parent.parent
95
+ rel = str(Path(str(item.fspath))).replace(f"{root}/", "")
96
+ out = root / ".consort"
97
+ out.mkdir(parents=True, exist_ok=True)
98
+ line = json.dumps(
99
+ {"ts": datetime.now(timezone.utc).isoformat(), "test_id": test_id, "status": status, "file": rel, "kind": "behavior"}
100
+ )
101
+ with open(out / "test-run.jsonl", "a", encoding="utf-8") as fh:
102
+ fh.write(line + "\n")
103
+ except (OSError, ValueError, TypeError):
104
+ pass
105
+
106
+
107
+ def _consort_capture_tables(test_id, item):
108
+ """Append a per-test TABLE snapshot to ``.consort/table-snapshots.jsonl``, keyed by test id: the
109
+ rows of each public app table AS THIS TEST LEFT THEM, captured on the CALL phase (before teardown
110
+ prunes). The dashboard shows the table 'as of' the running/selected test — same corpus/one-reader
111
+ path as the test-status view. Read-only + row-capped. A ``@migration`` test runs on its OWN
112
+ isolated ephemeral branch and drops/recreates the table, so its snapshot is flagged ``isolated``.
113
+ Best-effort with a SCOPED except (DB errors + file I/O + JSON) — never a blanket catch, never
114
+ breaks the suite."""
115
+ try:
116
+ import json
117
+ import re
118
+ from datetime import datetime, timezone
119
+ from pathlib import Path
120
+
121
+ from sqlalchemy import text
122
+ from sqlalchemy.exc import SQLAlchemyError
123
+
124
+ from app.database import engine
125
+
126
+ isolated = any(True for _ in item.iter_markers("migration"))
127
+ ident = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
128
+ root = Path(__file__).resolve().parent.parent
129
+ rel = str(Path(str(item.fspath))).replace(f"{root}/", "")
130
+ out = root / ".consort"
131
+ out.mkdir(parents=True, exist_ok=True)
132
+ ts = datetime.now(timezone.utc).isoformat()
133
+ lines = []
134
+ try:
135
+ with engine.connect() as conn:
136
+ names = [
137
+ r[0]
138
+ for r in conn.execute(
139
+ text(
140
+ "select tablename from pg_tables where schemaname='public' "
141
+ "and tablename <> 'alembic_version' order by 1"
142
+ )
143
+ )
144
+ ]
145
+ for name in names:
146
+ if not ident.fullmatch(name):
147
+ continue
148
+ rows = [dict(r) for r in conn.execute(text(f'SELECT * FROM "{name}" ORDER BY 1 DESC LIMIT 50')).mappings()]
149
+ total = conn.execute(text(f'SELECT count(*) FROM "{name}"')).scalar()
150
+ lines.append(
151
+ json.dumps(
152
+ {"ts": ts, "test_id": test_id, "file": rel, "table": name, "rows": rows, "row_count": total, "isolated": isolated},
153
+ default=str,
154
+ )
155
+ )
156
+ except SQLAlchemyError:
157
+ return # branch unreachable / mid-migration — skip this snapshot, never fail the test
158
+ if lines:
159
+ with open(out / "table-snapshots.jsonl", "a", encoding="utf-8") as fh:
160
+ fh.write("\n".join(lines) + "\n")
161
+ except (OSError, ValueError, TypeError):
162
+ pass
163
+
164
+
165
+ def pytest_runtest_setup(item):
166
+ """Emit ``running`` the moment a tagged test starts, so the dashboard shows it in-flight."""
167
+ test_id = _consort_testid(item)
168
+ if test_id:
169
+ _consort_emit_test_run(test_id, "running", item)
170
+
171
+
172
+ @pytest.hookimpl(wrapper=True)
173
+ def pytest_runtest_makereport(item, call):
174
+ """Emit the FINAL status on the CALL phase, and capture the per-test table snapshot (before
175
+ teardown prunes). A ``makereport`` wrapper (NOT the ``logreport`` hook, whose ``TestReport``
176
+ carries no test node) so the ``testid`` marker is reachable off ``item``; always returns the
177
+ real report unchanged so pytest's own reporting is untouched."""
178
+ report = yield
179
+ if report.when == "call":
180
+ test_id = _consort_testid(item)
181
+ if test_id:
182
+ status = "green" if report.passed else "red" if report.failed else "skipped"
183
+ _consort_emit_test_run(test_id, status, item)
184
+ _consort_capture_tables(test_id, item)
185
+ return report