Skip to content

fix(e2e): stop silently skipping the transformation suite; authenticate as a test tenant - #1287

Merged
mitasovr merged 5 commits into
constructorfabric:mainfrom
mitasovr:fix/e2e-fail-fast-and-test-tenant
Jun 11, 2026
Merged

fix(e2e): stop silently skipping the transformation suite; authenticate as a test tenant#1287
mitasovr merged 5 commits into
constructorfabric:mainfrom
mitasovr:fix/e2e-fail-fast-and-test-tenant

Conversation

@mitasovr

@mitasovr mitasovr commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Problem

The whole bronze→API transformation suite (fixtures/test_fixtures.py) can go green while testing nothing: when cargo build of analytics-api fails, the session-scoped analytics_api fixture calls pytest.skip, which silently skips every fixture test. Two real bugs hid behind this for a while (a stale-toolchain build failure and a MariaDB-incompatible seed migration — the latter already fixed on main).

Even with the binary building, the suite still cannot pass today: analytics-api's tenant middleware sits in front of all routes (including /health) and rejects requests without a resolvable non-nil tenant (auth.rs filters out the nil UUID by design). The harness sent no X-Insight-Tenant-Id at all → /health polling got 400 → spawn timeout → (previously) silent skip.

Fix — harness only, no backend changes

1. Fail instead of skip (conftest.py)

  • If analytics-api can't build, the fixture now pytest.fails. If the binary can't build, the transformation tests can't run — the only honest result is red. Identical behaviour locally and in CI.

2. Required Rust version from the single source of truth (e2e_lib/analytics_api.py)

  • The cargo precheck hardcoded 1.92 while src/backend/Cargo.toml had moved to rust-version = "1.95.0" — exactly the drift that masked the build failure. The precheck now reads [workspace.package].rust-version from Cargo.toml; on parse failure it degrades gracefully (the cargo build itself stays the hard gate).

3. Authenticate as a non-nil test tenant (e2e_lib/config.py, analytics_api.py, metric_seed.py)

  • New constants TEST_TENANT_ID (1111…1111) and TENANT_HEADER.
  • Every harness request — /health readiness polling and fixture calls — sends X-Insight-Tenant-Id: <TEST_TENANT_ID>.
  • metric_seed.seed_test_metrics re-homes migration-seeded metrics rows from the nil tenant onto TEST_TENANT_ID (idempotent UPDATE) and seeds overrides under the same tenant, because find_enabled_metric filters the metrics table by exact tenant.
  • Fixture CSVs need no change: the ClickHouse query path does not filter by tenant yet (MVP).

Plus: stale docs refreshed (README, module docstring, seed/metrics.yaml header — all claimed "everything resolves to the nil UUID", the opposite of current backend behaviour).

Verified

Full suite green locally in the dockerized runner (./e2e.sh test): 57 framework tests + test_analytics_api_health / test_analytics_api_lists_metrics + both transformation fixtures (people_smoke, tasks_closed_smoke) passing — not skipped — for the first time in a while.

Note for backend owners

Migrations seed metrics under the nil tenant, but the tenant middleware can never resolve nil — so those rows are invisible to every real tenant in any deployment. The harness re-tenant is a test-side workaround; the underlying product gap deserves its own fix (seed under the configured default tenant, or change the lookup).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests

    • E2E tests now send a non-nil tenant header for analytics-api flows; seeded metrics are re-homed to the test tenant.
    • Test harness now hard-fails the suite if analytics-api cannot be built.
    • E2E runs and CI validation run serially (no parallel xdist) due to shared-resource races.
  • Documentation

    • Clarified local E2E setup: Docker Engine is the only host prerequisite and guidance to update the Rust toolchain.
  • Chores

    • Added three nullable numeric columns to the placeholder table schema.
  • CI

    • E2E workflow now runs on every PR (removed path filter).

Also: fix the placeholder drift that has kept E2E red on main since Jun 4

Migration 20260601000000_ai-claude-team-metrics.sql selects c.cost_cents, c.prs_with_cc_count, c.prs_total_count from silver.class_ai_dev_usage, but the bootstrap placeholder in scripts/create-bronze-placeholders.sh was never extended with those columns. On a fresh cluster / e2e rig, ClickHouse validates the CREATE VIEW SELECT and aborts all migrations with Code: 47 UNKNOWN_IDENTIFIER. That is why the E2E — Bronze to API workflow has been failing on main for the last 4 runs — independently of this PR.

