package startruntime_test import ( "context" "encoding/json" "errors" "sync" "testing" "time" "galaxy/notificationintent" "galaxy/rtmanager/internal/adapters/docker/mocks" "galaxy/rtmanager/internal/config" "galaxy/rtmanager/internal/domain/health" "galaxy/rtmanager/internal/domain/operation" "galaxy/rtmanager/internal/domain/runtime" "galaxy/rtmanager/internal/ports" "galaxy/rtmanager/internal/service/startruntime" "galaxy/rtmanager/internal/telemetry" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" ) // --- test doubles ----------------------------------------------------- type fakeRuntimeRecords struct { mu sync.Mutex stored map[string]runtime.RuntimeRecord getErr error upsertErr error upserts []runtime.RuntimeRecord } func newFakeRuntimeRecords() *fakeRuntimeRecords { return &fakeRuntimeRecords{stored: map[string]runtime.RuntimeRecord{}} } func (s *fakeRuntimeRecords) Get(_ context.Context, gameID string) (runtime.RuntimeRecord, error) { s.mu.Lock() defer s.mu.Unlock() if s.getErr != nil { return runtime.RuntimeRecord{}, s.getErr } record, ok := s.stored[gameID] if !ok { return runtime.RuntimeRecord{}, runtime.ErrNotFound } return record, nil } func (s *fakeRuntimeRecords) Upsert(_ context.Context, record runtime.RuntimeRecord) error { s.mu.Lock() defer s.mu.Unlock() if s.upsertErr != nil { return s.upsertErr } s.upserts = append(s.upserts, record) s.stored[record.GameID] = record return nil } func (s *fakeRuntimeRecords) UpdateStatus(_ context.Context, _ ports.UpdateStatusInput) error { return errors.New("not used in start tests") } func (s *fakeRuntimeRecords) ListByStatus(_ context.Context, _ runtime.Status) ([]runtime.RuntimeRecord, error) { return nil, errors.New("not used in start tests") } func (s *fakeRuntimeRecords) List(_ context.Context) ([]runtime.RuntimeRecord, error) { return nil, errors.New("not used in start tests") } type fakeOperationLogs struct { mu sync.Mutex appendErr error appends []operation.OperationEntry } func (s *fakeOperationLogs) Append(_ context.Context, entry operation.OperationEntry) (int64, error) { s.mu.Lock() defer s.mu.Unlock() if s.appendErr != nil { return 0, s.appendErr } s.appends = append(s.appends, entry) return int64(len(s.appends)), nil } func (s *fakeOperationLogs) ListByGame(_ context.Context, _ string, _ int) ([]operation.OperationEntry, error) { return nil, errors.New("not used in start tests") } func (s *fakeOperationLogs) lastAppend() (operation.OperationEntry, bool) { s.mu.Lock() defer s.mu.Unlock() if len(s.appends) == 0 { return operation.OperationEntry{}, false } return s.appends[len(s.appends)-1], true } type fakeLeases struct { acquired bool acquireErr error releaseErr error mu sync.Mutex acquires []string releases []string } func (l *fakeLeases) TryAcquire(_ context.Context, _, token string, _ time.Duration) (bool, error) { l.mu.Lock() defer l.mu.Unlock() l.acquires = append(l.acquires, token) if l.acquireErr != nil { return false, l.acquireErr } return l.acquired, nil } func (l *fakeLeases) Release(_ context.Context, _, token string) error { l.mu.Lock() defer l.mu.Unlock() l.releases = append(l.releases, token) return l.releaseErr } type fakeHealthEvents struct { mu sync.Mutex publishErr error envelopes []ports.HealthEventEnvelope } func (h *fakeHealthEvents) Publish(_ context.Context, envelope ports.HealthEventEnvelope) error { h.mu.Lock() defer h.mu.Unlock() if h.publishErr != nil { return h.publishErr } h.envelopes = append(h.envelopes, envelope) return nil } type fakeNotifications struct { mu sync.Mutex publishErr error intents []notificationintent.Intent } func (n *fakeNotifications) Publish(_ context.Context, intent notificationintent.Intent) error { n.mu.Lock() defer n.mu.Unlock() if n.publishErr != nil { return n.publishErr } n.intents = append(n.intents, intent) return nil } type fakeLobby struct { record ports.LobbyGameRecord err error mu sync.Mutex calls []string } func (l *fakeLobby) GetGame(_ context.Context, gameID string) (ports.LobbyGameRecord, error) { l.mu.Lock() defer l.mu.Unlock() l.calls = append(l.calls, gameID) if l.err != nil { return ports.LobbyGameRecord{}, l.err } return l.record, nil } // --- harness ---------------------------------------------------------- type harness struct { records *fakeRuntimeRecords operationLogs *fakeOperationLogs docker *mocks.MockDockerClient leases *fakeLeases healthEvents *fakeHealthEvents notifications *fakeNotifications lobby *fakeLobby telemetry *telemetry.Runtime now time.Time stateDir string } func newHarness(t *testing.T) *harness { t.Helper() ctrl := gomock.NewController(t) t.Cleanup(ctrl.Finish) telemetryRuntime, err := telemetry.NewWithProviders(nil, nil) require.NoError(t, err) return &harness{ records: newFakeRuntimeRecords(), operationLogs: &fakeOperationLogs{}, docker: mocks.NewMockDockerClient(ctrl), leases: &fakeLeases{acquired: true}, healthEvents: &fakeHealthEvents{}, notifications: &fakeNotifications{}, lobby: &fakeLobby{}, telemetry: telemetryRuntime, now: time.Date(2026, 4, 27, 12, 0, 0, 0, time.UTC), stateDir: "/var/lib/galaxy/games/game-1", } } func (h *harness) build(t *testing.T) *startruntime.Service { t.Helper() containerCfg := config.ContainerConfig{ DefaultCPUQuota: 1.0, DefaultMemory: "512m", DefaultPIDsLimit: 512, StopTimeout: 30 * time.Second, Retention: 30 * 24 * time.Hour, EngineStateMountPath: "/var/lib/galaxy-game", EngineStateEnvName: "GAME_STATE_PATH", GameStateDirMode: 0o750, GameStateRoot: "/var/lib/galaxy/games", } dockerCfg := config.DockerConfig{ Host: "unix:///var/run/docker.sock", Network: "galaxy-net", LogDriver: "json-file", PullPolicy: config.ImagePullPolicyIfMissing, } coordinationCfg := config.CoordinationConfig{GameLeaseTTL: time.Minute} service, err := startruntime.NewService(startruntime.Dependencies{ RuntimeRecords: h.records, OperationLogs: h.operationLogs, Docker: h.docker, Leases: h.leases, HealthEvents: h.healthEvents, Notifications: h.notifications, Lobby: h.lobby, Container: containerCfg, DockerCfg: dockerCfg, Coordination: coordinationCfg, Telemetry: h.telemetry, Clock: func() time.Time { return h.now }, NewToken: func() string { return "token-A" }, PrepareStateDir: func(_ string) (string, error) { return h.stateDir, nil }, }) require.NoError(t, err) return service } func basicInput() startruntime.Input { return startruntime.Input{ GameID: "game-1", ImageRef: "registry.example.com/galaxy/game:1.4.7", OpSource: operation.OpSourceLobbyStream, SourceRef: "1700000000000-0", } } func sampleRunResult(now time.Time) ports.RunResult { return ports.RunResult{ ContainerID: "ctr-123", EngineEndpoint: "http://galaxy-game-game-1:8080", StartedAt: now, } } // --- happy path ------------------------------------------------------- func TestHandleHappyPath(t *testing.T) { h := newHarness(t) input := basicInput() h.docker.EXPECT().EnsureNetwork(gomock.Any(), "galaxy-net").Return(nil) h.docker.EXPECT().PullImage(gomock.Any(), input.ImageRef, ports.PullPolicy(config.ImagePullPolicyIfMissing)).Return(nil) h.docker.EXPECT().InspectImage(gomock.Any(), input.ImageRef).Return(ports.ImageInspect{ Ref: input.ImageRef, Labels: map[string]string{ "com.galaxy.cpu_quota": "0.5", "com.galaxy.memory": "256m", "com.galaxy.pids_limit": "256", }, }, nil) h.docker.EXPECT().Run(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, spec ports.RunSpec) (ports.RunResult, error) { assert.Equal(t, "galaxy-game-game-1", spec.Name) assert.Equal(t, "galaxy-game-game-1", spec.Hostname) assert.Equal(t, input.ImageRef, spec.Image) assert.Equal(t, "galaxy-net", spec.Network) assert.Equal(t, "json-file", spec.LogDriver) assert.InDelta(t, 0.5, spec.CPUQuota, 0) assert.Equal(t, "256m", spec.Memory) assert.Equal(t, 256, spec.PIDsLimit) assert.Equal(t, h.stateDir, spec.BindMounts[0].HostPath) assert.Equal(t, "/var/lib/galaxy-game", spec.BindMounts[0].MountPath) assert.Equal(t, "/var/lib/galaxy-game", spec.Env["GAME_STATE_PATH"]) assert.Equal(t, "/var/lib/galaxy-game", spec.Env["STORAGE_PATH"]) assert.Equal(t, "rtmanager", spec.Labels[startruntime.LabelOwner]) assert.Equal(t, "game-engine", spec.Labels[startruntime.LabelKind]) assert.Equal(t, input.GameID, spec.Labels[startruntime.LabelGameID]) assert.Equal(t, input.ImageRef, spec.Labels[startruntime.LabelEngineImageRef]) return sampleRunResult(h.now), nil }) service := h.build(t) result, err := service.Handle(context.Background(), input) require.NoError(t, err) assert.Equal(t, operation.OutcomeSuccess, result.Outcome) assert.Empty(t, result.ErrorCode) assert.Equal(t, runtime.StatusRunning, result.Record.Status) assert.Equal(t, "ctr-123", result.Record.CurrentContainerID) assert.Equal(t, input.ImageRef, result.Record.CurrentImageRef) assert.Equal(t, "http://galaxy-game-game-1:8080", result.Record.EngineEndpoint) assert.Equal(t, h.stateDir, result.Record.StatePath) assert.Equal(t, "galaxy-net", result.Record.DockerNetwork) require.NotNil(t, result.Record.StartedAt) assert.Equal(t, h.now, *result.Record.StartedAt) assert.Equal(t, h.now, result.Record.LastOpAt) assert.Equal(t, h.now, result.Record.CreatedAt) require.Len(t, h.records.upserts, 1) require.Len(t, h.operationLogs.appends, 1) last, _ := h.operationLogs.lastAppend() assert.Equal(t, operation.OpKindStart, last.OpKind) assert.Equal(t, operation.OutcomeSuccess, last.Outcome) assert.Empty(t, last.ErrorCode) assert.Equal(t, "ctr-123", last.ContainerID) require.Len(t, h.healthEvents.envelopes, 1) assert.Equal(t, health.EventTypeContainerStarted, h.healthEvents.envelopes[0].EventType) var details map[string]string require.NoError(t, json.Unmarshal(h.healthEvents.envelopes[0].Details, &details)) assert.Equal(t, input.ImageRef, details["image_ref"]) assert.Empty(t, h.notifications.intents, "no notification intent expected on success") assert.Equal(t, []string{"token-A"}, h.leases.acquires) assert.Equal(t, []string{"token-A"}, h.leases.releases) assert.Equal(t, []string{input.GameID}, h.lobby.calls) } // --- idempotent replay ------------------------------------------------ func TestHandleReplayNoOpForRunningRecordWithSameImageRef(t *testing.T) { h := newHarness(t) input := basicInput() startedAt := h.now.Add(-time.Hour) h.records.stored[input.GameID] = runtime.RuntimeRecord{ GameID: input.GameID, Status: runtime.StatusRunning, CurrentContainerID: "ctr-prev", CurrentImageRef: input.ImageRef, EngineEndpoint: "http://galaxy-game-game-1:8080", StatePath: h.stateDir, DockerNetwork: "galaxy-net", StartedAt: &startedAt, LastOpAt: startedAt, CreatedAt: startedAt, } service := h.build(t) result, err := service.Handle(context.Background(), input) require.NoError(t, err) assert.Equal(t, operation.OutcomeSuccess, result.Outcome) assert.Equal(t, startruntime.ErrorCodeReplayNoOp, result.ErrorCode) assert.Equal(t, "ctr-prev", result.Record.CurrentContainerID) assert.Empty(t, h.records.upserts, "replay must not Upsert a fresh record") require.Len(t, h.operationLogs.appends, 1) last, _ := h.operationLogs.lastAppend() assert.Equal(t, operation.OutcomeSuccess, last.Outcome) assert.Equal(t, startruntime.ErrorCodeReplayNoOp, last.ErrorCode) assert.Equal(t, "ctr-prev", last.ContainerID) assert.Empty(t, h.notifications.intents) assert.Equal(t, []string{"token-A"}, h.leases.releases, "lease must be released after replay no-op") } // --- conflicts -------------------------------------------------------- func TestHandleConflictWhenLeaseBusy(t *testing.T) { h := newHarness(t) h.leases.acquired = false input := basicInput() service := h.build(t) result, err := service.Handle(context.Background(), input) require.NoError(t, err) assert.Equal(t, operation.OutcomeFailure, result.Outcome) assert.Equal(t, startruntime.ErrorCodeConflict, result.ErrorCode) require.Len(t, h.operationLogs.appends, 1) last, _ := h.operationLogs.lastAppend() assert.Equal(t, operation.OutcomeFailure, last.Outcome) assert.Equal(t, startruntime.ErrorCodeConflict, last.ErrorCode) assert.Empty(t, h.notifications.intents, "lease conflicts must not raise admin notifications") assert.Empty(t, h.leases.releases, "release must not run when acquire returned false") } func TestHandleConflictWhenRunningWithDifferentImageRef(t *testing.T) { h := newHarness(t) input := basicInput() startedAt := h.now.Add(-time.Hour) h.records.stored[input.GameID] = runtime.RuntimeRecord{ GameID: input.GameID, Status: runtime.StatusRunning, CurrentContainerID: "ctr-prev", CurrentImageRef: "registry.example.com/galaxy/game:1.4.6", EngineEndpoint: "http://galaxy-game-game-1:8080", StatePath: h.stateDir, DockerNetwork: "galaxy-net", StartedAt: &startedAt, LastOpAt: startedAt, CreatedAt: startedAt, } service := h.build(t) result, err := service.Handle(context.Background(), input) require.NoError(t, err) assert.Equal(t, operation.OutcomeFailure, result.Outcome) assert.Equal(t, startruntime.ErrorCodeConflict, result.ErrorCode) last, _ := h.operationLogs.lastAppend() assert.Equal(t, startruntime.ErrorCodeConflict, last.ErrorCode) assert.Empty(t, h.notifications.intents) assert.Empty(t, h.records.upserts) } // --- start_config_invalid --------------------------------------------- func TestHandleStartConfigInvalidWhenImageRefMalformed(t *testing.T) { h := newHarness(t) input := basicInput() input.ImageRef = "::not a docker reference::" service := h.build(t) result, err := service.Handle(context.Background(), input) require.NoError(t, err) assert.Equal(t, operation.OutcomeFailure, result.Outcome) assert.Equal(t, startruntime.ErrorCodeStartConfigInvalid, result.ErrorCode) require.Len(t, h.notifications.intents, 1) assert.Equal(t, notificationintent.NotificationTypeRuntimeStartConfigInvalid, h.notifications.intents[0].NotificationType) last, _ := h.operationLogs.lastAppend() assert.Equal(t, operation.OutcomeFailure, last.Outcome) } func TestHandleStartConfigInvalidWhenNetworkMissing(t *testing.T) { h := newHarness(t) input := basicInput() h.docker.EXPECT().EnsureNetwork(gomock.Any(), "galaxy-net").Return(ports.ErrNetworkMissing) service := h.build(t) result, err := service.Handle(context.Background(), input) require.NoError(t, err) assert.Equal(t, startruntime.ErrorCodeStartConfigInvalid, result.ErrorCode) require.Len(t, h.notifications.intents, 1) assert.Equal(t, notificationintent.NotificationTypeRuntimeStartConfigInvalid, h.notifications.intents[0].NotificationType) } func TestHandleStartConfigInvalidWhenStateDirFails(t *testing.T) { h := newHarness(t) input := basicInput() h.docker.EXPECT().EnsureNetwork(gomock.Any(), "galaxy-net").Return(nil) h.docker.EXPECT().PullImage(gomock.Any(), input.ImageRef, gomock.Any()).Return(nil) h.docker.EXPECT().InspectImage(gomock.Any(), input.ImageRef).Return(ports.ImageInspect{Ref: input.ImageRef}, nil) service, err := startruntime.NewService(startruntime.Dependencies{ RuntimeRecords: h.records, OperationLogs: h.operationLogs, Docker: h.docker, Leases: h.leases, HealthEvents: h.healthEvents, Notifications: h.notifications, Lobby: h.lobby, Container: config.ContainerConfig{ DefaultCPUQuota: 1.0, DefaultMemory: "512m", DefaultPIDsLimit: 512, StopTimeout: 30 * time.Second, Retention: 30 * 24 * time.Hour, EngineStateMountPath: "/var/lib/galaxy-game", EngineStateEnvName: "GAME_STATE_PATH", GameStateDirMode: 0o750, GameStateRoot: "/var/lib/galaxy/games", }, DockerCfg: config.DockerConfig{ Host: "unix:///var/run/docker.sock", Network: "galaxy-net", LogDriver: "json-file", PullPolicy: config.ImagePullPolicyIfMissing, }, Coordination: config.CoordinationConfig{GameLeaseTTL: time.Minute}, Telemetry: h.telemetry, Clock: func() time.Time { return h.now }, NewToken: func() string { return "token-A" }, PrepareStateDir: func(_ string) (string, error) { return "", errors.New("disk full") }, }) require.NoError(t, err) result, err := service.Handle(context.Background(), input) require.NoError(t, err) assert.Equal(t, startruntime.ErrorCodeStartConfigInvalid, result.ErrorCode) require.Len(t, h.notifications.intents, 1) assert.Equal(t, notificationintent.NotificationTypeRuntimeStartConfigInvalid, h.notifications.intents[0].NotificationType) } // --- image_pull_failed ------------------------------------------------ func TestHandleImagePullFailed(t *testing.T) { h := newHarness(t) input := basicInput() h.docker.EXPECT().EnsureNetwork(gomock.Any(), "galaxy-net").Return(nil) h.docker.EXPECT().PullImage(gomock.Any(), input.ImageRef, gomock.Any()).Return(errors.New("manifest unknown")) service := h.build(t) result, err := service.Handle(context.Background(), input) require.NoError(t, err) assert.Equal(t, startruntime.ErrorCodeImagePullFailed, result.ErrorCode) require.Len(t, h.notifications.intents, 1) assert.Equal(t, notificationintent.NotificationTypeRuntimeImagePullFailed, h.notifications.intents[0].NotificationType) assert.Empty(t, h.records.upserts) } // --- container_start_failed ------------------------------------------ func TestHandleContainerStartFailedOnRunError(t *testing.T) { h := newHarness(t) input := basicInput() h.docker.EXPECT().EnsureNetwork(gomock.Any(), "galaxy-net").Return(nil) h.docker.EXPECT().PullImage(gomock.Any(), input.ImageRef, gomock.Any()).Return(nil) h.docker.EXPECT().InspectImage(gomock.Any(), input.ImageRef).Return(ports.ImageInspect{Ref: input.ImageRef}, nil) h.docker.EXPECT().Run(gomock.Any(), gomock.Any()).Return(ports.RunResult{}, errors.New("container name conflict")) service := h.build(t) result, err := service.Handle(context.Background(), input) require.NoError(t, err) assert.Equal(t, startruntime.ErrorCodeContainerStartFailed, result.ErrorCode) require.Len(t, h.notifications.intents, 1) assert.Equal(t, notificationintent.NotificationTypeRuntimeContainerStartFailed, h.notifications.intents[0].NotificationType) assert.Empty(t, h.records.upserts) } func TestHandleRollsBackContainerWhenUpsertFails(t *testing.T) { h := newHarness(t) h.records.upsertErr = errors.New("connection refused") input := basicInput() h.docker.EXPECT().EnsureNetwork(gomock.Any(), "galaxy-net").Return(nil) h.docker.EXPECT().PullImage(gomock.Any(), input.ImageRef, gomock.Any()).Return(nil) h.docker.EXPECT().InspectImage(gomock.Any(), input.ImageRef).Return(ports.ImageInspect{Ref: input.ImageRef}, nil) h.docker.EXPECT().Run(gomock.Any(), gomock.Any()).Return(sampleRunResult(h.now), nil) h.docker.EXPECT().Remove(gomock.Any(), "ctr-123").Return(nil) service := h.build(t) result, err := service.Handle(context.Background(), input) require.NoError(t, err) assert.Equal(t, startruntime.ErrorCodeContainerStartFailed, result.ErrorCode) require.Len(t, h.notifications.intents, 1) assert.Equal(t, notificationintent.NotificationTypeRuntimeContainerStartFailed, h.notifications.intents[0].NotificationType) } // --- best-effort degradation ----------------------------------------- func TestHandleSuccessSurvivesOperationLogFailure(t *testing.T) { h := newHarness(t) h.operationLogs.appendErr = errors.New("postgres down") input := basicInput() h.docker.EXPECT().EnsureNetwork(gomock.Any(), "galaxy-net").Return(nil) h.docker.EXPECT().PullImage(gomock.Any(), input.ImageRef, gomock.Any()).Return(nil) h.docker.EXPECT().InspectImage(gomock.Any(), input.ImageRef).Return(ports.ImageInspect{Ref: input.ImageRef}, nil) h.docker.EXPECT().Run(gomock.Any(), gomock.Any()).Return(sampleRunResult(h.now), nil) service := h.build(t) result, err := service.Handle(context.Background(), input) require.NoError(t, err) assert.Equal(t, operation.OutcomeSuccess, result.Outcome) assert.Empty(t, result.ErrorCode) assert.Len(t, h.records.upserts, 1) } func TestHandleSuccessSurvivesHealthPublishFailure(t *testing.T) { h := newHarness(t) h.healthEvents.publishErr = errors.New("redis down") input := basicInput() h.docker.EXPECT().EnsureNetwork(gomock.Any(), "galaxy-net").Return(nil) h.docker.EXPECT().PullImage(gomock.Any(), input.ImageRef, gomock.Any()).Return(nil) h.docker.EXPECT().InspectImage(gomock.Any(), input.ImageRef).Return(ports.ImageInspect{Ref: input.ImageRef}, nil) h.docker.EXPECT().Run(gomock.Any(), gomock.Any()).Return(sampleRunResult(h.now), nil) service := h.build(t) result, err := service.Handle(context.Background(), input) require.NoError(t, err) assert.Equal(t, operation.OutcomeSuccess, result.Outcome) assert.Len(t, h.records.upserts, 1) } // --- pre-existing stopped record proceeds with fresh start ---------- func TestHandlePreservesCreatedAtForExistingRecord(t *testing.T) { h := newHarness(t) input := basicInput() originalCreatedAt := h.now.Add(-72 * time.Hour) stoppedAt := h.now.Add(-time.Hour) h.records.stored[input.GameID] = runtime.RuntimeRecord{ GameID: input.GameID, Status: runtime.StatusStopped, CurrentImageRef: "registry.example.com/galaxy/game:1.4.6", EngineEndpoint: "http://galaxy-game-game-1:8080", StatePath: h.stateDir, DockerNetwork: "galaxy-net", StoppedAt: &stoppedAt, LastOpAt: stoppedAt, CreatedAt: originalCreatedAt, } h.docker.EXPECT().EnsureNetwork(gomock.Any(), "galaxy-net").Return(nil) h.docker.EXPECT().PullImage(gomock.Any(), input.ImageRef, gomock.Any()).Return(nil) h.docker.EXPECT().InspectImage(gomock.Any(), input.ImageRef).Return(ports.ImageInspect{Ref: input.ImageRef}, nil) h.docker.EXPECT().Run(gomock.Any(), gomock.Any()).Return(sampleRunResult(h.now), nil) service := h.build(t) result, err := service.Handle(context.Background(), input) require.NoError(t, err) assert.Equal(t, operation.OutcomeSuccess, result.Outcome) assert.Equal(t, originalCreatedAt, result.Record.CreatedAt, "created_at must be preserved across re-starts") assert.Equal(t, runtime.StatusRunning, result.Record.Status) assert.Equal(t, input.ImageRef, result.Record.CurrentImageRef) } // --- input validation ----------------------------------------------- func TestHandleRejectsInvalidInput(t *testing.T) { h := newHarness(t) service := h.build(t) cases := []startruntime.Input{ {GameID: "", ImageRef: "x", OpSource: operation.OpSourceLobbyStream}, {GameID: "g", ImageRef: "", OpSource: operation.OpSourceLobbyStream}, {GameID: "g", ImageRef: "x", OpSource: operation.OpSource("bogus")}, } for _, input := range cases { result, err := service.Handle(context.Background(), input) require.NoError(t, err) assert.Equal(t, startruntime.ErrorCodeStartConfigInvalid, result.ErrorCode) } } func TestNewServiceRejectsMissingDependencies(t *testing.T) { h := newHarness(t) deps := startruntime.Dependencies{ Container: config.ContainerConfig{ DefaultCPUQuota: 1.0, DefaultMemory: "512m", DefaultPIDsLimit: 512, StopTimeout: 30 * time.Second, Retention: 30 * 24 * time.Hour, EngineStateMountPath: "/var/lib/galaxy-game", EngineStateEnvName: "GAME_STATE_PATH", GameStateDirMode: 0o750, GameStateRoot: "/var/lib/galaxy/games", }, DockerCfg: config.DockerConfig{ Host: "unix:///var/run/docker.sock", Network: "galaxy-net", LogDriver: "json-file", PullPolicy: config.ImagePullPolicyIfMissing, }, Coordination: config.CoordinationConfig{GameLeaseTTL: time.Minute}, Telemetry: h.telemetry, } _, err := startruntime.NewService(deps) require.Error(t, err) }