package upsquality import ( "context" "os" "path/filepath" "strings" "testing" "scm.bstein.dev/bstein/ananke/internal/ups" ) // TestNUTProviderReadRejectsEmptyTarget runs one orchestration or CLI step. // Signature: TestNUTProviderReadRejectsEmptyTarget(t *testing.T). // Why: validates that provider configuration errors fail fast before shelling out. func TestNUTProviderReadRejectsEmptyTarget(t *testing.T) { p := ups.NewNUTProvider("") _, err := p.Read(context.Background()) if err == nil || !strings.Contains(err.Error(), "must not be empty") { t.Fatalf("expected empty target validation error, got %v", err) } } // TestNUTProviderReadParsesNominalSample runs one orchestration or CLI step. // Signature: TestNUTProviderReadParsesNominalSample(t *testing.T). // Why: covers normal NUT parsing for runtime, charge, load, and nominal power fields. func TestNUTProviderReadParsesNominalSample(t *testing.T) { dir := t.TempDir() upsc := filepath.Join(dir, "upsc") script := `#!/usr/bin/env sh cat <<'EOF' ups.status: OB LB battery.runtime: 812.0 battery.charge: 72.5 ups.load: 41 ups.realpower.nominal: 900 EOF ` if err := os.WriteFile(upsc, []byte(script), 0o755); err != nil { t.Fatalf("write fake upsc: %v", err) } t.Setenv("PATH", dir+":"+os.Getenv("PATH")) p := ups.NewNUTProvider("statera@localhost") sample, err := p.Read(context.Background()) if err != nil { t.Fatalf("read sample: %v", err) } if !sample.OnBattery || !sample.LowBattery { t.Fatalf("expected OB+LB status, got %+v", sample) } if sample.RuntimeSeconds != 812 || sample.BatteryCharge != 72.5 || sample.LoadPercent != 41 || sample.NominalPowerW != 900 { t.Fatalf("unexpected parsed values: %+v", sample) } } // TestNUTProviderReadHandlesMissingStatusAndNoisyNumbers runs one orchestration or CLI step. // Signature: TestNUTProviderReadHandlesMissingStatusAndNoisyNumbers(t *testing.T). // Why: covers parse failure paths for missing status and non-numeric field content. func TestNUTProviderReadHandlesMissingStatusAndNoisyNumbers(t *testing.T) { dir := t.TempDir() upsc := filepath.Join(dir, "upsc") // First script invocation: missing ups.status should fail. missingStatus := `#!/usr/bin/env sh cat <<'EOF' battery.runtime: 120 battery.charge: n/a ups.load: unknown EOF ` if err := os.WriteFile(upsc, []byte(missingStatus), 0o755); err != nil { t.Fatalf("write fake upsc: %v", err) } t.Setenv("PATH", dir+":"+os.Getenv("PATH")) p := ups.NewNUTProvider("pyrphoros@localhost") if _, err := p.Read(context.Background()); err == nil || !strings.Contains(err.Error(), "ups.status missing") { t.Fatalf("expected missing status error, got %v", err) } // Second script invocation: noisy numbers should parse to zeros where invalid. noisyNumbers := `#!/usr/bin/env sh cat <<'EOF' ups.status: OL battery.runtime: 61.4 battery.charge: --not-a-number-- ups.load: 34% ups.realpower.nominal: ###watts### EOF ` if err := os.WriteFile(upsc, []byte(noisyNumbers), 0o755); err != nil { t.Fatalf("rewrite fake upsc: %v", err) } sample, err := p.Read(context.Background()) if err != nil { t.Fatalf("read noisy sample: %v", err) } if sample.OnBattery { t.Fatalf("expected OL to clear OnBattery, got %+v", sample) } if sample.RuntimeSeconds != 61 { t.Fatalf("expected runtime integer parse from decimal, got %d", sample.RuntimeSeconds) } if sample.BatteryCharge != 0 { t.Fatalf("expected invalid charge to parse as 0, got %f", sample.BatteryCharge) } if sample.LoadPercent != 34 { t.Fatalf("expected percentage load parse, got %f", sample.LoadPercent) } if sample.NominalPowerW != 0 { t.Fatalf("expected invalid nominal power to parse as 0, got %f", sample.NominalPowerW) } }