package service import ( "encoding/json" "strings" "testing" "time" "browser.local/platform/domain" ) func TestSCUMRCONOperationApprovalDispatchesTransientCommandAndConfirms(t *testing.T) { svc, session, runSession, instance := newSourceRCONFixture(t) seedSCUMOperationTemplates(t, svc, instance.PluginID) request := domain.SCUMOperationRequest{TemplateKey: "player.fame.set", PlayerID: "76561198000000001", Payload: map[string]any{"fame": 123}, Reason: "restore fame", IdempotencyKey: "fame-restore-1"} operation, err := svc.RequestSCUMOperationForSession(session, instance.ID, request) if err != nil || operation.Status != domain.SCUMWorkflowStepWaiting { t.Fatalf("request operation=%+v err=%v", operation, err) } duplicate, err := svc.RequestSCUMOperationForSession(session, instance.ID, request) if err != nil || duplicate.ID != operation.ID { t.Fatalf("duplicate should return original operation: duplicate=%+v err=%v", duplicate, err) } approved, err := svc.ApproveSCUMOperationForSession(session, operation.ID) if err != nil || approved.Status != domain.SCUMWorkflowStepQueued || approved.RunJobID == "" { t.Fatalf("approve operation=%+v err=%v", approved, err) } job, err := svc.store.Jobs().Get(approved.RunJobID) if err != nil { t.Fatalf("get operation job: %v", err) } serializedOperation, _ := json.Marshal(approved) serializedJob, _ := json.Marshal(job) for _, forbidden := range []string{"#SetFamePoints", "SetCurrencyBalance", "password="} { if strings.Contains(string(serializedOperation), forbidden) || strings.Contains(string(serializedJob), forbidden) { t.Fatalf("operation/job persisted raw RCON text %q: operation=%s job=%s", forbidden, serializedOperation, serializedJob) } } claim, err := svc.ClaimRunJob(domain.RunJobClaim{RunEndpointID: "run-local", SessionToken: runSession, Capabilities: []string{domain.JobCapabilityRemoteRunRCONCommand}, Capacity: domain.RunCapacity{MaxJobs: 1}}) if err != nil || !claim.HasJob || claim.Job == nil || claim.Job.JobID != approved.RunJobID { t.Fatalf("claim operation RCON job: claim=%+v err=%v", claim, err) } ack, err := svc.AckRunJob(domain.RunJobAck{RunEndpointID: "run-local", SessionToken: runSession, JobID: claim.Job.JobID, LeaseToken: claim.Job.LeaseToken, Attempt: claim.Job.Attempt, Message: "accepted"}) if err != nil || !ack.Accepted { t.Fatalf("ack operation RCON job: ack=%+v err=%v", ack, err) } input, err := svc.GetSourceRCONExecutionInput(domain.SourceRCONExecutionInputRequest{RunEndpointID: "run-local", SessionToken: runSession, JobID: claim.Job.JobID, LeaseToken: ack.Job.LeaseToken, Attempt: ack.Job.Attempt}) if err != nil { t.Fatalf("read transient operation command: %v", err) } if input.Command != "#SetFamePoints 123 \"76561198000000001\"" { t.Fatalf("unexpected generated RCON command: %q", input.Command) } if _, err := svc.CompleteRunJob(domain.RunJobResult{RunEndpointID: "run-local", SessionToken: runSession, JobID: claim.Job.JobID, LeaseToken: ack.Job.LeaseToken, Attempt: ack.Job.Attempt, State: domain.JobStateSucceeded, Progress: domain.RunJobProgressReport{Percent: 100}, ExecutionResult: domain.JobExecutionResult{Kind: "source-rcon.succeeded", AuditSummary: "typed RCON delivered"}}); err != nil { t.Fatalf("complete operation job: %v", err) } reconciled, err := svc.ReconcileSCUMOperation(approved.ID) if err != nil || reconciled.Status != domain.SCUMWorkflowStepConfirming { t.Fatalf("expected confirming after delivery before readback: %+v err=%v", reconciled, err) } confirmed, err := svc.ConfirmSCUMOperation(approved.ID, domain.SCUMOperationConfirmation{Status: "confirmed", ConfirmedFields: map[string]any{"fame": 123}, ObservedAt: fixedTime.Add(time.Minute)}) if err != nil || confirmed.Status != domain.SCUMWorkflowStepConfirmed || confirmed.CompletedAt.IsZero() { t.Fatalf("confirm operation=%+v err=%v", confirmed, err) } } func TestSCUMRCONOperationPermissionUnknownAndConfirmationFailure(t *testing.T) { svc, session, runSession, instance := newSourceRCONFixture(t) seedSCUMOperationTemplates(t, svc, instance.PluginID) adminOnly, err := svc.RequestSCUMOperationForSession(session, instance.ID, domain.SCUMOperationRequest{TemplateKey: "player.currency.gold.set", PlayerID: "76561198000000002", Payload: map[string]any{"amount": 9}, Reason: "admin-only", IdempotencyKey: "gold-admin-only"}) if err != nil { t.Fatalf("request admin-only operation: %v", err) } if _, err := svc.ApproveSCUMOperationForSession(session, adminOnly.ID); err != ErrForbidden { t.Fatalf("expected platform-admin approval denial, got %v", err) } operation, err := svc.RequestSCUMOperationForSession(session, instance.ID, domain.SCUMOperationRequest{TemplateKey: "player.currency.normal.set", PlayerID: "76561198000000002", Payload: map[string]any{"amount": 500}, Reason: "repair balance", IdempotencyKey: "normal-unknown"}) if err != nil { t.Fatalf("request normal currency operation: %v", err) } approved, err := svc.ApproveSCUMOperationForSession(session, operation.ID) if err != nil { t.Fatalf("approve normal currency operation: %v", err) } claim, err := svc.ClaimRunJob(domain.RunJobClaim{RunEndpointID: "run-local", SessionToken: runSession, Capabilities: []string{domain.JobCapabilityRemoteRunRCONCommand}, Capacity: domain.RunCapacity{MaxJobs: 1}}) if err != nil || !claim.HasJob || claim.Job == nil || claim.Job.JobID != approved.RunJobID { t.Fatalf("claim normal currency job: claim=%+v err=%v", claim, err) } ack, err := svc.AckRunJob(domain.RunJobAck{RunEndpointID: "run-local", SessionToken: runSession, JobID: claim.Job.JobID, LeaseToken: claim.Job.LeaseToken, Attempt: claim.Job.Attempt}) if err != nil { t.Fatalf("ack normal currency job: %v", err) } if _, err := svc.GetSourceRCONExecutionInput(domain.SourceRCONExecutionInputRequest{RunEndpointID: "run-local", SessionToken: runSession, JobID: claim.Job.JobID, LeaseToken: ack.Job.LeaseToken, Attempt: ack.Job.Attempt}); err != nil { t.Fatalf("consume normal currency command: %v", err) } if _, err := svc.CompleteRunJob(domain.RunJobResult{RunEndpointID: "run-local", SessionToken: runSession, JobID: claim.Job.JobID, LeaseToken: ack.Job.LeaseToken, Attempt: ack.Job.Attempt, State: domain.JobStateFailed, Progress: domain.RunJobProgressReport{Percent: 100}, ExecutionResult: domain.JobExecutionResult{Kind: "source-rcon.unknown", AuditSummary: "unknown command state"}}); err != nil { t.Fatalf("complete unknown operation job: %v", err) } unknown, err := svc.ReconcileSCUMOperation(approved.ID) if err != nil || unknown.Status != domain.SCUMWorkflowStepUnknown { t.Fatalf("expected unknown terminal state: %+v err=%v", unknown, err) } failure, err := svc.ConfirmSCUMOperation(operation.ID, domain.SCUMOperationConfirmation{Status: "failed", SafeSummary: domain.SCUMSafeSummary{Title: "Readback mismatch", Message: "Projection did not match expected currency."}, ObservedAt: time.Date(2026, 8, 10, 12, 0, 0, 0, time.UTC)}) if err != nil || failure.Status != domain.SCUMWorkflowStepFailed { t.Fatalf("expected confirmation failure: %+v err=%v", failure, err) } } func TestSCUMSQLiteMutationOperationSafetyGatesAndDispatchesTypedJob(t *testing.T) { svc, session, runSession, instance := newSourceRCONFixture(t) seedSCUMOperationTemplates(t, svc, instance.PluginID) adminSession := enableSCUMSQLiteMutationOperationSupport(t, svc, instance) if _, err := svc.ApplySCUMObservationResult(domain.SCUMObservationResult{ServerInstanceID: instance.ID, PluginID: instance.PluginID, Source: "run.sqlite.read", QueryKey: "scum.player.profile", Sequence: 1, Checksum: "sha256:profile-online", ObservedAt: fixedTime, Rows: []map[string]any{{"gamePlayerId": "76561198000000855", "displayName": "Attribute Tester", "online": true, "855": 100}}}); err != nil { t.Fatalf("seed online projection: %v", err) } operation, err := svc.RequestSCUMOperationForSession(session, instance.ID, domain.SCUMOperationRequest{TemplateKey: "player.attribute.855.set", PlayerID: "76561198000000855", Payload: map[string]any{"fieldKey": "855", "before": 100, "after": 150, "safetyWindow": "maintenance-2026-08-10", "backupRef": "snapshot://scum/server-rcon/20260810"}, Reason: "repair attribute 855", IdempotencyKey: "attribute-855-1"}) if err != nil || operation.Status != domain.SCUMWorkflowStepWaiting { t.Fatalf("request sqlite mutation=%+v err=%v", operation, err) } waiting, err := svc.ApproveSCUMOperationForSession(adminSession, operation.ID) if err != nil || waiting.Status != domain.SCUMWorkflowStepWaiting || waiting.RunJobID != "" || !strings.Contains(waiting.SafeSummary.Title, "离线") { t.Fatalf("online player should block dispatch: %+v err=%v", waiting, err) } if _, err := svc.ApplySCUMObservationResult(domain.SCUMObservationResult{ServerInstanceID: instance.ID, PluginID: instance.PluginID, Source: "run.sqlite.read", QueryKey: "scum.player.profile", Sequence: 2, Checksum: "sha256:profile-offline", ObservedAt: fixedTime.Add(time.Minute), Rows: []map[string]any{{"gamePlayerId": "76561198000000855", "displayName": "Attribute Tester", "online": false, "855": 100}}}); err != nil { t.Fatalf("seed offline projection: %v", err) } approved, err := svc.ApproveSCUMOperationForSession(adminSession, operation.ID) if err != nil || approved.Status != domain.SCUMWorkflowStepQueued || approved.RunJobID == "" { t.Fatalf("approve sqlite mutation=%+v err=%v", approved, err) } job, err := svc.store.Jobs().Get(approved.RunJobID) if err != nil { t.Fatalf("get sqlite mutation job: %v", err) } if job.Capability != domain.JobCapabilityRemoteRunProtectedSQL || job.ExecutionInput.Inputs["fieldKey"] != "855" || job.ExecutionInput.Inputs["before"] != "100" || job.ExecutionInput.Inputs["after"] != "150" || job.ExecutionInput.Inputs["maxRowsAffected"] != "1" { t.Fatalf("unexpected typed mutation job: %+v", job) } serializedOperation, _ := json.Marshal(approved) serializedJob, _ := json.Marshal(job) for _, forbidden := range []string{"UPDATE ", "DELETE ", "INSERT ", "SELECT ", "SCUM.db", "/Saved/", "requestText"} { if strings.Contains(strings.ToUpper(string(serializedOperation)), strings.ToUpper(forbidden)) || strings.Contains(strings.ToUpper(string(serializedJob)), strings.ToUpper(forbidden)) { t.Fatalf("operation/job persisted raw DB material %q: operation=%s job=%s", forbidden, serializedOperation, serializedJob) } } claim, err := svc.ClaimRunJob(domain.RunJobClaim{RunEndpointID: "run-local", SessionToken: runSession, Capabilities: []string{domain.JobCapabilityRemoteRunProtectedSQL}, Capacity: domain.RunCapacity{MaxJobs: 1}}) if err != nil || !claim.HasJob || claim.Job == nil || claim.Job.JobID != approved.RunJobID { t.Fatalf("claim sqlite mutation job: claim=%+v err=%v", claim, err) } ack, err := svc.AckRunJob(domain.RunJobAck{RunEndpointID: "run-local", SessionToken: runSession, JobID: claim.Job.JobID, LeaseToken: claim.Job.LeaseToken, Attempt: claim.Job.Attempt, Message: "accepted"}) if err != nil || !ack.Accepted { t.Fatalf("ack sqlite mutation job: ack=%+v err=%v", ack, err) } mutationChecksum := "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" content := mustJSON(t, map[string]any{"outcome": "succeeded", "affectedRows": 1, "mutationChecksum": mutationChecksum, "confirmationRows": []map[string]any{{"playerId": "76561198000000855", "fieldKey": "855", "value": 150}}}) if _, err := svc.CompleteRunJob(domain.RunJobResult{RunEndpointID: "run-local", SessionToken: runSession, JobID: claim.Job.JobID, LeaseToken: ack.Job.LeaseToken, Attempt: ack.Job.Attempt, State: domain.JobStateSucceeded, Progress: domain.RunJobProgressReport{Percent: 100}, ExecutionResult: domain.JobExecutionResult{Kind: "scum.sqlite-mutation.succeeded", Checksum: mutationChecksum, AuditSummary: "typed SCUM DB mutation result", Content: content}}); err != nil { t.Fatalf("complete sqlite mutation job: %v", err) } confirmed, err := svc.ReconcileSCUMOperation(approved.ID) if err != nil || confirmed.Status != domain.SCUMWorkflowStepConfirmed || confirmed.Confirmation.AffectedRows != 1 || confirmed.Confirmation.MutationChecksum != mutationChecksum { t.Fatalf("expected confirmed sqlite mutation: %+v err=%v", confirmed, err) } } func TestSCUMSQLiteMutationBlocksMissingSafetyAndStaleBefore(t *testing.T) { svc, session, _, instance := newSourceRCONFixture(t) seedSCUMOperationTemplates(t, svc, instance.PluginID) adminSession := enableSCUMSQLiteMutationOperationSupport(t, svc, instance) if _, err := svc.ApplySCUMObservationResult(domain.SCUMObservationResult{ServerInstanceID: instance.ID, PluginID: instance.PluginID, Source: "run.sqlite.read", QueryKey: "scum.player.profile", Sequence: 1, Checksum: "sha256:profile-855", ObservedAt: fixedTime, Rows: []map[string]any{{"gamePlayerId": "steam-855", "displayName": "Guarded", "online": false, "855": 100}}}); err != nil { t.Fatalf("seed projection: %v", err) } missingSafety, err := svc.RequestSCUMOperationForSession(session, instance.ID, domain.SCUMOperationRequest{TemplateKey: "player.attribute.855.set", PlayerID: "steam-855", Payload: map[string]any{"fieldKey": "855", "before": 100, "after": 101}, Reason: "missing maintenance", IdempotencyKey: "attribute-855-missing-safety"}) if err != nil { t.Fatalf("request missing safety mutation: %v", err) } waiting, err := svc.ApproveSCUMOperationForSession(adminSession, missingSafety.ID) if err != nil || waiting.Status != domain.SCUMWorkflowStepWaiting || waiting.RunJobID != "" || !strings.Contains(waiting.SafeSummary.Title, "维护") { t.Fatalf("expected missing maintenance/backup wait: %+v err=%v", waiting, err) } stale, err := svc.RequestSCUMOperationForSession(session, instance.ID, domain.SCUMOperationRequest{TemplateKey: "player.attribute.855.set", PlayerID: "steam-855", Payload: map[string]any{"fieldKey": "855", "before": 99, "after": 101, "safetyWindow": "maintenance-2026-08-10", "backupRef": "snapshot://scum/server-rcon/stale"}, Reason: "stale before", IdempotencyKey: "attribute-855-stale-before"}) if err != nil { t.Fatalf("request stale mutation: %v", err) } blocked, err := svc.ApproveSCUMOperationForSession(adminSession, stale.ID) if err != nil || blocked.Status != domain.SCUMWorkflowStepBlocked || blocked.RunJobID != "" || !strings.Contains(blocked.SafeSummary.Title, "before") { t.Fatalf("expected stale before block: %+v err=%v", blocked, err) } } func TestSCUMSQLiteMutationResultValidationRejectsOverBoundRows(t *testing.T) { svc, session, runSession, instance := newSourceRCONFixture(t) seedSCUMOperationTemplates(t, svc, instance.PluginID) adminSession := enableSCUMSQLiteMutationOperationSupport(t, svc, instance) if _, err := svc.ApplySCUMObservationResult(domain.SCUMObservationResult{ServerInstanceID: instance.ID, PluginID: instance.PluginID, Source: "run.sqlite.read", QueryKey: "scum.player.profile", Sequence: 1, Checksum: "sha256:profile-overbound", ObservedAt: fixedTime, Rows: []map[string]any{{"gamePlayerId": "steam-overbound", "online": false, "855": 10}}}); err != nil { t.Fatalf("seed projection: %v", err) } operation, err := svc.RequestSCUMOperationForSession(session, instance.ID, domain.SCUMOperationRequest{TemplateKey: "player.attribute.855.set", PlayerID: "steam-overbound", Payload: map[string]any{"fieldKey": "855", "before": 10, "after": 11, "safetyWindow": "maintenance-2026-08-10", "backupRef": "snapshot://scum/server-rcon/overbound"}, Reason: "overbound test", IdempotencyKey: "attribute-855-overbound"}) if err != nil { t.Fatalf("request overbound mutation: %v", err) } approved, err := svc.ApproveSCUMOperationForSession(adminSession, operation.ID) if err != nil || approved.RunJobID == "" { t.Fatalf("approve overbound mutation=%+v err=%v", approved, err) } claim, err := svc.ClaimRunJob(domain.RunJobClaim{RunEndpointID: "run-local", SessionToken: runSession, Capabilities: []string{domain.JobCapabilityRemoteRunProtectedSQL}, Capacity: domain.RunCapacity{MaxJobs: 1}}) if err != nil || !claim.HasJob || claim.Job == nil { t.Fatalf("claim overbound job: claim=%+v err=%v", claim, err) } ack, err := svc.AckRunJob(domain.RunJobAck{RunEndpointID: "run-local", SessionToken: runSession, JobID: claim.Job.JobID, LeaseToken: claim.Job.LeaseToken, Attempt: claim.Job.Attempt}) if err != nil { t.Fatalf("ack overbound job: %v", err) } content := mustJSON(t, map[string]any{"outcome": "succeeded", "affectedRows": 2, "mutationChecksum": "sha256:mutation-overbound"}) if _, err := svc.CompleteRunJob(domain.RunJobResult{RunEndpointID: "run-local", SessionToken: runSession, JobID: claim.Job.JobID, LeaseToken: ack.Job.LeaseToken, Attempt: ack.Job.Attempt, State: domain.JobStateSucceeded, Progress: domain.RunJobProgressReport{Percent: 100}, ExecutionResult: domain.JobExecutionResult{Kind: "scum.sqlite-mutation.succeeded", Content: content, AuditSummary: "typed SCUM DB mutation result"}}); err != nil { t.Fatalf("complete overbound job: %v", err) } unknown, err := svc.ReconcileSCUMOperation(approved.ID) if err != nil || unknown.Status != domain.SCUMWorkflowStepUnknown { t.Fatalf("expected over-bound rows to become unknown: %+v err=%v", unknown, err) } } func seedSCUMOperationTemplates(t *testing.T, svc *CoreService, pluginID string) { t.Helper() plugin, err := svc.store.GamePlugins().Get(pluginID) if err != nil { t.Fatal(err) } plugin.DeclaredPermissions = append(plugin.DeclaredPermissions, "server.game-client.command") plugin.GameClientBridge.OperationTemplates = []domain.GameClientBridgeOperationTemplateDeclaration{ {Key: "player.fame.set", Title: "Set player fame", Permission: "server.game-client.command", ApprovalLevel: domain.GameClientBridgeApprovalLevelOperator, Kind: domain.GameClientBridgeOperationKindRCON, TransportKey: "rcon", TargetKey: "rcon", PayloadSchemaRef: "schemas/bridge/player-fame-set.payload.schema.json", TimeoutSeconds: 60, MaxPayloadBytes: 2048, Safety: domain.GameClientBridgeOperationSafety{RequiresApproval: true, RequiresConfirmation: true}}, {Key: "player.currency.normal.set", Title: "Set player normal currency", Permission: "server.game-client.command", ApprovalLevel: domain.GameClientBridgeApprovalLevelOperator, Kind: domain.GameClientBridgeOperationKindRCON, TransportKey: "rcon", TargetKey: "rcon", PayloadSchemaRef: "schemas/bridge/player-currency-set.payload.schema.json", TimeoutSeconds: 60, MaxPayloadBytes: 2048, Safety: domain.GameClientBridgeOperationSafety{RequiresApproval: true, RequiresConfirmation: true}}, {Key: "player.currency.gold.set", Title: "Set player gold currency", Permission: "server.game-client.command", ApprovalLevel: domain.GameClientBridgeApprovalLevelPlatformAdmin, Kind: domain.GameClientBridgeOperationKindRCON, TransportKey: "rcon", TargetKey: "rcon", PayloadSchemaRef: "schemas/bridge/player-currency-set.payload.schema.json", TimeoutSeconds: 60, MaxPayloadBytes: 2048, Safety: domain.GameClientBridgeOperationSafety{RequiresApproval: true, RequiresConfirmation: true}}, } if err := svc.store.GamePlugins().Update(plugin); err != nil { t.Fatalf("update plugin operation templates: %v", err) } } func enableSCUMSQLiteMutationOperationSupport(t *testing.T, svc *CoreService, instance domain.ServerInstance) string { t.Helper() adminSession := createServiceUserAndLogin(t, svc, domain.User{ID: "platform-admin-scum", DisplayName: "SCUM Admin", Email: "scum-admin@example.test", Roles: []string{"platform-admin"}, PasswordHash: "secret-password"}) plugin, err := svc.store.GamePlugins().Get(instance.PluginID) if err != nil { t.Fatal(err) } plugin.DeclaredPermissions = append(plugin.DeclaredPermissions, "server.game-client.maintenance") plugin.RequiredRunCapabilities = append(plugin.RequiredRunCapabilities, domain.JobCapabilityRemoteRunProtectedSQL) plugin.RemoteAccess.RunCapabilities = append(plugin.RemoteAccess.RunCapabilities, domain.JobCapabilityRemoteRunProtectedSQL) plugin.RemoteAccess.DatabaseEngines = append(plugin.RemoteAccess.DatabaseEngines, "sqlite") plugin.RuntimeProfiles.TransportProfiles = append(plugin.RuntimeProfiles.TransportProfiles, domain.RuntimeTransportProfile{Key: "scum-database", Kind: "sqlite", TargetKey: "scum-database", Capabilities: []string{domain.JobCapabilityRemoteRunProtectedSQL}}) plugin.GameClientBridge.OperationTemplates = append(plugin.GameClientBridge.OperationTemplates, domain.GameClientBridgeOperationTemplateDeclaration{Key: "player.attribute.855.set", Title: "Set player attribute 855", Permission: "server.game-client.maintenance", ApprovalLevel: domain.GameClientBridgeApprovalLevelPlatformAdmin, Kind: domain.GameClientBridgeOperationKindSQLiteMutation, TransportKey: "scum-database", TargetKey: "scum-database", PayloadSchemaRef: "schemas/bridge/player-attribute-855-set.payload.schema.json", ResultSchemaRef: "schemas/bridge/player-attribute-855-set.result.schema.json", ConfirmationSchemaRef: "schemas/bridge/player-attribute-855-set.confirmation.schema.json", TimeoutSeconds: 120, MaxPayloadBytes: 4096, MaxRowsAffected: 1, Mutation: domain.GameClientBridgeOperationMutationDeclaration{FieldKey: "855", TableKey: "prisoner", IdentityKey: "user_profile_id", ValueKey: "value", ConfirmationQueryKey: "scum.player.profile", AllowedValueType: "integer", MinValue: 0, MaxValue: 100000}, Safety: domain.GameClientBridgeOperationSafety{RequiresApproval: true, RequiresOfflinePlayer: true, RequiresMaintenanceWindow: true, RequiresBeforeValue: true, RequiresConfirmation: true, BackupRequired: true}}) if err := svc.store.GamePlugins().Update(plugin); err != nil { t.Fatalf("update SCUM DB mutation plugin: %v", err) } endpoint, err := svc.store.RunEndpoints().Get(instance.RunEndpointID) if err != nil { t.Fatal(err) } endpoint.Capabilities = append(endpoint.Capabilities, domain.JobCapabilityRemoteRunProtectedSQL) if err := svc.store.RunEndpoints().Update(endpoint); err != nil { t.Fatalf("update SCUM DB mutation endpoint: %v", err) } return adminSession } func mustJSON(t *testing.T, value any) string { t.Helper() encoded, err := json.Marshal(value) if err != nil { t.Fatalf("marshal test JSON: %v", err) } return string(encoded) }