You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

148 lines
4.3 KiB

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
}