Files
ThothII/tools/tht/internal/service/service_test.go
T

128 lines
4.2 KiB
Go

package service
import (
"context"
"errors"
"io"
"strings"
"testing"
"github.com/aritmolab/thothii/tools/tht/internal/compose"
"github.com/aritmolab/thothii/tools/tht/internal/config"
)
// Catches --build being ignored, which would run stale images after a checkout update.
func TestStartBuildsBeforeStartingAndCheckingHealth(t *testing.T) {
stubModelProjection(t, nil)
runner := &recordingRunner{}
installation := testInstallation()
if err := Start(context.Background(), installation, runner, true); err != nil {
t.Fatal(err)
}
if got, want := strings.Join(runner.stages, " -> "), "build -> up -> health"; got != want {
t.Fatalf("Start(..., build=true) stages = %q, want %q", got, want)
}
}
// Catches a normal start unnecessarily rebuilding images.
func TestStartSkipsBuildUnlessRequested(t *testing.T) {
stubModelProjection(t, nil)
runner := &recordingRunner{}
installation := testInstallation()
if err := Start(context.Background(), installation, runner, false); err != nil {
t.Fatal(err)
}
if got, want := strings.Join(runner.stages, " -> "), "up -> health"; got != want {
t.Fatalf("Start(..., build=false) stages = %q, want %q", got, want)
}
}
func TestStartRefusesProjectedAuthenticationBeforeComposeWhenNotReady(t *testing.T) {
stubModelProjection(t, nil)
for _, state := range []string{"missing", "blocked", "divergent"} {
t.Run(state, func(t *testing.T) {
previous := requireRuntimeAuthProjectionReady
requireRuntimeAuthProjectionReady = func(installation config.Installation) error {
if !installation.HasRuntimeAuthProjection() {
t.Fatal("readiness gate received an unprojected installation")
}
return errors.New("synthetic " + state + " projection")
}
t.Cleanup(func() { requireRuntimeAuthProjectionReady = previous })
runner := &recordingRunner{}
err := Start(context.Background(), projectedTestInstallation(), runner, true)
if err == nil {
t.Fatalf("Start() accepted %s runtime projection", state)
}
if len(runner.stages) != 0 {
t.Fatalf("Start() reached Compose for %s projection: %v", state, runner.stages)
}
})
}
}
func TestStartRefusesProjectionFailureBeforeCompose(t *testing.T) {
stubModelProjection(t, errors.New("synthetic projection failure"))
runner := &recordingRunner{}
err := Start(context.Background(), testInstallation(), runner, false)
if err == nil || !strings.Contains(err.Error(), "model runtime projection") {
t.Fatalf("Start() error = %v", err)
}
if len(runner.stages) != 0 {
t.Fatalf("Start() reached Compose: %v", runner.stages)
}
}
func stubModelProjection(t *testing.T, result error) {
t.Helper()
previous := generateModelProjections
generateModelProjections = func(config.Installation) error { return result }
t.Cleanup(func() { generateModelProjections = previous })
}
type recordingRunner struct{ stages []string }
func (r *recordingRunner) Run(_ context.Context, args []string, _ io.Reader) (compose.Result, error) {
joined := strings.Join(args, " ")
switch {
case strings.HasSuffix(joined, " build"):
r.stages = append(r.stages, "build")
case strings.HasSuffix(joined, " up --detach --remove-orphans"):
r.stages = append(r.stages, "up")
case strings.HasSuffix(joined, " ps --all --format json"):
r.stages = append(r.stages, "health")
return compose.Result{Stdout: healthyServices}, nil
}
return compose.Result{}, nil
}
func testInstallation() config.Installation {
return config.Installation{
Path: "/tmp/thothii/deploy/local/thothii-installation.yaml",
Profile: "local",
ProjectDirectory: "/tmp/thothii",
EnvFile: "/tmp/thothii/deploy/local/operator.env",
}
}
func projectedTestInstallation() config.Installation {
installation := testInstallation()
installation.Profile = "server"
installation.Authentication.RuntimeProjection = &config.RuntimeProjection{
Directory: "/runtime-auth", UID: 10001, GID: 10001,
}
return installation
}
const healthyServices = `[
{"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}
]`