Files
browser/platform/api/scum_handlers_test.go
T

110 lines
7.6 KiB
Go

package api
import (
"net/http"
"strings"
"testing"
"browser.local/platform/domain"
"browser.local/platform/repo"
"browser.local/platform/service"
)
func TestSCUMSchemaProbeEndpointQueuesPlatformScheduledDurableJob(t *testing.T) {
store := repo.NewMemoryStore()
core := service.NewCoreService(store)
plugin, err := core.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{"process.start", domain.JobCapabilityRemoteRunDBSQLiteProbe},
DeclaredPermissions: []string{"server.remote.access"},
Permissions: domain.PluginPermissions{Jobs: true, RemoteAccess: true},
LifecycleActions: domain.PluginLifecycleActions{Start: "actions/start.json"},
RemoteAccess: domain.GamePluginRemoteAccess{Methods: []string{"run"}, RunCapabilities: []string{domain.JobCapabilityRemoteRunDBSQLiteProbe}},
RuntimeProfiles: domain.GamePluginRuntimeProfiles{
LifecycleProfiles: []domain.RuntimeLifecycleProfile{{Key: "local", Mode: "local-process", Capabilities: []string{"process.start"}}},
TransportProfiles: []domain.RuntimeTransportProfile{{Key: "scum-database", Kind: "sqlite", TargetKey: "scum-database", Capabilities: []string{domain.JobCapabilityRemoteRunDBSQLiteProbe}}},
},
SCUMLiveData: domain.SCUMLiveDataManifest{SchemaVersion: "1", Probe: domain.SCUMSchemaProbeDeclaration{Capability: domain.JobCapabilityRemoteRunDBSQLiteProbe, TargetKey: "scum-database", Bounds: domain.DefaultSCUMSchemaProbeBounds()}, CapabilityGates: []domain.SCUMLiveDataCapabilityGateDeclaration{{Capability: domain.SCUMDataCapabilitySchemaProbe, Gate: domain.SCUMCapabilityGateDisabled, AdapterVersion: "scum-live-data-v0", EvidenceStatus: domain.SCUMCapabilityEvidenceMissing, SafeReason: "waiting for current service evidence"}}},
})
if err != nil {
t.Fatalf("create plugin: %v", err)
}
endpoint, err := core.CreateRunEndpoint(domain.RunEndpoint{ID: "run-local", DisplayName: "Local Run", Version: "0.1.0", Capabilities: []string{"process.start", domain.JobCapabilityRemoteRunDBSQLiteProbe}, Capacity: domain.RunCapacity{MaxJobs: 4}})
if err != nil {
t.Fatalf("create run endpoint: %v", err)
}
if _, err := core.CreateUser(domain.User{ID: "scum-probe-owner", DisplayName: "SCUM Probe Owner", Email: "scum-probe-owner@example.test", Status: domain.UserStatusActive, Roles: []string{"server-owner"}, PasswordHash: "secret-password"}); err != nil {
t.Fatalf("create owner: %v", err)
}
auth, err := core.LoginUser(domain.UserLogin{Account: "scum-probe-owner@example.test", Password: "secret-password"})
if err != nil {
t.Fatalf("login: %v", err)
}
instance, err := core.CreateServerInstance(domain.ServerInstance{ID: "server-scum-probe-api", PluginID: plugin.ID, RunEndpointID: endpoint.ID, Name: "SCUM Probe API", OwnerUserID: "scum-probe-owner", State: domain.ServerInstanceStateRunning, ConfigVersion: 1})
if err != nil {
t.Fatalf("create server: %v", err)
}
if _, err := core.UpdateServerRuntimeBindingForSession(auth.SessionID, instance.ID, domain.RuntimeBindingUpdate{ProfileKey: "local", Bindings: map[string]string{}}); err != nil {
t.Fatalf("create runtime binding: %v", err)
}
router := NewAuthorizedRouterWithCore(core)
unauthorized := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/server-instances/"+instance.ID+"/scum/schema-probe", map[string]string{"idempotencyKey": "probe-api-denied"}, "")
assertStatus(t, unauthorized, http.StatusUnauthorized)
recorder := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/server-instances/"+instance.ID+"/scum/schema-probe", map[string]string{"idempotencyKey": "probe-api-current"}, auth.SessionID)
assertStatus(t, recorder, http.StatusAccepted)
body := recorder.Body.String()
for _, forbidden := range []string{"SCUM.db", "sqlite_master", "SELECT", "C:\\", "secret://", "password"} {
if strings.Contains(body, forbidden) {
t.Fatalf("schema probe dispatch response leaked forbidden material %q: %s", forbidden, body)
}
}
jobs, err := core.ListJobsForSession(auth.SessionID, domain.JobFilter{ServerInstanceID: instance.ID})
if err != nil {
t.Fatalf("list jobs: %v", err)
}
if len(jobs) != 1 || jobs[0].Capability != domain.JobCapabilityRemoteRunDBSQLiteProbe || jobs[0].TargetKey != "databases/scum-database" || jobs[0].InputRef != "" || len(jobs[0].ExecutionInput.Inputs) != 0 || jobs[0].ExecutionInput.RemoteAdapterKey != "" || jobs[0].ExecutionInput.RemoteAdapterKind != "" {
t.Fatalf("schema probe endpoint did not queue fenced durable job: %+v", jobs)
}
if jobs[0].ExecutionInput.SQLiteSchemaProbe == nil || jobs[0].ExecutionInput.SQLiteSchemaProbe.RequestID != jobs[0].ID || jobs[0].ExecutionInput.SQLiteSchemaProbe.Binding.DatabaseIdentity != "scum-database" || jobs[0].ExecutionInput.SQLiteSchemaProbe.Bounds.MaxResultBytes != 524288 {
t.Fatalf("schema probe endpoint did not attach typed probe request: %+v", jobs[0].ExecutionInput.SQLiteSchemaProbe)
}
}
func TestLegacySCUMEndpointsReturnNotFoundWithoutDispatchingJobs(t *testing.T) {
store := repo.NewMemoryStore()
core := service.NewCoreService(store)
if _, err := core.CreateUser(domain.User{ID: "scum-api-owner", DisplayName: "SCUM API Owner", Email: "scum-api-owner@example.test", Status: domain.UserStatusActive, Roles: []string{"server-owner"}, PasswordHash: "secret-password"}); err != nil {
t.Fatalf("create owner: %v", err)
}
if _, err := core.CreateGamePlugin(validGamePluginRequest().ToDomain()); err != nil {
t.Fatalf("create plugin: %v", err)
}
if _, err := core.CreateRunEndpoint(validRunEndpointRequest().ToDomain()); err != nil {
t.Fatalf("create endpoint: %v", err)
}
if _, err := core.CreateServerInstance(domain.ServerInstance{ID: "server-scum-api", PluginID: "server.scum", RunEndpointID: "run-local", Name: "SCUM API", OwnerUserID: "scum-api-owner", State: domain.ServerInstanceStateRunning, ConfigVersion: 1}); err != nil {
t.Fatalf("create server: %v", err)
}
auth, err := core.LoginUser(domain.UserLogin{Account: "scum-api-owner@example.test", Password: "secret-password"})
if err != nil {
t.Fatalf("login: %v", err)
}
router := NewAuthorizedRouterWithCore(core)
for _, legacy := range []struct{ method, path string }{
{http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/players"}, {http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/squads"}, {http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/squad-members"}, {http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/vehicles"}, {http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/flags"}, {http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/positions"},
{http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/operations"}, {http.MethodPost, "/api/v1/server-instances/server-scum-api/scum/operations"}, {http.MethodPost, "/api/v1/server-instances/server-scum-api/scum/operations/op-1/approve"}, {http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/workflows"}, {http.MethodPost, "/api/v1/server-instances/server-scum-api/scum/workflows"}, {http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/workflow-steps?workflowId=workflow-1"},
} {
assertStatus(t, requestWithAuth(t, router, legacy.method, legacy.path, `{}`, auth.SessionID), http.StatusNotFound)
}
jobs, err := core.ListJobsForSession(auth.SessionID, domain.JobFilter{ServerInstanceID: "server-scum-api"})
if err != nil || len(jobs) != 0 {
t.Fatalf("removed SCUM endpoints must not dispatch jobs, got jobs=%+v err=%v", jobs, err)
}
}