Files
browser/platform/dto/resources_test.go
T

200 lines
9.5 KiB
Go

package dto
import (
"encoding/json"
"reflect"
"testing"
"browser.local/platform/domain"
)
func TestAIProviderResponseExposesOnlySecretPresence(t *testing.T) {
responseType := reflect.TypeOf(AIProviderResponse{})
if _, ok := responseType.FieldByName("BaseURL"); ok {
t.Fatal("AI provider response must not expose the provider base URL")
}
if _, ok := responseType.FieldByName("APIKey"); ok {
t.Fatal("AI provider response must not expose raw API key")
}
if _, ok := responseType.FieldByName("RawAPIKey"); ok {
t.Fatal("AI provider response must not expose raw API key")
}
if _, ok := responseType.FieldByName("APIKeyRef"); ok {
t.Fatal("AI provider response must not expose internal API key reference")
}
if _, ok := responseType.FieldByName("APIKeyConfigured"); !ok {
t.Fatal("AI provider response must expose API key presence")
}
if _, ok := responseType.FieldByName("BaseURLConfigured"); !ok {
t.Fatal("AI provider response must expose base URL presence")
}
}
func TestAIProviderFromDomainCopiesModels(t *testing.T) {
provider := 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",
}
response := AIProviderFromDomain(provider)
response.Models[0] = "mutated"
if provider.Models[0] != "gpt-4.1" {
t.Fatalf("expected response models to be copied, got source models %+v", provider.Models)
}
if !response.APIKeyConfigured {
t.Fatal("expected configured API key presence")
}
if !response.BaseURLConfigured {
t.Fatal("expected configured base URL presence")
}
}
func TestGamePluginManifestRegistrationToDomainCopiesSlices(t *testing.T) {
request := GamePluginManifestRegistrationRequest{
ManifestRef: "artifact://manifests/game.example/0.1.0",
Manifest: GamePluginManifestBody{
ID: "game.example",
Name: "Example Server",
Version: "0.1.0",
Kind: "game-plugin",
Tags: []string{"example"},
Capabilities: []string{"process.start"},
Permissions: []string{"server.lifecycle"},
Server: GamePluginManifestServerBody{
Type: "example",
DisplayName: "Example Server",
SupportedOS: []string{"linux"},
CreateFormSchema: "schemas/create-form.schema.json",
},
Actions: PluginLifecycleActionsBody{Install: "actions/install.json", Start: "actions/start.json", Stop: "actions/stop.json"},
AssetFiles: []PluginAssetFileBody{
{Path: "actions/install.json", Mode: 0o600},
},
Pages: []GamePluginPageBody{
{Key: "logs", Title: "Logs", Path: "/logs", Permissions: []string{"server.logs.read"}},
},
AI: GamePluginManifestAIBody{Purposes: []string{"logs.diagnose"}},
},
AssetFiles: []PluginAssetFileBody{
{Path: "actions/install.json", Content: "{}", Mode: 0o600},
},
}
domainRegistration := request.ToDomain()
domainRegistration.Manifest.Tags[0] = "mutated"
domainRegistration.Manifest.Server.SupportedOS[0] = "darwin"
domainRegistration.Manifest.AssetFiles[0].Path = "actions/mutated.json"
domainRegistration.AssetFiles[0].Content = "mutated"
domainRegistration.Manifest.Pages[0].Permissions[0] = "ai.invoke"
domainRegistration.Manifest.AI.Purposes[0] = "config.suggest"
if request.Manifest.Tags[0] != "example" || request.Manifest.Server.SupportedOS[0] != "linux" || request.Manifest.AssetFiles[0].Path != "actions/install.json" || request.AssetFiles[0].Content != "{}" || request.Manifest.Pages[0].Permissions[0] != "server.logs.read" || request.Manifest.AI.Purposes[0] != "logs.diagnose" {
t.Fatalf("expected manifest request slices to be copied, got %+v", request)
}
}
func TestGamePluginFromDomainCopiesRegistryMetadata(t *testing.T) {
plugin := domain.GamePlugin{
ID: "game.example",
Name: "Example Server",
Version: "0.1.0",
ServerType: "example",
RequiredRunCapabilities: []string{"process.start"},
DeclaredPermissions: []string{"server.lifecycle"},
SupportedOS: []string{"linux"},
Pages: []domain.GamePluginPage{
{Key: "logs", Title: "Logs", Path: "/logs", Permissions: []string{"server.logs.read"}},
},
Tags: []string{"example"},
AIPurposes: []string{"logs.diagnose"},
}
response := GamePluginFromDomain(plugin)
response.RequiredRunCapabilities[0] = "files.read"
response.DeclaredPermissions[0] = "ai.invoke"
response.SupportedOS[0] = "darwin"
response.Pages[0].Permissions[0] = "ai.invoke"
response.Tags[0] = "mutated"
response.AIPurposes[0] = "config.suggest"
if plugin.RequiredRunCapabilities[0] != "process.start" || plugin.DeclaredPermissions[0] != "server.lifecycle" || plugin.SupportedOS[0] != "linux" || plugin.Pages[0].Permissions[0] != "server.logs.read" || plugin.Tags[0] != "example" || plugin.AIPurposes[0] != "logs.diagnose" {
t.Fatalf("expected plugin response registry metadata to be copied, got %+v", plugin)
}
}
func TestGameClientBridgeQueryTemplateDeclarationRoundTripIsSafe(t *testing.T) {
body := GameClientBridgeManifestBody{
QueryTemplates: []GameClientBridgeQueryTemplateDeclarationBody{{
Key: "player.lookup", Title: "Player lookup", Permission: "server.game-client.read", Engine: "sqlite", TransportKey: "sqlite-db", TargetKey: "db/sqlite",
ParameterSchemaRef: "schemas/bridge/query/player-lookup.parameters.schema.json", ResultSchemaRef: "schemas/bridge/query/player-lookup.result.schema.json", SQLRef: "sql/player-lookup.sql", MaxRows: 50, TimeoutSeconds: 10, PollIntervalSeconds: 3,
}},
LogProjections: []GameClientBridgeLogProjectionDeclarationBody{{
Key: "player.login", StreamKeys: []string{"process.stdout"}, Steps: []GameClientBridgeLogProjectionStepDeclarationBody{{Pattern: `Player (?<slot>\d+)`}}, CorrelationFields: []string{"slot"}, MaxInterveningLines: 8,
Target: GameClientBridgeLogProjectionTargetDeclarationBody{Collection: "users", UpsertKeys: []string{"steamId"}, CaptureMappings: map[string]string{"steamId": "steamId"}, FixedValues: map[string]string{"source": "stdout"}, ObservedAtField: "lastLoginAt"},
Presence: &GameClientBridgeLogProjectionPresenceDeclarationBody{TimestampField: "lastLoginAt", ActiveWindowSeconds: 600, ActivityTarget: &GameClientBridgeLogProjectionTargetDeclarationBody{Collection: "activity", UpsertKeys: []string{"steamId"}, CaptureMappings: map[string]string{"steamId": "steamId"}}},
}},
DataPacks: []GameClientBridgeDataPackDeclarationBody{{Key: "db-v1", DatabaseUserVersion: 1, LogParserRefs: []string{"data/logs.json"}, ConfigMapRefs: []string{"data/config.json"}, DataRefs: []string{"data/items.json"}}},
CommandRetentionSeconds: 86400,
MaxCommands: 1000,
Pages: []GameClientBridgePageContractBody{{PageKey: "players", QueryTemplateKeys: []string{"player.lookup"}}},
}
domainManifest := body.ToDomain()
if len(domainManifest.QueryTemplates) != 1 || domainManifest.QueryTemplates[0].SQLRef != "sql/player-lookup.sql" || domainManifest.QueryTemplates[0].PollIntervalSeconds != 3 || len(domainManifest.LogProjections) != 1 || domainManifest.LogProjections[0].Presence.ActiveWindowSeconds != 600 || len(domainManifest.DataPacks) != 1 || domainManifest.DataPacks[0].DataRefs[0] != "data/items.json" || domainManifest.Pages[0].QueryTemplateKeys[0] != "player.lookup" {
t.Fatalf("query template conversion lost declaration fields: %#v", domainManifest)
}
domainManifest.LogProjections[0].Target.CaptureMappings["steamId"] = "mutated"
if body.LogProjections[0].Target.CaptureMappings["steamId"] != "steamId" {
t.Fatal("log projection target aliases request DTO data")
}
domainManifest.LogProjections[0].Target.CaptureMappings["steamId"] = "steamId"
domainManifest.Pages[0].QueryTemplateKeys[0] = "mutated"
if body.Pages[0].QueryTemplateKeys[0] != "player.lookup" {
t.Fatal("query template page keys alias request DTO data")
}
domainManifest.Pages[0].QueryTemplateKeys[0] = "player.lookup"
domainManifest.DataPacks[0].DataRefs[0] = "mutated"
if body.DataPacks[0].DataRefs[0] != "data/items.json" {
t.Fatal("data pack data refs alias request DTO data")
}
domainManifest.DataPacks[0].DataRefs[0] = "data/items.json"
response := gameClientBridgeManifestFromDomain(domainManifest)
response.LogProjections[0].Target.FixedValues["source"] = "mutated"
if domainManifest.LogProjections[0].Target.FixedValues["source"] != "stdout" {
t.Fatal("log projection target aliases domain data")
}
response.Pages[0].QueryTemplateKeys[0] = "mutated"
if domainManifest.Pages[0].QueryTemplateKeys[0] != "player.lookup" {
t.Fatal("query template page keys alias domain data")
}
encoded, err := json.Marshal(response.QueryTemplates[0])
if err != nil {
t.Fatalf("marshal safe query template projection: %v", err)
}
var projection map[string]any
if err := json.Unmarshal(encoded, &projection); err != nil {
t.Fatalf("decode safe query template projection: %v", err)
}
expectedFields := []string{"key", "title", "permission", "engine", "transportKey", "targetKey", "parameterSchemaRef", "resultSchemaRef", "sqlRef", "maxRows", "timeoutSeconds", "pollIntervalSeconds"}
if len(projection) != len(expectedFields) {
t.Fatalf("query template projection contains unexpected fields: %s", encoded)
}
for _, field := range expectedFields {
if _, exists := projection[field]; !exists {
t.Fatalf("query template projection is missing %q: %s", field, encoded)
}
}
}