diff --git a/platform/api/resource_handlers.go b/platform/api/resource_handlers.go index d068381..451d87d 100644 --- a/platform/api/resource_handlers.go +++ b/platform/api/resource_handlers.go @@ -1123,14 +1123,13 @@ func (h *coreHandlers) gamePlugins(w http.ResponseWriter, r *http.Request) { // gamePluginManifestRegistration godoc // @Summary Register game management plugin manifest -// @Description Validates and registers one game management plugin manifest as installed registry metadata. +// @Description Validates and registers one game management plugin manifest as installed registry metadata, refreshing an existing manifest with the same ID in place. // @Tags game-plugins // @Accept json // @Produce json // @Param body body dto.GamePluginManifestRegistrationRequest true "Game plugin manifest registration request" // @Success 201 {object} dto.GamePluginResponse // @Failure 400 {object} dto.ErrorResponse -// @Failure 409 {object} dto.ErrorResponse // @Failure 405 {object} dto.ErrorResponse // @Router /api/v1/game-plugins/register-manifest [post] func (h *coreHandlers) gamePluginManifestRegistration(w http.ResponseWriter, r *http.Request) { diff --git a/platform/api/resource_handlers_test.go b/platform/api/resource_handlers_test.go index 954fd40..2bb240a 100644 --- a/platform/api/resource_handlers_test.go +++ b/platform/api/resource_handlers_test.go @@ -1292,8 +1292,15 @@ func TestGamePluginManifestRegistryAPI(t *testing.T) { t.Fatalf("unexpected plugin detail: %+v", detail) } - duplicate := performJSON(t, router, http.MethodPost, "/api/v1/game-plugins/register-manifest", registration) - assertErrorResponse(t, duplicate, http.StatusConflict, errorCodeDuplicate) + registration.Manifest.Version = "0.1.1" + registration.Manifest.Description = "Development plugin refreshed" + registration.ManifestRef = "artifact://manifests/game.example/0.1.1" + refreshed := postJSON[dto.GamePluginResponse](t, router, "/api/v1/game-plugins/register-manifest", registration) + if refreshed.ID != created.ID || refreshed.Version != "0.1.1" || refreshed.ManifestRef != "artifact://manifests/game.example/0.1.1" { + t.Fatalf("expected manifest registration to refresh existing plugin, got %+v", refreshed) + } + listed = getJSON[dto.GamePluginListResponse](t, router, "/api/v1/game-plugins?serverType=example&status=installed") + assertListCount(t, listed.Count, 1) } func TestPluginMarketplaceAPIListsDetailsAndChangesStateSafely(t *testing.T) { diff --git a/platform/service/resources.go b/platform/service/resources.go index fba0d40..9483734 100644 --- a/platform/service/resources.go +++ b/platform/service/resources.go @@ -766,7 +766,20 @@ func (svc *CoreService) RegisterGamePluginManifest(registration domain.GamePlugi if err := validator.ValidateGamePluginManifestRegistration(registration); err != nil { return domain.GamePlugin{}, err } - return svc.CreateGamePlugin(gamePluginFromManifestRegistration(registration)) + plugin := gamePluginFromManifestRegistration(registration) + plugin.ProductionLifecycle = normalizedProductionLifecycle(plugin.ProductionLifecycle) + if err := validator.ValidateGamePlugin(plugin); err != nil { + return domain.GamePlugin{}, err + } + if err := svc.store.GamePlugins().Create(plugin); err != nil { + if !errors.Is(err, repo.ErrDuplicate) { + return domain.GamePlugin{}, err + } + if err := svc.store.GamePlugins().Update(plugin); err != nil { + return domain.GamePlugin{}, err + } + } + return domain.CopyGamePlugin(plugin), nil } func gamePluginFromManifestRegistration(registration domain.GamePluginManifestRegistration) domain.GamePlugin { diff --git a/platform/service/resources_test.go b/platform/service/resources_test.go index 4cfb142..5603fd1 100644 --- a/platform/service/resources_test.go +++ b/platform/service/resources_test.go @@ -1605,16 +1605,26 @@ func TestCoreServiceRejectsArbitrarySQLBridgeInputBeforeJob(t *testing.T) { } } -func TestCoreServiceRejectsDuplicateGamePluginManifest(t *testing.T) { +func TestCoreServiceUpsertsDuplicateGamePluginManifest(t *testing.T) { svc := newTestCoreService() registration := validPluginManifestRegistration() if _, err := svc.RegisterGamePluginManifest(registration); err != nil { t.Fatalf("register first manifest: %v", err) } - _, err := svc.RegisterGamePluginManifest(registration) - if !errors.Is(err, repo.ErrDuplicate) { - t.Fatalf("expected duplicate plugin registration, got %v", err) + registration.Manifest.Version = "0.1.1" + registration.Manifest.Description = "Development plugin refreshed" + registration.ManifestRef = "artifact://manifests/game.example/0.1.1" + updated, err := svc.RegisterGamePluginManifest(registration) + if err != nil { + t.Fatalf("upsert manifest: %v", err) + } + if updated.ID != "game.example" || updated.Version != "0.1.1" || updated.ManifestRef != "artifact://manifests/game.example/0.1.1" { + t.Fatalf("expected existing plugin to update in place, got %+v", updated) + } + listed, err := svc.ListGamePlugins(domain.GamePluginFilter{ServerType: "example", Status: domain.GamePluginStatusInstalled}) + if err != nil || len(listed) != 1 || listed[0].ID != "game.example" || listed[0].Version != "0.1.1" { + t.Fatalf("expected one refreshed plugin after upsert, listed=%+v err=%v", listed, err) } }