Skip to content

feat(test): unified fixture framework with e2e DB test runner#1972

Merged
moshloop merged 3 commits intomainfrom
feat/unified-fixture-framework
Mar 11, 2026
Merged

feat(test): unified fixture framework with e2e DB test runner#1972
moshloop merged 3 commits intomainfrom
feat/unified-fixture-framework

Conversation

@moshloop
Copy link
Member

@moshloop moshloop commented Mar 11, 2026

Summary

  • Split extract testdata into unit/ and e2e/ subdirectories for clear separation
  • New e2e test runner (scrapers/extract_e2e_test.go) reads fixtures with a spec field and runs the full scraper pipeline (Run -> SaveResults -> CEL assertions)
  • Add 4 e2e fixtures testing metadata-only config changes with default vs explicit config_type and cross-config change targeting

Test plan

  • make test-fast passes (40 unit fixtures, 4 e2e fixtures)
  • Verify e2e fixtures run correctly in CI

Summary by CodeRabbit

  • Tests
    • Added comprehensive end-to-end test suite for metadata extraction scenarios.
    • Test fixtures validate extraction with default and explicit configuration type handling across single and multi-change scenarios.
    • Expanded unit test coverage for metadata-only extraction configurations.

Split extract testdata into unit/ and e2e/ subdirectories.
Unit fixtures use MockResolver (no DB), e2e fixtures run
the full scraper pipeline (Run -> SaveResults -> CEL assertions).

Add 4 e2e fixtures testing metadata-only config changes with
variations of default vs explicit config_type and cross-config
change targeting.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 11, 2026

Warning

Rate limit exceeded

@moshloop has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 16 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4a60c98a-78d2-4b1f-ad9b-3a28f4dd1658

📥 Commits

Reviewing files that changed from the base of the PR and between e53652c and ff2408e.

📒 Files selected for processing (5)
  • .github/workflows/codeql.yml
  • .github/workflows/lint.yml
  • .github/workflows/release.yml
  • .github/workflows/test.yml
  • scrapers/extract_e2e_test.go

Walkthrough

The pull request adds comprehensive test coverage for metadata-only extraction scenarios, including new JSON fixture files and YAML test specifications for both unit and end-to-end tests. It introduces a new end-to-end test framework in Go, refactors test fixture discovery, and modifies the default-detection logic in the extraction code. A .gitignore entry is also added.

Changes

