128 lines
4.2 KiB
Go
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}
|
|
]`
|