From 4d945eb80991f2d70c1bdf659ae5b2357d66e0b5 Mon Sep 17 00:00:00 2001 From: npc0-hue Date: Sat, 22 Aug 2026 22:31:33 +0800 Subject: [PATCH] Harden marketplace listing and debug smoke errors --- platform/service/resources.go | 7 +++++++ platform/service/resources_test.go | 24 ++++++++++++++++++++++++ scripts/local-debug/smoke.sh | 10 +++++++--- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/platform/service/resources.go b/platform/service/resources.go index 20d0e9f..673c442 100644 --- a/platform/service/resources.go +++ b/platform/service/resources.go @@ -1532,6 +1532,13 @@ func (svc *CoreService) ListMarketplacePlugins(filter domain.PluginMarketplaceFi if filter.Keyword != "" && !marketplacePluginMatchesKeyword(projected, filter.Keyword) { continue } + // The registry can outlive a manifest contract. Do not let a historical + // plugin with removed capabilities make the usable marketplace entries + // fail as one invalid response; it is still available through the plugin + // registry for an explicit refresh or migration. + if err := validator.ValidatePluginMarketplacePlugin(projected); err != nil { + continue + } items = append(items, projected) } if err := validator.ValidatePluginMarketplacePlugins(items); err != nil { diff --git a/platform/service/resources_test.go b/platform/service/resources_test.go index 315bad3..29dbac8 100644 --- a/platform/service/resources_test.go +++ b/platform/service/resources_test.go @@ -1324,6 +1324,30 @@ func TestCoreServiceMarketplacePluginsAreFilteredSafeAndStateful(t *testing.T) { } } +func TestCoreServiceMarketplaceListSkipsHistoricalInvalidCapabilities(t *testing.T) { + svc := newTestCoreService() + if _, err := svc.RegisterGamePluginManifest(validPluginManifestRegistration()); err != nil { + t.Fatalf("register current manifest: %v", err) + } + + legacy := gamePluginFromManifestRegistration(validPluginManifestRegistration()) + legacy.ID = "game.legacy" + legacy.Name = "Legacy Server" + legacy.RequiredRunCapabilities = append(legacy.RequiredRunCapabilities, "remote.run.protected.sql", "remote.run.protected.rcon") + legacy.RemoteAccess.RunCapabilities = append(legacy.RemoteAccess.RunCapabilities, "remote.run.protected.sql", "remote.run.protected.rcon") + if err := svc.store.GamePlugins().Create(legacy); err != nil { + t.Fatalf("seed historical plugin: %v", err) + } + + listed, err := svc.ListMarketplacePlugins(domain.PluginMarketplaceFilter{}) + if err != nil { + t.Fatalf("list marketplace plugins with historical record: %v", err) + } + if len(listed) != 1 || listed[0].ID != "game.example" { + t.Fatalf("expected only current marketplace plugin, got %+v", listed) + } +} + func TestCoreServiceAuthorizesPluginBridgeActions(t *testing.T) { svc := newTestCoreService() if _, err := svc.RegisterGamePluginManifest(validPluginManifestRegistration()); err != nil { diff --git a/scripts/local-debug/smoke.sh b/scripts/local-debug/smoke.sh index 6537e37..5d1b62f 100755 --- a/scripts/local-debug/smoke.sh +++ b/scripts/local-debug/smoke.sh @@ -323,9 +323,13 @@ json_post() { 2*) return 0 ;; - *) + *) if [[ ! -s "$output_file" ]] || ! response_code_is_duplicate "$output_file"; then printf 'POST %s failed with HTTP %s\n' "$url" "$status" >&2 + if [[ -s "$output_file" ]]; then + printf 'response body:\n' >&2 + sed -n '1,160p' "$output_file" >&2 + fi fi return 1 ;; @@ -1416,7 +1420,7 @@ if [[ "${LOCAL_DEBUG_LOG_SESSION_SMOKE_ONLY:-false}" == "true" ]]; then fi printf 'generating host-native example Run through platform Docker builder\n' -curl -fsS -H 'Content-Type: application/json' "${AUTH_HEADER[@]}" --data-binary "@$WORK_DIR/server-run-generate.request.json" "$API_URL/server-instances/$SERVER_ID/run/generate" >"$WORK_DIR/server-run-generate.response.json" +json_post "$API_URL/server-instances/$SERVER_ID/run/generate" "$WORK_DIR/server-run-generate.request.json" "$WORK_DIR/server-run-generate.response.json" "${AUTH_HEADER[@]}" reject_forbidden_fragments "$WORK_DIR/server-run-generate.response.json" node - "$WORK_DIR/server-run-generate.response.json" "$SERVER_ID" "$GENERATED_RUN_ENDPOINT_ID" "$GENERATED_RUN_TARGET_OS" "$GENERATED_RUN_TARGET_ARCH" <<'NODE' const fs = require("fs"); @@ -1499,7 +1503,7 @@ if ((action.reason || "").includes("run endpoint")) { NODE printf 'generating SCUM run package through platform API\n' -curl -fsS -H 'Content-Type: application/json' "${AUTH_HEADER[@]}" --data-binary "@$WORK_DIR/scum-alpha-run-generate.request.json" "$API_URL/server-instances/$SCUM_ALPHA_ID/run/generate" >"$WORK_DIR/scum-alpha-run-generate.response.json" +json_post "$API_URL/server-instances/$SCUM_ALPHA_ID/run/generate" "$WORK_DIR/scum-alpha-run-generate.request.json" "$WORK_DIR/scum-alpha-run-generate.response.json" "${AUTH_HEADER[@]}" reject_forbidden_fragments "$WORK_DIR/scum-alpha-run-generate.response.json" require_file_contains "$WORK_DIR/scum-alpha-run-generate.response.json" "\"serverInstanceId\"[[:space:]]*:[[:space:]]*\"$SCUM_ALPHA_ID\"" require_file_contains "$WORK_DIR/scum-alpha-run-generate.response.json" "\"artifactId\"[[:space:]]*:[[:space:]]*\"artifact-run-dist-$SCUM_ALPHA_ID"