181 lines
7.8 KiB
Go
181 lines
7.8 KiB
Go
package model
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"browser.local/platform/domain"
|
|
)
|
|
|
|
func TestTableNames(t *testing.T) {
|
|
tests := map[string]string{
|
|
User{}.TableName(): "users",
|
|
AIProvider{}.TableName(): "ai_providers",
|
|
GamePlugin{}.TableName(): "game_plugins",
|
|
ServerInstance{}.TableName(): "server_instances",
|
|
RunEndpoint{}.TableName(): "run_endpoints",
|
|
Job{}.TableName(): "jobs",
|
|
Artifact{}.TableName(): "artifacts",
|
|
LogStream{}.TableName(): "log_streams",
|
|
PluginLifecycleInstallation{}.TableName(): "plugin_lifecycle_installations",
|
|
AIConfigDiff{}.TableName(): "ai_config_diffs",
|
|
SCUMUser{}.TableName(): "scum_user",
|
|
SCUMUserTrajectory{}.TableName(): "scum_user_trajectory",
|
|
SCUMVehicle{}.TableName(): "scum_vehicle",
|
|
SCUMVehicleTrajectory{}.TableName(): "scum_vehicle_trajectory",
|
|
SCUMVehicleLock{}.TableName(): "scum_vehicle_lock",
|
|
}
|
|
|
|
for got, want := range tests {
|
|
if got != want {
|
|
t.Fatalf("expected table name %q, got %q", want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGamePluginModelRoundTripCopiesSlices(t *testing.T) {
|
|
source := domain.GamePlugin{
|
|
ID: "server.scum",
|
|
Name: "SCUM",
|
|
Version: "1.0.0",
|
|
ServerType: "scum",
|
|
ManifestRef: "artifact://manifest",
|
|
CreateFormSchemaRef: "artifact://schema",
|
|
RequiredRunCapabilities: []string{"process.start", "logs.read"},
|
|
DeclaredPermissions: []string{"server.logs.read"},
|
|
SupportedOS: []string{"linux"},
|
|
Pages: []domain.GamePluginPage{
|
|
{Key: "logs", Title: "Logs", Path: "/logs", Permissions: []string{"server.logs.read"}},
|
|
},
|
|
Tags: []string{"survival"},
|
|
AIPurposes: []string{"logs.diagnose"},
|
|
ProductionLifecycle: domain.GamePluginProductionLifecycle{
|
|
Operations: []string{"install", "upgrade", "rollback"},
|
|
DependencyPolicy: "required",
|
|
},
|
|
RuntimeProfiles: domain.GamePluginRuntimeProfiles{LifecycleProfiles: []domain.RuntimeLifecycleProfile{{Key: "local", Mode: "local-process", Capabilities: []string{"process.start"}}}},
|
|
Permissions: domain.PluginPermissions{
|
|
Jobs: true,
|
|
Logs: true,
|
|
},
|
|
Status: domain.GamePluginStatusInstalled,
|
|
}
|
|
|
|
row := GamePluginFromDomain(source)
|
|
roundTrip := row.ToDomain()
|
|
roundTrip.RequiredRunCapabilities[0] = "files.read"
|
|
roundTrip.DeclaredPermissions[0] = "ai.invoke"
|
|
roundTrip.SupportedOS[0] = "darwin"
|
|
roundTrip.Pages[0].Permissions[0] = "ai.invoke"
|
|
roundTrip.Tags[0] = "mutated"
|
|
roundTrip.AIPurposes[0] = "config.suggest"
|
|
roundTrip.ProductionLifecycle.Operations[0] = "retire"
|
|
roundTrip.RuntimeProfiles.LifecycleProfiles[0].Capabilities[0] = "process.stop"
|
|
|
|
if source.RequiredRunCapabilities[0] != "process.start" {
|
|
t.Fatalf("expected source plugin capabilities to remain unchanged, got %+v", source.RequiredRunCapabilities)
|
|
}
|
|
if row.RequiredRunCapabilities[0] != "process.start" {
|
|
t.Fatalf("expected model plugin capabilities to remain unchanged, got %+v", row.RequiredRunCapabilities)
|
|
}
|
|
if source.DeclaredPermissions[0] != "server.logs.read" || source.Pages[0].Permissions[0] != "server.logs.read" || source.Tags[0] != "survival" || source.AIPurposes[0] != "logs.diagnose" {
|
|
t.Fatalf("expected source plugin registry metadata to remain unchanged, got %+v", source)
|
|
}
|
|
if row.DeclaredPermissions[0] != "server.logs.read" || row.Pages[0].Permissions[0] != "server.logs.read" || row.Tags[0] != "survival" || row.AIPurposes[0] != "logs.diagnose" {
|
|
t.Fatalf("expected model plugin registry metadata to remain unchanged, got %+v", row)
|
|
}
|
|
if source.ProductionLifecycle.Operations[0] != "install" || row.ProductionLifecycle.Operations[0] != "install" {
|
|
t.Fatalf("expected production lifecycle declaration to round-trip without aliasing, source=%+v row=%+v", source.ProductionLifecycle, row.ProductionLifecycle)
|
|
}
|
|
if source.RuntimeProfiles.LifecycleProfiles[0].Capabilities[0] != "process.start" || row.RuntimeProfiles.LifecycleProfiles[0].Capabilities[0] != "process.start" {
|
|
t.Fatalf("expected runtime profiles to round-trip without aliasing, source=%+v row=%+v", source.RuntimeProfiles, row.RuntimeProfiles)
|
|
}
|
|
}
|
|
|
|
func TestJobExecutionInputModelRoundTripPreservesLifecycleMetadata(t *testing.T) {
|
|
source := domain.JobExecutionInput{
|
|
WorkspaceScope: "server-workspace",
|
|
PluginID: "game.scum",
|
|
LifecycleOperation: "upgrade",
|
|
TargetVersion: "2.0.0",
|
|
Inputs: map[string]string{"templateKey": "players.by-id", "playerId": "steam-123"},
|
|
Deployment: &domain.ServerDeploymentDefinition{
|
|
Mode: domain.ServerDeploymentModeGuided,
|
|
ProfileKey: "run-local",
|
|
RuntimeBindings: map[string]string{"installRoot": "D:\\scumserver"},
|
|
CreateInputs: map[string]string{"gamePort": "27000", "maxPlayers": "128"},
|
|
ServerRoot: "D:\\scumserver",
|
|
WorkingDirectory: "D:\\scumserver",
|
|
InstallCommand: "install.cmd",
|
|
StartCommand: "start.cmd",
|
|
Shell: domain.ServerCommandShellCmd,
|
|
Revision: 3,
|
|
},
|
|
ServerDeploymentPlan: &domain.ServerDeploymentPlan{
|
|
SchemaVersion: "1",
|
|
Operation: "install",
|
|
PluginID: "game.scum",
|
|
Prerequisites: []domain.RuntimeServerPrerequisite{{Key: "steamcmd", Kind: "tool"}},
|
|
},
|
|
}
|
|
|
|
row := executionInputFromDomain(source)
|
|
source.Inputs["playerId"] = "source-mutated"
|
|
if row.Inputs["playerId"] != "steam-123" {
|
|
t.Fatalf("expected model execution inputs to be isolated from source mutation, row=%+v", row.Inputs)
|
|
}
|
|
source.Deployment.CreateInputs["maxPlayers"] = "64"
|
|
if row.Deployment == nil || row.Deployment.CreateInputs["maxPlayers"] != "128" {
|
|
t.Fatalf("expected model deployment inputs to be isolated from source mutation, row=%+v", row.Deployment)
|
|
}
|
|
source.Inputs["playerId"] = "steam-123"
|
|
source.Deployment.CreateInputs["maxPlayers"] = "128"
|
|
row.Inputs["playerId"] = "row-mutated"
|
|
if source.Inputs["playerId"] != "steam-123" {
|
|
t.Fatalf("expected source execution inputs to be isolated from model mutation, source=%+v", source.Inputs)
|
|
}
|
|
row.Deployment.CreateInputs["maxPlayers"] = "32"
|
|
if source.Deployment.CreateInputs["maxPlayers"] != "128" {
|
|
t.Fatalf("expected source deployment inputs to be isolated from model mutation, source=%+v", source.Deployment)
|
|
}
|
|
row.Inputs["playerId"] = "steam-123"
|
|
row.Deployment.CreateInputs["maxPlayers"] = "128"
|
|
|
|
roundTrip := row.ToDomain()
|
|
if !reflect.DeepEqual(roundTrip, source) {
|
|
t.Fatalf("expected lifecycle execution metadata to round-trip, got %+v", roundTrip)
|
|
}
|
|
roundTrip.Inputs["playerId"] = "mutated"
|
|
roundTrip.Deployment.CreateInputs["maxPlayers"] = "16"
|
|
if source.Inputs["playerId"] != "steam-123" || row.Inputs["playerId"] != "steam-123" || source.Deployment.CreateInputs["maxPlayers"] != "128" || row.Deployment.CreateInputs["maxPlayers"] != "128" {
|
|
t.Fatalf("expected execution inputs to round-trip without aliasing, source=%+v row=%+v", source, row)
|
|
}
|
|
}
|
|
|
|
func TestAIProviderModelUsesKeyReference(t *testing.T) {
|
|
source := domain.AIProvider{
|
|
ID: "ai.openai",
|
|
Name: "OpenAI",
|
|
Kind: domain.AIProviderKindOpenAI,
|
|
BaseURL: "https://api.openai.com/v1",
|
|
APIKeyRef: "secret://providers/openai",
|
|
Models: []string{"gpt-4.1"},
|
|
DefaultModel: "gpt-4.1",
|
|
RelayMode: domain.AIRelayModeDirect,
|
|
TimeoutMS: 30000,
|
|
Status: domain.AIProviderStatusActive,
|
|
RedactionPolicy: "default",
|
|
}
|
|
|
|
row := AIProviderFromDomain(source)
|
|
if row.APIKeyRef != source.APIKeyRef {
|
|
t.Fatalf("expected API key reference %q, got %q", source.APIKeyRef, row.APIKeyRef)
|
|
}
|
|
|
|
roundTrip := row.ToDomain()
|
|
roundTrip.Models[0] = "mutated"
|
|
if row.Models[0] != "gpt-4.1" {
|
|
t.Fatalf("expected model provider models to remain unchanged, got %+v", row.Models)
|
|
}
|
|
}
|