From 38e2c29ff93afe124f6ce4fb07104694c3309575 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Mon, 19 Oct 2026 21:20:44 +0200 Subject: [PATCH] fix(csv): label purchase IDs with the coverage the CSV run applied Without --coverage an --input-csv run buys 100% of the CSV counts, but the purchase ID carried the unused flag default (80pct). Pin cfg.Coverage to determineCSVCoverage at the start of runCSVPurchases so the label and the sizing share one source. Fixes #2248 Co-Authored-By: Claude Opus 5.5 --- cmd/csv_real_purchase_test.go | 29 +++++++++++++++++++++++++++++ cmd/multi_service.go | 4 ++++ 2 files changed, 33 insertions(+) diff --git a/cmd/csv_real_purchase_test.go b/cmd/csv_real_purchase_test.go index a3deb8aae..90def6f85 100644 --- a/cmd/csv_real_purchase_test.go +++ b/cmd/csv_real_purchase_test.go @@ -103,6 +103,8 @@ type csvPurchaseEnv struct { // expectPurchaseFailure makes run require errPurchasesFailed (#2157): // a real run with any failed purchase now returns it so the CLI exits non-zero. expectPurchaseFailure bool + // cfgMod adjusts the Config run builds, e.g. to mimic the flags of a real invocation. + cfgMod func(*Config) } // newCSVPurchaseEnv wires the stub with the RDS and ElastiCache offering and @@ -135,6 +137,9 @@ func newCSVPurchaseEnvFor(t *testing.T, csv string, fixtureSets ...map[string]aw recs, err := loadRecommendationsFromCSV(csvPath) require.NoError(t, err) cfg := Config{CallerAccountID: defaultTestAccount, CSVOutput: filepath.Join(dir, report), AuditLog: filepath.Join(dir, "audit.jsonl")} + if env.cfgMod != nil { + env.cfgMod(&cfg) + } err = runCSVPurchases(context.Background(), awsCfg, recs, false, cfg, "run-"+report) if env.expectPurchaseFailure { require.ErrorIs(t, err, errPurchasesFailed) @@ -392,3 +397,27 @@ func TestLoadRecommendationsFromCSV_MemoryDBEngineIsCanonical(t *testing.T) { assert.Equal(t, &common.CacheDetails{Engine: "redis", NodeType: "db.t4g.small"}, recs[0].Details, "engine=%q", engine) } } + +// TestCSVRealPurchase_IDLabelsAppliedCoverage pins #2248: the generated id's +// sizing label must match the coverage the CSV run applied. Without --coverage +// the flag default (80) is unused and the rows are bought at 100%. RDS uses the +// generated id as ReservedDBInstanceId; the ElastiCache client names its own. +func TestCSVRealPurchase_IDLabelsAppliedCoverage(t *testing.T) { + tests := []struct { + name string + cfg func(*Config) + want string + }{ + {"no --coverage buys 100%", func(c *Config) { c.Coverage = 80; c.CoverageFromDefault = true }, "-100pct-"}, + {"explicit --coverage 100", func(c *Config) { c.Coverage = 100 }, "-100pct-"}, + {"target-coverage wins", func(c *Config) { c.Coverage = 80; c.CoverageFromDefault = true; c.TargetCoverage = 100 }, "-100pct-"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + env := newCSVPurchaseEnv(t) + env.cfgMod = tt.cfg + env.run("report1.csv") + assert.Contains(t, env.purchaseForm(t, rdsHost+" "+rdsPurchase).Get("ReservedDBInstanceId"), tt.want) + }) + } +} diff --git a/cmd/multi_service.go b/cmd/multi_service.go index bdab60015..981b40ebc 100644 --- a/cmd/multi_service.go +++ b/cmd/multi_service.go @@ -771,6 +771,10 @@ func runToolFromCSV(ctx context.Context, cfg Config) error { // them, and writes the report and summary for whatever ran. A non-nil error // is errAuditTrailLost and/or errPurchasesFailed. func runCSVPurchases(ctx context.Context, awsCfg aws.Config, recs []common.Recommendation, isDryRun bool, cfg Config, runID string) error { + // Purchase IDs label the sizing via cfg.Coverage (effectiveSizingPct); + // pin it to the coverage the CSV sizing used so the label cannot drift (#2248). + cfg.Coverage = determineCSVCoverage(cfg) + // Create account alias cache for lookup accountCache := NewAccountAliasCache(awsCfg)