Cohort / File(s) Summary
Configuration
.gitignore
Adds ignore pattern for .claude/ directory.
JSON Test Fixtures
fixtures/data/metadata_only_*.json
Introduces four JSON fixture files containing test data for metadata-only changes with varying configurations (default config type, explicit config type, cross-config, and no config type scenarios).
YAML Unit Test Data
scrapers/extract/testdata/unit/metadata_only_*.yaml
Adds two YAML unit test files validating parsed metadata structure with different external ID configurations and change types.
YAML E2E Test Data
scrapers/extract/testdata/e2e/metadata_only_*.yaml
Introduces four YAML end-to-end test specifications for metadata extraction scenarios, including default config type handling, explicit config type override, and cross-config scenarios.
Extraction Logic & Tests
scrapers/extract/extract.go, scrapers/extract/extract_test.go
Modifies default-detection condition in applyConfigRefDefaults to explicitly check individual fields instead of using IsEmpty(), and updates test fixture discovery to load from testdata/unit/*.yaml with environment setup ensuring "config" key presence.
E2E Test Framework
scrapers/extract_e2e_test.go
Introduces comprehensive end-to-end test infrastructure with fixture-based test discovery, database pre-population, scraper execution, result capture, and CEL-based assertion evaluation using Ginkgo/Gomega.

Possibly related PRs

Suggested reviewers

  • adityathebe
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: introducing a unified fixture framework with an end-to-end database test runner, which is reflected throughout the changeset.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/unified-fixture-framework

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
.gitignore (1)

52-52: Remove the duplicate ignore rule.

Line 46 already ignores .claude as both a file and a directory, so .claude/ is redundant here. Keeping just one entry makes the ignore list easier to maintain.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore at line 52, The .gitignore contains a duplicate entry for the
same ignore target: remove the redundant ".claude/" line so only the existing
".claude" entry remains; edit the .gitignore and delete the ".claude/" entry
(the unique symbol to look for is the ".claude" ignore rule) to avoid duplicate
rules and keep the file concise.
scrapers/extract_e2e_test.go (1)

144-151: Consider handling JSON marshal/unmarshal errors for better debuggability.

Silently ignoring errors on lines 145 and 147 could mask issues during test debugging. While the nil check on line 148 provides a fallback, unexpected serialization failures would be harder to diagnose.

♻️ Optional: Add explicit error handling
 	// Marshal changes to map form for CEL
-	changesRaw, _ := json.Marshal(allChanges)
+	changesRaw, err := json.Marshal(allChanges)
+	if err != nil {
+		// Fall back to empty slice if marshal fails
+		return map[string]any{"config": nil, "changes": []any{}}
+	}
 	var changesSlice []any
-	_ = json.Unmarshal(changesRaw, &changesSlice)
+	if err := json.Unmarshal(changesRaw, &changesSlice); err != nil {
+		changesSlice = []any{}
+	}
 	if changesSlice == nil {
 		changesSlice = []any{}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scrapers/extract_e2e_test.go` around lines 144 - 151, The code silently
ignores errors from json.Marshal(allChanges) and json.Unmarshal into
changesSlice; update the block around changesRaw, changesSlice and
env["changes"] to capture and handle both errors (from json.Marshal and
json.Unmarshal) — e.g., check the returned error values and call t.Fatalf or
t.Errorf with descriptive messages including the error and relevant data
(allChanges or changesRaw) so the test fails loudly and is debuggable, and keep
the nil fallback for changesSlice if unmarshalling yields nil.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scrapers/extract_e2e_test.go`:
- Around line 58-63: The defer calling os.Remove(tmpFile.Name()) has its error
return unchecked; change it to explicitly ignore the error (e.g. defer func() {
_ = os.Remove(tmpFile.Name()) }() or defer func() { if err :=
os.Remove(tmpFile.Name()); err != nil && !os.IsNotExist(err) { /* optional log
*/ } }()) so the linter is satisfied; update the defer that references tmpFile
and os.Remove accordingly in extract_e2e_test.go near the tmpFile
creation/cleanup block.
- Around line 88-95: The test cleanup currently deletes ConfigChange,
ConfigItem, and ConfigScraper but misses ConfigLocation rows created by
SaveResults; update the deferred cleanup to also delete orphaned ConfigLocation
records for each id in createdItems by issuing DefaultContext.DB().Where("id =
?", id).Delete(&dutymodels.ConfigLocation{}) alongside the existing deletes so
ConfigLocation entries related to createdItems are removed.

---

Nitpick comments:
In @.gitignore:
- Line 52: The .gitignore contains a duplicate entry for the same ignore target:
remove the redundant ".claude/" line so only the existing ".claude" entry
remains; edit the .gitignore and delete the ".claude/" entry (the unique symbol
to look for is the ".claude" ignore rule) to avoid duplicate rules and keep the
file concise.

In `@scrapers/extract_e2e_test.go`:
- Around line 144-151: The code silently ignores errors from
json.Marshal(allChanges) and json.Unmarshal into changesSlice; update the block
around changesRaw, changesSlice and env["changes"] to capture and handle both
errors (from json.Marshal and json.Unmarshal) — e.g., check the returned error
values and call t.Fatalf or t.Errorf with descriptive messages including the
error and relevant data (allChanges or changesRaw) so the test fails loudly and
is debuggable, and keep the nil fallback for changesSlice if unmarshalling
yields nil.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 550277f0-fce3-4f4a-8d6c-c8502056be06

📥 Commits

Reviewing files that changed from the base of the PR and between 81d70b8 and e53652c.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (52)
  • .gitignore
  • fixtures/data/metadata_only_changes.json
  • fixtures/data/metadata_only_cross_config.json
  • fixtures/data/metadata_only_no_config_type.json
  • fixtures/data/metadata_only_with_config_type.json
  • scrapers/extract/extract.go
  • scrapers/extract/extract_test.go
  • scrapers/extract/testdata/e2e/metadata_only_cross_config.yaml
  • scrapers/extract/testdata/e2e/metadata_only_default_config_type.yaml
  • scrapers/extract/testdata/e2e/metadata_only_e2e.yaml
  • scrapers/extract/testdata/e2e/metadata_only_explicit_config_type.yaml
  • scrapers/extract/testdata/unit/access_invalid_ref.yaml
  • scrapers/extract/testdata/unit/access_logs_basic.yaml
  • scrapers/extract/testdata/unit/access_logs_missing_user.yaml
  • scrapers/extract/testdata/unit/access_logs_resolve_user_alias.yaml
  • scrapers/extract/testdata/unit/access_logs_shorthand_external.yaml
  • scrapers/extract/testdata/unit/access_logs_shorthand_uuid.yaml
  • scrapers/extract/testdata/unit/access_logs_user_shorthand.yaml
  • scrapers/extract/testdata/unit/access_skipped_config_not_found.yaml
  • scrapers/extract/testdata/unit/analysis_basic.yaml
  • scrapers/extract/testdata/unit/analysis_default_from_parent.yaml
  • scrapers/extract/testdata/unit/analysis_invalid_ref.yaml
  • scrapers/extract/testdata/unit/analysis_no_parent.yaml
  • scrapers/extract/testdata/unit/changes_and_access.yaml
  • scrapers/extract/testdata/unit/changes_basic.yaml
  • scrapers/extract/testdata/unit/changes_default_from_parent.yaml
  • scrapers/extract/testdata/unit/changes_invalid_ref.yaml
  • scrapers/extract/testdata/unit/changes_missing_change_id.yaml
  • scrapers/extract/testdata/unit/changes_missing_change_type.yaml
  • scrapers/extract/testdata/unit/changes_no_parent.yaml
  • scrapers/extract/testdata/unit/changes_with_details.yaml
  • scrapers/extract/testdata/unit/combined_aliases.yaml
  • scrapers/extract/testdata/unit/config_ref_defaults.yaml
  • scrapers/extract/testdata/unit/config_ref_shorthand.yaml
  • scrapers/extract/testdata/unit/config_ref_uuid.yaml
  • scrapers/extract/testdata/unit/drop_nil_user_groups.yaml
  • scrapers/extract/testdata/unit/empty_config_access.yaml
  • scrapers/extract/testdata/unit/external_config_id.yaml
  • scrapers/extract/testdata/unit/group_aliases.yaml
  • scrapers/extract/testdata/unit/json_roundtrip.yaml
  • scrapers/extract/testdata/unit/metadata_only_changes.yaml
  • scrapers/extract/testdata/unit/metadata_only_external_id_only.yaml
  • scrapers/extract/testdata/unit/missing_config_access.yaml
  • scrapers/extract/testdata/unit/remap_user_groups.yaml
  • scrapers/extract/testdata/unit/resolve_after_sync.yaml
  • scrapers/extract/testdata/unit/resolve_aliases.yaml
  • scrapers/extract/testdata/unit/reuse_existing_ids.yaml
  • scrapers/extract/testdata/unit/role_aliases.yaml
  • scrapers/extract/testdata/unit/stub_entity_creation.yaml
  • scrapers/extract/testdata/unit/unmarshal_all_aliases.yaml
  • scrapers/extract/testdata/unit/user_aliases.yaml
  • scrapers/extract_e2e_test.go

Comment on lines +88 to +95
// Cleanup after test
defer func() {
for _, id := range createdItems {
DefaultContext.DB().Where("config_id = ?", id).Delete(&models.ConfigChange{})
DefaultContext.DB().Delete(&models.ConfigItem{}, "id = ?", id)
}
DefaultContext.DB().Where("id = ?", scraperModel.ID).Delete(&dutymodels.ConfigScraper{})
}()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check what tables SaveResults writes to
rg -n '\.Create|\.Save|\.Clauses.*Create' db/update.go -A2 | head -50

Repository: flanksource/config-db

Length of output: 1797


🏁 Script executed:

#!/bin/bash
# Find SaveResults function and what it does
rg -n 'func.*SaveResults|func (.*) SaveResults' db/update.go -A 100 | head -150

Repository: flanksource/config-db

Length of output: 3233


🏁 Script executed:

#!/bin/bash
# Search for config_locations table writes across the codebase
rg -n 'config_locations|ConfigLocation' --type go -i | head -30

Repository: flanksource/config-db

Length of output: 234


🏁 Script executed:

#!/bin/bash
# Check what the test actually creates and verifies
cat -n scrapers/extract_e2e_test.go | sed -n '70,100p'

Repository: flanksource/config-db

Length of output: 1401


🏁 Script executed:

#!/bin/bash
# Find the actual saveResults implementation - should show what it does with locations
rg -n 'func saveResults|func \(.*\) saveResults' db/update.go -A 200 | grep -E '(func|locations|CreateInBatches|Create.*Location)' | head -40

Repository: flanksource/config-db

Length of output: 505


🏁 Script executed:

#!/bin/bash
# More targeted search for where locations are persisted
rg -n 'locations.*CreateInBatches|CreateInBatches.*locations|\.CreateInBatches.*Location' db/update.go -B 3 -A 3

Repository: flanksource/config-db

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Check if the test actually invokes SaveResults or just creates test data
rg -n 'SaveResults|saveResults' scrapers/extract_e2e_test.go -B 2 -A 2

Repository: flanksource/config-db

Length of output: 215


🏁 Script executed:

#!/bin/bash
# Check ConfigLocation model definition and foreign keys
rg -n 'type ConfigLocation|ConfigLocation struct' --type go -A 10 | head -30

Repository: flanksource/config-db

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Check if tests use transactions or DB isolation
rg -n 'Transact|tx\.|Transaction|BeginTx' scrapers/extract_e2e_test.go -B 2 -A 2

Repository: flanksource/config-db

Length of output: 236


🏁 Script executed:

#!/bin/bash
# Search more broadly for ConfigLocation definition
rg -n 'ConfigLocation' --type go | head -30

Repository: flanksource/config-db

Length of output: 234


🏁 Script executed:

#!/bin/bash
# Check if test uses any DB isolation or transaction mechanism in the setup
cat -n scrapers/extract_e2e_test.go | sed -n '1,70p'

Repository: flanksource/config-db

Length of output: 2645


🏁 Script executed:

#!/bin/bash
# Search for ConfigLocation model definition in any go files
find . -name "*.go" -type f | xargs grep -l "type ConfigLocation struct" 2>/dev/null

Repository: flanksource/config-db

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check the duty models package import and look for where it might be defined
rg -n 'dutyModels.ConfigLocation' --type go -B 2 -A 2 | head -30

Repository: flanksource/config-db

Length of output: 685


🏁 Script executed:

#!/bin/bash
# Check if there's a go.mod to understand if duty is external
cat go.mod | grep -i duty

Repository: flanksource/config-db

Length of output: 153


Add ConfigLocation to test cleanup.

The test calls SaveResults at line 105, which creates ConfigLocation records (see db/update.go:631-635). The cleanup only removes ConfigChange, ConfigItem, and ConfigScraper records. Add cleanup for orphaned ConfigLocation records related to the test's ConfigItem IDs:

for _, id := range createdItems {
    DefaultContext.DB().Where("id = ?", id).Delete(&dutymodels.ConfigLocation{})
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scrapers/extract_e2e_test.go` around lines 88 - 95, The test cleanup
currently deletes ConfigChange, ConfigItem, and ConfigScraper but misses
ConfigLocation rows created by SaveResults; update the deferred cleanup to also
delete orphaned ConfigLocation records for each id in createdItems by issuing
DefaultContext.DB().Where("id = ?", id).Delete(&dutymodels.ConfigLocation{})
alongside the existing deletes so ConfigLocation entries related to createdItems
are removed.

@moshloop moshloop merged commit d5596ad into main Mar 11, 2026
13 of 15 checks passed
@moshloop moshloop deleted the feat/unified-fixture-framework branch March 11, 2026 05:45
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