Added the three columns as Nullable(UInt32) (matching all four AI connectors' staging projections). The migration references nothing else that is missing (only silver.class_ai_dev_usage + insight.people).

Without this, the suite this PR un-skips cannot go green — the failure happens at session setup, before any test runs.

For repo admins: making this gate mandatory

The paths: filter has been removed from e2e-bronze-to-api.yml, so the suite now runs on every PR to main (~4 min serial with warm cargo cache). To block merging on it, add a ruleset / branch protection on main with Require status checks to pass and select the check named Run E2E suite (plus DCO). With no path filter the check reports on every PR, so nothing will hang on "Expected".

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Require X-Insight-Tenant-Id on all analytics-api requests, re-tenant seeded metrics to a fixed test UUID, validate Rust toolchain from src/backend/Cargo.toml, change analytics-api build failures to hard fails, serialize CI E2E runs, and update docs and a placeholder schema.

Changes

E2E Tenant Enforcement and Toolchain Updates

Layer / File(s) Summary
Tenant configuration foundation
src/ingestion/tests/e2e/e2e_lib/config.py
uuid import and two new tenant constants: TENANT_HEADER (X-Insight-Tenant-Id) and TEST_TENANT_ID (fixed non-nil UUID) used throughout e2e execution.
Cargo version validation from Cargo.toml
src/ingestion/tests/e2e/e2e_lib/analytics_api.py
New _required_cargo_version helper parses rust-version from src/backend/Cargo.toml and validates cargo version during build(), raising ApiSpawnError with requirement details if toolchain is too old.
Tenant headers on all harness requests
src/ingestion/tests/e2e/e2e_lib/analytics_api.py
Module docstring and client() include tenant header on all API requests; _wait_healthy() health-check polling also includes tenant header, ensuring middleware acceptance and metric re-homing.
Metric seeding tenant migration
src/ingestion/tests/e2e/e2e_lib/metric_seed.py
seed_test_metrics refactored to re-tenant nil-UUID metrics to TEST_TENANT_ID via new _retenant_seeded_metrics helper, then upsert metric overrides under test tenant; always runs re-tenant step without early-return on missing seed file.
Build failure handling, docs, and CI serialization
src/ingestion/tests/e2e/conftest.py, src/ingestion/tests/e2e/meta/test_session_smoke.py, src/ingestion/tests/e2e/README.md, src/ingestion/tests/e2e/seed/metrics.yaml, .github/workflows/e2e-bronze-to-api.yml, src/ingestion/tests/e2e/meta/test_ci_workflow.py
Build failures hard-fail tests instead of skipping; docstrings and README reflect Cargo.toml rust-version and rustup update stable guidance; README and metrics.yaml document tenant header requirements; CI workflow and validation test changed to run tests serially (no -n).
Bronze placeholder schema update
src/ingestion/scripts/create-bronze-placeholders.sh
Add nullable cost_cents, prs_with_cc_count, and prs_total_count UInt32 columns to silver.class_ai_dev_usage placeholder CREATE TABLE statement.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 I hop through tests with headers bright,
A tenant UUID tucked in tight.
Cargo checks and metrics roam,
Re-homed rows find a cozy home.
E2E sleeps soundly through the night.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.42% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: fixing silent test skipping and adding tenant authentication, which are the core objectives of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

insight-ci and others added 3 commits June 11, 2026 13:29
…Rust from Cargo.toml

A `cargo build` failure made the analytics_api fixture `pytest.skip`, which
silently skipped EVERY bronze→API transformation test — the suite stayed green
while testing nothing. That is exactly how the runner's stale rustc 1.92 hid the
fact that src/backend now requires 1.95.

- conftest.py: the analytics_api fixture now `pytest.fail`s instead of skipping
  when the binary can't build. Identical behaviour locally and in CI — if the
  binary can't build, the transformation tests cannot run, so the only honest
  result is red.
- analytics_api.py: drop the hardcoded MIN_CARGO_MINOR (pinned at 92 while the
  crates moved to 95). The required version is now read from the single source
  of truth — `[workspace.package].rust-version` in src/backend/Cargo.toml — so
  the precheck can't drift behind the real requirement again.
- Dockerfile.runner: bump RUST_VERSION 1.92.0 -> 1.95.0 so the runner image can
  actually build the backend and run the transformation fixtures.
- Refresh stale "1.92 / edition2024" mentions in the smoke-test docstring and
  README to point at Cargo.toml's rust-version.

Not changed: zero-fixtures still skips (nothing to run), and a fixture without
`dbt_selector` still passes without running dbt (view-only metrics, by design).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: insight-ci <ci@cyberfabric.local>
…sion log

analytics-api's tenant middleware rejects requests without a resolvable
non-nil tenant (auth.rs rejects the nil UUID by design), so after the binary
finally built (rustc 1.95), /health polling got 400 and every fixture errored.
Tests must work on top of the system as-is, so the fix is harness-only:

- config.py: introduce TEST_TENANT_ID (1111…) + TENANT_HEADER constants.
- analytics_api.py: send X-Insight-Tenant-Id on every request — both the
  /health readiness polling and fixture calls. Module docstring updated
  (it claimed "all requests resolve to nil UUID", the opposite of reality).
- metric_seed.py: re-home migration-seeded metrics from the nil tenant onto
  TEST_TENANT_ID (find_enabled_metric filters the metrics table by exact
  tenant) and seed overrides under the same tenant. Idempotent UPDATE.
- Fix NameError in build(): `version` was only bound when Cargo.toml parsing
  succeeded, but the log line below used it unconditionally — the graceful
  fallback path crashed instead of proceeding to the build.
- Refresh stale comments (README Rust version, seed/metrics.yaml header).

Note: migrations seeding `metrics` under the nil tenant means those rows are
invisible to every real tenant in production too — that product gap needs its
own backend fix; this commit only unblocks the harness.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: insight-ci <ci@cyberfabric.local>
…ge placeholder

Migration 20260601000000_ai-claude-team-metrics.sql creates a gold view
selecting c.cost_cents, c.prs_with_cc_count and c.prs_total_count from
silver.class_ai_dev_usage, but the bootstrap placeholder for that table was
not extended when those columns were added to the dbt model. On a fresh
cluster (and in the e2e rig, which applies placeholders + migrations from
scratch) ClickHouse validates the CREATE VIEW SELECT and fails with
`Code: 47 UNKNOWN_IDENTIFIER: 'c.cost_cents'`, aborting all migrations —
this is why the E2E suite on main has been red since 2026-06-04.

Add the three columns as Nullable(UInt32), matching the connector staging
models (claude_team__ai_dev_usage et al.). The placeholder is dropped and
replaced by dbt with the real schema on the first run, so minimum-viable
parity with the migrations is all that is required.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: insight-ci <ci@cyberfabric.local>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ingestion/tests/e2e/meta/test_ci_workflow.py`:
- Around line 71-73: The current assertion only checks for "-n " and trailing
"-n" and misses attached forms like "-nauto"; update the guard to split the
command string (use shlex.split on test_step["run"]) and assert that no token
startswith("-n") to reject any -n variant. Replace the existing assertion that
references test_step["run"] with a check using the tokenized command and ensure
the failure message remains descriptive (CI must run the e2e suite serially
until the rig is xdist-safe).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 482532d2-4fa8-43e8-ac6d-b9ea9f1e68d7

📥 Commits

Reviewing files that changed from the base of the PR and between 3f2ce3beeea041f5e56a8bd09dbe0ce79e13f4b6 and 283a4eaf40595710c1786e714d5a1231972e3b73.

📒 Files selected for processing (3)
  • .github/workflows/e2e-bronze-to-api.yml
  • src/ingestion/tests/e2e/README.md
  • src/ingestion/tests/e2e/meta/test_ci_workflow.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/ingestion/tests/e2e/README.md

Comment on lines +71 to +73
assert "-n " not in test_step["run"] and not test_step["run"].rstrip().endswith("-n"), (
"CI must run the e2e suite serially until the rig is xdist-safe"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Serialization guard is bypassable with attached -n form.

This assertion only blocks "-n " and terminal "-n", so an attached form like -nauto would pass while still enabling parallelism. Tighten the check to reject any -n token variant.

Suggested fix
-    assert "-n " not in test_step["run"] and not test_step["run"].rstrip().endswith("-n"), (
+    run_cmd = test_step["run"]
+    assert " -n " not in f" {run_cmd} "
+    assert " -nauto" not in f" {run_cmd} "
+    assert "\n-n " not in run_cmd
+    assert not run_cmd.rstrip().endswith("-n"), (
         "CI must run the e2e suite serially until the rig is xdist-safe"
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert "-n " not in test_step["run"] and not test_step["run"].rstrip().endswith("-n"), (
"CI must run the e2e suite serially until the rig is xdist-safe"
)
run_cmd = test_step["run"]
assert " -n " not in f" {run_cmd} "
assert " -nauto" not in f" {run_cmd} "
assert "\n-n " not in run_cmd
assert not run_cmd.rstrip().endswith("-n"), (
"CI must run the e2e suite serially until the rig is xdist-safe"
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ingestion/tests/e2e/meta/test_ci_workflow.py` around lines 71 - 73, The
current assertion only checks for "-n " and trailing "-n" and misses attached
forms like "-nauto"; update the guard to split the command string (use
shlex.split on test_step["run"]) and assert that no token startswith("-n") to
reject any -n variant. Replace the existing assertion that references
test_step["run"] with a check using the tokenized command and ensure the failure
message remains descriptive (CI must run the e2e suite serially until the rig is
xdist-safe).

…-safe

With the suite finally reaching the analytics-api spawn (earlier commits in
this PR), `-n auto` exposed four cross-worker races on the shared data plane:

- each xdist worker spawns its own analytics-api, and concurrent SeaORM
  migrations on the shared MariaDB collide (1061 Duplicate key
  'uq_metric_catalog_metric_key');
- only the primary worker applies ClickHouse migrations and the others do
  not wait, so tests run against a half-migrated database ('identity' not
  yet created);
- meta/test_ch_seeder.py's module fixture DROP+CREATEs the same
  bronze_e2e_test.events from several workers (TABLE_ALREADY_EXISTS);
- DbtRunner.cleanup() rmtree's the shared target/dbt dir, so whichever
  worker finishes first deletes the profiles out from under the rest.

conftest.py has documented "do NOT support xdist yet" since the scaffold
landed; CI invoking `-n auto` contradicted that. Drop xdist from the CI
invocation (wall-time impact is negligible — the image and cargo builds
dominate the job), flip the meta test that previously REQUIRED `-n auto`
into one that forbids it until worker isolation lands, and mark the README
line accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: insight-ci <ci@cyberfabric.local>
@mitasovr
mitasovr force-pushed the fix/e2e-fail-fast-and-test-tenant branch from 283a4ea to b64c810 Compare June 11, 2026 10:29
The E2E check is about to become a required status check on main. A
path-filtered required check never reports on PRs outside the filter, so
those PRs hang on "Expected" forever. Run the suite on every PR instead:
the gate stays uniform and the serial suite costs ~4 minutes with a warm
cargo cache.

Flip the meta test that previously required specific entries in the paths
filter into one that forbids any path filtering on pull_request.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: insight-ci <ci@cyberfabric.local>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/ingestion/tests/e2e/meta/test_ci_workflow.py (1)

72-74: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Incomplete check allows -nauto and other attached forms to bypass the serialization requirement.

The current assertion only checks for -n (with trailing space) and terminal -n, so attached forms like -nauto or -n2 would pass. Use tokenization to check if any token starts with -n.

🔧 Recommended fix using simpler split approach
-    assert "-n " not in test_step["run"] and not test_step["run"].rstrip().endswith("-n"), (
-        "CI must run the e2e suite serially until the rig is xdist-safe"
-    )
+    tokens = test_step["run"].split()
+    assert not any(tok.startswith("-n") for tok in tokens), (
+        "CI must run the e2e suite serially until the rig is xdist-safe"
+    )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ingestion/tests/e2e/meta/test_ci_workflow.py` around lines 72 - 74, The
assertion in test_ci_workflow.py uses a fragile string check on test_step["run"]
that misses attached forms like "-nauto" or "-n2"; replace the check with a
whitespace-tokenized inspection (split the run string into tokens and assert
that no token starts with "-n") so any "-n" flag in attached or standalone form
is detected (reference test_step["run"] in the failing assert and update the
assertion message accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/ingestion/tests/e2e/meta/test_ci_workflow.py`:
- Around line 72-74: The assertion in test_ci_workflow.py uses a fragile string
check on test_step["run"] that misses attached forms like "-nauto" or "-n2";
replace the check with a whitespace-tokenized inspection (split the run string
into tokens and assert that no token starts with "-n") so any "-n" flag in
attached or standalone form is detected (reference test_step["run"] in the
failing assert and update the assertion message accordingly).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 51c20cfe-b1e4-4d1f-8dec-7da9620135d7

📥 Commits

Reviewing files that changed from the base of the PR and between b64c810 and 3ca9d0f.

📒 Files selected for processing (2)
  • .github/workflows/e2e-bronze-to-api.yml
  • src/ingestion/tests/e2e/meta/test_ci_workflow.py

@mitasovr
mitasovr merged commit 09a58ff into constructorfabric:main Jun 11, 2026
12 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant