1+ package output
2+
3+ import (
4+ "testing"
5+ "fmt"
6+ "os"
7+ "time"
8+
9+ "github.com/gpuaudit/cli/internal/models"
10+ )
11+
12+ // Shared test fixture: a single GPU instance with one waste signal and recommendation.
13+ var instance = models.GPUInstance {
14+ InstanceID : "i-1234567890abcdef0" ,
15+ Name : "test-instance" ,
16+ Source : models .SourceEC2 ,
17+ Region : "us-west-2" ,
18+ InstanceType : "p5.24xlarge" ,
19+ GPUModel : "NVIDIA A100" ,
20+ GPUCount : 8 ,
21+ State : "running" ,
22+ MonthlyCost : 24.00 ,
23+ EstimatedSavings : 12.00 ,
24+ WasteSignals : []models.WasteSignal {
25+ {
26+ Type : "underutilized" ,
27+ Severity : models .SeverityWarning ,
28+ Confidence : 0.8 ,
29+ Evidence : "Average GPU utilization is 10%" ,
30+ },
31+ },
32+ Recommendations : []models.Recommendation {
33+ {
34+ Action : "downsize" ,
35+ },
36+ },
37+ }
38+
39+ // Shared test fixture: a scan result wrapping the test instance above.
40+ var result = & models.ScanResult {
41+ Timestamp : time .Now (),
42+ AccountID : "123456789012" ,
43+ Targets : []string {"ec2" },
44+ Regions : []string {"us-west-2" },
45+ ScanDuration : "60" ,
46+ Instances : []models.GPUInstance {instance },
47+ Summary : models.ScanSummary {
48+ TotalInstances : 1 ,
49+ TotalMonthlyCost : 24.00 ,
50+ TotalEstimatedWaste : 12.00 ,
51+ WastePercent : 50.0 ,
52+ CriticalCount : 0 ,
53+ WarningCount : 1 ,
54+ InfoCount : 0 ,
55+ HealthyCount : 0 ,
56+ },
57+ TargetSummaries : []models.TargetSummary {
58+ {
59+ Target : "ec2" ,
60+ TotalInstances : 1 ,
61+ TotalMonthlyCost : 24.00 ,
62+ TotalEstimatedWaste : 12.00 ,
63+ WastePercent : 50.0 ,
64+ CriticalCount : 0 ,
65+ WarningCount : 1 ,
66+ },
67+ },
68+ TargetErrors : []models.TargetErrorInfo {
69+ {
70+ Target : "sagemaker-endpoint" ,
71+ Error : "Access denied" ,
72+ },
73+ },
74+ }
75+
76+ // TestFormatCSV writes a scan result to a temp file and checks for no errors.
77+ func TestFormatCSV (t * testing.T ) {
78+ fileName := "test_output.csv"
79+ file , err := os .OpenFile (fileName , os .O_CREATE | os .O_WRONLY | os .O_TRUNC , 0644 )
80+ if err != nil {
81+ t .Fatalf ("failed to create test output file: %v" , err )
82+ }
83+ defer file .Close ()
84+ defer os .Remove (fileName ) // Clean up after test
85+
86+ if err := FormatCSV (file , result ); err != nil {
87+ t .Fatalf ("FormatCSV failed: %v" , err )
88+ }
89+ }
90+
91+ // TestToCSVRecords checks that the CSV output matches the expected row layout.
92+ func TestToCSVRecords (t * testing.T ) {
93+ // Build the expected row using the same formatting logic as the production code.
94+ expected := [][]string {
95+ {
96+ instance .InstanceID ,
97+ instance .Name ,
98+ fmt .Sprintf ("%s" , instance .Source ),
99+ instance .Region ,
100+ instance .InstanceType ,
101+ instance .GPUModel ,
102+ fmt .Sprintf ("%d" , instance .GPUCount ),
103+ instance .State ,
104+ fmt .Sprintf ("%.4f" , instance .MonthlyCost ),
105+ fmt .Sprintf ("%.4f" , instance .EstimatedSavings ),
106+ "warning" ,
107+ instance .WasteSignals [0 ].Type ,
108+ "downsize" ,
109+ },
110+ }
111+
112+ result := ToCSVRecords (result )
113+
114+ // Only checking length here; a deeper field-by-field check would be more thorough.
115+ if len (result ) != len (expected ) {
116+ t .Fatalf ("expected: %v\n got: %v" , expected , result )
117+ }
118+ }
0 commit comments