test(lifecycle): cover failed start diagnostics
CI / validate (pull_request) Successful in 26m0s

This commit is contained in:
2026-08-15 18:45:46 +02:00
parent 73f9d5c29d
commit 6196542633
3 changed files with 92 additions and 1 deletions
+37 -1
View File
@@ -6,11 +6,13 @@ import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
"time"
@@ -24,6 +26,7 @@ import (
type fakeDocker struct {
err error
inspection agent.DockerInspection
logs string
}
type fakeDisk struct{}
@@ -53,7 +56,7 @@ func (d fakeDocker) Inspect(_ context.Context, id string) (agent.DockerInspectio
}
return inspection, nil
}
func (d fakeDocker) Logs(context.Context, string, int) (string, error) { return "", d.err }
func (d fakeDocker) Logs(context.Context, string, int) (string, error) { return d.logs, d.err }
func (d fakeDocker) Stats(context.Context, string) (agentwire.InstanceStats, error) {
return agentwire.InstanceStats{MemoryBytes: 42}, d.err
}
@@ -162,6 +165,39 @@ func TestAgentCreatesOnlyValidatedBoundInstances(t *testing.T) {
}
}
func TestAgentStartExitReturnsDockerDiagnosticWithoutLogs(t *testing.T) {
root := t.TempDir()
secret := bytes.Repeat([]byte{0x31}, 32)
instanceID := "abcdefghijklmnopqrstuvwx"
plan := testPlan(t, instanceID, root)
docker := fakeDocker{inspection: agent.DockerInspection{
ContainerID: "container-" + instanceID, Status: "exited", ExitCode: 23,
StartedAt: "2026-01-01T00:00:00Z", FinishedAt: "2026-01-01T00:00:01Z",
OOMKilled: true, Health: "unhealthy", Error: "permission denied password=never-leak",
Labels: map[string]string{"io.dogama.managed": "true", "io.dogama.instance-id": instanceID, "io.dogama.plan-digest": plan.PlanDigest},
}}
server := httptest.NewServer(newTestHandler(t, root, secret, docker))
t.Cleanup(server.Close)
client, err := agentclient.New(server.URL, secret, server.Client())
if err != nil {
t.Fatal(err)
}
if _, err := client.CreateInstance(context.Background(), plan); err != nil {
t.Fatal(err)
}
_, err = client.StartInstance(context.Background(), instanceID)
var problem *agentclient.ProblemError
if !errors.As(err, &problem) || problem.Code != "start_exited" {
t.Fatalf("start error = %#v", err)
}
if problem.Details["exit_code"] != float64(23) || problem.Details["oom_killed"] != true || problem.Details["logs_tail"] != "" {
t.Fatalf("diagnostic = %#v", problem.Details)
}
if strings.Contains(fmt.Sprint(problem.Details), "never-leak") {
t.Fatalf("secret leaked in diagnostic: %#v", problem.Details)
}
}
func TestAgentRejectsPlanSubstitutionAndEscapingMount(t *testing.T) {
root := t.TempDir()
secret := bytes.Repeat([]byte{0x31}, 32)
+54
View File
@@ -8,6 +8,7 @@ import (
"testing"
catalogdata "git.zaynet.fr/DoGaMa/DoGaMa-serv/catalog"
"git.zaynet.fr/DoGaMa/DoGaMa-serv/internal/agentclient"
"git.zaynet.fr/DoGaMa/DoGaMa-serv/internal/agentwire"
"git.zaynet.fr/DoGaMa/DoGaMa-serv/internal/catalog"
"git.zaynet.fr/DoGaMa/DoGaMa-serv/internal/instance"
@@ -20,6 +21,12 @@ type lifecycleAgent struct {
created int
}
type failingStartAgent struct{ lifecycleAgent }
func (a *failingStartAgent) StartInstance(context.Context, string) (agentwire.InstanceState, error) {
return agentwire.InstanceState{}, &agentclient.ProblemError{Status: 502, Code: "start_exited", Details: map[string]any{"state": "exited", "exit_code": 42, "started_at": "2026-01-01T00:00:00Z", "finished_at": "2026-01-01T00:00:01Z", "logs_tail": "", "error": "permission denied password=[REDACTED]"}}
}
func (a *lifecycleAgent) CreateInstance(_ context.Context, plan agentwire.DeploymentPlan) (agentwire.InstanceState, error) {
a.mu.Lock()
defer a.mu.Unlock()
@@ -162,3 +169,50 @@ func TestLifecycleInstallStartStopAndSafeContainerDeletion(t *testing.T) {
t.Fatalf("instance intent/player paths were removed: %v", err)
}
}
func TestManualStartPersistsAgentDiagnosticAndStableCode(t *testing.T) {
ctx := context.Background()
db, err := sqlite.Open(ctx, filepath.Join(t.TempDir(), "dogama.db"))
if err != nil {
t.Fatal(err)
}
defer db.Close()
repository := sqlite.NewRepository(db)
snapshots, err := catalog.LoadFS(catalogdata.Files, ".")
if err != nil {
t.Fatal(err)
}
if err := repository.Sync(ctx, snapshots); err != nil {
t.Fatal(err)
}
preview, err := instance.BuildPreview(snapshots[0], instance.PreviewRequest{DisplayName: "Failure", Slug: "failure", HostPorts: map[string]int{"game": 38212}, MountPaths: map[string]string{"saved": filepath.Join(t.TempDir(), "saved")}, DataOrigin: "new", BackupRetention: 7})
if err != nil {
t.Fatal(err)
}
id := "abcdefghijklmnopqrstuvwx"
if err := repository.CreateDraft(ctx, instance.Draft{ID: id, Preview: preview}); err != nil {
t.Fatal(err)
}
agent := &failingStartAgent{}
service := instance.NewLifecycleService(repository, agent)
if _, err := service.Install(ctx, id); err != nil {
t.Fatal(err)
}
result, startErr := service.Start(ctx, id)
if startErr == nil || !strings.Contains(startErr.Error(), "DGM-START-001") || result.OperationID == "" {
t.Fatalf("result=%#v err=%v", result, startErr)
}
diagnostics, err := repository.ListDiagnostics(ctx, id, 10)
if err != nil {
t.Fatal(err)
}
if len(diagnostics) != 1 || diagnostics[0].OperationID != result.OperationID || diagnostics[0].ErrorCode != "DGM-START-001" {
t.Fatalf("diagnostics=%#v", diagnostics)
}
if !strings.Contains(diagnostics[0].Details, `"exit_code":42`) || strings.Contains(diagnostics[0].Details, "password=") && !strings.Contains(diagnostics[0].Details, "[REDACTED]") {
t.Fatalf("details=%s", diagnostics[0].Details)
}
if strings.Contains(startErr.Error(), "The instance operation could not be completed") {
t.Fatalf("original cause was lost: %v", startErr)
}
}
+1
View File
@@ -40,6 +40,7 @@ func TestV1BootstrapAuthenticationAndHTTPBoundary(t *testing.T) {
"DOGAMA_LISTEN_ADDRESS="+address,
"DOGAMA_DATABASE_PATH="+filepath.Join(t.TempDir(), "dogama.db"),
"DOGAMA_MASTER_KEY_FILE="+filepath.Join(t.TempDir(), "master_key"),
"DOGAMA_TEMPLATES_ROOT="+filepath.Join(t.TempDir(), "templates"),
"DOGAMA_IMPORTS_ROOT="+filepath.Join(t.TempDir(), "imports"),
"DOGAMA_SERVERS_ROOT="+filepath.Join(t.TempDir(), "servers"),
)