first commit
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"browser.local/platform/domain"
|
||||
)
|
||||
|
||||
func TestCoreServiceServerLifecycleWorkflows(t *testing.T) {
|
||||
svc, sessionToken := newLifecycleRunService(t)
|
||||
createLifecyclePlugin(t, svc)
|
||||
|
||||
created, err := svc.CreateServerInstanceWorkflow(domain.ServerLifecycleCreate{
|
||||
ID: "server-1",
|
||||
PluginID: "server.scum",
|
||||
RunEndpointID: "run-local",
|
||||
Name: "SCUM #1",
|
||||
IdempotencyKey: "idem-create",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create lifecycle workflow: %v", err)
|
||||
}
|
||||
if created.Action != domain.ServerLifecycleActionCreate || created.Instance.State != domain.ServerInstanceStateInstalling || created.Job.Capability != domain.LifecycleCapabilityInstall {
|
||||
t.Fatalf("expected install workflow result, got %+v", created)
|
||||
}
|
||||
|
||||
claimAndCompleteLifecycleJob(t, svc, sessionToken, domain.LifecycleCapabilityInstall, domain.JobStateSucceeded)
|
||||
ready, err := svc.GetServerInstance("server-1")
|
||||
if err != nil {
|
||||
t.Fatalf("get ready instance: %v", err)
|
||||
}
|
||||
if ready.State != domain.ServerInstanceStateReady {
|
||||
t.Fatalf("expected install result to mark ready, got %+v", ready)
|
||||
}
|
||||
|
||||
started, err := svc.StartServerInstance(domain.ServerLifecycleCommand{
|
||||
ServerInstanceID: "server-1",
|
||||
ExpectedConfigVersion: ready.ConfigVersion,
|
||||
IdempotencyKey: "idem-start",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("start lifecycle workflow: %v", err)
|
||||
}
|
||||
if started.Action != domain.ServerLifecycleActionStart || started.Job.Capability != domain.LifecycleCapabilityStart {
|
||||
t.Fatalf("expected start workflow result, got %+v", started)
|
||||
}
|
||||
claimAndCompleteLifecycleJob(t, svc, sessionToken, domain.LifecycleCapabilityStart, domain.JobStateSucceeded)
|
||||
running, err := svc.GetServerInstance("server-1")
|
||||
if err != nil {
|
||||
t.Fatalf("get running instance: %v", err)
|
||||
}
|
||||
if running.State != domain.ServerInstanceStateRunning {
|
||||
t.Fatalf("expected start result to mark running, got %+v", running)
|
||||
}
|
||||
|
||||
stopped, err := svc.StopServerInstance(domain.ServerLifecycleCommand{
|
||||
ServerInstanceID: "server-1",
|
||||
ExpectedConfigVersion: running.ConfigVersion,
|
||||
IdempotencyKey: "idem-stop",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("stop lifecycle workflow: %v", err)
|
||||
}
|
||||
if stopped.Action != domain.ServerLifecycleActionStop || stopped.Job.Capability != domain.LifecycleCapabilityStop {
|
||||
t.Fatalf("expected stop workflow result, got %+v", stopped)
|
||||
}
|
||||
claimAndCompleteLifecycleJob(t, svc, sessionToken, domain.LifecycleCapabilityStop, domain.JobStateSucceeded)
|
||||
final, err := svc.GetServerInstance("server-1")
|
||||
if err != nil {
|
||||
t.Fatalf("get stopped instance: %v", err)
|
||||
}
|
||||
if final.State != domain.ServerInstanceStateStopped {
|
||||
t.Fatalf("expected stop result to mark stopped, got %+v", final)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreServiceServerLifecycleRejectsInvalidCommands(t *testing.T) {
|
||||
svc := newTestCoreService()
|
||||
plugin, endpoint := createPluginAndRunEndpoint(t, svc)
|
||||
instance, err := svc.CreateServerInstance(domain.ServerInstance{
|
||||
ID: "server-ready",
|
||||
PluginID: plugin.ID,
|
||||
RunEndpointID: endpoint.ID,
|
||||
Name: "Ready Server",
|
||||
State: domain.ServerInstanceStateReady,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create ready server: %v", err)
|
||||
}
|
||||
|
||||
_, err = svc.StartServerInstance(domain.ServerLifecycleCommand{
|
||||
ServerInstanceID: instance.ID,
|
||||
ExpectedConfigVersion: instance.ConfigVersion + 1,
|
||||
IdempotencyKey: "idem-stale",
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "expectedConfigVersion") {
|
||||
t.Fatalf("expected stale config rejection, got %v", err)
|
||||
}
|
||||
|
||||
_, err = svc.StopServerInstance(domain.ServerLifecycleCommand{
|
||||
ServerInstanceID: instance.ID,
|
||||
ExpectedConfigVersion: instance.ConfigVersion,
|
||||
IdempotencyKey: "idem-stop-invalid",
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "cannot stop") {
|
||||
t.Fatalf("expected invalid stop state rejection, got %v", err)
|
||||
}
|
||||
|
||||
weakEndpoint := endpoint
|
||||
weakEndpoint.ID = "run-no-stop"
|
||||
weakEndpoint.Capabilities = []string{domain.LifecycleCapabilityInstall, domain.LifecycleCapabilityStart, "logs.read"}
|
||||
if _, err := svc.CreateRunEndpoint(weakEndpoint); err != nil {
|
||||
t.Fatalf("create weak endpoint: %v", err)
|
||||
}
|
||||
running, err := svc.CreateServerInstance(domain.ServerInstance{
|
||||
ID: "server-running",
|
||||
PluginID: plugin.ID,
|
||||
RunEndpointID: weakEndpoint.ID,
|
||||
Name: "Running Server",
|
||||
State: domain.ServerInstanceStateRunning,
|
||||
})
|
||||
if err == nil {
|
||||
_, err = svc.StopServerInstance(domain.ServerLifecycleCommand{
|
||||
ServerInstanceID: running.ID,
|
||||
ExpectedConfigVersion: running.ConfigVersion,
|
||||
IdempotencyKey: "idem-stop-missing-capability",
|
||||
})
|
||||
}
|
||||
if err == nil || !strings.Contains(err.Error(), domain.LifecycleCapabilityStop) {
|
||||
t.Fatalf("expected missing stop capability rejection, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreServiceLifecycleFailureProjectsFailedState(t *testing.T) {
|
||||
svc, sessionToken := newLifecycleRunService(t)
|
||||
createLifecyclePlugin(t, svc)
|
||||
if _, err := svc.CreateServerInstanceWorkflow(domain.ServerLifecycleCreate{
|
||||
ID: "server-1",
|
||||
PluginID: "server.scum",
|
||||
RunEndpointID: "run-local",
|
||||
Name: "SCUM #1",
|
||||
IdempotencyKey: "idem-create",
|
||||
}); err != nil {
|
||||
t.Fatalf("create lifecycle workflow: %v", err)
|
||||
}
|
||||
|
||||
claimAndCompleteLifecycleJob(t, svc, sessionToken, domain.LifecycleCapabilityInstall, domain.JobStateFailed)
|
||||
instance, err := svc.GetServerInstance("server-1")
|
||||
if err != nil {
|
||||
t.Fatalf("get failed instance: %v", err)
|
||||
}
|
||||
if instance.State != domain.ServerInstanceStateFailed {
|
||||
t.Fatalf("expected failed install result to mark failed, got %+v", instance)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreServicePluginLifecycleManagesMultipleInstancesIndependently(t *testing.T) {
|
||||
svc, sessionToken := newLifecycleRunService(t)
|
||||
createLifecyclePlugin(t, svc)
|
||||
|
||||
for _, id := range []string{"server-alpha", "server-beta"} {
|
||||
if _, err := svc.CreateServerInstanceWorkflow(domain.ServerLifecycleCreate{
|
||||
ID: id,
|
||||
PluginID: "server.scum",
|
||||
RunEndpointID: "run-local",
|
||||
Name: id,
|
||||
IdempotencyKey: "idem-create-" + id,
|
||||
}); err != nil {
|
||||
t.Fatalf("create %s: %v", id, err)
|
||||
}
|
||||
claimAndCompleteLifecycleJobForServer(t, svc, sessionToken, id, domain.LifecycleCapabilityInstall, domain.JobStateSucceeded)
|
||||
}
|
||||
|
||||
alpha, err := svc.GetServerInstance("server-alpha")
|
||||
if err != nil {
|
||||
t.Fatalf("get alpha: %v", err)
|
||||
}
|
||||
beta, err := svc.GetServerInstance("server-beta")
|
||||
if err != nil {
|
||||
t.Fatalf("get beta: %v", err)
|
||||
}
|
||||
if alpha.State != domain.ServerInstanceStateReady || beta.State != domain.ServerInstanceStateReady || alpha.ID == beta.ID || alpha.PluginID != beta.PluginID {
|
||||
t.Fatalf("expected distinct ready sibling instances, alpha=%+v beta=%+v", alpha, beta)
|
||||
}
|
||||
|
||||
if _, err := svc.StartServerInstance(domain.ServerLifecycleCommand{
|
||||
ServerInstanceID: alpha.ID,
|
||||
ExpectedConfigVersion: alpha.ConfigVersion,
|
||||
IdempotencyKey: "idem-start-alpha",
|
||||
}); err != nil {
|
||||
t.Fatalf("start alpha: %v", err)
|
||||
}
|
||||
claimAndCompleteLifecycleJobForServer(t, svc, sessionToken, alpha.ID, domain.LifecycleCapabilityStart, domain.JobStateSucceeded)
|
||||
|
||||
alpha, _ = svc.GetServerInstance("server-alpha")
|
||||
beta, _ = svc.GetServerInstance("server-beta")
|
||||
if alpha.State != domain.ServerInstanceStateRunning || beta.State != domain.ServerInstanceStateReady {
|
||||
t.Fatalf("expected alpha running and beta unchanged, alpha=%+v beta=%+v", alpha, beta)
|
||||
}
|
||||
|
||||
if _, err := svc.StopServerInstance(domain.ServerLifecycleCommand{
|
||||
ServerInstanceID: alpha.ID,
|
||||
ExpectedConfigVersion: alpha.ConfigVersion,
|
||||
IdempotencyKey: "idem-stop-alpha",
|
||||
}); err != nil {
|
||||
t.Fatalf("stop alpha: %v", err)
|
||||
}
|
||||
claimAndCompleteLifecycleJobForServer(t, svc, sessionToken, alpha.ID, domain.LifecycleCapabilityStop, domain.JobStateSucceeded)
|
||||
|
||||
alpha, _ = svc.GetServerInstance("server-alpha")
|
||||
beta, _ = svc.GetServerInstance("server-beta")
|
||||
if alpha.State != domain.ServerInstanceStateStopped || beta.State != domain.ServerInstanceStateReady {
|
||||
t.Fatalf("expected alpha stopped and beta still unchanged, alpha=%+v beta=%+v", alpha, beta)
|
||||
}
|
||||
}
|
||||
|
||||
func newLifecycleRunService(t *testing.T) (*CoreService, string) {
|
||||
t.Helper()
|
||||
svc := newTestCoreService()
|
||||
helloRequest := validRunControlHello()
|
||||
helloRequest.CapabilityReport.Capabilities = append(helloRequest.CapabilityReport.Capabilities,
|
||||
domain.LifecycleCapabilityInstall,
|
||||
domain.LifecycleCapabilityStart,
|
||||
domain.LifecycleCapabilityStop,
|
||||
"logs.read",
|
||||
"files.read",
|
||||
)
|
||||
helloRequest.CapabilityReport.Fingerprint = "cap-lifecycle"
|
||||
hello, err := svc.RegisterRunHello(helloRequest)
|
||||
if err != nil {
|
||||
t.Fatalf("register run hello: %v", err)
|
||||
}
|
||||
return svc, hello.SessionToken
|
||||
}
|
||||
|
||||
func createLifecyclePlugin(t *testing.T, svc *CoreService) domain.GamePlugin {
|
||||
t.Helper()
|
||||
plugin, err := svc.CreateGamePlugin(domain.GamePlugin{
|
||||
ID: "server.scum",
|
||||
Name: "SCUM",
|
||||
Version: "1.0.0",
|
||||
ServerType: "scum",
|
||||
ManifestRef: "artifact://manifests/server.scum/1.0.0",
|
||||
CreateFormSchemaRef: "artifact://schemas/server.scum/create-form/1.0.0",
|
||||
RequiredRunCapabilities: []string{domain.LifecycleCapabilityInstall, domain.LifecycleCapabilityStart, domain.LifecycleCapabilityStop, "logs.read"},
|
||||
LifecycleActions: domain.PluginLifecycleActions{
|
||||
Install: "actions/install.json",
|
||||
Start: "actions/start.json",
|
||||
Stop: "actions/stop.json",
|
||||
},
|
||||
Permissions: domain.PluginPermissions{Jobs: true, Logs: true},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create lifecycle plugin: %v", err)
|
||||
}
|
||||
return plugin
|
||||
}
|
||||
|
||||
func claimAndCompleteLifecycleJob(t *testing.T, svc *CoreService, sessionToken string, capability string, state domain.JobState) {
|
||||
t.Helper()
|
||||
claimAndCompleteLifecycleJobForServer(t, svc, sessionToken, "", capability, state)
|
||||
}
|
||||
|
||||
func claimAndCompleteLifecycleJobForServer(t *testing.T, svc *CoreService, sessionToken string, serverInstanceID string, capability string, state domain.JobState) {
|
||||
t.Helper()
|
||||
claim, err := svc.ClaimRunJob(domain.RunJobClaim{
|
||||
RunEndpointID: "run-local",
|
||||
SessionToken: sessionToken,
|
||||
Capabilities: []string{capability},
|
||||
Capacity: domain.RunCapacity{MaxJobs: 4},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("claim lifecycle job %s: %v", capability, err)
|
||||
}
|
||||
if !claim.HasJob || claim.Job.Capability != capability {
|
||||
t.Fatalf("expected claimed lifecycle job %s, got %+v", capability, claim)
|
||||
}
|
||||
if serverInstanceID != "" && claim.Job.ServerInstanceID != serverInstanceID {
|
||||
t.Fatalf("expected claimed lifecycle job for %s, got %+v", serverInstanceID, claim.Job)
|
||||
}
|
||||
if _, err := svc.CompleteRunJob(domain.RunJobResult{
|
||||
RunEndpointID: "run-local",
|
||||
SessionToken: sessionToken,
|
||||
JobID: claim.Job.JobID,
|
||||
LeaseToken: claim.Job.LeaseToken,
|
||||
Attempt: claim.Job.Attempt,
|
||||
State: state,
|
||||
Progress: domain.RunJobProgressReport{Percent: 100, Message: string(state)},
|
||||
Message: string(state),
|
||||
}); err != nil {
|
||||
t.Fatalf("complete lifecycle job %s: %v", capability, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user