@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.
- package/dist/scripts/index.cjs +2 -2
- package/dist/scripts/index.js +2 -2
- package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2 -2
- package/dist/scripts/lakebase/ci-resolve-branch.cli.js +2 -2
- package/dist/scripts/lakebase/collapse-heads.cli.cjs +2 -2
- package/dist/scripts/lakebase/collapse-heads.cli.js +2 -2
- package/dist/scripts/lakebase/detect-language.cli.cjs +2 -2
- package/dist/scripts/lakebase/detect-language.cli.js +2 -2
- package/dist/scripts/lakebase/doctor.cli.cjs +2 -2
- package/dist/scripts/lakebase/doctor.cli.js +2 -2
- package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
- package/dist/scripts/lakebase/get-connection.cli.js +2 -2
- package/dist/scripts/lakebase/index.cjs +2 -2
- package/dist/scripts/lakebase/index.js +2 -2
- package/dist/scripts/lakebase/infra-runner.cli.cjs +2 -2
- package/dist/scripts/lakebase/infra-runner.cli.js +2 -2
- package/dist/scripts/lakebase/new-migration.cli.cjs +2 -2
- package/dist/scripts/lakebase/new-migration.cli.js +2 -2
- package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
- package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
- package/dist/scripts/lakebase/schema-migrate.cli.cjs +2 -2
- package/dist/scripts/lakebase/schema-migrate.cli.js +2 -2
- package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-claim-feature.cli.js +2 -2
- package/dist/scripts/lakebase/scm-doctor.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-doctor.cli.js +2 -2
- package/dist/scripts/lakebase/scm-merge.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-merge.cli.js +2 -2
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +2 -2
- package/dist/scripts/util/index.cjs +2 -2
- package/dist/scripts/util/index.js +2 -2
- package/package.json +1 -1
- package/templates/project/client/consort-vitest-reporter.ts +96 -0
- package/templates/project/client/package.json +1 -0
- package/templates/project/client/playwright.config.ts +18 -3
- package/templates/project/client/tests/e2e/about.spec.ts +1 -1
- package/templates/project/client/tests/e2e/consort-fixtures.ts +23 -0
- package/templates/project/client/tests/e2e/home.spec.ts +1 -1
- package/templates/project/client/tests/setup.ts +79 -4
- package/templates/project/client/tsconfig.json +1 -1
- package/templates/project/client/vite.config.ts +8 -0
- package/templates/project/client/vitest.globalSetup.ts +121 -0
- package/templates/project/common/consort-capture.ts +87 -0
- package/templates/project/common/consort-test-reporter.ts +45 -0
- package/templates/project/common/playwright.config.ts +6 -1
- package/templates/project/python/app/consort_capture.py +130 -0
- package/templates/project/python/app/database.py +6 -6
- package/templates/project/python/app/main.py +52 -0
- package/templates/project/python/app/repositories/__init__.py +8 -0
- package/templates/project/python/app/repositories/session.py +28 -0
- package/templates/project/python/pyproject.toml +1 -0
- 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
|
-
|
|
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
|