Skip to content

fix(ci): repair Lint, Integration, Security and E2E jobs failing on main - #1220

Merged
cristim merged 8 commits into
mainfrom
fix/main-ci-failing-jobs
Jun 11, 2026
Merged

fix(ci): repair Lint, Integration, Security and E2E jobs failing on main#1220
cristim merged 8 commits into
mainfrom
fix/main-ci-failing-jobs

Conversation

@cristim

@cristim cristim commented Jun 11, 2026

Copy link
Copy Markdown
Member

Fixes all four CI jobs failing on main: Lint Code, Integration Tests, Security Scanning, and E2E Tests. Refs #1180.

Lint Code (golangci-lint typecheck)

Root cause: PR #1206 renamed RecommendationFilter.MinSavings to MinSavingsUSD/MinSavingsPct but only updated the unit test file; the integration-tagged internal/config/store_postgres_recommendations_test.go still used MinSavings, failing typecheck.

Fix: use MinSavingsUSD (the dollar floor pushed to SQL, matching the test's data and assertions).

Integration Tests

Three independent root causes:

  1. internal/server build failure: integration_test.go used scheduler.CollectResult without importing the package (and fixing that surfaced an unused ctx). Imports fixed.

  2. Analytics matview refresh (SQLSTATE 55000): migration 000074 recreated the unique indexes of the three savings materialized views as COALESCE(...) expression indexes. REFRESH MATERIALIZED VIEW CONCURRENTLY requires a unique index on plain column names, so refresh_savings_materialized_views() failed on every call (production runtime is affected too, not just tests). New migration 000076 recreates the indexes on plain columns with NULLS NOT DISTINCT (PG 15+; all environments run 16.x), preserving the NULL-dedup intent. Also fixed stale test expectations asserting pre-000074 SUM semantics (snapshots are run-rates, aggregated over time with AVG) and an InDelta call on a *float64.

  3. Migration regression tests anchored to head: most migration tests pinned the schema via "migrate to head, roll back N steps", which breaks every time a newer migration lands (the rollback undoes whatever is newest, not the migration under test). Added migrations.MigrateToVersion and converted the tests to version pins, cycling each migration's own up/down. Converting surfaced several latent test bugs that had been masked because the tests failed earlier: a "char" (OID 18) column scanned into *string (binary format unsupported by pgx), a wrong FK expectation (the recommendations FK has been ON DELETE CASCADE since 000030, never SET NULL), non-hex characters in a seeded UUID, an expectation that ON DELETE RESTRICT allows deletion once rows are cancelled (it does not; the row must be removed), and a rollback-step count derived from schema_migrations row count (golang-migrate keeps a single row, so the count was always 1).

Security Scanning (govulncheck exit 3)

Root cause: reachable vulnerabilities in golang.org/x/crypto@v0.49.0 (GO-2026-5013/5017/5018/5019/5020), golang.org/x/net@v0.52.0 (GO-2026-5026, GO-2026-4918), and github.com/docker/docker@v28.5.1 (GO-2026-4883, GO-2026-4887; no fixed release exists under that module path, fix only in moby/moby/v2).

Fix: bumped x/crypto to v0.53.0 and x/net to v0.56.0; upgraded testcontainers-go to v0.42.0, which switched to the moby client modules and removes every call path into the vulnerable docker/docker code (the module remains in the graph but is no longer called, which govulncheck accepts). providers/azure and providers/gcp got the same x/net (and gcp protobuf) bumps. No ignore flags or package exclusions were needed. The testcontainers bump required adapting testhelpers to the new network.Port API (port.Num()).

E2E Tests (refs #1180)

Root cause: the job failed in seconds because the compose invocation omitted --profile test (the test-runner service is profile-gated, so --exit-code-from test-runner aborted immediately), and the referenced Dockerfile.test plus the actual test suite never existed. continue-on-error: true masked all of this from the ci-success gate.

Fix:

  • New tests/e2e module: stdlib-only black-box suite run against the app over HTTP (health readiness polling with a lazy-DB-init nudge, /version metadata, auth enforcement on /api/recommendations).
  • New Dockerfile.test for the test-runner image, digest-pinned to the same golang base as Dockerfile, pre-compiling the suite at image build.
  • docker-compose.test.yml: wire the admin bootstrap via the env secret provider, allow the dev encryption key (no real credentials in this stack), disable email and scheduled-task auth, run the e2e-tagged suite as the command.
  • scripts/entrypoint.sh: guard DB_PASSWORD_SECRET/SECRET_PROVIDER expansions for set -u so the env-provider stack boots.
  • ci.yml: pass --profile test to every compose call, build images via compose, drop continue-on-error so the job genuinely gates ci-success, and add tests/e2e to the govulncheck module loop.

Test evidence (local, macOS, go 1.26, Docker)

  • go build ./..., go vet ./..., go vet -tags=integration ./...: clean (root, pkg, providers/*)
  • go test -tags=integration -race ./internal/database/postgres/migrations/ ./internal/analytics/: all pass (previously 9 + 8 failing tests)
  • go test -tags=integration -race for the fixed internal/config tests: pass
  • go test -short ./...: 5466 passed; pkg: 443 passed; providers aws/azure/gcp: 813/696/259 passed
  • govulncheck@v1.1.4 (same pin as CI) in all five modules CI scans: zero call-level findings
  • docker compose -f docker-compose.test.yml --profile test build && ... up --abort-on-container-exit --exit-code-from test-runner: full stack (postgres + app + test-runner) builds and the E2E suite passes end to end

Not verified locally: golangci-lint v2.10.1 itself (not installed here; go vet with the same build tags is clean), and the exact GitHub Actions environment. The CI run on this PR is the authoritative check for those.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed savings aggregation to use run-rate methodology for more accurate financial reporting.
  • Chores

    • Enhanced end-to-end testing infrastructure and coverage.
    • Updated dependencies for improved compatibility and security.
    • Improved database migration handling and test framework reliability.
    • Refined execution and recommendation handling for consistency.

cristim added 6 commits June 10, 2026 17:51
PR #1206 split RecommendationFilter.MinSavings into MinSavingsUSD and
MinSavingsPct but only updated the unit test file. The integration-tagged
store_postgres_recommendations_test.go still referenced the removed
MinSavings field, breaking golangci-lint typecheck on main.
internal/server/integration_test.go referenced scheduler.CollectResult
without importing the scheduler package, failing the integration build
on main. Fixing the import surfaced an unused ctx variable in
TestApplicationLifecycle, which is also removed.
…efresh

Migration 000074 recreated the unique indexes of monthly_savings_summary,
daily_savings_trend and provider_savings_summary as COALESCE expression
indexes. REFRESH MATERIALIZED VIEW CONCURRENTLY requires a unique index
using only column names, so refresh_savings_materialized_views() failed
with SQLSTATE 55000 on every call, in production and in the analytics
integration tests.

Migration 000076 recreates the three indexes on plain columns with NULLS
NOT DISTINCT (PostgreSQL 15+; all environments run 16.x), preserving the
dedup intent of the COALESCE for NULL cloud_account_id while satisfying
the CONCURRENTLY prerequisite.
QueryByProvider deliberately averages run-rate snapshots over time
(migration 000074 / PR #1127); the tests still asserted the pre-000074
SUM semantics (300 = 200 + 100) and passed the *float64 AvgCoverage
pointer straight into InDelta, which fails with "Parameters must be
numerical". Expect AVG(200, 100) = 150 and dereference the pointer
after a nil check.
Most migration regression tests pinned the schema with "run to head,
then roll back N steps", which silently drifts every time a newer
migration lands: rolling back one step undoes whatever migration is
newest instead of the one under test. All of these tests have been
failing on main since the suite moved past their anchors.

Add migrations.MigrateToVersion (wraps golang-migrate's Migrate) and
convert the tests to pin the exact version below the migration under
test, cycling that migration's own up/down before running the full
chain to head.

Also repaired while converting:
- 000053: confdeltype is the "char" type (OID 18), which pgx cannot
  scan into *string in binary format; cast to text. The negative-space
  guard expected the recommendations FK to be SET NULL, but it has been
  ON DELETE CASCADE since its creation in 000030.
- 000057/backfill: later group migrations (000064 Purchaser backfill)
  legitimately add groups to admins, so assert exact mappings right
  after 000057 / 000056 and only invariants at head.
- 000060: purchase_history.id is a UUID; the seeded value used
  non-hex characters and could never insert.
- savings_snapshots_pk: golang-migrate keeps a single schema_migrations
  row, so counting rows above version 26 always returned 1 and the
  rollback loop never reached 000026.
…ners bumps

govulncheck (v1.1.4, as pinned in CI) reported reachable
vulnerabilities on main:
- golang.org/x/crypto v0.49.0 (GO-2026-5013/5017/5018/5019/5020) ->
  v0.53.0
- golang.org/x/net v0.52.0 (GO-2026-5026, GO-2026-4918) -> v0.56.0
- github.com/docker/docker v28.5.1 (GO-2026-4883, GO-2026-4887; no
  fixed release under that module path) -> unreachable after upgrading
  testcontainers-go to v0.42.0, which switched to the moby client
  modules.
- providers/azure and providers/gcp: x/net and protobuf bumps for the
  same advisories.

testcontainers v0.42 changed MappedPort to return the moby
network.Port value type; adapt testhelpers to port.Num().

All five modules scanned by the CI security job now report zero
call-level findings with the same pinned govulncheck.
@cristim cristim added triaged Item has been triaged priority/p1 Next up; this sprint severity/high Significant harm urgency/this-sprint Within the current sprint impact/internal Team-internal only effort/m Days type/bug Defect labels Jun 11, 2026
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR introduces comprehensive E2E testing infrastructure alongside a major refactoring of database migration tests to use deterministic schema pinning. It updates analytics and store contracts to reflect run-rate averaging and nil-return patterns, hardens CI/entrypoint scripts, refactors materialized view indexes, and bumps module dependencies.

Changes

E2E Testing Infrastructure & CI Integration

Layer / File(s) Summary
E2E Test Module and Docker Configuration
tests/e2e/go.mod, tests/e2e/e2e_test.go, Dockerfile.test, .gitignore
New standalone E2E module targets running HTTP server via API_URL, implements health/version/auth-check tests with 90s readiness polling, builds test image with pinned Go Alpine, pre-compiles and validates tests at build time, and explicitly includes Dockerfile.test in git tracking.
Docker Compose E2E Stack Configuration
docker-compose.test.yml
Adds admin credentials, disables scheduled-task auth for local/CI, sets email fallback, updates test-runner to execute go test -v -tags=e2e ./... with test profile gating, and clarifies E2E test scope targeting cudly-app.
CI Pipeline E2E Support and Entrypoint Hardening
.github/workflows/ci.yml, scripts/entrypoint.sh
Extends govulncheck to scan tests/e2e module, replaces E2E job with explicit Docker Compose build/run/teardown steps using test profile and --abort-on-container-exit, removes continue-on-error for strict exit-code handling, and hardens entrypoint to handle unset DB_PASSWORD_SECRET/SECRET_PROVIDER under set -u.

Migration Testing Infrastructure and Deterministic Schema Pinning

Layer / File(s) Summary
MigrateToVersion Helper Function
internal/database/postgres/migrations/migrate.go
Adds new exported MigrateToVersion(ctx, pool, migrationsPath, version) that migrates to exact target version, validates non-dirty state and version match, logs completion, and skips post-migration Go bootstrap—enabling deterministic test setup without rollback-from-head.
FK Constraint Migration Tests (000053, 000057)
internal/database/postgres/migrations/000053_executions_account_fk_restrict_test.go, 000057_drop_user_role_to_groups_test.go
Pins 000053 FK test to version 53 and 000057 role-to-groups test to version 56/57 using MigrateToVersion, replaces rollback-from-head approach, updates FK assertions to cast confdeltype to text and expect recommendations.cloud_account_id as ON DELETE CASCADE, adjusts test sequence to delete execution rows instead of cancelling for RESTRICT semantics.
Savings Plan & Cleanup Migration Tests (000060, 000040, 000027)
internal/database/postgres/migrations/000060_cleanup_universal_plans_test.go, split_savingsplans_test.go, savings_snapshots_pk_test.go
Refactors 000060 to pin to version 59 before cleanup verification, 000040 to pin to version 39 with fixture seeding and full-chain re-application, and savings-snapshots PK to pin to version 27 then rollback exactly one step; replaces count-rows rollback logic with explicit version targeting and comprehensive idempotency checks.
Admin Group Backfill Migration Test (000055/56/57)
internal/database/postgres/migrations/backfill_admin_group_ids_test.go
Pins test to version 55 before admin seeding, applies migration 56 in isolation to test SQL backfill mechanism, refactors idempotency to migrate to 57, rollback 1, then run to head, and asserts Administrators group counts instead of full array equality.
Purchaser Group Relocation Migration Test (000064)
internal/database/postgres/migrations/000064_relocate_purchaser_group_test.go
Pins test to version 63 immediately before 000064 application, ensuring isolated backfill behavior test instead of head-then-rollback setup.

Schema and Behavior Contract Updates

Layer / File(s) Summary
Materialized View Unique Index Schema Migration
internal/database/postgres/migrations/000076_matview_unique_indexes_plain_columns.up.sql, 000076_matview_unique_indexes_plain_columns.down.sql
Drops expression-based unique indexes on monthly_savings_summary, daily_savings_trend, and provider_savings_summary materialized views, recreates plain-column unique indexes with NULLS NOT DISTINCT to support concurrent refresh (avoiding SQLSTATE 55000 expression-index limitation); down migration restores expression indexes with COALESCE wrapping.
Analytics Run-Rate Averaging Contract
internal/analytics/postgres_analytics_db_test.go, postgres_analytics_integration_test.go
Updates QueryByProvider test assertions to expect TotalSavings as average of snapshots (150.00 instead of 300.00), explicitly verifies AvgCoverage presence and averaged value (77.5), aligns test expectations with run-rate aggregation semantics.
Store Contract Updates and Test Infrastructure
internal/config/store_postgres_db_test.go, store_postgres_recommendations_test.go, internal/server/integration_test.go, internal/database/postgres/testhelpers/postgres.go
Updates GetExecutionByID to return (nil, nil) for not-found cases instead of error, changes recommendation filter field from MinSavings to MinSavingsUSD reflecting dollar-floor semantics, adds internal/scheduler import for mock types, and fixes test container port initialization to use int(port.Num()) instead of port.Int().

Module Dependency Updates

Layer / File(s) Summary
Root Module Dependencies
go.mod
Bumps github.com/stretchr/objx, golang.org/x/crypto, golang.org/x/net, golang.org/x/oauth2, golang.org/x/sync, golang.org/x/sys, golang.org/x/text, golang.org/x/term, github.com/testcontainers/testcontainers-go, github.com/Azure/go-ansiterm, and multiple transitive indirect libraries (github.com/ebitengine/purego, github.com/klauspost/compress, github.com/moby/*, github.com/shirou/gopsutil/v4, github.com/sirupsen/logrus); removes indirect entries for github.com/grpc-ecosystem/grpc-gateway/v2, github.com/moby/sys/user, github.com/morikuni/aec, github.com/pkg/errors.
Azure and GCP Provider Dependencies
providers/azure/go.mod, providers/gcp/go.mod
Adds github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/billingbenefits/armbillingbenefits v1.0.0, upgrades golang.org/x/sync to v0.21.0, bumps golang.org/x/crypto, golang.org/x/net, golang.org/x/sys, golang.org/x/text to newer versions, upgrades google.golang.org/protobuf to v1.36.10 in GCP provider.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • LeanerCloud/CUDly#1136: Both PRs modify the CI E2E job to run Docker Compose against docker-compose.test.yml using the docker compose CLI (v2) with similar profile and teardown patterns.
  • LeanerCloud/CUDly#1049: Main PR's analytics tests adjust TotalSavings assertions to use run-rate averaging semantics, directly aligned with the analytics snapshot/query contract changes from this PR.
  • LeanerCloud/CUDly#579: Both PRs touch internal/database/postgres/migrations/backfill_admin_group_ids_test.go—retrieved PR adds the backfill idempotency test, main PR refactors it to use MigrateToVersion for deterministic schema pinning.

Suggested labels

impact/many, type/testing

Poem

🐇 Bouncing through migrations with version-pinned grace,
E2E tests in Docker now run their own race,
Run-rate averages and nil-returns so clean,
The finest-grained testing I've ever seen!
🧪✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% 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 PR title accurately describes the main objective: fixing CI jobs (Lint, Integration, Security, and E2E) that were failing on main.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/main-ci-failing-jobs

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

@cristim

cristim commented Jun 11, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

cristim added 2 commits June 10, 2026 18:04
…ract

The integration test expected GetExecutionByID to return a "not found"
error, but the store's documented contract (and all 14 production call
sites) returns (nil, nil) for missing executions, with each caller
mapping the nil result to its own not-found error. The stale assertion
also dereferenced the nil error, panicking the suite. This was masked
until the package's integration build was fixed.
The E2E Tests job failed in seconds on main because the compose
invocation omitted --profile test (the test-runner service is
profile-gated) and the referenced Dockerfile.test plus the test suite
did not exist; continue-on-error then masked the failure from the
ci-success gate.

- Add tests/e2e: stdlib-only black-box suite (health readiness with
  lazy-init nudge, /version, auth enforcement) as its own Go module.
- Add Dockerfile.test for the test-runner image, digest-pinned to the
  same golang base as Dockerfile, pre-compiling the suite at build.
- docker-compose.test.yml: wire admin bootstrap secrets via the env
  secret provider, allow the dev encryption key, disable email and
  scheduled-task auth, and run the e2e-tagged suite as the command.
- scripts/entrypoint.sh: guard DB_PASSWORD_SECRET/SECRET_PROVIDER
  expansions for set -u so the env-provider compose stack boots.
- ci.yml: pass --profile test to every compose call, build via
  compose, drop continue-on-error so the job gates ci-success, and
  scan the new tests/e2e module with govulncheck.
- .gitignore: exempt Dockerfile.test from the *.test binary pattern.

Verified locally: the full compose stack builds and the suite passes
end to end against postgres + the app container.
@cristim cristim changed the title fix(ci): repair Lint, Integration and Security jobs failing on main fix(ci): repair Lint, Integration, Security and E2E jobs failing on main Jun 11, 2026
@cristim

cristim commented Jun 11, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim
cristim merged commit 4108e51 into main Jun 11, 2026
10 of 15 checks passed
cristim added a commit that referenced this pull request Jul 1, 2026
- golangci-lint: add only-new-issues: true so the action uses
  --new-from-rev to report only violations introduced in the diff;
  2876 pre-existing violations (surfaced after the v1-to-v2 config
  migration in #1220) are not new code and should be addressed in
  follow-up PRs rather than blocking every CI run
- npm audit: add --omit=dev so the HIGH-level gate covers only
  production dependencies; all remaining HIGH vulnerabilities are in
  dev tooling (typescript-eslint, webpack plugins) and do not affect
  the deployed application
@cristim
cristim deleted the fix/main-ci-failing-jobs branch July 27, 2026 11:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/m Days impact/internal Team-internal only priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/bug Defect urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant