package main import ( "bytes" "context" "encoding/json" "errors" "fmt" "net" "path/filepath" "testing" "time" "tera-clone/services/tera-api/internal/serial" ) type stubController struct { states []serial.State err error } func (s stubController) States(context.Context) ([]serial.State, error) { return s.states, s.err } func (stubController) Apply(context.Context, []serial.ChannelConfig) ([]serial.State, error) { panic("read-only checker must not apply serial configuration") } func TestInspectPassesCanonicalAvailableInventory(t *testing.T) { checkedAt := time.Date(2026, 7, 21, 8, 0, 0, 0, time.UTC) report := inspect(context.Background(), stubController{states: canonicalStates()}, defaultHardwareProfile, defaultMappingVersion, checkedAt) if report.Status != "pass" || report.ObservedChannels != 48 || report.AvailableChannels != 48 { t.Fatalf("report = %#v", report) } if len(report.Findings) != 0 { t.Fatalf("findings = %#v", report.Findings) } } func TestRunChecksSerialdSocketEndToEnd(t *testing.T) { socketPath := filepath.Join(t.TempDir(), "serial-check.sock") listener, err := net.Listen("unix", socketPath) if err != nil { t.Skipf("unix sockets unavailable: %v", err) } server := serial.SocketServer{Controller: stubController{states: canonicalStates()}} done := make(chan error, 1) go func() { done <- server.Serve(listener) }() t.Cleanup(func() { _ = listener.Close() select { case err := <-done: if err != nil { t.Errorf("socket server: %v", err) } case <-time.After(time.Second): t.Error("socket server did not stop") } }) var stdout bytes.Buffer var stderr bytes.Buffer exitCode := run( []string{"-socket", socketPath}, &stdout, &stderr, func() time.Time { return time.Date(2026, 7, 21, 8, 0, 0, 0, time.UTC) }, ) if exitCode != 0 { t.Fatalf("exit = %d, stderr = %q, stdout = %q", exitCode, stderr.String(), stdout.String()) } var report checkReport if err := json.Unmarshal(stdout.Bytes(), &report); err != nil { t.Fatalf("decode report: %v", err) } if report.Status != "pass" || report.AvailableChannels != serial.ChannelCount { t.Fatalf("report = %#v", report) } } func TestInspectFailsOnUnavailableChannelWithoutApplying(t *testing.T) { states := canonicalStates() states[47].Available = false states[47].Message = "serial device PCI ancestry is unavailable" report := inspect(context.Background(), stubController{states: states}, defaultHardwareProfile, defaultMappingVersion, time.Time{}) if report.Status != "fail" || report.AvailableChannels != 47 { t.Fatalf("report = %#v", report) } if !hasFinding(report.Findings, "CH48", "availability") { t.Fatalf("findings = %#v", report.Findings) } } func TestInspectFailsOnNCNodeOrWrongPCIGroup(t *testing.T) { states := canonicalStates() states[0].Device = "/dev/ttyWCH0" states[24].PCIBDF = "0000:01:00.0" report := inspect(context.Background(), stubController{states: states}, defaultHardwareProfile, defaultMappingVersion, time.Time{}) if report.Status != "fail" { t.Fatalf("report = %#v", report) } if !hasFinding(report.Findings, "CH01", "device_mapping") || !hasFinding(report.Findings, "CH25", "pci_ancestry") { t.Fatalf("findings = %#v", report.Findings) } } func TestInspectReportsSerialdFailure(t *testing.T) { report := inspect(context.Background(), stubController{err: errors.New("socket unavailable")}, defaultHardwareProfile, defaultMappingVersion, time.Time{}) if report.Status != "fail" || !hasFinding(report.Findings, "", "seriald_states") { t.Fatalf("report = %#v", report) } } func canonicalStates() []serial.State { states := make([]serial.State, 0, serial.ChannelCount) for channelNumber := 1; channelNumber <= serial.ChannelCount; channelNumber++ { ttyNumber := channelNumber + 3 bdf := "0000:01:00.0" if channelNumber > 24 { ttyNumber = channelNumber + 7 bdf = "0002:21:00.0" } states = append(states, serial.State{ Channel: fmt.Sprintf("CH%02d", channelNumber), Device: fmt.Sprintf("/dev/ttyWCH%d", ttyNumber), PCIBDF: bdf, HardwareProfile: defaultHardwareProfile, MappingVersion: defaultMappingVersion, Available: true, }) } return states } func hasFinding(findings []finding, channel, check string) bool { for _, item := range findings { if item.Channel == channel && item.Check == check { return true } } return false }