package preflight import ( "context" "encoding/json" "errors" "io" "strings" "testing" "github.com/aritmolab/thothii/tools/tht/internal/compose" "github.com/aritmolab/thothii/tools/tht/internal/config" ) type fakeDocker struct { calls [][]string fail string effective string } func (f *fakeDocker) Run(_ context.Context, args []string, _ io.Reader) (compose.Result, error) { f.calls = append(f.calls, args) if strings.Contains(strings.Join(args, " "), f.fail) && f.fail != "" { return compose.Result{Stderr: "PRIVATE_SENTINEL"}, errors.New("PRIVATE_SENTINEL") } switch args[0] { case "info": return compose.Result{Stdout: `{"OSType":"linux","Architecture":"x86_64","NCPU":4,"MemTotal":17179869184}`}, nil case "compose": if args[len(args)-1] == "json" { if f.effective != "" { return compose.Result{Stdout: f.effective}, nil } return compose.Result{Stdout: `{"services":{"core":{"image":"example/core:latest"}}}`}, nil } return compose.Result{Stdout: "2.39.0"}, nil case "manifest": return compose.Result{Stdout: `{"Descriptor":{"digest":"sha256:` + strings.Repeat("a", 64) + `","platform":{"os":"linux","architecture":"amd64"}}}`}, nil } return compose.Result{}, errors.New("unexpected command") } func TestComposeRejectsIncompleteMutableServiceSet(t *testing.T) { r := CheckCompose(context.Background(), &fakeDocker{}, config.Installation{ProjectDirectory: "/private", EnvFile: "/private/operator.env"}, Manifest{Compose: []string{"compose.yaml"}}, "/release/manifest.json", "linux/amd64") if r.OK { t.Fatal("unreleased service set accepted") } } func TestComposeRejectsPlatformOverrideAgainstSelectedImage(t *testing.T) { m := Manifest{Images: map[string]map[string]string{}, Compose: []string{"compose.yaml"}} services := map[string]map[string]string{} for service, role := range map[string]string{"core": "core", "frontend": "frontend", "catalog-db": "catalog", "catalog-migrate": "core", "workspace-maintenance": "core", "qdrant": "qdrant", "embedding": "embedding", "embedding-model-init": "embedding"} { m.Images[role] = map[string]string{"linux/amd64": "docker.io/example/" + role + "@sha256:" + strings.Repeat("a", 64)} services[service] = map[string]string{"image": m.Images[role]["linux/amd64"]} } services["core"]["platform"] = "linux/arm64" data, _ := json.Marshal(map[string]any{"services": services}) r := CheckCompose(context.Background(), &fakeDocker{effective: string(data)}, config.Installation{}, m, "/release/manifest.json", "linux/amd64") if r.OK { t.Fatal("incompatible Compose platform accepted") } } func TestHostChecksAreReadOnlyAndRejectUnavailableDocker(t *testing.T) { f := &fakeDocker{} host := Host{OS: "linux", Arch: "amd64", Kernel: "6.6-microsoft-standard-WSL2", Distribution: "ubuntu", FreeBytes: 30 << 30} r := CheckHost(context.Background(), f, host, Requirements{CPUs: 2, MemoryBytes: 4 << 30, DiskBytes: 10 << 30}) if !r.OK { t.Fatalf("host rejected: %+v", r) } for _, args := range f.calls { if args[0] != "info" && !(args[0] == "compose" && args[1] == "version") { t.Fatalf("mutating call: %v", args) } } f.fail = "info" r = CheckHost(context.Background(), f, host, Requirements{CPUs: 2, MemoryBytes: 4 << 30, DiskBytes: 10 << 30}) if r.OK || strings.Contains(r.JSON(), "PRIVATE_SENTINEL") { t.Fatalf("unsafe success/report: %s", r.JSON()) } host.Kernel = "4.4-microsoft" if CheckHost(context.Background(), &fakeDocker{}, host, Requirements{CPUs: 2, MemoryBytes: 4 << 30, DiskBytes: 10 << 30}).OK { t.Fatal("WSL1 accepted") } host.OS = "windows" if CheckHost(context.Background(), &fakeDocker{}, host, Requirements{}).OK { t.Fatal("native Windows accepted instead of WSL2") } } func TestReleaseChecksEveryImmutableImageAndPlatform(t *testing.T) { m := Manifest{SchemaVersion: 1, Version: "1.0.0", Revision: strings.Repeat("b", 40), ValidatorProtocol: 1, Requirements: Requirements{CPUs: 2, MemoryBytes: 4 << 30, DiskBytes: 10 << 30}, Components: []string{"pi", "catalog-migrations", "workspace-maintenance"}, Images: map[string]map[string]string{}} for _, service := range []string{"core", "frontend", "catalog", "qdrant", "embedding"} { m.Images[service] = map[string]string{"linux/amd64": "docker.io/example/" + service + "@sha256:" + strings.Repeat("a", 64)} } m.Files = map[string]string{"deploy/compose.yaml": strings.Repeat("c", 64)} m.Compose = []string{"deploy/compose.yaml"} if err := m.Validate(); err != nil { t.Fatal(err) } f := &fakeDocker{} r := CheckImages(context.Background(), f, m, "linux/amd64") if !r.OK || len(f.calls) != 5 { t.Fatalf("images not checked: %s calls=%d", r.JSON(), len(f.calls)) } if CheckImages(context.Background(), f, m, "linux/arm64").OK { t.Fatal("unsupported release architecture accepted") } f.fail = "frontend" if CheckImages(context.Background(), f, m, "linux/amd64").OK { t.Fatal("missing image accepted") } m.Images["core"]["linux/amd64"] = "example/core:latest" if m.Validate() == nil { t.Fatal("mutable tag accepted") } }