feat: 完整游戏运维功能
This commit is contained in:
@@ -36,8 +36,8 @@ func TestCoreAPICreateListDetailWorkflows(t *testing.T) {
|
||||
assertListCount(t, users.Count, 2)
|
||||
|
||||
providerResponse := createAIProviderFixture(t, router, adminSession)
|
||||
if providerResponse.APIKeyRef != "secret://providers/openai" {
|
||||
t.Fatalf("expected AI provider key reference, got %+v", providerResponse)
|
||||
if !providerResponse.APIKeyConfigured {
|
||||
t.Fatalf("expected AI provider key presence, got %+v", providerResponse)
|
||||
}
|
||||
getJSONWithAuth[dto.AIProviderResponse](t, router, "/api/v1/ai-providers/ai.openai", adminSession)
|
||||
providers := getJSONWithAuth[dto.AIProviderListResponse](t, router, "/api/v1/ai-providers?kind=openai&status=active", adminSession)
|
||||
@@ -222,6 +222,7 @@ func TestConfigWriteAndFileDispatchAPIAreScopedAndSafe(t *testing.T) {
|
||||
Name: "Config API Server",
|
||||
State: domain.ServerInstanceStateRunning,
|
||||
}, ownerSession)
|
||||
putJSONWithAuth[dto.RuntimeBindingResponse](t, router, "/api/v1/server-instances/"+instance.ID+"/runtime-binding", dto.RuntimeBindingUpdateRequest{ProfileKey: "local", Bindings: map[string]string{}}, ownerSession)
|
||||
config := getJSONWithAuth[dto.ServerConfigResponse](t, router, "/api/v1/server-instances/server-config-api/config", ownerSession)
|
||||
proposed := strings.Replace(config.Content, "state=running", "state=running\nmotd=Approved", 1)
|
||||
|
||||
@@ -317,6 +318,19 @@ func TestCoreAPIServerRuntimeDistributionAndJobWorkflows(t *testing.T) {
|
||||
}
|
||||
clientDownloadRecorder := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/server-instances/"+serverID+"/client-managers/download", dto.ClientManagerDownloadRequest{ProfileKey: "scum-client-manager"}, adminSession)
|
||||
assertErrorResponse(t, clientDownloadRecorder, http.StatusNotFound, errorCodeNotFound)
|
||||
clientLinux := postJSONWithAuth[dto.ClientManagerDistributionResponse](t, router, "/api/v1/server-instances/"+serverID+"/client-managers/generate", dto.ClientManagerBuildRequest{ProfileKey: "scum-client-manager", TargetOS: "linux", TargetArch: "amd64", RepositoryURL: "https://github.com/F88888/scum_client.git", SourceRevision: "main", IdempotencyKey: "api-client-manager-linux"}, adminSession)
|
||||
lifecycleList := getJSONWithAuth[dto.ClientManagerInstallationListResponse](t, router, "/api/v1/server-instances/"+serverID+"/client-managers", adminSession)
|
||||
if lifecycleList.Count != 1 || lifecycleList.Items[0].Status != string(domain.ClientManagerLifecycleBuilding) || lifecycleList.Items[0].Distribution == nil {
|
||||
t.Fatalf("expected safe client-manager lifecycle projection, got %+v", lifecycleList)
|
||||
}
|
||||
deployRecorder := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/server-instances/"+serverID+"/client-managers/deploy", dto.ClientManagerDeployRequest{ProfileKey: "scum-client-manager", DistributionID: clientLinux.ID, IdempotencyKey: "api-client-manager-deploy"}, adminSession)
|
||||
assertErrorResponse(t, deployRecorder, http.StatusBadRequest, errorCodeValidation)
|
||||
detail := getJSONWithAuth[dto.ClientManagerInstallationResponse](t, router, "/api/v1/server-instances/"+serverID+"/client-managers/scum-client-manager", adminSession)
|
||||
if detail.CurrentJobID != "" || detail.KeyGeneration <= 0 {
|
||||
t.Fatalf("unexpected client-manager lifecycle detail: %+v", detail)
|
||||
}
|
||||
unauthorizedLifecycle := requestWithAuth(t, router, http.MethodGet, "/api/v1/server-instances/"+serverID+"/client-managers", "", "")
|
||||
assertErrorResponse(t, unauthorizedLifecycle, http.StatusUnauthorized, errorCodeUnauthorized)
|
||||
|
||||
dependencyCheckRecorder := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/server-instances/"+serverID+"/dependencies/check", dto.DependencyJobRequest{ProbeKey: "java-runtime", IdempotencyKey: "api-dependency-check"}, adminSession)
|
||||
assertStatus(t, dependencyCheckRecorder, http.StatusAccepted)
|
||||
@@ -324,7 +338,11 @@ func TestCoreAPIServerRuntimeDistributionAndJobWorkflows(t *testing.T) {
|
||||
if dependencyCheck.Capability != domain.JobCapabilityDependenciesCheck || dependencyCheck.TargetKey != "dependencies/java-runtime" {
|
||||
t.Fatalf("unexpected dependency check job: %+v", dependencyCheck)
|
||||
}
|
||||
dependencyInstallRecorder := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/server-instances/"+serverID+"/dependencies/install", dto.DependencyJobRequest{ProbeKey: "java-runtime", InstallPlanKey: "java-install", IdempotencyKey: "api-dependency-install"}, adminSession)
|
||||
dependencyCatalog := getJSONWithAuth[dto.DependencyCatalogResponse](t, router, "/api/v1/server-instances/"+serverID+"/dependencies", adminSession)
|
||||
if len(dependencyCatalog.Plans) != 1 || dependencyCatalog.Plans[0].Digest == "" {
|
||||
t.Fatalf("expected reviewable dependency plan, got %+v", dependencyCatalog)
|
||||
}
|
||||
dependencyInstallRecorder := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/server-instances/"+serverID+"/dependencies/install", dto.DependencyJobRequest{ProbeKey: "java-runtime", InstallPlanKey: "java-install", PlanDigest: dependencyCatalog.Plans[0].Digest, IdempotencyKey: "api-dependency-install"}, adminSession)
|
||||
assertStatus(t, dependencyInstallRecorder, http.StatusAccepted)
|
||||
dependencyInstall := decodeBody[dto.JobResponse](t, dependencyInstallRecorder)
|
||||
if dependencyInstall.Capability != domain.JobCapabilityDependenciesInstall || dependencyInstall.TargetKey != "dependencies/install/java-install" {
|
||||
@@ -466,22 +484,39 @@ func TestAuthSessionAPI(t *testing.T) {
|
||||
|
||||
func TestDefaultRouterSeedsLocalPlatformAdmin(t *testing.T) {
|
||||
router, err := NewRouterFromConfig(config.Config{
|
||||
StorageBackend: "file",
|
||||
MetadataPath: filepath.Join(t.TempDir(), "metadata.json"),
|
||||
LogDir: filepath.Join(t.TempDir(), "logs"),
|
||||
StorageBackend: "file",
|
||||
MetadataPath: filepath.Join(t.TempDir(), "metadata.json"),
|
||||
LogDir: filepath.Join(t.TempDir(), "logs"),
|
||||
BootstrapAdminEmail: "operator.local@example.test",
|
||||
BootstrapAdminPassword: "operator-local",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create default router: %v", err)
|
||||
}
|
||||
login := postOKJSON[dto.AuthSessionResponse](t, router, "/api/v1/auth/login", dto.LoginRequest{
|
||||
loginRecorder := performJSON(t, router, http.MethodPost, "/api/v1/auth/login", dto.LoginRequest{
|
||||
Account: "operator.local@example.test",
|
||||
Password: "operator-local",
|
||||
})
|
||||
if login.SessionID == "" || login.Status != "authenticated" || login.User.ID != "user-admin" {
|
||||
assertStatus(t, loginRecorder, http.StatusOK)
|
||||
login := decodeBody[dto.AuthSessionResponse](t, loginRecorder)
|
||||
if login.SessionID != "" || login.Status != "authenticated" || login.User.ID != "user-admin" {
|
||||
t.Fatalf("unexpected default operator login response: %+v", login)
|
||||
}
|
||||
var sessionCookie *http.Cookie
|
||||
for _, cookie := range loginRecorder.Result().Cookies() {
|
||||
if cookie.Name == platformSessionCookieName {
|
||||
sessionCookie = cookie
|
||||
break
|
||||
}
|
||||
}
|
||||
if sessionCookie == nil || !sessionCookie.HttpOnly || sessionCookie.SameSite != http.SameSiteStrictMode {
|
||||
t.Fatalf("expected strict HttpOnly session cookie, got %+v", sessionCookie)
|
||||
}
|
||||
|
||||
current := requestWithAuth(t, router, http.MethodGet, "/api/v1/users/current", "", login.SessionID)
|
||||
currentRequest := httptest.NewRequest(http.MethodGet, "/api/v1/users/current", nil)
|
||||
currentRequest.AddCookie(sessionCookie)
|
||||
current := httptest.NewRecorder()
|
||||
router.ServeHTTP(current, currentRequest)
|
||||
assertStatus(t, current, http.StatusOK)
|
||||
currentUser := decodeBody[dto.CurrentUserResponse](t, current)
|
||||
if currentUser.ID != "user-admin" || len(currentUser.Roles) == 0 || currentUser.Roles[0] != "platform-admin" {
|
||||
@@ -601,6 +636,7 @@ func TestServerLifecycleWorkflowAPI(t *testing.T) {
|
||||
RunEndpointID: "run-local",
|
||||
Name: "SCUM Create",
|
||||
IdempotencyKey: "idem-create",
|
||||
ProfileKey: "local",
|
||||
}, adminSession)
|
||||
if created.Action != domain.ServerLifecycleActionCreate || created.Instance.State != domain.ServerInstanceStateInstalling || created.Job.Capability != domain.LifecycleCapabilityInstall {
|
||||
t.Fatalf("expected create workflow response, got %+v", created)
|
||||
@@ -613,6 +649,7 @@ func TestServerLifecycleWorkflowAPI(t *testing.T) {
|
||||
Name: "SCUM Ready",
|
||||
State: domain.ServerInstanceStateReady,
|
||||
}, adminSession)
|
||||
putJSONWithAuth[dto.RuntimeBindingResponse](t, router, "/api/v1/server-instances/server-ready/runtime-binding", dto.RuntimeBindingUpdateRequest{ProfileKey: "local", Bindings: map[string]string{}}, adminSession)
|
||||
started := postOKJSONWithAuth[dto.ServerLifecycleResponse](t, router, "/api/v1/server-instances/server-ready/start", dto.ServerLifecycleCommandRequest{
|
||||
ExpectedConfigVersion: ready.ConfigVersion,
|
||||
IdempotencyKey: "idem-start",
|
||||
@@ -628,6 +665,7 @@ func TestServerLifecycleWorkflowAPI(t *testing.T) {
|
||||
Name: "SCUM Running",
|
||||
State: domain.ServerInstanceStateRunning,
|
||||
}, adminSession)
|
||||
putJSONWithAuth[dto.RuntimeBindingResponse](t, router, "/api/v1/server-instances/server-running/runtime-binding", dto.RuntimeBindingUpdateRequest{ProfileKey: "local", Bindings: map[string]string{}}, adminSession)
|
||||
stopped := postOKJSONWithAuth[dto.ServerLifecycleResponse](t, router, "/api/v1/server-instances/server-running/stop", dto.ServerLifecycleCommandRequest{
|
||||
ExpectedConfigVersion: running.ConfigVersion,
|
||||
IdempotencyKey: "idem-stop",
|
||||
@@ -795,8 +833,8 @@ func TestAIProviderAPIResponseDoesNotExposeRawKeyFields(t *testing.T) {
|
||||
if _, exists := body["rawApiKey"]; exists {
|
||||
t.Fatalf("AI provider response must not expose rawApiKey: %+v", body)
|
||||
}
|
||||
if body["apiKeyRef"] != "secret://providers/openai" {
|
||||
t.Fatalf("expected apiKeyRef only, got %+v", body)
|
||||
if _, exists := body["apiKeyRef"]; exists || body["apiKeyConfigured"] != true {
|
||||
t.Fatalf("expected API key presence only, got %+v", body)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -809,7 +847,7 @@ func TestAIProviderManagementAPI(t *testing.T) {
|
||||
updatedRecorder := requestJSONWithAuth(t, router, http.MethodPut, "/api/v1/ai-providers/ai.openai", update, adminSession)
|
||||
assertStatus(t, updatedRecorder, http.StatusOK)
|
||||
updated := decodeBody[dto.AIProviderResponse](t, updatedRecorder)
|
||||
if updated.Name != "OpenAI Relay" || updated.APIKeyRef != "vault://providers/openai" || updated.Status != domain.AIProviderStatusActive {
|
||||
if updated.Name != "OpenAI Relay" || !updated.APIKeyConfigured || updated.Status != domain.AIProviderStatusActive {
|
||||
t.Fatalf("unexpected updated provider: %+v", updated)
|
||||
}
|
||||
|
||||
@@ -1133,6 +1171,7 @@ func TestPluginBridgeExecuteAPI(t *testing.T) {
|
||||
Name: "Bridge API Server",
|
||||
State: domain.ServerInstanceStateRunning,
|
||||
}, ownerSession)
|
||||
putJSONWithAuth[dto.RuntimeBindingResponse](t, router, "/api/v1/server-instances/"+instance.ID+"/runtime-binding", dto.RuntimeBindingUpdateRequest{ProfileKey: "local", Bindings: map[string]string{}}, ownerSession)
|
||||
lifecycleInstance := postJSONWithAuth[dto.ServerInstanceResponse](t, router, "/api/v1/server-instances", dto.ServerInstanceCreateRequest{
|
||||
ID: "server-bridge-lifecycle-api",
|
||||
PluginID: "game.example",
|
||||
@@ -1140,6 +1179,7 @@ func TestPluginBridgeExecuteAPI(t *testing.T) {
|
||||
Name: "Bridge Lifecycle API Server",
|
||||
State: domain.ServerInstanceStateReady,
|
||||
}, ownerSession)
|
||||
putJSONWithAuth[dto.RuntimeBindingResponse](t, router, "/api/v1/server-instances/"+lifecycleInstance.ID+"/runtime-binding", dto.RuntimeBindingUpdateRequest{ProfileKey: "local", Bindings: map[string]string{}}, ownerSession)
|
||||
stream := postJSON[dto.LogStreamResponse](t, router, "/api/v1/log-streams", dto.LogStreamCreateRequest{
|
||||
ID: "log-bridge-api",
|
||||
ServerInstanceID: instance.ID,
|
||||
@@ -1276,6 +1316,82 @@ func TestGamePluginManifestRegistryAPIRejectsUnsafeManifest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuntimeBindingAPIIsAuthorizedValidatedAndRedacted(t *testing.T) {
|
||||
router := newTestRouter()
|
||||
adminSession := createAdminSession(t, router)
|
||||
registration := validGamePluginManifestRegistrationRequest()
|
||||
registration.Manifest.Capabilities = append(registration.Manifest.Capabilities, "remote.run.rcon.command")
|
||||
registration.Manifest.RuntimeProfiles = dto.GamePluginRuntimeProfilesBody{
|
||||
Discovery: []dto.RuntimeDiscoveryProbeBody{{Key: "server-root-check", Kind: "file.exists", TargetKey: "server-root", Required: true}},
|
||||
LifecycleProfiles: []dto.RuntimeLifecycleProfileBody{{Key: "local", Mode: "local-process", Capabilities: []string{"process.install", "process.start", "process.stop"}, TransportKeys: []string{"rcon"}}},
|
||||
TransportProfiles: []dto.RuntimeTransportProfileBody{{Key: "rcon", Kind: "rcon", TargetKey: "rcon.password", Capabilities: []string{"remote.run.rcon.command"}}},
|
||||
}
|
||||
registered := postJSON[dto.GamePluginResponse](t, router, "/api/v1/game-plugins/register-manifest", registration)
|
||||
if len(registered.RuntimeProfiles.LifecycleProfiles) != 1 || registered.RuntimeProfiles.LifecycleProfiles[0].Key != "local" {
|
||||
t.Fatalf("runtime profiles were not projected: %+v", registered.RuntimeProfiles)
|
||||
}
|
||||
endpoint := validRunEndpointRequest()
|
||||
endpoint.Capabilities = append(endpoint.Capabilities, "remote.run.rcon.command")
|
||||
postJSON[dto.RunEndpointResponse](t, router, "/api/v1/run/endpoints", endpoint)
|
||||
server := postJSONWithAuth[dto.ServerInstanceResponse](t, router, "/api/v1/server-instances", dto.ServerInstanceCreateRequest{ID: "runtime-binding-api", PluginID: registration.Manifest.ID, RunEndpointID: "run-local", Name: "Runtime Binding API", State: domain.ServerInstanceStateReady}, adminSession)
|
||||
postJSONWithAuth[dto.UserResponse](t, router, "/api/v1/users", dto.UserCreateRequest{ID: "runtime-binding-other", DisplayName: "Runtime Binding Other", Email: "runtime-binding-other@example.test", Roles: []string{"server-admin"}, Password: "secret-password"}, adminSession)
|
||||
otherSession := postOKJSON[dto.AuthSessionResponse](t, router, "/api/v1/auth/login", dto.LoginRequest{Account: "runtime-binding-other@example.test", Password: "secret-password"}).SessionID
|
||||
assertErrorResponse(t, performRequest(t, router, http.MethodGet, "/api/v1/server-instances/"+server.ID+"/runtime-binding", nil), http.StatusUnauthorized, errorCodeUnauthorized)
|
||||
assertErrorResponse(t, requestWithAuth(t, router, http.MethodGet, "/api/v1/server-instances/"+server.ID+"/runtime-binding", "", otherSession), http.StatusForbidden, errorCodeForbidden)
|
||||
assertErrorResponse(t, requestJSONWithAuth(t, router, http.MethodPut, "/api/v1/server-instances/"+server.ID+"/runtime-binding", dto.RuntimeBindingUpdateRequest{ProfileKey: "local"}, otherSession), http.StatusForbidden, errorCodeForbidden)
|
||||
|
||||
unconfigured := getJSONWithAuth[dto.RuntimeBindingResponse](t, router, "/api/v1/server-instances/"+server.ID+"/runtime-binding", adminSession)
|
||||
if unconfigured.Configured || unconfigured.Reason != "runtime profile is not configured" {
|
||||
t.Fatalf("unexpected unconfigured projection: %+v", unconfigured)
|
||||
}
|
||||
missingStart := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/server-instances/"+server.ID+"/start", dto.ServerLifecycleCommandRequest{ExpectedConfigVersion: server.ConfigVersion, IdempotencyKey: "api-start-missing-binding"}, adminSession)
|
||||
assertErrorResponse(t, missingStart, http.StatusBadRequest, errorCodeValidation)
|
||||
|
||||
incompleteRecorder := requestJSONWithAuth(t, router, http.MethodPut, "/api/v1/server-instances/"+server.ID+"/runtime-binding", dto.RuntimeBindingUpdateRequest{ProfileKey: "local", Bindings: map[string]string{"server-root": "runtime.server-root"}}, adminSession)
|
||||
assertStatus(t, incompleteRecorder, http.StatusOK)
|
||||
incompleteBody := incompleteRecorder.Body.String()
|
||||
incomplete := decodeBody[dto.RuntimeBindingResponse](t, incompleteRecorder)
|
||||
if incomplete.Status != domain.RuntimeBindingStatusIncomplete || len(incomplete.MissingKeys) != 1 || incomplete.MissingKeys[0] != "rcon.password" {
|
||||
t.Fatalf("unexpected incomplete projection: %+v", incomplete)
|
||||
}
|
||||
if strings.Contains(incompleteBody, "runtime.server-root") {
|
||||
t.Fatalf("binding response exposed stored logical ref: %s", incompleteBody)
|
||||
}
|
||||
|
||||
completeRecorder := requestJSONWithAuth(t, router, http.MethodPut, "/api/v1/server-instances/"+server.ID+"/runtime-binding", dto.RuntimeBindingUpdateRequest{ProfileKey: "local", Bindings: map[string]string{"rcon.password": "secret://runtime-binding-api/rcon"}}, adminSession)
|
||||
assertStatus(t, completeRecorder, http.StatusOK)
|
||||
completeBody := completeRecorder.Body.String()
|
||||
complete := decodeBody[dto.RuntimeBindingResponse](t, completeRecorder)
|
||||
secretFlag := false
|
||||
for _, key := range complete.Keys {
|
||||
if key.Key == "rcon.password" {
|
||||
secretFlag = key.Configured && key.Secret
|
||||
}
|
||||
}
|
||||
if complete.Status != domain.RuntimeBindingStatusComplete || !secretFlag {
|
||||
t.Fatalf("unexpected complete projection: %+v", complete)
|
||||
}
|
||||
for _, forbidden := range []string{"secret://runtime-binding-api/rcon", "runtime.server-root", "/srv/game", "unix://", "tcp://", "password="} {
|
||||
if strings.Contains(completeBody, forbidden) {
|
||||
t.Fatalf("runtime binding response exposed %q: %s", forbidden, completeBody)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe := requestJSONWithAuth(t, router, http.MethodPut, "/api/v1/server-instances/"+server.ID+"/runtime-binding", dto.RuntimeBindingUpdateRequest{ProfileKey: "local", Bindings: map[string]string{"server-root": "/srv/game"}}, adminSession)
|
||||
assertErrorResponse(t, unsafe, http.StatusBadRequest, errorCodeValidation)
|
||||
undeclared := requestJSONWithAuth(t, router, http.MethodPut, "/api/v1/server-instances/"+server.ID+"/runtime-binding", dto.RuntimeBindingUpdateRequest{ProfileKey: "local", Bindings: map[string]string{"host.socket": "runtime.socket"}}, adminSession)
|
||||
assertErrorResponse(t, undeclared, http.StatusBadRequest, errorCodeValidation)
|
||||
|
||||
created := postOKJSONWithAuth[dto.ServerLifecycleResponse](t, router, "/api/v1/server-instances/workflows/create", dto.ServerLifecycleCreateRequest{ID: "runtime-create-complete", PluginID: registration.Manifest.ID, RunEndpointID: "run-local", Name: "Runtime Create Complete", IdempotencyKey: "runtime-create-complete", ProfileKey: "local", Bindings: map[string]string{"server-root": "runtime.server-root", "rcon.password": "secret://runtime-create-complete/rcon"}}, adminSession)
|
||||
if created.Job.TargetKey != "local" {
|
||||
t.Fatalf("create workflow did not dispatch selected profile: %+v", created.Job)
|
||||
}
|
||||
incompleteCreate := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/server-instances/workflows/create", dto.ServerLifecycleCreateRequest{ID: "runtime-create-incomplete", PluginID: registration.Manifest.ID, RunEndpointID: "run-local", Name: "Runtime Create Incomplete", IdempotencyKey: "runtime-create-incomplete", ProfileKey: "local", Bindings: map[string]string{"server-root": "runtime.server-root"}}, adminSession)
|
||||
assertErrorResponse(t, incompleteCreate, http.StatusBadRequest, errorCodeValidation)
|
||||
missingServer := requestWithAuth(t, router, http.MethodGet, "/api/v1/server-instances/runtime-create-incomplete", "", adminSession)
|
||||
assertErrorResponse(t, missingServer, http.StatusNotFound, errorCodeNotFound)
|
||||
}
|
||||
|
||||
func TestGamePluginRegistryResponseDoesNotExposeRawInternals(t *testing.T) {
|
||||
router := newTestRouter()
|
||||
recorder := performJSON(t, router, http.MethodPost, "/api/v1/game-plugins/register-manifest", validGamePluginManifestRegistrationRequest())
|
||||
@@ -1297,11 +1413,11 @@ func newTestRouter() http.Handler {
|
||||
if err := core.SeedLocalPlatformAdmin(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return NewRouterWithCore(core)
|
||||
return NewTestRouterWithCore(core)
|
||||
}
|
||||
|
||||
func apiRouterWithoutSeededAdmin() http.Handler {
|
||||
return NewRouterWithCore(service.NewCoreService(repo.NewMemoryStore()))
|
||||
return NewTestRouterWithCore(service.NewCoreService(repo.NewMemoryStore()))
|
||||
}
|
||||
|
||||
func postJSON[T any](t *testing.T, router http.Handler, path string, body any) T {
|
||||
@@ -1501,6 +1617,11 @@ func createRuntimeAPIFixtures(t *testing.T, router http.Handler, adminSession st
|
||||
domain.JobCapabilityDependenciesCheck,
|
||||
domain.JobCapabilityDependenciesInstall,
|
||||
domain.JobCapabilityLogsBackfill,
|
||||
domain.JobCapabilityClientManagerDeploy,
|
||||
domain.JobCapabilityClientManagerControl,
|
||||
domain.JobCapabilityClientManagerUpdate,
|
||||
domain.JobCapabilityClientManagerRollback,
|
||||
domain.JobCapabilityClientManagerUninstall,
|
||||
}
|
||||
pluginRequest.DeclaredPermissions = []string{
|
||||
"server.read",
|
||||
@@ -1516,16 +1637,26 @@ func createRuntimeAPIFixtures(t *testing.T, router http.Handler, adminSession st
|
||||
string(domain.PluginBridgeActionDependenciesRequest),
|
||||
string(domain.PluginBridgeActionLogsBackfillRequest),
|
||||
}
|
||||
pluginRequest.RuntimeProfiles.DependencyProbes = []dto.RuntimeDependencyProbeBody{{Key: "java-runtime", Kind: "command.version", TargetKey: "java", Platforms: []string{"linux"}}}
|
||||
pluginRequest.RuntimeProfiles.InstallPlans = []dto.RuntimeInstallPlanBody{{Key: "java-install", Title: "Install Java", Platforms: []string{"linux"}, Steps: []dto.RuntimeInstallStepBody{{Type: "package", TargetKey: "java", PackageManager: "apt", PackageName: "openjdk-21-jre"}}}}
|
||||
pluginRequest.RuntimeProfiles.ClientManagers = []dto.RuntimeClientManagerProfileBody{{Key: "scum-client-manager", DisplayName: "SCUM Client Manager", Version: "1.0.0", Repository: dto.RuntimeRepositoryBody{URL: "https://github.com/F88888/scum_client.git", RevisionPolicy: "branch", Branch: "main"}, SupportedTargets: []dto.RuntimeTargetBody{{OS: "windows", Arch: "amd64"}, {OS: "linux", Arch: "amd64"}}, Build: dto.RuntimeBuildBody{System: "go", EntryRef: "main.go"}, OutputArtifacts: []string{"scum_client.exe"}, Deployment: dto.RuntimeClientManagerDeploymentBody{Mode: "run-supervised", ExecutableRef: "scum_client.exe", RequiredRunCapabilities: []string{domain.JobCapabilityClientManagerDeploy, domain.JobCapabilityClientManagerControl, domain.JobCapabilityClientManagerUpdate, domain.JobCapabilityClientManagerRollback, domain.JobCapabilityClientManagerUninstall}}, Lifecycle: dto.RuntimeClientManagerLifecycleBody{Actions: []string{"start", "stop", "restart", "status", "update", "rollback", "uninstall"}, StartupTimeoutSeconds: 60, StopTimeoutSeconds: 30}, Health: dto.RuntimeClientManagerHealthBody{Mode: "component-heartbeat", IntervalSeconds: 15, DegradedAfterSeconds: 45, OfflineAfterSeconds: 120, RequiredCapabilities: []string{"component.register", "component.heartbeat", "component.health"}}, Compatibility: dto.RuntimeClientManagerCompatibilityBody{MinimumVersion: "1.0.0"}, UpdatePolicy: dto.RuntimeClientManagerUpdatePolicyBody{Strategy: "manual-staged", RequireApproval: true, HealthConfirmationSeconds: 60, RetainPrevious: true}}}
|
||||
postJSON[dto.GamePluginResponse](t, router, "/api/v1/game-plugins", pluginRequest)
|
||||
|
||||
endpoint := validRunEndpointRequest()
|
||||
endpoint.ID = "run-runtime"
|
||||
endpoint.Platform = "linux"
|
||||
endpoint.Architecture = "amd64"
|
||||
endpoint.Capabilities = append(endpoint.Capabilities,
|
||||
domain.JobCapabilityDistributionBuild,
|
||||
domain.JobCapabilityRunSelfUpdate,
|
||||
domain.JobCapabilityDependenciesCheck,
|
||||
domain.JobCapabilityDependenciesInstall,
|
||||
domain.JobCapabilityLogsBackfill,
|
||||
domain.JobCapabilityClientManagerDeploy,
|
||||
domain.JobCapabilityClientManagerControl,
|
||||
domain.JobCapabilityClientManagerUpdate,
|
||||
domain.JobCapabilityClientManagerRollback,
|
||||
domain.JobCapabilityClientManagerUninstall,
|
||||
)
|
||||
postJSON[dto.RunEndpointResponse](t, router, "/api/v1/run/endpoints", endpoint)
|
||||
|
||||
@@ -1536,6 +1667,7 @@ func createRuntimeAPIFixtures(t *testing.T, router http.Handler, adminSession st
|
||||
Name: "Runtime API Server",
|
||||
State: domain.ServerInstanceStateReady,
|
||||
}, adminSession)
|
||||
putJSONWithAuth[dto.RuntimeBindingResponse](t, router, "/api/v1/server-instances/"+server.ID+"/runtime-binding", dto.RuntimeBindingUpdateRequest{ProfileKey: "local", Bindings: map[string]string{}}, adminSession)
|
||||
postJSON[dto.LogStreamResponse](t, router, "/api/v1/log-streams", dto.LogStreamCreateRequest{
|
||||
ID: "log-runtime-api",
|
||||
ServerInstanceID: server.ID,
|
||||
@@ -1594,6 +1726,7 @@ func validGamePluginRequest() dto.GamePluginCreateRequest {
|
||||
Start: "actions/start.json",
|
||||
Stop: "actions/stop.json",
|
||||
},
|
||||
RuntimeProfiles: dto.GamePluginRuntimeProfilesBody{LifecycleProfiles: []dto.RuntimeLifecycleProfileBody{{Key: "local", Mode: "local-process", Capabilities: []string{"process.install", "process.start", "process.stop"}}}},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1638,7 +1771,8 @@ func validGamePluginManifestRegistrationRequest() dto.GamePluginManifestRegistra
|
||||
BridgeActions: []string{string(domain.PluginBridgeActionLogsQuery), string(domain.PluginBridgeActionFilesRequest), string(domain.PluginBridgeActionAIInvoke)},
|
||||
},
|
||||
},
|
||||
AI: dto.GamePluginManifestAIBody{Purposes: []string{"logs.diagnose"}},
|
||||
AI: dto.GamePluginManifestAIBody{Purposes: []string{"logs.diagnose"}},
|
||||
RuntimeProfiles: dto.GamePluginRuntimeProfilesBody{LifecycleProfiles: []dto.RuntimeLifecycleProfileBody{{Key: "local", Mode: "local-process", Capabilities: []string{"process.install", "process.start", "process.stop"}}}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user