功能修改

This commit is contained in:
npc0-hue
2026-07-20 16:42:33 +08:00
parent 48b8ad8d6c
commit a0e69417db
224 changed files with 22015 additions and 884 deletions
+93 -6
View File
@@ -3,6 +3,7 @@ package api
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
@@ -391,6 +392,37 @@ func TestCoreAPIServerRuntimeDistributionAndJobWorkflows(t *testing.T) {
}
}
func TestCoreAPIRunDistributionDenialNamesMissingPluginPermission(t *testing.T) {
router := newTestRouter()
adminSession := createAdminSession(t, router)
postJSON[dto.GamePluginResponse](t, router, "/api/v1/game-plugins/register-manifest", validGamePluginManifestRegistrationRequest())
postJSON[dto.RunEndpointResponse](t, router, "/api/v1/run/endpoints", validRunEndpointRequest())
server := postJSONWithAuth[dto.ServerInstanceResponse](t, router, "/api/v1/server-instances", dto.ServerInstanceCreateRequest{
ID: "server-plugin-denied",
PluginID: "game.example",
RunEndpointID: "run-local",
Name: "Plugin Denied",
}, adminSession)
recorder := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/server-instances/"+server.ID+"/run/generate", dto.RunDistributionGenerateRequest{
TargetOS: "linux",
TargetArch: "amd64",
IdempotencyKey: "missing-plugin-permission",
}, adminSession)
assertStatus(t, recorder, http.StatusForbidden)
body := decodeBody[dto.ErrorResponse](t, recorder)
if body.Code != errorCodeForbidden || body.Message != "plugin does not declare required permission: server.run.distribution" {
t.Fatalf("expected plugin permission denial, got %+v", body)
}
dependenciesRecorder := requestWithAuth(t, router, http.MethodGet, "/api/v1/server-instances/"+server.ID+"/dependencies", "", adminSession)
assertStatus(t, dependenciesRecorder, http.StatusForbidden)
dependenciesBody := decodeBody[dto.ErrorResponse](t, dependenciesRecorder)
if dependenciesBody.Code != errorCodeForbidden || dependenciesBody.Message != "plugin does not declare required permission: server.dependencies.manage" {
t.Fatalf("expected dependency permission denial, got %+v", dependenciesBody)
}
}
func TestCoreAPIErrorResponses(t *testing.T) {
router := newTestRouter()
adminSession := createAdminSession(t, router)
@@ -817,7 +849,7 @@ func TestServerAccessAPIScopesOwnersAndAdministrators(t *testing.T) {
assertErrorResponse(t, forbiddenDetail, http.StatusForbidden, errorCodeForbidden)
}
func TestAIProviderAPIResponseDoesNotExposeRawKeyFields(t *testing.T) {
func TestAIProviderAPIResponseDoesNotExposeRawSecretFields(t *testing.T) {
router := newTestRouter()
adminSession := createAdminSession(t, router)
recorder := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/ai-providers", validAIProviderRequest(), adminSession)
@@ -836,6 +868,9 @@ func TestAIProviderAPIResponseDoesNotExposeRawKeyFields(t *testing.T) {
if _, exists := body["apiKeyRef"]; exists || body["apiKeyConfigured"] != true {
t.Fatalf("expected API key presence only, got %+v", body)
}
if _, exists := body["baseUrl"]; exists || body["baseUrlConfigured"] != true {
t.Fatalf("expected base URL presence only, got %+v", body)
}
}
func TestAIProviderManagementAPI(t *testing.T) {
@@ -844,13 +879,13 @@ func TestAIProviderManagementAPI(t *testing.T) {
createAIProviderFixture(t, router, adminSession)
update := validAIProviderUpdateRequest()
update.BaseURL = ""
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.APIKeyConfigured || updated.Status != domain.AIProviderStatusActive {
if updated.Name != "OpenAI Relay" || !updated.BaseURLConfigured || !updated.APIKeyConfigured || updated.Status != domain.AIProviderStatusActive {
t.Fatalf("unexpected updated provider: %+v", updated)
}
statusRecorder := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/ai-providers/ai.openai/status", dto.AIProviderStatusRequest{Status: domain.AIProviderStatusDisabled}, adminSession)
assertStatus(t, statusRecorder, http.StatusOK)
disabled := decodeBody[dto.AIProviderResponse](t, statusRecorder)
@@ -861,7 +896,7 @@ func TestAIProviderManagementAPI(t *testing.T) {
testRecorder := requestWithAuth(t, router, http.MethodPost, "/api/v1/ai-providers/ai.openai/test", "", adminSession)
assertStatus(t, testRecorder, http.StatusOK)
testResult := decodeBody[dto.AIProviderTestResponse](t, testRecorder)
if testResult.Success || testResult.Mode != "metadata" {
if testResult.Success || testResult.Mode != "provider" {
t.Fatalf("expected metadata test failure for disabled provider, got %+v", testResult)
}
@@ -1408,6 +1443,56 @@ func TestGamePluginRegistryResponseDoesNotExposeRawInternals(t *testing.T) {
}
}
func TestProductionOperationsGovernanceRoutesAreDurableAndRedacted(t *testing.T) {
router := newTestRouter()
adminSession := createAdminSession(t, router)
serverID := createRuntimeAPIFixtures(t, router, adminSession)
createAIProviderFixture(t, router, adminSession)
capacity := getJSONWithAuth[dto.ProductionCapacitySummaryResponse](t, router, "/api/v1/production/capacity", adminSession)
if len(capacity.Endpoints) == 0 {
t.Fatalf("expected persisted capacity endpoints, got %+v", capacity)
}
decision := postOKJSONWithAuth[dto.CapacityAdmissionDecisionResponse](t, router, "/api/v1/production/capacity/admission", dto.CapacityAdmissionRequest{ServerInstanceID: serverID, Capability: domain.JobCapabilityConfigWrite, IdempotencyKey: "api-capacity-check"}, adminSession)
if decision.Accepted || decision.AlertID == "" || decision.AuditEventID == "" {
t.Fatalf("expected stale endpoint admission to create durable evidence, got %+v", decision)
}
alerts := getJSONWithAuth[dto.AlertListResponse](t, router, "/api/v1/alerts?state=active", adminSession)
if alerts.Count == 0 {
t.Fatalf("expected durable alert list, got %+v", alerts)
}
acknowledged := postOKJSONWithAuth[dto.AlertResponse](t, router, "/api/v1/alerts/"+decision.AlertID+"/acknowledge", dto.AlertAcknowledgeRequest{Note: "operator review"}, adminSession)
if acknowledged.State != string(domain.AlertStateAcknowledged) {
t.Fatalf("expected acknowledged state, got %+v", acknowledged)
}
resolved := postOKJSONWithAuth[dto.AlertResponse](t, router, "/api/v1/alerts/"+decision.AlertID+"/resolve", dto.AlertResolveRequest{Note: "review complete"}, adminSession)
if resolved.State != string(domain.AlertStateResolved) {
t.Fatalf("expected resolved state, got %+v", resolved)
}
invocation := postOKJSONWithAuth[dto.AIInvocationResponse](t, router, "/api/v1/ai/invocations", dto.AIInvocationRequest{RequestID: "api-ai-config-diff", ServerInstanceID: serverID, Purpose: "config.suggest", ProviderID: "ai.openai", Prompt: "disable pvp"}, adminSession)
if invocation.ConfigRecommendation == nil || invocation.ConfigRecommendation.DiffID == "" {
t.Fatalf("expected persisted AI config diff, got %+v", invocation)
}
diffs := getJSONWithAuth[dto.AIConfigDiffListResponse](t, router, "/api/v1/ai/config-diffs?serverInstanceId="+serverID, adminSession)
if diffs.Count != 1 || diffs.Items[0].State != string(domain.AIConfigDiffStatePending) {
t.Fatalf("expected one pending AI diff, got %+v", diffs)
}
approvalRecorder := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/ai/config-diffs/"+invocation.ConfigRecommendation.DiffID+"/approve", dto.AIConfigDiffApprovalRequest{IdempotencyKey: "api-ai-diff-approve"}, adminSession)
assertStatus(t, approvalRecorder, http.StatusAccepted)
approval := decodeBody[dto.AIConfigDiffApprovalResponse](t, approvalRecorder)
if approval.Preview.State != string(domain.AIConfigDiffStateApproved) || approval.Dispatch.Job.Capability != domain.JobCapabilityConfigWrite {
t.Fatalf("expected approved diff with config write job, got %+v", approval)
}
evidence := fmt.Sprintf("%+v %+v %+v %+v %+v %+v", capacity, decision, alerts, resolved, diffs, approval)
for _, forbidden := range []string{"/Users/", "/private/", "unix://", "tcp://", "Bearer ", "sk-", "password=", "apiKeyRef", "rawApiKey", "https://api.openai.com"} {
if strings.Contains(evidence, forbidden) {
t.Fatalf("production governance response leaked forbidden fragment %q: %s", forbidden, evidence)
}
}
}
func newTestRouter() http.Handler {
core := service.NewCoreService(repo.NewMemoryStore())
if err := core.SeedLocalPlatformAdmin(); err != nil {
@@ -1613,6 +1698,7 @@ func createRuntimeAPIFixtures(t *testing.T, router http.Handler, adminSession st
"process.start",
"process.stop",
"logs.read",
domain.JobCapabilityConfigWrite,
domain.JobCapabilityRunSelfUpdate,
domain.JobCapabilityDependenciesCheck,
domain.JobCapabilityDependenciesInstall,
@@ -1771,8 +1857,9 @@ func validGamePluginManifestRegistrationRequest() dto.GamePluginManifestRegistra
BridgeActions: []string{string(domain.PluginBridgeActionLogsQuery), string(domain.PluginBridgeActionFilesRequest), string(domain.PluginBridgeActionAIInvoke)},
},
},
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"}}}},
AI: dto.GamePluginManifestAIBody{Purposes: []string{"logs.diagnose"}, Mediation: "platform", ConfigWritePolicy: "review-required"},
ProductionLifecycle: dto.GamePluginProductionLifecycleBody{Operations: []string{"install", "enable", "disable", "upgrade", "rollback", "retire", "dependency-check"}, DependencyPolicy: "optional", ApprovalRequired: []string{"disable", "rollback", "retire"}},
RuntimeProfiles: dto.GamePluginRuntimeProfilesBody{LifecycleProfiles: []dto.RuntimeLifecycleProfileBody{{Key: "local", Mode: "local-process", Capabilities: []string{"process.install", "process.start", "process.stop"}}}},
},
}
}