diff --git a/cmd/multi_service_csv_strict_test.go b/cmd/multi_service_csv_strict_test.go index f629cac1c..ac0ed3fba 100644 --- a/cmd/multi_service_csv_strict_test.go +++ b/cmd/multi_service_csv_strict_test.go @@ -1,7 +1,7 @@ package main import ( - "os" + "context" "path/filepath" "testing" @@ -11,9 +11,7 @@ import ( func loadCSVContent(t *testing.T, content string) error { t.Helper() - csvPath := filepath.Join(t.TempDir(), "recs.csv") - require.NoError(t, os.WriteFile(csvPath, []byte(content), 0o600)) - _, err := loadRecommendationsFromCSV(csvPath) + _, err := loadRecommendationsFromCSV(writeTestRecommendationsCSV(t, content)) return err } @@ -46,8 +44,7 @@ func TestLoadRecommendationsFromCSV_StrictCount(t *testing.T) { } func TestLoadRecommendationsFromCSV_CountTrimmed(t *testing.T) { - csvPath := filepath.Join(t.TempDir(), "recs.csv") - require.NoError(t, os.WriteFile(csvPath, []byte("Service,Region,ResourceType,Count\nrds,us-east-1,db.t3.micro,\" 3 \"\n"), 0o600)) + csvPath := writeTestRecommendationsCSV(t, "Service,Region,ResourceType,Count\nrds,us-east-1,db.t3.micro,\" 3 \"\n") recs, err := loadRecommendationsFromCSV(csvPath) require.NoError(t, err) require.Len(t, recs, 1) @@ -86,10 +83,42 @@ func TestLoadRecommendationsFromCSV_StrictEstimatedSavings(t *testing.T) { // A blank EstimatedSavings cell stays absent-as-zero: requireRankingSignal // relies on it to refuse a binding --max-instances cap. func TestLoadRecommendationsFromCSV_BlankSavingsStillZero(t *testing.T) { - csvPath := filepath.Join(t.TempDir(), "recs.csv") - require.NoError(t, os.WriteFile(csvPath, []byte("Service,Region,ResourceType,Count,EstimatedSavings\nrds,us-east-1,db.t3.micro,2, \n"), 0o600)) + csvPath := writeTestRecommendationsCSV(t, "Service,Region,ResourceType,Count,EstimatedSavings\nrds,us-east-1,db.t3.micro,2, \n") recs, err := loadRecommendationsFromCSV(csvPath) require.NoError(t, err) require.Len(t, recs, 1) assert.Zero(t, recs[0].EstimatedSavings) } + +func TestRunToolFromCSV_RejectsMalformedNumbers(t *testing.T) { + isolateAWSEnv(t) + tests := []struct { + name string + count string + savings string + column string + }{ + {"fractional count", "3.7", "100", `column 4 "Count"`}, + {"count suffix", "12 units", "100", `column 4 "Count"`}, + {"negative count", "-5", "100", `column 4 "Count"`}, + {"count overflow", "99999999999999999999", "100", `column 4 "Count"`}, + {"savings suffix", "2", "1000 USD", `column 5 "EstimatedSavings"`}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := Config{ + CSVInput: writeTestRecommendationsCSV(t, "Service,Region,ResourceType,Count,EstimatedSavings\n"+ + "rds,us-east-1,db.t3.micro,"+tt.count+","+tt.savings+"\n"), + CSVOutput: filepath.Join(t.TempDir(), "report.csv"), + AuditLog: filepath.Join(t.TempDir(), "audit.jsonl"), + ActualPurchase: false, + Coverage: 100, + } + err := runToolFromCSV(context.Background(), cfg) + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to read CSV file: CSV line 2:") + assert.Contains(t, err.Error(), tt.column) + assert.NoFileExists(t, cfg.CSVOutput) + }) + } +}