diff --git a/.claude/skills/metric-e2e-test/SKILL.md b/.claude/skills/metric-test/SKILL.md similarity index 92% rename from .claude/skills/metric-e2e-test/SKILL.md rename to .claude/skills/metric-test/SKILL.md index 11cade452..52a792d03 100644 --- a/.claude/skills/metric-e2e-test/SKILL.md +++ b/.claude/skills/metric-test/SKILL.md @@ -1,12 +1,12 @@ --- -name: metric-e2e-test -description: "Author and validate declarative YAML e2e tests for analytics metrics (src/ingestion/tests/e2e/specs/*.test.yaml). Use when asked to write/scaffold/validate an e2e test for a metric, seed bronze data for a test, add a fixture for a dashboard metric, or check a *.test.yaml. Covers schemas/, templates/, $ref+sibling composition, bronze records with duplicates, the batch endpoint POST /v1/metrics/queries, and expect rules (in / mongo-style find / equal subset / CEL assert)." +name: metric-test +description: "Author and validate declarative YAML tests for analytics metrics (src/ingestion/tests/e2e/metrics/*.test.yaml). Use when asked to write/scaffold/validate a test for a metric, seed bronze data for a test, add a fixture for a dashboard metric, or check a *.test.yaml. Covers schemas/, templates/, $ref+sibling composition, bronze records with duplicates, the batch endpoint POST /v1/metrics/queries, and expect rules (in / mongo-style find / equal subset / CEL assert)." disable-model-invocation: false user-invocable: true allowed-tools: Bash, Read, Write, Edit, Glob, Grep --- -# Author a metric e2e test (declarative YAML) +# Author a metric test (declarative YAML) This skill writes and validates `*.test.yaml` fixtures that drive the full `bronze → dbt silver → gold view → analytics-api` path and assert the result. @@ -22,15 +22,15 @@ no need to load them every time: ## Commands -- `/metric-e2e-test create --metric --tables ` — scaffold a new `.test.yaml` (+ any missing `schemas/` and `templates/`). -- `/metric-e2e-test validate ` — resolve refs, schema-validate records, lint `cases`/`expect` without running ClickHouse. +- `/metric-test create --metric --tables ` — scaffold a new `.test.yaml` (+ any missing `schemas/` and `templates/`). +- `/metric-test validate ` — resolve refs, schema-validate records, lint `cases`/`expect` without running ClickHouse. -(Plain prose like "write an e2e test for the emails-sent metric" triggers the same flow.) +(Plain prose like "write a test for the emails-sent metric" triggers the same flow.) ## File layout ``` -src/ingestion/tests/e2e/specs/ +src/ingestion/tests/e2e/metrics/ schemas/..yaml # one JSON schema per bronze table (all real columns) templates/.yaml # reusable records (people, m365_email, …) .test.yaml # ONE metric per file (discovered by the *.test.yaml suffix) @@ -157,7 +157,7 @@ disagree with the analytics team (org_unit) — irrelevant to these assertions.) ### `assert` (CEL) bindings -Assembled in `e2e_lib/expect_engine.py::evaluate_case` (the `bindings` dict), +Assembled in `lib/expect_engine.py::evaluate_case` (the `bindings` dict), converted to CEL in `_eval_cel`: | Binding | Value | Present when | @@ -178,7 +178,7 @@ For exact / `null`, use `equal` (Python `==`), not `assert`. CEL macros availabl `metric_date` bounds are **inclusive on both ends**: `metric_date ge '' and metric_date le ''` includes rows ON `` and ON ``. When a metric is date-windowed, prove the bounds -with a dedicated spec (see `specs/collab_emails_read.test.yaml`): +with a dedicated spec (see `metrics/collab_emails_read.test.yaml`): - **Boundary-value (BVA):** seed rows one-day-BEFORE the lower bound (must be excluded), AT the lower bound (included), AT the upper bound (included), and one-day-AFTER the upper @@ -274,18 +274,18 @@ with a dedicated spec (see `specs/collab_emails_read.test.yaml`): ```bash cd src/ingestion/tests/e2e -ls specs/*.test.yaml # list existing tests -./e2e.sh test # run all tests (specs/ + meta/) +ls metrics/*.test.yaml # list existing tests +./e2e.sh test # run all tests (metrics/ + meta/) ./e2e.sh test -k # run one test by name ./e2e.sh test -k -v # verbose (per-step log) ./e2e.sh down # tear down the e2e compose stack + volumes (full reset) ``` -`` is the file stem (e.g. `collab_emails_sent` for `specs/collab_emails_sent.test.yaml`). +`` is the file stem (e.g. `collab_emails_sent` for `metrics/collab_emails_sent.test.yaml`). Warm re-runs are fine. Isolation is **per-test**: each test first `TRUNCATE`s only the tables the PRIOR test recorded in a per-test ledger (`CHSeeder.TouchedLedger`; -`specs/test_fixtures.py`, `e2e_lib/ch_seeder.py`), then seeds its own. The rig auto-records +`metrics/test_fixtures.py`, `lib/ch_seeder.py`), then seeds its own. The rig auto-records the built **staging** and **silver** models too (not just bronze) — staging especially, because silver reads staging via the `union_by_tag` macro, so an un-truncated prior staging row would contaminate the silver rebuild. On TOP of that, a one-time session-start truncate @@ -294,7 +294,7 @@ row would contaminate the silver rebuild. On TOP of that, a one-time session-sta makes WARM re-runs deterministic (CI starts fresh). `./e2e.sh down` is the e2e compose teardown (not a deploy), for when you want a fully clean ClickHouse. -To create a new test, use `/metric-e2e-test create` or hand-author `specs/.test.yaml` +To create a new test, use `/metric-test create` or hand-author `metrics/.test.yaml` as above. There is no `./e2e.sh new` — the old CSV-rig scaffolder was removed when the declarative `*.test.yaml` rig replaced the CSV rig. @@ -321,7 +321,7 @@ connector that isn't there yet: reliably advance, so cargo relinked a stale object and the binary silently lacked new SeaORM migrations (symptoms: `query_ref`/catalog changes have no effect, a `find` matches 0 rows, `size(items)` off by your new key). FIXED in - `e2e_lib/analytics_api.py::build` — it now `touch`es the analytics-api crate + `lib/analytics_api.py::build` — it now `touch`es the analytics-api crate sources before `cargo build`, forcing a recompile every run (~1-2 min, only that crate). So a plain `./e2e.sh test` picks up new migrations now; you should NOT need `down -v` for this. If you still suspect a stale binary, confirm by diff --git a/.github/workflows/e2e-bronze-to-api.yml b/.github/workflows/e2e-bronze-to-api.yml index 08dfeec68..e50aeb10c 100644 --- a/.github/workflows/e2e-bronze-to-api.yml +++ b/.github/workflows/e2e-bronze-to-api.yml @@ -1,7 +1,7 @@ name: E2E — Bronze to API # End-to-end transformation tests covering bronze → dbt → ClickHouse -# migration views → analytics-api HTTP response → CSV assert. +# migration views → analytics-api HTTP response → expect rules. # # Everything (Python, Rust, dbt, pytest) runs inside the runner image # defined at src/ingestion/tests/e2e/compose/Dockerfile.runner. The same @@ -28,7 +28,7 @@ on: env: # NB: forbids developers from running `pytest --update-snapshots` in CI. - # The guard lives in src/ingestion/tests/e2e/fixtures/test_fixtures.py. + # The guard lives in src/ingestion/tests/e2e/metrics/test_fixtures.py. CI: "true" jobs: @@ -55,14 +55,10 @@ jobs: - name: Build runner image run: ./e2e.sh build - # ─── Pre-pull data plane images in parallel with whatever else ────── - - name: Pre-pull ClickHouse + MariaDB - run: | - docker pull clickhouse/clickhouse-server:24.8.4.13 & - docker pull mariadb:11.4.4 & - wait - # ─── Run the suite inside the runner ──────────────────────────────── + # `./e2e.sh test` (docker compose run runner) starts the clickhouse + + # mariadb `depends_on` services — compose pulls their images on demand + # and waits for `service_healthy` — so no explicit image pre-pull is needed. # Serial on purpose: the session rig is not xdist-safe yet (see # conftest.py — per-worker analytics-api spawns race SeaORM migrations # in the shared MariaDB, non-primary workers don't wait for ClickHouse diff --git a/cypilot/.core/skills/connector/workflows/create.md b/cypilot/.core/skills/connector/workflows/create.md index 4a4a57399..230b119a3 100644 --- a/cypilot/.core/skills/connector/workflows/create.md +++ b/cypilot/.core/skills/connector/workflows/create.md @@ -491,7 +491,7 @@ metric ids for a genuinely new section): `source_tags: [""]`) + a product-default `metric_threshold`. Model it on `m20260620_000002_seed_wiki_catalog.rs`. Register in `mod.rs`. -Then prove the whole chain with a `/metric-e2e-test` fixture (see that skill) — +Then prove the whole chain with a `/metric-test` fixture (see that skill) — seed bronze, query the bullet metric, assert your `metric_key`'s value. Adding a key to a shared section raises its `size(items)`, so bump any sibling test that asserts the old count. diff --git a/cypilot/.core/skills/connector/workflows/validate.md b/cypilot/.core/skills/connector/workflows/validate.md index 46c063744..5e1b4962a 100644 --- a/cypilot/.core/skills/connector/workflows/validate.md +++ b/cypilot/.core/skills/connector/workflows/validate.md @@ -237,7 +237,7 @@ so and skip this section. - [ ] The gold `insight.
_bullet_rows` view has a branch `FROM silver.class_ WHERE data_source = 'insight_'` emitting the connector's `metric_key`(s). (`data_source` literal == what the silver model SELECTs.) - [ ] EVERY bullet `query_ref` that should show the key was re-set in a NEW append-only SeaORM migration — the key appears in both the `sumIf(... metric_key='') AS _v` list AND the `ARRAY JOIN [('', _v), …]` unpivot. A section typically has several copies (IC `…0012`-style, Team, member-values `…0041`, dept-dist `…0045`); each must be updated for the surface it backs. Base the new SQL on the LATEST migration that set that id (grep the metric hex id), and register the migration in `migration/mod.rs`. - [ ] A `metric_catalog` row + product-default `metric_threshold` for `
_bullet_rows.` exists (new append-only migration, `source_tags: [""]`, registered in `mod.rs`). -- [ ] An e2e fixture (`/metric-e2e-test`) seeds bronze and asserts the metric_key's value end-to-end; any sibling test asserting the section's `size(items)` was bumped for the new key. +- [ ] An e2e fixture (`/metric-test`) seeds bronze and asserts the metric_key's value end-to-end; any sibling test asserting the section's `size(items)` was bumped for the new key. ### Credentials Template - [ ] `credentials.yaml.example` lists all required fields diff --git a/docs/domain/bronze-to-api-e2e/specs/DECOMPOSITION.md b/docs/domain/bronze-to-api-e2e/specs/DECOMPOSITION.md index 81bf43f5c..92f275f9c 100644 --- a/docs/domain/bronze-to-api-e2e/specs/DECOMPOSITION.md +++ b/docs/domain/bronze-to-api-e2e/specs/DECOMPOSITION.md @@ -55,7 +55,7 @@ The DESIGN is decomposed into 8 features. The order is deliberate: a foundation - **Depends On**: None - **Scope**: - - Directory layout: `src/ingestion/tests/e2e/{compose,fixtures,e2e_lib,meta}` + - Directory layout: `src/ingestion/tests/e2e/{compose,fixtures,lib,meta}` - `compose/docker-compose.yml` with ClickHouse 24.x + MariaDB 11.x pinned to production parity, ports on `127.0.0.1` only, randomized credentials per run - `conftest.py` with `pytest_sessionstart` hook orchestrating compose-up + healthcheck wait - `migration-applier` logic (small enough to live inline, not as a separate feature): `clickhouse-client --multiquery` over `src/ingestion/scripts/migrations/*.sql` in lexical order @@ -137,7 +137,7 @@ The DESIGN is decomposed into 8 features. The order is deliberate: a foundation - **Domain Model Entities**: `Fixture`, `SpecYaml` -- **API**: (internal Python module — `e2e_lib.fixture_loader.load(path) -> Fixture`) +- **API**: (internal Python module — `lib.fixture_loader.load(path) -> Fixture`) - **Data**: (read-only filesystem) diff --git a/docs/domain/bronze-to-api-e2e/specs/DESIGN.md b/docs/domain/bronze-to-api-e2e/specs/DESIGN.md index ab46b4c16..81827179c 100644 --- a/docs/domain/bronze-to-api-e2e/specs/DESIGN.md +++ b/docs/domain/bronze-to-api-e2e/specs/DESIGN.md @@ -136,13 +136,13 @@ The test file is what the test asserts on. There is no "regenerate from current - [ ] `p1` - **ID**: `cpt-bronze-to-api-e2e-principle-record-composition` -A bronze row (or a template) is a field map that may carry a `$ref: "#/"` to inherit from another record; sibling keys override the base (closest wins). Reusable people/source records live in `specs/templates/*.yaml`. A test spells out only the fields it exercises; everything else is inherited. This keeps a test small while the seeded row stays complete. +A bronze row (or a template) is a field map that may carry a `$ref: "#/"` to inherit from another record; sibling keys override the base (closest wins). Reusable people/source records live in `metrics/templates/*.yaml`. A test spells out only the fields it exercises; everything else is inherited. This keeps a test small while the seeded row stays complete. #### The table schema is the source of truth for a row's shape - [ ] `p1` - **ID**: `cpt-bronze-to-api-e2e-principle-schema-is-truth` -Per-table JSON schemas live in `specs/schemas/.
.yaml` (the file stem is the full bronze table name, e.g. `bronze_m365.email_activity.yaml`) and are resolved by that name (the `bronze` key IS the table). After `$ref` resolution a row is padded with every missing schema column as `null` and validated; `additionalProperties:false` catches a misspelled column. Base templates carry the full column set (including the non-nullable `_airbyte_*` CDK columns, which transforms such as `insight.people`'s `argMax(..., _airbyte_extracted_at)` depend on). +Per-table JSON schemas live in `metrics/schemas/.
.yaml` (the file stem is the full bronze table name, e.g. `bronze_m365.email_activity.yaml`) and are resolved by that name (the `bronze` key IS the table). After `$ref` resolution a row is padded with every missing schema column as `null` and validated; `additionalProperties:false` catches a misspelled column. Base templates carry the full column set (including the non-nullable `_airbyte_*` CDK columns, which transforms such as `insight.people`'s `argMax(..., _airbyte_extracted_at)` depend on). ### 2.2 Constraints @@ -322,7 +322,7 @@ A resolved bronze row must be a complete, well-typed table row. Centralizing sch ##### Responsibility scope -Loads `specs/schemas/.
.yaml`; pads a resolved record with missing schema properties as `null`; validates against the JSON schema (`additionalProperties:false`). Implements the post-step of `cpt-bronze-to-api-e2e-algo-yaml-resolve-refs`. +Loads `metrics/schemas/.
.yaml`; pads a resolved record with missing schema properties as `null`; validates against the JSON schema (`additionalProperties:false`). Implements the post-step of `cpt-bronze-to-api-e2e-algo-yaml-resolve-refs`. ##### Responsibility boundaries @@ -345,7 +345,7 @@ Replaces `csv-asserter`. Dashboard metrics return many rows over a batch of quer Implements `cpt-bronze-to-api-e2e-algo-yaml-eval-expect`: selects a batch result by `id`; filters `result.items` to exactly one row by exact field equality; compares a subset of fields (`equal`, explicit `null`) or evaluates a CEL boolean (`assert`). Renders a precise failing-rule report. -The CEL `assert` bindings are assembled in `e2e_lib/expect_engine.py::evaluate_case` (the `bindings` dict) and converted to CEL in `_eval_cel`: +The CEL `assert` bindings are assembled in `lib/expect_engine.py::evaluate_case` (the `bindings` dict) and converted to CEL in `_eval_cel`: | Binding | Value | Present when | |---|---|---| diff --git a/docs/domain/bronze-to-api-e2e/specs/feature-csv-rig/FEATURE.md b/docs/domain/bronze-to-api-e2e/specs/feature-csv-rig/FEATURE.md index bcaa2518c..211363301 100644 --- a/docs/domain/bronze-to-api-e2e/specs/feature-csv-rig/FEATURE.md +++ b/docs/domain/bronze-to-api-e2e/specs/feature-csv-rig/FEATURE.md @@ -201,7 +201,7 @@ The system **MUST** discover every subfolder of `src/ingestion/tests/e2e/fixture **Touches**: -- Files: `src/ingestion/tests/e2e/e2e_lib/fixture_loader.py`, `src/ingestion/tests/e2e/conftest.py` +- Files: `src/ingestion/tests/e2e/lib/fixture_loader.py`, `src/ingestion/tests/e2e/conftest.py` - Components: `cpt-bronze-to-api-e2e-component-fixture-loader` ### Typed Bronze INSERT @@ -217,7 +217,7 @@ The system **MUST** INSERT each CSV row into the bronze table with column types **Touches**: - API: ClickHouse HTTP 8123 / native 9000 -- Files: `src/ingestion/tests/e2e/e2e_lib/ch_seeder.py` +- Files: `src/ingestion/tests/e2e/lib/ch_seeder.py` - Components: `cpt-bronze-to-api-e2e-component-ch-seeder` ### Scoped Dbt Build @@ -234,7 +234,7 @@ The system **MUST** invoke `dbt build` with the selector from `spec.yaml` (e.g. **Touches**: -- Files: `src/ingestion/tests/e2e/e2e_lib/dbt_runner.py` +- Files: `src/ingestion/tests/e2e/lib/dbt_runner.py` - Components: `cpt-bronze-to-api-e2e-component-dbt-runner` ### API Roundtrip @@ -252,7 +252,7 @@ The system **MUST** POST the request derived from `spec.yaml` to the analytics-a **Touches**: - API: `POST /v1/metrics/{id}/query` -- Files: `src/ingestion/tests/e2e/e2e_lib/api_client.py` +- Files: `src/ingestion/tests/e2e/lib/api_client.py` - Components: `cpt-bronze-to-api-e2e-component-api-client` ### Cell-Precise CSV Diff @@ -267,7 +267,7 @@ On failure the system **MUST** render the first 20 mismatched cells as `(key, co **Touches**: -- Files: `src/ingestion/tests/e2e/e2e_lib/csv_asserter.py` +- Files: `src/ingestion/tests/e2e/lib/csv_asserter.py` - Components: `cpt-bronze-to-api-e2e-component-csv-asserter` ### Reference Test against insight.people diff --git a/docs/domain/bronze-to-api-e2e/specs/feature-yaml-rig/FEATURE.md b/docs/domain/bronze-to-api-e2e/specs/feature-yaml-rig/FEATURE.md index ca9e705d8..c424a9962 100644 --- a/docs/domain/bronze-to-api-e2e/specs/feature-yaml-rig/FEATURE.md +++ b/docs/domain/bronze-to-api-e2e/specs/feature-yaml-rig/FEATURE.md @@ -45,7 +45,7 @@ Replace the folder-based CSV rig (`feature-csv-rig`: `bronze/*.csv` + `spec.yaml The readability goals are: - **Small surface, full data on demand.** A bronze row is written as a `$ref` to a reusable record template plus only the fields the test actually exercises. After resolution the row is padded to every column of the table schema, so the seeded record is complete without the author spelling out 29 columns. -- **Reusable building blocks in separate files.** Per-table JSON schemas live in `specs/schemas/.
.yaml` (e.g. `bronze_m365.email_activity.yaml`); record templates live in `specs/templates/*.yaml`. A test references them with the standard `$ref: "#/"` form. +- **Reusable building blocks in separate files.** Per-table JSON schemas live in `metrics/schemas/.
.yaml` (e.g. `bronze_m365.email_activity.yaml`); record templates live in `metrics/templates/*.yaml`. A test references them with the standard `$ref: "#/"` form. - **Assert what matters, not the whole body.** `expect` is a list of rules; each selects a row with an exact-equality `find` and either compares a subset of fields (`equal`) or evaluates a CEL boolean (`assert`). Anything richer than equality lives in the CEL `assert`, so there is no second selector language. This feature supersedes `feature-csv-rig` end to end: the `spec.yaml`/CSV loader, the `csv-asserter`, and the per-folder fixture layout are removed. The bronze→silver→gold→API path itself is unchanged — only the authoring format and the assertion engine change. @@ -113,7 +113,7 @@ The CSV rig proved the path but was verbose (one CSV per table, full column rows **Steps**: -1. [ ] - `p1` - Author creates `src/ingestion/tests/e2e/specs/.test.yaml` - `inst-yaml-author-file` +1. [ ] - `p1` - Author creates `src/ingestion/tests/e2e/metrics/.test.yaml` - `inst-yaml-author-file` 2. [ ] - `p1` - Author writes `bronze:` keyed by table name; each row is a `$ref` to a template plus the overridden fields under test (duplicates allowed) - `inst-yaml-author-bronze` 3. [ ] - `p1` - Author writes `cases:` with `request.body.queries[]` (batch) and an `expect` list of rules - `inst-yaml-author-cases` 4. [ ] - `p1` - Author runs `pytest -k ` - `inst-yaml-author-run` @@ -189,7 +189,7 @@ The CSV rig proved the path but was verbose (one CSV per table, full column rows 6. [ ] - `p1` - **RETURN** Pass | Fail(field/expr, expected, actual) - `inst-eval-return` **CEL `assert` bindings** — the variables a `assert` expression may reference. -Assembled in `e2e_lib/expect_engine.py::evaluate_case` (the `bindings` dict) and +Assembled in `lib/expect_engine.py::evaluate_case` (the `bindings` dict) and converted to CEL in `_eval_cel`: | Binding | Value | Present when | @@ -234,11 +234,11 @@ are integers (compare with integer literals). Exact / `null` checks belong in - [ ] `p1` - **ID**: `cpt-bronze-to-api-e2e-dod-yaml-discovery` -The system **MUST** discover tests as `src/ingestion/tests/e2e/specs/**/*.test.yaml`. Files under `specs/schemas/` and `specs/templates/` (and any `*.yaml` without the `.test.yaml` suffix) **MUST NOT** be collected as tests. A malformed `.test.yaml` **MUST** fail the collection of only that test. +The system **MUST** discover tests as `src/ingestion/tests/e2e/metrics/**/*.test.yaml`. Files under `metrics/schemas/` and `metrics/templates/` (and any `*.yaml` without the `.test.yaml` suffix) **MUST NOT** be collected as tests. A malformed `.test.yaml` **MUST** fail the collection of only that test. **Implements**: `cpt-bronze-to-api-e2e-flow-yaml-author-and-run` -**Touches**: `src/ingestion/tests/e2e/conftest.py`, `src/ingestion/tests/e2e/e2e_lib/fixture_loader.py`; Components: `cpt-bronze-to-api-e2e-component-fixture-loader` +**Touches**: `src/ingestion/tests/e2e/conftest.py`, `src/ingestion/tests/e2e/lib/fixture_loader.py`; Components: `cpt-bronze-to-api-e2e-component-fixture-loader` ### Reference Resolution @@ -261,17 +261,17 @@ The resolver (`cpt-bronze-to-api-e2e-algo-yaml-resolve-refs`) **MUST** satisfy, **Implements**: `cpt-bronze-to-api-e2e-algo-yaml-resolve-refs` -**Touches**: `src/ingestion/tests/e2e/e2e_lib/ref_resolver.py`, `src/ingestion/tests/e2e/meta/test_ref_resolver.py`; Components: `cpt-bronze-to-api-e2e-component-ref-resolver` +**Touches**: `src/ingestion/tests/e2e/lib/ref_resolver.py`, `src/ingestion/tests/e2e/meta/test_ref_resolver.py`; Components: `cpt-bronze-to-api-e2e-component-ref-resolver` ### Schema Resolution, Padding and Validation - [ ] `p1` - **ID**: `cpt-bronze-to-api-e2e-dod-yaml-schema-resolution` -For each `bronze.
`, the system **MUST** resolve the schema by table name from `specs/schemas/.
.yaml`, pad every resolved record with the schema's missing properties as `null`, and validate the record against the JSON schema. `additionalProperties:false` **MUST** reject an unknown field name. The `_airbyte_*` columns **MUST** be carried from the record (not auto-stamped), since transforms depend on them. +For each `bronze.
`, the system **MUST** resolve the schema by table name from `metrics/schemas/.
.yaml`, pad every resolved record with the schema's missing properties as `null`, and validate the record against the JSON schema. `additionalProperties:false` **MUST** reject an unknown field name. The `_airbyte_*` columns **MUST** be carried from the record (not auto-stamped), since transforms depend on them. **Implements**: `cpt-bronze-to-api-e2e-algo-yaml-resolve-refs` -**Touches**: `src/ingestion/tests/e2e/e2e_lib/schema_validator.py`; Components: `cpt-bronze-to-api-e2e-component-schema-validator` +**Touches**: `src/ingestion/tests/e2e/lib/schema_validator.py`; Components: `cpt-bronze-to-api-e2e-component-schema-validator` ### Typed Bronze Seed from Records @@ -281,7 +281,7 @@ The system **MUST** INSERT each resolved record into its bronze table with colum **Implements**: `cpt-bronze-to-api-e2e-algo-yaml-execute-test` -**Touches**: `src/ingestion/tests/e2e/e2e_lib/ch_seeder.py`; Components: `cpt-bronze-to-api-e2e-component-ch-seeder` +**Touches**: `src/ingestion/tests/e2e/lib/ch_seeder.py`; Components: `cpt-bronze-to-api-e2e-component-ch-seeder` ### Batch API Roundtrip @@ -293,7 +293,7 @@ The system **MUST** POST `case.request.body` (`{ queries: [...] }`) to `POST /v1 **Constraints**: `cpt-bronze-to-api-e2e-constraint-loopback-only` -**Touches**: `src/ingestion/tests/e2e/e2e_lib/api_client.py`; API: `POST /v1/metrics/queries`; Components: `cpt-bronze-to-api-e2e-component-api-client` +**Touches**: `src/ingestion/tests/e2e/lib/api_client.py`; API: `POST /v1/metrics/queries`; Components: `cpt-bronze-to-api-e2e-component-api-client` ### Expect Engine @@ -303,17 +303,17 @@ The system **MUST** evaluate `expect` rules per `cpt-bronze-to-api-e2e-algo-yaml **Implements**: `cpt-bronze-to-api-e2e-algo-yaml-eval-expect` -**Touches**: `src/ingestion/tests/e2e/e2e_lib/expect_engine.py`, `src/ingestion/tests/e2e/meta/test_expect_engine.py`; Components: `cpt-bronze-to-api-e2e-component-expect-engine` +**Touches**: `src/ingestion/tests/e2e/lib/expect_engine.py`, `src/ingestion/tests/e2e/meta/test_expect_engine.py`; Components: `cpt-bronze-to-api-e2e-component-expect-engine` ### Reference Test (collab_emails_sent) - [ ] `p1` - **ID**: `cpt-bronze-to-api-e2e-dod-yaml-reference-test` -The system **MUST** ship one working test `specs/collab_emails_sent.test.yaml` plus shared `specs/schemas/{bronze_bamboohr.employees,bronze_m365.email_activity}.yaml` and `specs/templates/{people,m365_email}.yaml`. It **MUST** exercise the IC Bullet Collaboration metric (`…0012`) over `bronze_m365.email_activity` + `bronze_bamboohr.employees`, assert the team median of `m365_emails_sent` (`[40,20,10] → median 20`, value 40 for the requested person, range `[10,40]`), and prove the Airbyte re-sync duplicate of `alice` does not inflate her sum. +The system **MUST** ship one working test `metrics/collab_emails_sent.test.yaml` plus shared `metrics/schemas/{bronze_bamboohr.employees,bronze_m365.email_activity}.yaml` and `metrics/templates/{people,m365_email}.yaml`. It **MUST** exercise the IC Bullet Collaboration metric (`…0012`) over `bronze_m365.email_activity` + `bronze_bamboohr.employees`, assert the team median of `m365_emails_sent` (`[40,20,10] → median 20`, value 40 for the requested person, range `[10,40]`), and prove the Airbyte re-sync duplicate of `alice` does not inflate her sum. **Implements**: `cpt-bronze-to-api-e2e-flow-yaml-author-and-run` -**Touches**: `src/ingestion/tests/e2e/specs/collab_emails_sent.test.yaml`, `specs/schemas/*`, `specs/templates/*`; integrates all components +**Touches**: `src/ingestion/tests/e2e/metrics/collab_emails_sent.test.yaml`, `metrics/schemas/*`, `metrics/templates/*`; integrates all components ## 6. Acceptance Criteria diff --git a/src/ingestion/tests/e2e/README.md b/src/ingestion/tests/e2e/README.md index d5b36bdf8..ffb073c51 100644 --- a/src/ingestion/tests/e2e/README.md +++ b/src/ingestion/tests/e2e/README.md @@ -3,7 +3,7 @@ Test framework that exercises the full data path: ``` -specs/.test.yaml (bronze records) → bronze tables → dbt staging/silver → +metrics/.test.yaml (bronze records) → bronze tables → dbt staging/silver → ClickHouse migration gold-views → analytics-api HTTP (POST /v1/metrics/queries) → expect rules ``` @@ -50,13 +50,13 @@ pytest -k collab_emails_sent -v # session-rig brings compose up automatically ``` e2e/ -├── pyproject.toml # deps; defines e2e_lib package +├── pyproject.toml # deps; defines lib package ├── pytest.ini # pytest config ├── conftest.py # session-scoped pytest fixtures (the orchestrator) ├── compose/ │ ├── docker-compose.yml # ClickHouse + MariaDB, loopback-only │ └── .env.example # example creds (real values generated per-session) -├── e2e_lib/ # framework Python package +├── lib/ # framework Python package │ ├── compose.py # docker compose up/down + healthcheck wait │ ├── clickhouse.py # CH HTTP client wrapper │ ├── mariadb.py # MariaDB connection helper @@ -66,7 +66,7 @@ e2e/ │ └── config.py # session config (ports, random creds) ├── seed/ │ └── metrics.yaml # optional test-specific metric overrides (default: empty) -├── specs/ # .test.yaml + schemas/ + templates/ +├── metrics/ # .test.yaml + schemas/ + templates/ └── meta/ # framework's own smoke tests └── test_session_smoke.py ``` @@ -84,14 +84,14 @@ These ports avoid conflict with a local gitops dev cluster (which forwards 8123 ## Notes for fixture authors -- Auth in `analytics-api` requires no Bearer token, but its tenant middleware rejects requests without a non-nil tenant. The harness sends `X-Insight-Tenant-Id` with `e2e_lib.config.TEST_TENANT_ID` on every request and re-homes seeded metric definitions onto that tenant (`metric_seed.py`). The ClickHouse query path does not filter by tenant yet, so seeded bronze rows may use any tenant value. +- Auth in `analytics-api` requires no Bearer token, but its tenant middleware rejects requests without a non-nil tenant. The harness sends `X-Insight-Tenant-Id` with `lib.config.TEST_TENANT_ID` on every request and re-homes seeded metric definitions onto that tenant (`metric_seed.py`). The ClickHouse query path does not filter by tenant yet, so seeded bronze rows may use any tenant value. - Metric definitions are auto-seeded by the analytics-api binary's SeaORM migrations. Look up the metric UUID with `GET /v1/metrics` once the session is up, or add overrides in `seed/metrics.yaml`. ## `cases` / `expect` (declarative YAML rig) -Tests are `specs/**/*.test.yaml`; each `case` POSTs a batch to `/v1/metrics/queries` and checks an `expect` list of rules. A rule selects with `in` (batch result by `id`) + an exact-equality `find` (`{field: value}`), then asserts via `equal` (subset of fields, exact / `null`) or `assert` (a CEL boolean). Anything richer than equality (inequalities, counts, predicates) goes in a CEL `assert` — the rig deliberately has no second selector language. See the [yaml-rig FEATURE](../../../../docs/domain/bronze-to-api-e2e/specs/feature-yaml-rig/FEATURE.md) and the `/metric-e2e-test` skill. +Tests are `metrics/**/*.test.yaml`; each `case` POSTs a batch to `/v1/metrics/queries` and checks an `expect` list of rules. A rule selects with `in` (batch result by `id`) + an exact-equality `find` (`{field: value}`), then asserts via `equal` (subset of fields, exact / `null`) or `assert` (a CEL boolean). Anything richer than equality (inequalities, counts, predicates) goes in a CEL `assert` — the rig deliberately has no second selector language. See the [yaml-rig FEATURE](../../../../docs/domain/bronze-to-api-e2e/specs/feature-yaml-rig/FEATURE.md) and the `/metric` skill. -Variables available in an `assert` (CEL) expression — assembled in `e2e_lib/expect_engine.py::evaluate_case` (the `bindings` dict), converted to CEL in `_eval_cel`: +Variables available in an `assert` (CEL) expression — assembled in `lib/expect_engine.py::evaluate_case` (the `bindings` dict), converted to CEL in `_eval_cel`: | Binding | Value | Present when | |---------|-------|--------------| @@ -105,7 +105,7 @@ CEL is strictly typed and will not compare an `int` to a `double`. Bindings are ### What is CEL -`assert` expressions are written in **CEL — the [Common Expression Language](https://github.com/google/cel-spec)** (the same expression language used by Kubernetes admission policies and Envoy). It is a small, side-effect-free language for boolean/value expressions over structured data: no statements, no loops, no I/O — an expression is evaluated against the bindings above and must return a boolean. The rig evaluates it with the [`cel-python`](https://pypi.org/project/cel-python/) library (`celpy`) in `e2e_lib/expect_engine.py::_eval_cel`. +`assert` expressions are written in **CEL — the [Common Expression Language](https://github.com/google/cel-spec)** (the same expression language used by Kubernetes admission policies and Envoy). It is a small, side-effect-free language for boolean/value expressions over structured data: no statements, no loops, no I/O — an expression is evaluated against the bindings above and must return a boolean. The rig evaluates it with the [`cel-python`](https://pypi.org/project/cel-python/) library (`celpy`) in `lib/expect_engine.py::_eval_cel`. Operators: `== != < <= > >=`, `&& || !`, `+ - * / %`, `in`, ternary `cond ? a : b`. Field/index access: `it.value`, `result.status`, `items[0]`. Useful built-ins & macros: `size(x)`, `has(x.field)`, `x.exists(e, )`, `x.all(e, )`, `x.filter(e, )`, `x.map(e, )`, string `.startsWith()/.endsWith()/.contains()/.matches(re)`. diff --git a/src/ingestion/tests/e2e/api/__init__.py b/src/ingestion/tests/e2e/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/ingestion/tests/e2e/meta/test_session_smoke.py b/src/ingestion/tests/e2e/api/test_session_smoke.py similarity index 92% rename from src/ingestion/tests/e2e/meta/test_session_smoke.py rename to src/ingestion/tests/e2e/api/test_session_smoke.py index 0ec90aae3..a44b27d60 100644 --- a/src/ingestion/tests/e2e/meta/test_session_smoke.py +++ b/src/ingestion/tests/e2e/api/test_session_smoke.py @@ -2,7 +2,7 @@ Verifies each session-scoped fixture in isolation, then end-to-end. Run with: - pytest src/ingestion/tests/e2e/meta/ -m smoke + pytest src/ingestion/tests/e2e/api/ -m smoke These tests are pre-MVP — they do NOT use any real fixture folder; they just poke each layer to confirm the rig comes up cleanly. @@ -12,10 +12,10 @@ import pytest -from e2e_lib import clickhouse as ch -from e2e_lib import mariadb -from e2e_lib.analytics_api import AnalyticsApiProcess -from e2e_lib.config import SessionConfig +from lib import clickhouse as ch +from lib import mariadb +from lib.analytics_api import AnalyticsApiProcess +from lib.config import SessionConfig pytestmark = pytest.mark.smoke diff --git a/src/ingestion/tests/e2e/compose/Dockerfile.runner b/src/ingestion/tests/e2e/compose/Dockerfile.runner index d1b25f29b..dbe879f03 100644 --- a/src/ingestion/tests/e2e/compose/Dockerfile.runner +++ b/src/ingestion/tests/e2e/compose/Dockerfile.runner @@ -1,7 +1,7 @@ # Dev/CI runner for the Bronze-to-API E2E test framework. # # Bakes in: -# - Python 3.12 + e2e_lib deps (pytest, clickhouse-connect, pandas, dbt-clickhouse, …) +# - Python 3.12 + lib deps (pytest, clickhouse-connect, pandas, dbt-clickhouse, …) # - the analytics-api binary and each connector's enrich binary, each COMPILED # FROM ITS OWN Dockerfile and pulled in via `COPY --from=` (the contexts # are wired in docker-compose.runner.yml `runner.build.additional_contexts`). @@ -32,10 +32,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # /workspace via bind mount. WORKDIR /workspace/src/ingestion/tests/e2e COPY src/ingestion/tests/e2e/pyproject.toml ./pyproject.toml -RUN mkdir -p e2e_lib && touch e2e_lib/__init__.py \ +RUN mkdir -p lib && touch lib/__init__.py \ && pip install --no-cache-dir --upgrade pip \ && pip install --no-cache-dir -e . \ - && rm -rf e2e_lib + && rm -rf lib # ── Pre-built binaries (baked from each component's OWN Dockerfile) ─────── # Named build contexts come from docker-compose.runner.yml build-only services; diff --git a/src/ingestion/tests/e2e/compose/docker-compose.runner.yml b/src/ingestion/tests/e2e/compose/docker-compose.runner.yml index 00dbd40ca..36c97ef2e 100644 --- a/src/ingestion/tests/e2e/compose/docker-compose.runner.yml +++ b/src/ingestion/tests/e2e/compose/docker-compose.runner.yml @@ -60,7 +60,7 @@ services: condition: service_healthy environment: # Service names on the insight-e2e network — overrides the localhost - # defaults in e2e_lib/config.py. The session-rig honors these in + # defaults in lib/config.py. The session-rig honors these in # E2E_RUN_MODE=docker. E2E_RUN_MODE: docker E2E_CH_HOST: clickhouse diff --git a/src/ingestion/tests/e2e/compose/docker-compose.yml b/src/ingestion/tests/e2e/compose/docker-compose.yml index d8f2d47c0..052baec76 100644 --- a/src/ingestion/tests/e2e/compose/docker-compose.yml +++ b/src/ingestion/tests/e2e/compose/docker-compose.yml @@ -4,7 +4,7 @@ # Versions are pinned to production parity (per cpt-bronze-to-api-e2e-constraint-version-parity); # update both pins together when the prod Helm chart bumps either image. # -# Credentials are passed via .env (generated per-session by e2e_lib.config) and +# Credentials are passed via .env (generated per-session by lib.config) and # MUST NOT be committed. services: diff --git a/src/ingestion/tests/e2e/conftest.py b/src/ingestion/tests/e2e/conftest.py index 7e27a08ab..88fe0460b 100644 --- a/src/ingestion/tests/e2e/conftest.py +++ b/src/ingestion/tests/e2e/conftest.py @@ -29,17 +29,17 @@ from pathlib import Path -from e2e_lib import clickhouse as ch -from e2e_lib import compose, mariadb -from e2e_lib.analytics_api import AnalyticsApiProcess, find_free_port, locate_binary -from e2e_lib.ch_seeder import CHSeeder -from e2e_lib.config import SessionConfig -from e2e_lib.dbt_runner import DbtRunner -from e2e_lib.enrich import EnrichRunner -from e2e_lib.fixture_loader import TestYaml, discover_tests, load as load_test -from e2e_lib.metric_seed import seed_test_metrics -from e2e_lib.migration_applier import apply_all as apply_ch_migrations -from e2e_lib.worker import WorkerContext +from lib import clickhouse as ch +from lib import compose, mariadb +from lib.analytics_api import AnalyticsApiProcess, find_free_port, locate_binary +from lib.ch_seeder import CHSeeder +from lib.config import SessionConfig +from lib.dbt_runner import DbtRunner +from lib.enrich import EnrichRunner +from lib.fixture_loader import TestYaml, discover_tests, load as load_test +from lib.metric_seed import seed_test_metrics +from lib.migration_applier import apply_all as apply_ch_migrations +from lib.worker import WorkerContext LOG = logging.getLogger("e2e.rig") @@ -171,11 +171,11 @@ def analytics_api(ch_migrations_applied: SessionConfig): If the binary is missing, this is a hard FAIL — identical locally and in CI. A skip here would make the whole transformation suite silently green while testing nothing. The binary is built FROM ITS OWN Dockerfile and baked into the - runner image (see e2e_lib.analytics_api.locate_binary); if it isn't there the + runner image (see lib.analytics_api.locate_binary); if it isn't there the bronze→API tests cannot run, so the only honest result is red. """ cfg = ch_migrations_applied - from e2e_lib.analytics_api import ApiSpawnError # local import to keep top clean + from lib.analytics_api import ApiSpawnError # local import to keep top clean try: binary = locate_binary(cfg) except ApiSpawnError as e: @@ -205,18 +205,18 @@ def enrich_runner(ch_migrations_applied: SessionConfig) -> EnrichRunner: # ---------------------------------------------------------------------- -_SPECS_ROOT = Path(__file__).parent / "specs" +_METRICS_ROOT = Path(__file__).parent / "metrics" def pytest_collection_modifyitems(config, items): - """Convenience: order smoke tests under meta/ first.""" - items.sort(key=lambda i: 0 if "meta/" in str(i.path) else 1) + """Convenience: order rig smoke tests (meta/ + api/) first.""" + items.sort(key=lambda i: 0 if ("meta/" in str(i.path) or "api/" in str(i.path)) else 1) def pytest_generate_tests(metafunc): - """Generate one `test_e2e_metric_smoke` invocation per discovered `*.test.yaml`.""" - if "test_yaml" in metafunc.fixturenames and metafunc.function.__name__ == "test_e2e_metric_smoke": - paths = discover_tests(_SPECS_ROOT) + """Generate one `test_metric_smoke` invocation per discovered `*.test.yaml`.""" + if "test_yaml" in metafunc.fixturenames and metafunc.function.__name__ == "test_metric_smoke": + paths = discover_tests(_METRICS_ROOT) metafunc.parametrize( "test_path", paths, diff --git a/src/ingestion/tests/e2e/e2e_lib/__init__.py b/src/ingestion/tests/e2e/e2e_lib/__init__.py deleted file mode 100644 index b3c981448..000000000 --- a/src/ingestion/tests/e2e/e2e_lib/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -"""Bronze-to-API E2E test framework.""" - -from e2e_lib.config import SessionConfig -from e2e_lib.fixture_loader import FixtureError, TestYaml -from e2e_lib.worker import WorkerContext - -__all__ = [ - "FixtureError", - "SessionConfig", - "TestYaml", - "WorkerContext", -] diff --git a/src/ingestion/tests/e2e/lib/__init__.py b/src/ingestion/tests/e2e/lib/__init__.py new file mode 100644 index 000000000..ffe901e3a --- /dev/null +++ b/src/ingestion/tests/e2e/lib/__init__.py @@ -0,0 +1,12 @@ +"""Bronze-to-API E2E test framework.""" + +from lib.config import SessionConfig +from lib.fixture_loader import FixtureError, TestYaml +from lib.worker import WorkerContext + +__all__ = [ + "FixtureError", + "SessionConfig", + "TestYaml", + "WorkerContext", +] diff --git a/src/ingestion/tests/e2e/e2e_lib/analytics_api.py b/src/ingestion/tests/e2e/lib/analytics_api.py similarity index 98% rename from src/ingestion/tests/e2e/e2e_lib/analytics_api.py rename to src/ingestion/tests/e2e/lib/analytics_api.py index bef26c14f..25d82f58e 100644 --- a/src/ingestion/tests/e2e/e2e_lib/analytics_api.py +++ b/src/ingestion/tests/e2e/lib/analytics_api.py @@ -28,7 +28,7 @@ import httpx -from e2e_lib.config import SessionConfig, TENANT_HEADER, TEST_TENANT_ID +from lib.config import SessionConfig, TENANT_HEADER, TEST_TENANT_ID LOG = logging.getLogger("e2e.api") @@ -82,7 +82,7 @@ def locate_binary(cfg: SessionConfig) -> Path: ships the prod image — no build-recipe duplication) and baked onto PATH at `/usr/local/bin/analytics-api` via docker-compose.runner.yml `additional_contexts` + a Dockerfile.runner `COPY --from=analytics-api …`. Same pattern as the connector - enrich binaries (see e2e_lib/enrich.py). + enrich binaries (see lib/enrich.py). Falls back to a PATH lookup and a host-mode cargo target (for running pytest directly on the host with a manual `cargo build`), then fails clearly. diff --git a/src/ingestion/tests/e2e/e2e_lib/ch_seeder.py b/src/ingestion/tests/e2e/lib/ch_seeder.py similarity index 98% rename from src/ingestion/tests/e2e/e2e_lib/ch_seeder.py rename to src/ingestion/tests/e2e/lib/ch_seeder.py index 4fe8adce6..d1304c9ee 100644 --- a/src/ingestion/tests/e2e/e2e_lib/ch_seeder.py +++ b/src/ingestion/tests/e2e/lib/ch_seeder.py @@ -18,8 +18,8 @@ from datetime import datetime from typing import Any, Iterable -from e2e_lib import clickhouse as ch -from e2e_lib.config import SessionConfig +from lib import clickhouse as ch +from lib.config import SessionConfig LOG = logging.getLogger("e2e.seeder") diff --git a/src/ingestion/tests/e2e/e2e_lib/clickhouse.py b/src/ingestion/tests/e2e/lib/clickhouse.py similarity index 97% rename from src/ingestion/tests/e2e/e2e_lib/clickhouse.py rename to src/ingestion/tests/e2e/lib/clickhouse.py index 7e37dc655..ab62a6a40 100644 --- a/src/ingestion/tests/e2e/e2e_lib/clickhouse.py +++ b/src/ingestion/tests/e2e/lib/clickhouse.py @@ -7,7 +7,7 @@ import clickhouse_connect -from e2e_lib.config import SessionConfig +from lib.config import SessionConfig LOG = logging.getLogger("e2e.ch") diff --git a/src/ingestion/tests/e2e/e2e_lib/compose.py b/src/ingestion/tests/e2e/lib/compose.py similarity index 99% rename from src/ingestion/tests/e2e/e2e_lib/compose.py rename to src/ingestion/tests/e2e/lib/compose.py index 52caaff8b..ebe8e5e51 100644 --- a/src/ingestion/tests/e2e/e2e_lib/compose.py +++ b/src/ingestion/tests/e2e/lib/compose.py @@ -13,7 +13,7 @@ import time from typing import Mapping -from e2e_lib.config import SessionConfig +from lib.config import SessionConfig LOG = logging.getLogger("e2e.compose") diff --git a/src/ingestion/tests/e2e/e2e_lib/config.py b/src/ingestion/tests/e2e/lib/config.py similarity index 98% rename from src/ingestion/tests/e2e/e2e_lib/config.py rename to src/ingestion/tests/e2e/lib/config.py index e7417e0c9..35c42f7b9 100644 --- a/src/ingestion/tests/e2e/e2e_lib/config.py +++ b/src/ingestion/tests/e2e/lib/config.py @@ -16,7 +16,7 @@ # Resolve the repo root from this file's location: -# src/ingestion/tests/e2e/e2e_lib/config.py -> ../../../../../ +# src/ingestion/tests/e2e/lib/config.py -> ../../../../../ _REPO_ROOT = Path(__file__).resolve().parents[5] diff --git a/src/ingestion/tests/e2e/e2e_lib/dbt_runner.py b/src/ingestion/tests/e2e/lib/dbt_runner.py similarity index 99% rename from src/ingestion/tests/e2e/e2e_lib/dbt_runner.py rename to src/ingestion/tests/e2e/lib/dbt_runner.py index 7ab50d992..6d8ea2804 100644 --- a/src/ingestion/tests/e2e/e2e_lib/dbt_runner.py +++ b/src/ingestion/tests/e2e/lib/dbt_runner.py @@ -21,8 +21,8 @@ import yaml -from e2e_lib.config import SessionConfig -from e2e_lib.worker import WorkerContext +from lib.config import SessionConfig +from lib.worker import WorkerContext LOG = logging.getLogger("e2e.dbt") diff --git a/src/ingestion/tests/e2e/e2e_lib/enrich.py b/src/ingestion/tests/e2e/lib/enrich.py similarity index 98% rename from src/ingestion/tests/e2e/e2e_lib/enrich.py rename to src/ingestion/tests/e2e/lib/enrich.py index 07744c0da..f36cea417 100644 --- a/src/ingestion/tests/e2e/e2e_lib/enrich.py +++ b/src/ingestion/tests/e2e/lib/enrich.py @@ -32,8 +32,8 @@ import yaml -from e2e_lib import clickhouse as ch -from e2e_lib.config import SessionConfig +from lib import clickhouse as ch +from lib.config import SessionConfig LOG = logging.getLogger("e2e.enrich") diff --git a/src/ingestion/tests/e2e/e2e_lib/expect_engine.py b/src/ingestion/tests/e2e/lib/expect_engine.py similarity index 98% rename from src/ingestion/tests/e2e/e2e_lib/expect_engine.py rename to src/ingestion/tests/e2e/lib/expect_engine.py index bf4b3303f..23211b255 100644 --- a/src/ingestion/tests/e2e/e2e_lib/expect_engine.py +++ b/src/ingestion/tests/e2e/lib/expect_engine.py @@ -95,7 +95,7 @@ def evaluate_case(case: dict, batch: dict, http_status: int) -> None: elif "assert" in rule: # CANONICAL source of the CEL `assert` bindings (documented in the # yaml-rig FEATURE, DESIGN expect-engine component, README, and the - # /metric-e2e-test skill). `it` is None unless this rule had a `find`. + # /metric-test skill). `it` is None unless this rule had a `find`. bindings = { "it": it, "items": items, diff --git a/src/ingestion/tests/e2e/e2e_lib/fixture_loader.py b/src/ingestion/tests/e2e/lib/fixture_loader.py similarity index 95% rename from src/ingestion/tests/e2e/e2e_lib/fixture_loader.py rename to src/ingestion/tests/e2e/lib/fixture_loader.py index 1ff39691e..3820b9a28 100644 --- a/src/ingestion/tests/e2e/e2e_lib/fixture_loader.py +++ b/src/ingestion/tests/e2e/lib/fixture_loader.py @@ -26,7 +26,7 @@ import yaml -from e2e_lib import ref_resolver, schema_validator +from lib import ref_resolver, schema_validator LOG = logging.getLogger("e2e.fixture") @@ -54,7 +54,7 @@ def touched_tables(self) -> set[tuple[str, str]]: def discover_tests(specs_root: Path) -> list[Path]: - """Every `**/*.test.yaml` under specs/. Shared schemas/templates are excluded + """Every `**/*.test.yaml` under metrics/. Shared schemas/templates are excluded by the suffix; nothing else is collected as a test.""" if not specs_root.is_dir(): return [] @@ -113,9 +113,9 @@ def load(path: Path, *, schemas_dir: Path | None = None) -> TestYaml: def _find_schemas_dir(test_path: Path) -> Path: - """Walk up to the `specs/` dir and use its `schemas/` subdir.""" + """Walk up to the `metrics/` dir and use its `schemas/` subdir.""" for parent in test_path.parents: - if parent.name == "specs": + if parent.name == "metrics": return parent / "schemas" if (parent / "schemas").is_dir(): return parent / "schemas" diff --git a/src/ingestion/tests/e2e/e2e_lib/mariadb.py b/src/ingestion/tests/e2e/lib/mariadb.py similarity index 97% rename from src/ingestion/tests/e2e/e2e_lib/mariadb.py rename to src/ingestion/tests/e2e/lib/mariadb.py index 37a743b02..fa748c776 100644 --- a/src/ingestion/tests/e2e/e2e_lib/mariadb.py +++ b/src/ingestion/tests/e2e/lib/mariadb.py @@ -12,7 +12,7 @@ import pymysql -from e2e_lib.config import SessionConfig +from lib.config import SessionConfig LOG = logging.getLogger("e2e.mariadb") diff --git a/src/ingestion/tests/e2e/e2e_lib/metric_seed.py b/src/ingestion/tests/e2e/lib/metric_seed.py similarity index 97% rename from src/ingestion/tests/e2e/e2e_lib/metric_seed.py rename to src/ingestion/tests/e2e/lib/metric_seed.py index b8335631b..4374d4292 100644 --- a/src/ingestion/tests/e2e/e2e_lib/metric_seed.py +++ b/src/ingestion/tests/e2e/lib/metric_seed.py @@ -17,8 +17,8 @@ import yaml -from e2e_lib import mariadb -from e2e_lib.config import SessionConfig, TEST_TENANT_ID +from lib import mariadb +from lib.config import SessionConfig, TEST_TENANT_ID LOG = logging.getLogger("e2e.metric_seed") diff --git a/src/ingestion/tests/e2e/e2e_lib/migration_applier.py b/src/ingestion/tests/e2e/lib/migration_applier.py similarity index 99% rename from src/ingestion/tests/e2e/e2e_lib/migration_applier.py rename to src/ingestion/tests/e2e/lib/migration_applier.py index 409859b88..efa0e81d5 100644 --- a/src/ingestion/tests/e2e/e2e_lib/migration_applier.py +++ b/src/ingestion/tests/e2e/lib/migration_applier.py @@ -24,8 +24,8 @@ import re from pathlib import Path -from e2e_lib import clickhouse as ch -from e2e_lib.config import SessionConfig +from lib import clickhouse as ch +from lib.config import SessionConfig LOG = logging.getLogger("e2e.migration") diff --git a/src/ingestion/tests/e2e/e2e_lib/ref_resolver.py b/src/ingestion/tests/e2e/lib/ref_resolver.py similarity index 100% rename from src/ingestion/tests/e2e/e2e_lib/ref_resolver.py rename to src/ingestion/tests/e2e/lib/ref_resolver.py diff --git a/src/ingestion/tests/e2e/e2e_lib/schema_validator.py b/src/ingestion/tests/e2e/lib/schema_validator.py similarity index 96% rename from src/ingestion/tests/e2e/e2e_lib/schema_validator.py rename to src/ingestion/tests/e2e/lib/schema_validator.py index a8d190423..eac9e297f 100644 --- a/src/ingestion/tests/e2e/e2e_lib/schema_validator.py +++ b/src/ingestion/tests/e2e/lib/schema_validator.py @@ -2,7 +2,7 @@ Implements the post-step of `cpt-bronze-to-api-e2e-algo-yaml-resolve-refs` (DoD `cpt-bronze-to-api-e2e-dod-yaml-schema-resolution`). A schema lives in -`specs/schemas/
.yaml` under a top-level `schemas: {
: {...} }`. +`metrics/schemas/
.yaml` under a top-level `schemas: {
: {...} }`. After `$ref` resolution a bronze record is padded with every missing schema property as `null`, then validated; `additionalProperties:false` catches a misspelled column name. diff --git a/src/ingestion/tests/e2e/e2e_lib/worker.py b/src/ingestion/tests/e2e/lib/worker.py similarity index 100% rename from src/ingestion/tests/e2e/e2e_lib/worker.py rename to src/ingestion/tests/e2e/lib/worker.py diff --git a/src/ingestion/tests/e2e/meta/test_dbt_runner.py b/src/ingestion/tests/e2e/meta/test_dbt_runner.py index 965ed5681..83338f0a5 100644 --- a/src/ingestion/tests/e2e/meta/test_dbt_runner.py +++ b/src/ingestion/tests/e2e/meta/test_dbt_runner.py @@ -13,8 +13,8 @@ import pytest -from e2e_lib.dbt_runner import DbtError, DbtRunner -from e2e_lib.worker import WorkerContext +from lib.dbt_runner import DbtError, DbtRunner +from lib.worker import WorkerContext pytestmark = pytest.mark.smoke diff --git a/src/ingestion/tests/e2e/meta/test_expect_engine.py b/src/ingestion/tests/e2e/meta/test_expect_engine.py index 62c1195fd..36164fe6b 100644 --- a/src/ingestion/tests/e2e/meta/test_expect_engine.py +++ b/src/ingestion/tests/e2e/meta/test_expect_engine.py @@ -6,7 +6,7 @@ import pytest -from e2e_lib.expect_engine import ExpectError, evaluate_case +from lib.expect_engine import ExpectError, evaluate_case pytestmark = pytest.mark.smoke diff --git a/src/ingestion/tests/e2e/meta/test_ref_resolver.py b/src/ingestion/tests/e2e/meta/test_ref_resolver.py index 8cee1dac5..e4e66a765 100644 --- a/src/ingestion/tests/e2e/meta/test_ref_resolver.py +++ b/src/ingestion/tests/e2e/meta/test_ref_resolver.py @@ -8,8 +8,8 @@ import pytest -from e2e_lib import ref_resolver -from e2e_lib.ref_resolver import RefError, resolve +from lib import ref_resolver +from lib.ref_resolver import RefError, resolve pytestmark = pytest.mark.smoke diff --git a/src/ingestion/tests/e2e/specs/collab_active_days.test.yaml b/src/ingestion/tests/e2e/metrics/collab_active_days.test.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/collab_active_days.test.yaml rename to src/ingestion/tests/e2e/metrics/collab_active_days.test.yaml diff --git a/src/ingestion/tests/e2e/specs/collab_emails_read.test.yaml b/src/ingestion/tests/e2e/metrics/collab_emails_read.test.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/collab_emails_read.test.yaml rename to src/ingestion/tests/e2e/metrics/collab_emails_read.test.yaml diff --git a/src/ingestion/tests/e2e/specs/collab_emails_received.test.yaml b/src/ingestion/tests/e2e/metrics/collab_emails_received.test.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/collab_emails_received.test.yaml rename to src/ingestion/tests/e2e/metrics/collab_emails_received.test.yaml diff --git a/src/ingestion/tests/e2e/specs/collab_emails_sent.test.yaml b/src/ingestion/tests/e2e/metrics/collab_emails_sent.test.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/collab_emails_sent.test.yaml rename to src/ingestion/tests/e2e/metrics/collab_emails_sent.test.yaml diff --git a/src/ingestion/tests/e2e/specs/collab_files_engaged.test.yaml b/src/ingestion/tests/e2e/metrics/collab_files_engaged.test.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/collab_files_engaged.test.yaml rename to src/ingestion/tests/e2e/metrics/collab_files_engaged.test.yaml diff --git a/src/ingestion/tests/e2e/specs/collab_files_shared_external.test.yaml b/src/ingestion/tests/e2e/metrics/collab_files_shared_external.test.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/collab_files_shared_external.test.yaml rename to src/ingestion/tests/e2e/metrics/collab_files_shared_external.test.yaml diff --git a/src/ingestion/tests/e2e/specs/collab_files_shared_internal.test.yaml b/src/ingestion/tests/e2e/metrics/collab_files_shared_internal.test.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/collab_files_shared_internal.test.yaml rename to src/ingestion/tests/e2e/metrics/collab_files_shared_internal.test.yaml diff --git a/src/ingestion/tests/e2e/specs/collab_meeting_free.test.yaml b/src/ingestion/tests/e2e/metrics/collab_meeting_free.test.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/collab_meeting_free.test.yaml rename to src/ingestion/tests/e2e/metrics/collab_meeting_free.test.yaml diff --git a/src/ingestion/tests/e2e/specs/collab_meeting_hours.test.yaml b/src/ingestion/tests/e2e/metrics/collab_meeting_hours.test.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/collab_meeting_hours.test.yaml rename to src/ingestion/tests/e2e/metrics/collab_meeting_hours.test.yaml diff --git a/src/ingestion/tests/e2e/specs/collab_meetings_count.test.yaml b/src/ingestion/tests/e2e/metrics/collab_meetings_count.test.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/collab_meetings_count.test.yaml rename to src/ingestion/tests/e2e/metrics/collab_meetings_count.test.yaml diff --git a/src/ingestion/tests/e2e/specs/collab_teams_chats.test.yaml b/src/ingestion/tests/e2e/metrics/collab_teams_chats.test.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/collab_teams_chats.test.yaml rename to src/ingestion/tests/e2e/metrics/collab_teams_chats.test.yaml diff --git a/src/ingestion/tests/e2e/specs/collab_teams_meeting_hours.test.yaml b/src/ingestion/tests/e2e/metrics/collab_teams_meeting_hours.test.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/collab_teams_meeting_hours.test.yaml rename to src/ingestion/tests/e2e/metrics/collab_teams_meeting_hours.test.yaml diff --git a/src/ingestion/tests/e2e/specs/collab_teams_meetings.test.yaml b/src/ingestion/tests/e2e/metrics/collab_teams_meetings.test.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/collab_teams_meetings.test.yaml rename to src/ingestion/tests/e2e/metrics/collab_teams_meetings.test.yaml diff --git a/src/ingestion/tests/e2e/specs/collab_zulip_chat.test.yaml b/src/ingestion/tests/e2e/metrics/collab_zulip_chat.test.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/collab_zulip_chat.test.yaml rename to src/ingestion/tests/e2e/metrics/collab_zulip_chat.test.yaml diff --git a/src/ingestion/tests/e2e/specs/schemas/bronze_bamboohr.employees.yaml b/src/ingestion/tests/e2e/metrics/schemas/bronze_bamboohr.employees.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/schemas/bronze_bamboohr.employees.yaml rename to src/ingestion/tests/e2e/metrics/schemas/bronze_bamboohr.employees.yaml diff --git a/src/ingestion/tests/e2e/specs/schemas/bronze_jira.jira_fields.yaml b/src/ingestion/tests/e2e/metrics/schemas/bronze_jira.jira_fields.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/schemas/bronze_jira.jira_fields.yaml rename to src/ingestion/tests/e2e/metrics/schemas/bronze_jira.jira_fields.yaml diff --git a/src/ingestion/tests/e2e/specs/schemas/bronze_jira.jira_issue.yaml b/src/ingestion/tests/e2e/metrics/schemas/bronze_jira.jira_issue.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/schemas/bronze_jira.jira_issue.yaml rename to src/ingestion/tests/e2e/metrics/schemas/bronze_jira.jira_issue.yaml diff --git a/src/ingestion/tests/e2e/specs/schemas/bronze_jira.jira_issue_history.yaml b/src/ingestion/tests/e2e/metrics/schemas/bronze_jira.jira_issue_history.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/schemas/bronze_jira.jira_issue_history.yaml rename to src/ingestion/tests/e2e/metrics/schemas/bronze_jira.jira_issue_history.yaml diff --git a/src/ingestion/tests/e2e/specs/schemas/bronze_jira.jira_user.yaml b/src/ingestion/tests/e2e/metrics/schemas/bronze_jira.jira_user.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/schemas/bronze_jira.jira_user.yaml rename to src/ingestion/tests/e2e/metrics/schemas/bronze_jira.jira_user.yaml diff --git a/src/ingestion/tests/e2e/specs/schemas/bronze_m365.email_activity.yaml b/src/ingestion/tests/e2e/metrics/schemas/bronze_m365.email_activity.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/schemas/bronze_m365.email_activity.yaml rename to src/ingestion/tests/e2e/metrics/schemas/bronze_m365.email_activity.yaml diff --git a/src/ingestion/tests/e2e/specs/schemas/bronze_m365.onedrive_activity.yaml b/src/ingestion/tests/e2e/metrics/schemas/bronze_m365.onedrive_activity.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/schemas/bronze_m365.onedrive_activity.yaml rename to src/ingestion/tests/e2e/metrics/schemas/bronze_m365.onedrive_activity.yaml diff --git a/src/ingestion/tests/e2e/specs/schemas/bronze_m365.sharepoint_activity.yaml b/src/ingestion/tests/e2e/metrics/schemas/bronze_m365.sharepoint_activity.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/schemas/bronze_m365.sharepoint_activity.yaml rename to src/ingestion/tests/e2e/metrics/schemas/bronze_m365.sharepoint_activity.yaml diff --git a/src/ingestion/tests/e2e/specs/schemas/bronze_m365.teams_activity.yaml b/src/ingestion/tests/e2e/metrics/schemas/bronze_m365.teams_activity.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/schemas/bronze_m365.teams_activity.yaml rename to src/ingestion/tests/e2e/metrics/schemas/bronze_m365.teams_activity.yaml diff --git a/src/ingestion/tests/e2e/specs/schemas/bronze_zoom.participants.yaml b/src/ingestion/tests/e2e/metrics/schemas/bronze_zoom.participants.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/schemas/bronze_zoom.participants.yaml rename to src/ingestion/tests/e2e/metrics/schemas/bronze_zoom.participants.yaml diff --git a/src/ingestion/tests/e2e/specs/schemas/bronze_zulip_proxy.messages.yaml b/src/ingestion/tests/e2e/metrics/schemas/bronze_zulip_proxy.messages.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/schemas/bronze_zulip_proxy.messages.yaml rename to src/ingestion/tests/e2e/metrics/schemas/bronze_zulip_proxy.messages.yaml diff --git a/src/ingestion/tests/e2e/specs/schemas/bronze_zulip_proxy.users.yaml b/src/ingestion/tests/e2e/metrics/schemas/bronze_zulip_proxy.users.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/schemas/bronze_zulip_proxy.users.yaml rename to src/ingestion/tests/e2e/metrics/schemas/bronze_zulip_proxy.users.yaml diff --git a/src/ingestion/tests/e2e/specs/task_delivery_bugs_to_task_ratio_jira.test.yaml b/src/ingestion/tests/e2e/metrics/task_delivery_bugs_to_task_ratio_jira.test.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/task_delivery_bugs_to_task_ratio_jira.test.yaml rename to src/ingestion/tests/e2e/metrics/task_delivery_bugs_to_task_ratio_jira.test.yaml diff --git a/src/ingestion/tests/e2e/specs/task_delivery_due_date_compliance_jira.test.yaml b/src/ingestion/tests/e2e/metrics/task_delivery_due_date_compliance_jira.test.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/task_delivery_due_date_compliance_jira.test.yaml rename to src/ingestion/tests/e2e/metrics/task_delivery_due_date_compliance_jira.test.yaml diff --git a/src/ingestion/tests/e2e/specs/task_delivery_tasks_completed_jira.test.yaml b/src/ingestion/tests/e2e/metrics/task_delivery_tasks_completed_jira.test.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/task_delivery_tasks_completed_jira.test.yaml rename to src/ingestion/tests/e2e/metrics/task_delivery_tasks_completed_jira.test.yaml diff --git a/src/ingestion/tests/e2e/specs/templates/jira_task.yaml b/src/ingestion/tests/e2e/metrics/templates/jira_task.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/templates/jira_task.yaml rename to src/ingestion/tests/e2e/metrics/templates/jira_task.yaml diff --git a/src/ingestion/tests/e2e/specs/templates/m365_email.yaml b/src/ingestion/tests/e2e/metrics/templates/m365_email.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/templates/m365_email.yaml rename to src/ingestion/tests/e2e/metrics/templates/m365_email.yaml diff --git a/src/ingestion/tests/e2e/specs/templates/m365_onedrive.yaml b/src/ingestion/tests/e2e/metrics/templates/m365_onedrive.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/templates/m365_onedrive.yaml rename to src/ingestion/tests/e2e/metrics/templates/m365_onedrive.yaml diff --git a/src/ingestion/tests/e2e/specs/templates/m365_sharepoint.yaml b/src/ingestion/tests/e2e/metrics/templates/m365_sharepoint.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/templates/m365_sharepoint.yaml rename to src/ingestion/tests/e2e/metrics/templates/m365_sharepoint.yaml diff --git a/src/ingestion/tests/e2e/specs/templates/m365_teams.yaml b/src/ingestion/tests/e2e/metrics/templates/m365_teams.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/templates/m365_teams.yaml rename to src/ingestion/tests/e2e/metrics/templates/m365_teams.yaml diff --git a/src/ingestion/tests/e2e/specs/templates/people.yaml b/src/ingestion/tests/e2e/metrics/templates/people.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/templates/people.yaml rename to src/ingestion/tests/e2e/metrics/templates/people.yaml diff --git a/src/ingestion/tests/e2e/specs/templates/zoom.yaml b/src/ingestion/tests/e2e/metrics/templates/zoom.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/templates/zoom.yaml rename to src/ingestion/tests/e2e/metrics/templates/zoom.yaml diff --git a/src/ingestion/tests/e2e/specs/templates/zulip.yaml b/src/ingestion/tests/e2e/metrics/templates/zulip.yaml similarity index 100% rename from src/ingestion/tests/e2e/specs/templates/zulip.yaml rename to src/ingestion/tests/e2e/metrics/templates/zulip.yaml diff --git a/src/ingestion/tests/e2e/specs/test_fixtures.py b/src/ingestion/tests/e2e/metrics/test_fixtures.py similarity index 92% rename from src/ingestion/tests/e2e/specs/test_fixtures.py rename to src/ingestion/tests/e2e/metrics/test_fixtures.py index 97c17b883..e83c1e35f 100644 --- a/src/ingestion/tests/e2e/specs/test_fixtures.py +++ b/src/ingestion/tests/e2e/metrics/test_fixtures.py @@ -20,20 +20,20 @@ import pytest -from e2e_lib.analytics_api import AnalyticsApiProcess -from e2e_lib.ch_seeder import CHSeeder -from e2e_lib.dbt_runner import DbtRunner -from e2e_lib.enrich import EnrichRunner -from e2e_lib.expect_engine import evaluate_case -from e2e_lib.fixture_loader import TestYaml -from e2e_lib.migration_applier import refresh_intermediates, reapply_migrations -from e2e_lib.worker import WorkerContext +from lib.analytics_api import AnalyticsApiProcess +from lib.ch_seeder import CHSeeder +from lib.dbt_runner import DbtRunner +from lib.enrich import EnrichRunner +from lib.expect_engine import evaluate_case +from lib.fixture_loader import TestYaml +from lib.migration_applier import refresh_intermediates, reapply_migrations +from lib.worker import WorkerContext pytestmark = pytest.mark.fixture LOG = logging.getLogger("e2e.runner") -def test_e2e_metric_smoke( +def test_metric_smoke( test_yaml: TestYaml, ch_seeder: CHSeeder, dbt_runner: DbtRunner, diff --git a/src/ingestion/tests/e2e/pyproject.toml b/src/ingestion/tests/e2e/pyproject.toml index 944e75566..96918d903 100644 --- a/src/ingestion/tests/e2e/pyproject.toml +++ b/src/ingestion/tests/e2e/pyproject.toml @@ -19,8 +19,8 @@ dependencies = [ ] [tool.setuptools.packages.find] -include = ["e2e_lib*"] -exclude = ["specs*", "compose*", "seed*", "meta*"] +include = ["lib*"] +exclude = ["metrics*", "compose*", "seed*", "meta*", "api*"] [build-system] requires = ["setuptools>=68"] diff --git a/src/ingestion/tests/e2e/pytest.ini b/src/ingestion/tests/e2e/pytest.ini index 20ef144c7..8f5c67107 100644 --- a/src/ingestion/tests/e2e/pytest.ini +++ b/src/ingestion/tests/e2e/pytest.ini @@ -1,5 +1,5 @@ [pytest] -testpaths = specs meta +testpaths = metrics meta api python_files = test_*.py python_classes = Test* python_functions = test_* @@ -10,8 +10,8 @@ addopts = --tb=short markers = slow: tests that take > 5s - smoke: framework smoke tests (under meta/) - fixture: tests generated from specs/*.test.yaml + smoke: framework smoke tests (under meta/ and api/) + fixture: tests generated from metrics/*.test.yaml log_cli = true log_cli_level = INFO log_cli_format = %(asctime)s | %(levelname)-7s | %(name)-20s | %(message)s diff --git a/src/ingestion/tests/e2e/seed/metrics.yaml b/src/ingestion/tests/e2e/seed/metrics.yaml index 38b9c462f..968886908 100644 --- a/src/ingestion/tests/e2e/seed/metrics.yaml +++ b/src/ingestion/tests/e2e/seed/metrics.yaml @@ -7,7 +7,7 @@ # # Each row is inserted under config.TEST_TENANT_ID — the tenant the harness # sends as X-Insight-Tenant-Id on every request (the analytics-api tenant -# middleware rejects nil/absent tenants, see e2e_lib/metric_seed.py). +# middleware rejects nil/absent tenants, see lib/metric_seed.py). overrides: [] # Example (uncomment & adapt for a fixture needing a metric not in the prod catalog):