109 lines
4.5 KiB
Go
109 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Catches version regressing into a descriptor-dependent operator command.
|
|
func TestVersionCommandIsDescriptorFreeAndMachineReadable(t *testing.T) {
|
|
projectRoot := newProjectWithoutInstallation(t)
|
|
previousDirectory, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Chdir(filepath.Join(projectRoot, "frontend")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = os.Chdir(previousDirectory) })
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
if code := run(context.Background(), []string{"version", "--json"}, &stdout, &stderr); code != 0 {
|
|
t.Fatalf("run(version --json) = %d, stderr = %q", code, stderr.String())
|
|
}
|
|
var value struct {
|
|
Version string `json:"version"`
|
|
Commit string `json:"commit"`
|
|
OS string `json:"os"`
|
|
Arch string `json:"arch"`
|
|
}
|
|
decoder := json.NewDecoder(&stdout)
|
|
if err := decoder.Decode(&value); err != nil || value.Version == "" || value.Commit == "" || value.OS == "" || value.Arch == "" {
|
|
t.Fatalf("version JSON = %q, decoded %#v, err = %v", stdout.String(), value, err)
|
|
}
|
|
if err := decoder.Decode(&struct{}{}); err != io.EOF {
|
|
t.Fatalf("version stdout contains more than one JSON document: %q", stdout.String())
|
|
}
|
|
}
|
|
|
|
// Catches doctor --json writing a banner, progress, or Docker diagnostics to stdout.
|
|
func TestDoctorJSONWritesOnlyOneReportDocument(t *testing.T) {
|
|
fixture := newCLIFixture(t, "SAFE_VALUE=1\n")
|
|
fixture.setEnvironment(t)
|
|
fixture.generateModelProjection(t)
|
|
fullHealth := `[{"Service":"core","State":"running","Health":"healthy"},{"Service":"frontend","State":"running","Health":"healthy"},{"Service":"qdrant","State":"running","Health":"healthy"},{"Service":"embedding","State":"running","Health":"healthy"},{"Service":"embedding-model-init","State":"exited","ExitCode":0}]`
|
|
t.Setenv("THT_FAKE_PS", fullHealth)
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
code := run(context.Background(), []string{"--installation", fixture.installationPath, "doctor", "--json"}, &stdout, &stderr)
|
|
if code != 0 && code != 1 {
|
|
t.Fatalf("run(doctor --json) = %d, stderr = %q", code, stderr.String())
|
|
}
|
|
var report struct {
|
|
OK *bool `json:"ok"`
|
|
Checks []json.RawMessage `json:"checks"`
|
|
}
|
|
decoder := json.NewDecoder(&stdout)
|
|
if err := decoder.Decode(&report); err != nil || report.OK == nil || report.Checks == nil {
|
|
t.Fatalf("doctor JSON = %q, decoded %#v, err = %v", stdout.String(), report, err)
|
|
}
|
|
if err := decoder.Decode(&struct{}{}); err != io.EOF {
|
|
t.Fatalf("doctor stdout contains more than one JSON document: %q", stdout.String())
|
|
}
|
|
if strings.Contains(stdout.String(), "Doctor checks passed") || strings.Contains(stdout.String(), "tht:") {
|
|
t.Fatalf("doctor JSON stdout contains presentation text: %q", stdout.String())
|
|
}
|
|
}
|
|
|
|
// Catches start --build being parsed as an argument but never reaching the shared lifecycle runner.
|
|
func TestStartBuildOptionBuildsBeforeUpWhilePlainStartDoesNotBuild(t *testing.T) {
|
|
fullHealth := `[{"Service":"core","State":"running","Health":"healthy"},{"Service":"frontend","State":"running","Health":"healthy"},{"Service":"qdrant","State":"running","Health":"healthy"},{"Service":"embedding","State":"running","Health":"healthy"},{"Service":"embedding-model-init","State":"exited","ExitCode":0}]`
|
|
for _, test := range []struct {
|
|
name string
|
|
args []string
|
|
wantBuild bool
|
|
}{
|
|
{name: "plain", args: []string{"start"}, wantBuild: false},
|
|
{name: "build", args: []string{"start", "--build"}, wantBuild: true},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
fixture := newCLIFixture(t, "SAFE_VALUE=1\n")
|
|
fixture.setEnvironment(t)
|
|
t.Setenv("THT_FAKE_PS", fullHealth)
|
|
var stdout, stderr bytes.Buffer
|
|
if code := run(context.Background(), append([]string{"--installation", fixture.installationPath}, test.args...), &stdout, &stderr); code != 0 {
|
|
t.Fatalf("run(%v) = %d, stderr = %q", test.args, code, stderr.String())
|
|
}
|
|
calls := fixture.invocations(t)
|
|
joined := make([]string, 0, len(calls))
|
|
for _, call := range calls {
|
|
joined = append(joined, strings.Join(call, " "))
|
|
}
|
|
sequence := strings.Join(joined, "\n")
|
|
hasBuild := strings.Contains(sequence, " build")
|
|
if hasBuild != test.wantBuild {
|
|
t.Fatalf("start calls = %s, build present = %t, want %t", sequence, hasBuild, test.wantBuild)
|
|
}
|
|
if test.wantBuild && strings.Index(sequence, " build") > strings.Index(sequence, " up --detach --remove-orphans") {
|
|
t.Fatalf("start calls = %s, want build before up", sequence)
|
|
}
|
|
})
|
|
}
|
|
}
|