Stabilize management reload race tests

This commit is contained in:
LTbinglingfeng
2026-06-17 02:43:12 +08:00
parent a4756ab7a9
commit 7b16321e50
16 changed files with 187 additions and 63 deletions
@@ -24,7 +24,6 @@ func sumRecentRequestBuckets(buckets []coreauth.RecentRequestBucket) (int64, int
func TestGetAPIKeyUsage_GroupsByProviderAndAPIKey(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
manager := coreauth.NewManager(nil, nil, nil)
if _, err := manager.Register(context.Background(), &coreauth.Auth{
@@ -18,7 +18,6 @@ import (
func TestUploadAuthFile_BatchMultipart(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
authDir := t.TempDir()
manager := coreauth.NewManager(nil, nil, nil)
@@ -86,7 +85,6 @@ func TestUploadAuthFile_BatchMultipart(t *testing.T) {
func TestUploadAuthFile_BatchMultipart_InvalidJSONDoesNotOverwriteExistingFile(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
authDir := t.TempDir()
manager := coreauth.NewManager(nil, nil, nil)
@@ -152,7 +150,6 @@ func TestUploadAuthFile_BatchMultipart_InvalidJSONDoesNotOverwriteExistingFile(t
func TestDeleteAuthFile_BatchQuery(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
authDir := t.TempDir()
files := []string{"alpha.json", "beta.json"}
@@ -17,7 +17,6 @@ import (
func TestDeleteAuthFile_UsesAuthPathFromManager(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
tempDir := t.TempDir()
authDir := filepath.Join(tempDir, "auth")
@@ -101,7 +100,6 @@ func TestDeleteAuthFile_UsesAuthPathFromManager(t *testing.T) {
func TestDeleteAuthFile_FallbackToAuthDirPath(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
authDir := t.TempDir()
fileName := "fallback-user.json"
@@ -130,7 +128,6 @@ func TestDeleteAuthFile_FallbackToAuthDirPath(t *testing.T) {
func TestDeleteAuthFile_RemovesRuntimeAuth(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
authDir := t.TempDir()
fileName := "runtime-remove-user.json"
@@ -14,7 +14,6 @@ import (
func TestDownloadAuthFile_ReturnsFile(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
authDir := t.TempDir()
fileName := "download-user.json"
@@ -40,7 +39,6 @@ func TestDownloadAuthFile_ReturnsFile(t *testing.T) {
func TestDownloadAuthFile_RejectsPathSeparators(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
h := NewHandlerWithoutConfigFilePath(&config.Config{AuthDir: t.TempDir()}, nil)
@@ -16,7 +16,6 @@ import (
func TestDownloadAuthFile_PreventsWindowsSlashTraversal(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
tempDir := t.TempDir()
authDir := filepath.Join(tempDir, "auth")
@@ -18,7 +18,6 @@ import (
func TestPatchAuthFileFields_MergeHeadersAndDeleteEmptyValues(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
store := &memoryAuthStore{}
manager := coreauth.NewManager(store, nil, nil)
@@ -113,7 +112,6 @@ func TestPatchAuthFileFields_MergeHeadersAndDeleteEmptyValues(t *testing.T) {
func TestPatchAuthFileFields_HeadersEmptyMapIsNoop(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
store := &memoryAuthStore{}
manager := coreauth.NewManager(store, nil, nil)
@@ -168,7 +166,6 @@ func TestPatchAuthFileFields_HeadersEmptyMapIsNoop(t *testing.T) {
func TestPatchAuthFileFields_WebsocketsFalseIsUpdate(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
store := &memoryAuthStore{}
manager := coreauth.NewManager(store, nil, nil)
@@ -217,7 +214,6 @@ func TestPatchAuthFileFields_WebsocketsFalseIsUpdate(t *testing.T) {
func TestPatchAuthFileFields_ArbitraryFieldsPersistToFile(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
authDir := t.TempDir()
fileName := "generic.json"
@@ -16,7 +16,6 @@ import (
func TestListAuthFiles_IncludesProjectIDFromManager(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
authDir := t.TempDir()
fileName := "[email protected]"
@@ -55,7 +54,6 @@ func TestListAuthFiles_IncludesProjectIDFromManager(t *testing.T) {
func TestListAuthFilesFromDisk_IncludesProjectID(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
authDir := t.TempDir()
filePath := filepath.Join(authDir, "[email protected]")
@@ -73,7 +71,6 @@ func TestListAuthFilesFromDisk_IncludesProjectID(t *testing.T) {
func TestListAuthFiles_IncludesWebsocketsFromManager(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
authDir := t.TempDir()
fileName := "[email protected]"
@@ -111,7 +108,6 @@ func TestListAuthFiles_IncludesWebsocketsFromManager(t *testing.T) {
func TestListAuthFilesFromDisk_IncludesWebsockets(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
authDir := t.TempDir()
filePath := filepath.Join(authDir, "[email protected]")
@@ -14,7 +14,6 @@ import (
func TestListAuthFiles_IncludesRecentRequestsBuckets(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
gin.SetMode(gin.TestMode)
manager := coreauth.NewManager(nil, nil, nil)
record := &coreauth.Auth{
@@ -24,7 +24,6 @@ func writeTestConfigFile(t *testing.T) string {
func TestDeleteGeminiKey_RequiresBaseURLWhenAPIKeyDuplicated(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
h := &Handler{
cfg: &config.Config{
@@ -52,7 +51,6 @@ func TestDeleteGeminiKey_RequiresBaseURLWhenAPIKeyDuplicated(t *testing.T) {
func TestDeleteGeminiKey_DeletesOnlyMatchingBaseURL(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
h := &Handler{
cfg: &config.Config{
@@ -83,7 +81,6 @@ func TestDeleteGeminiKey_DeletesOnlyMatchingBaseURL(t *testing.T) {
func TestDeleteClaudeKey_DeletesEmptyBaseURLWhenExplicitlyProvided(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
h := &Handler{
cfg: &config.Config{
@@ -114,7 +111,6 @@ func TestDeleteClaudeKey_DeletesEmptyBaseURLWhenExplicitlyProvided(t *testing.T)
func TestDeleteVertexCompatKey_DeletesOnlyMatchingBaseURL(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
h := &Handler{
cfg: &config.Config{
@@ -145,7 +141,6 @@ func TestDeleteVertexCompatKey_DeletesOnlyMatchingBaseURL(t *testing.T) {
func TestDeleteCodexKey_RequiresBaseURLWhenAPIKeyDuplicated(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
h := &Handler{
cfg: &config.Config{
@@ -41,7 +41,6 @@ func TestAuthenticateManagementKey_LocalhostIPBan_BlocksCorrectKeyDuringBan(t *t
}
func TestMiddlewareSetsSupportPluginHeader(t *testing.T) {
gin.SetMode(gin.TestMode)
h := &Handler{
cfg: &config.Config{},
@@ -706,7 +706,6 @@ func performGetLogs(t *testing.T, h *Handler, target string) logsAPIResponse {
func performGetLogsRaw(t *testing.T, h *Handler, target string) (int, string) {
t.Helper()
gin.SetMode(gin.TestMode)
rec := httptest.NewRecorder()
c, _ := gin.CreateTestContext(rec)
c.Request = httptest.NewRequest(http.MethodGet, target, nil)
@@ -14,7 +14,6 @@ import (
)
func TestPostOAuthCallbackCreatesMissingAuthDir(t *testing.T) {
gin.SetMode(gin.TestMode)
authDir := filepath.Join(t.TempDir(), "missing-auth")
state := "test-antigravity-state"
@@ -3,7 +3,6 @@ package management
import (
"archive/zip"
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
@@ -25,7 +24,6 @@ import (
func TestListPluginStoreMergesInstalledStatus(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
pluginsDir := writeManagementPluginFile(t, "sample-provider")
h := &Handler{
@@ -84,7 +82,6 @@ func TestListPluginStoreMergesInstalledStatus(t *testing.T) {
func TestListPluginStoreEscapesRegistryStrings(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
h := &Handler{
cfg: &config.Config{
@@ -150,7 +147,6 @@ func TestListPluginStoreEscapesRegistryStrings(t *testing.T) {
func TestListPluginStoreShowsLatestReleaseVersionAndCaches(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
httpClient := &countingPluginStoreHTTPClient{responses: fakePluginStoreHTTPClient{
"https://registry.example/registry.json": registryJSON(t),
@@ -203,7 +199,6 @@ func TestListPluginStoreShowsLatestReleaseVersionAndCaches(t *testing.T) {
func TestListPluginStoreFallsBackToRegistryVersion(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
h := &Handler{
cfg: &config.Config{
@@ -242,7 +237,6 @@ func TestListPluginStoreFallsBackToRegistryVersion(t *testing.T) {
func TestListPluginStoreIncludesThirdPartySources(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
h := &Handler{
cfg: &config.Config{
@@ -304,7 +298,6 @@ func TestListPluginStoreIncludesThirdPartySources(t *testing.T) {
func TestInstallPluginFromStoreWritesFileAndEnablesConfig(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
pluginsDir := t.TempDir()
archiveData := makeManagementPluginStoreZip(t, "sample-provider"+managementPluginExtension(runtime.GOOS), "library-data")
@@ -335,6 +328,7 @@ func TestInstallPluginFromStoreWritesFileAndEnablesConfig(t *testing.T) {
"https://downloads.example/checksums.txt": []byte(hex.EncodeToString(checksum[:]) + " " + archiveName + "\n"),
},
}
reloads, reloadDone := captureConfigReload(h)
rec := httptest.NewRecorder()
c, _ := gin.CreateTestContext(rec)
@@ -346,6 +340,11 @@ func TestInstallPluginFromStoreWritesFileAndEnablesConfig(t *testing.T) {
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String())
}
cfgSnapshot := waitForAsyncReload(t, reloads)
waitForReloadDone(t, reloadDone)
if cfgSnapshot == h.cfg {
t.Fatalf("reload config = handler config %p, want independent snapshot", h.cfg)
}
var body pluginInstallResponse
if errDecode := json.Unmarshal(rec.Body.Bytes(), &body); errDecode != nil {
t.Fatalf("Unmarshal() error = %v; body=%s", errDecode, rec.Body.String())
@@ -371,18 +370,27 @@ func TestInstallPluginFromStoreWritesFileAndEnablesConfig(t *testing.T) {
if item.Enabled == nil || !*item.Enabled {
t.Fatalf("plugin enabled = %#v, want true", item.Enabled)
}
snapshotItem := cfgSnapshot.Plugins.Configs["sample-provider"]
if snapshotItem.Enabled == nil || !*snapshotItem.Enabled {
t.Fatalf("snapshot plugin enabled = %#v, want true", snapshotItem.Enabled)
}
if h.cfg.Plugins.Enabled {
t.Fatal("global plugins.enabled changed to true")
}
if cfgSnapshot.Plugins.Enabled {
t.Fatal("snapshot global plugins.enabled changed to true")
}
raw := marshalPluginRaw(t, item)
if !strings.Contains(raw, "mode: fast") {
t.Fatalf("plugin raw config lost custom field:\n%s", raw)
}
if raw := marshalPluginRaw(t, snapshotItem); !strings.Contains(raw, "mode: fast") {
t.Fatalf("snapshot plugin raw config lost custom field:\n%s", raw)
}
}
func TestInstallPluginFromStoreUsesRequestedThirdPartySource(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
pluginsDir := t.TempDir()
archiveData := makeManagementPluginStoreZip(t, "sample-provider"+managementPluginExtension(runtime.GOOS), "third-party-library-data")
@@ -411,6 +419,7 @@ func TestInstallPluginFromStoreUsesRequestedThirdPartySource(t *testing.T) {
"https://downloads.example/checksums.txt": []byte(hex.EncodeToString(checksum[:]) + " " + archiveName + "\n"),
},
}
reloads, reloadDone := captureConfigReload(h)
rec := httptest.NewRecorder()
c, _ := gin.CreateTestContext(rec)
@@ -423,6 +432,11 @@ func TestInstallPluginFromStoreUsesRequestedThirdPartySource(t *testing.T) {
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String())
}
cfgSnapshot := waitForAsyncReload(t, reloads)
waitForReloadDone(t, reloadDone)
if cfgSnapshot == h.cfg {
t.Fatalf("reload config = handler config %p, want independent snapshot", h.cfg)
}
var body pluginInstallResponse
if errDecode := json.Unmarshal(rec.Body.Bytes(), &body); errDecode != nil {
t.Fatalf("Unmarshal() error = %v; body=%s", errDecode, rec.Body.String())
@@ -438,11 +452,14 @@ func TestInstallPluginFromStoreUsesRequestedThirdPartySource(t *testing.T) {
if string(data) != "third-party-library-data" {
t.Fatalf("installed file = %q, want third-party-library-data", data)
}
snapshotItem := cfgSnapshot.Plugins.Configs["sample-provider"]
if snapshotItem.Enabled == nil || !*snapshotItem.Enabled {
t.Fatalf("snapshot plugin enabled = %#v, want true", snapshotItem.Enabled)
}
}
func TestInstallPluginFromStoreRequiresSourceForDuplicateIDs(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
h := &Handler{
cfg: &config.Config{
@@ -476,7 +493,6 @@ func TestInstallPluginFromStoreRequiresSourceForDuplicateIDs(t *testing.T) {
func TestInstallPluginFromStoreOverwritesFilePreservesConfigAndReloads(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
pluginsDir := t.TempDir()
existingPath := filepath.Join(pluginsDir, "sample-provider"+managementPluginExtension(runtime.GOOS))
@@ -511,10 +527,7 @@ func TestInstallPluginFromStoreOverwritesFilePreservesConfigAndReloads(t *testin
"https://downloads.example/checksums.txt": []byte(hex.EncodeToString(checksum[:]) + " " + archiveName + "\n"),
},
}
reloads := make(chan *config.Config, 1)
h.SetConfigReloadHook(func(_ context.Context, cfg *config.Config) {
reloads <- cfg
})
reloads, reloadDone := captureConfigReload(h)
rec := httptest.NewRecorder()
c, _ := gin.CreateTestContext(rec)
@@ -526,8 +539,10 @@ func TestInstallPluginFromStoreOverwritesFilePreservesConfigAndReloads(t *testin
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String())
}
if cfg := waitForAsyncReload(t, reloads); cfg != h.cfg {
t.Fatalf("reload config = %p, want handler config %p", cfg, h.cfg)
cfgSnapshot := waitForAsyncReload(t, reloads)
waitForReloadDone(t, reloadDone)
if cfgSnapshot == h.cfg {
t.Fatalf("reload config = handler config %p, want independent snapshot", h.cfg)
}
data, errRead := os.ReadFile(existingPath)
if errRead != nil {
@@ -540,13 +555,23 @@ func TestInstallPluginFromStoreOverwritesFilePreservesConfigAndReloads(t *testin
if item.Enabled == nil || !*item.Enabled {
t.Fatalf("plugin enabled = %#v, want true", item.Enabled)
}
snapshotItem := cfgSnapshot.Plugins.Configs["sample-provider"]
if snapshotItem.Enabled == nil || !*snapshotItem.Enabled {
t.Fatalf("snapshot plugin enabled = %#v, want true", snapshotItem.Enabled)
}
if item.Priority != 5 {
t.Fatalf("plugin priority = %d, want 5", item.Priority)
}
if snapshotItem.Priority != 5 {
t.Fatalf("snapshot plugin priority = %d, want 5", snapshotItem.Priority)
}
raw := marshalPluginRaw(t, item)
if !strings.Contains(raw, "mode: fast") || !strings.Contains(raw, "extra: keep") {
t.Fatalf("plugin raw config lost custom fields:\n%s", raw)
}
if raw := marshalPluginRaw(t, snapshotItem); !strings.Contains(raw, "mode: fast") || !strings.Contains(raw, "extra: keep") {
t.Fatalf("snapshot plugin raw config lost custom fields:\n%s", raw)
}
}
func TestEnablePluginConfigLockedPreservesExistingFields(t *testing.T) {
+133 -18
View File
@@ -32,9 +32,27 @@ func waitForAsyncReload(t *testing.T, reloads <-chan *config.Config) *config.Con
}
}
func waitForReloadDone(t *testing.T, done <-chan struct{}) {
t.Helper()
select {
case <-done:
case <-time.After(time.Second):
t.Fatal("timed out waiting for config reload hook to finish")
}
}
func captureConfigReload(h *Handler) (<-chan *config.Config, <-chan struct{}) {
reloads := make(chan *config.Config, 1)
done := make(chan struct{})
h.SetConfigReloadHook(func(_ context.Context, cfg *config.Config) {
defer close(done)
reloads <- cfg
})
return reloads, done
}
func TestListPluginsIncludesScannedAndConfiguredPlugins(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
pluginsDir := writeManagementPluginFile(t, "scanned")
disabled := false
@@ -117,7 +135,6 @@ func TestListPluginsIncludesScannedAndConfiguredPlugins(t *testing.T) {
func TestGetPluginConfigReturnsPreservedRawConfig(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
h := &Handler{
cfg: &config.Config{
@@ -174,7 +191,6 @@ options:
func TestGetPluginConfigReturnsEmptyObjectForKnownUnconfiguredPlugin(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
pluginsDir := writeManagementPluginFile(t, "scanned")
h := &Handler{
@@ -207,7 +223,6 @@ func TestGetPluginConfigReturnsEmptyObjectForKnownUnconfiguredPlugin(t *testing.
func TestGetPluginConfigReturnsNotFoundForUnknownPlugin(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
h := &Handler{
cfg: &config.Config{},
@@ -228,7 +243,6 @@ func TestGetPluginConfigReturnsNotFoundForUnknownPlugin(t *testing.T) {
func TestPatchPluginEnabledUpdatesOnlyPluginConfig(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
h := &Handler{
cfg: &config.Config{
@@ -241,10 +255,7 @@ func TestPatchPluginEnabledUpdatesOnlyPluginConfig(t *testing.T) {
},
configFilePath: writeTestConfigFile(t),
}
reloads := make(chan *config.Config, 1)
h.SetConfigReloadHook(func(_ context.Context, cfg *config.Config) {
reloads <- cfg
})
reloads, reloadDone := captureConfigReload(h)
rec := httptest.NewRecorder()
c, _ := gin.CreateTestContext(rec)
@@ -257,8 +268,20 @@ func TestPatchPluginEnabledUpdatesOnlyPluginConfig(t *testing.T) {
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String())
}
if cfg := waitForAsyncReload(t, reloads); cfg != h.cfg {
t.Fatalf("reload config = %p, want handler config %p", cfg, h.cfg)
cfgSnapshot := waitForAsyncReload(t, reloads)
waitForReloadDone(t, reloadDone)
if cfgSnapshot == h.cfg {
t.Fatalf("reload config = handler config %p, want independent snapshot", h.cfg)
}
if cfgSnapshot.Plugins.Enabled {
t.Fatal("snapshot global Plugins.Enabled changed to true")
}
snapshotItem := cfgSnapshot.Plugins.Configs["sample"]
if snapshotItem.Enabled == nil || !*snapshotItem.Enabled {
t.Fatalf("snapshot sample enabled = %#v, want true", snapshotItem.Enabled)
}
if raw := marshalPluginRaw(t, snapshotItem); !strings.Contains(raw, "mode: safe") {
t.Fatalf("snapshot raw config lost custom field:\n%s", raw)
}
if h.cfg.Plugins.Enabled {
t.Fatal("global Plugins.Enabled changed to true")
@@ -273,9 +296,71 @@ func TestPatchPluginEnabledUpdatesOnlyPluginConfig(t *testing.T) {
}
}
func TestPatchPluginEnabledReloadSnapshotRawImmutability(t *testing.T) {
t.Parallel()
h := &Handler{
cfg: &config.Config{
Plugins: config.PluginsConfig{
Configs: map[string]config.PluginInstanceConfig{
"sample": pluginConfigFromYAML(t, "enabled: false\nmode: first\n"),
},
},
},
configFilePath: writeTestConfigFile(t),
}
reloads := make(chan *config.Config, 1)
releaseReload := make(chan struct{})
reloadDone := make(chan struct{})
h.SetConfigReloadHook(func(_ context.Context, cfg *config.Config) {
defer close(reloadDone)
reloads <- cfg
<-releaseReload
})
rec := httptest.NewRecorder()
c, _ := gin.CreateTestContext(rec)
c.Params = gin.Params{{Key: "id", Value: "sample"}}
c.Request = httptest.NewRequest(http.MethodPatch, "/v0/management/plugins/sample/enabled", strings.NewReader(`{"enabled":true}`))
c.Request.Header.Set("Content-Type", "application/json")
h.PatchPluginEnabled(c)
if rec.Code != http.StatusOK {
close(releaseReload)
waitForReloadDone(t, reloadDone)
t.Fatalf("status = %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String())
}
cfgSnapshot := waitForAsyncReload(t, reloads)
h.mu.Lock()
item := h.cfg.Plugins.Configs["sample"]
setPluginRawScalarValue(t, &item.Raw, "mode", "second")
h.cfg.Plugins.Configs["sample"] = item
h.mu.Unlock()
if cfgSnapshot == h.cfg {
t.Fatalf("reload config = handler config %p, want independent snapshot", h.cfg)
}
snapshotItem := cfgSnapshot.Plugins.Configs["sample"]
if snapshotItem.Enabled == nil || !*snapshotItem.Enabled {
t.Fatalf("snapshot sample enabled = %#v, want true", snapshotItem.Enabled)
}
if got := pluginRawScalarValue(t, snapshotItem, "mode"); got != "first" {
t.Fatalf("snapshot raw mode = %q, want first", got)
}
h.mu.Lock()
handlerItem := h.cfg.Plugins.Configs["sample"]
h.mu.Unlock()
if got := pluginRawScalarValue(t, handlerItem, "mode"); got != "second" {
t.Fatalf("handler raw mode = %q, want second", got)
}
close(releaseReload)
waitForReloadDone(t, reloadDone)
}
func TestPutPluginConfigReplacesPluginConfig(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
h := &Handler{
cfg: &config.Config{
@@ -311,7 +396,6 @@ func TestPutPluginConfigReplacesPluginConfig(t *testing.T) {
func TestPatchPluginConfigMergesAndDeletesFields(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
h := &Handler{
cfg: &config.Config{
@@ -347,7 +431,6 @@ func TestPatchPluginConfigMergesAndDeletesFields(t *testing.T) {
func TestDeletePluginRemovesDiscoveredFileAndConfig(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
pluginsDir := writeManagementPluginFile(t, "sample")
h := &Handler{
@@ -363,8 +446,9 @@ func TestDeletePluginRemovesDiscoveredFileAndConfig(t *testing.T) {
}
reloads := make(chan *config.Config, 1)
releaseReload := make(chan struct{})
defer close(releaseReload)
reloadDone := make(chan struct{})
h.SetConfigReloadHook(func(_ context.Context, cfg *config.Config) {
defer close(reloadDone)
reloads <- cfg
<-releaseReload
})
@@ -403,14 +487,23 @@ func TestDeletePluginRemovesDiscoveredFileAndConfig(t *testing.T) {
if _, errStat := os.Stat(path); !os.IsNotExist(errStat) {
t.Fatalf("plugin file stat error = %v, want not exist", errStat)
}
if cfg := waitForAsyncReload(t, reloads); cfg != h.cfg {
t.Fatalf("reload config = %p, want handler config %p", cfg, h.cfg)
cfgSnapshot := waitForAsyncReload(t, reloads)
if cfgSnapshot == h.cfg {
close(releaseReload)
waitForReloadDone(t, reloadDone)
t.Fatalf("reload config = handler config %p, want independent snapshot", h.cfg)
}
if _, ok := cfgSnapshot.Plugins.Configs["sample"]; ok {
close(releaseReload)
waitForReloadDone(t, reloadDone)
t.Fatal("snapshot plugin config still exists after delete")
}
close(releaseReload)
waitForReloadDone(t, reloadDone)
}
func TestDeletePluginReturnsNotFoundForUnknownPlugin(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
h := &Handler{
cfg: &config.Config{},
@@ -523,3 +616,25 @@ func marshalPluginRaw(t *testing.T, item config.PluginInstanceConfig) string {
}
return string(data)
}
func pluginRawScalarValue(t *testing.T, item config.PluginInstanceConfig, key string) string {
t.Helper()
for i := 0; i+1 < len(item.Raw.Content); i += 2 {
if item.Raw.Content[i] != nil && item.Raw.Content[i].Value == key && item.Raw.Content[i+1] != nil {
return item.Raw.Content[i+1].Value
}
}
t.Fatalf("plugin raw missing scalar key %q", key)
return ""
}
func setPluginRawScalarValue(t *testing.T, node *yaml.Node, key, value string) {
t.Helper()
for i := 0; i+1 < len(node.Content); i += 2 {
if node.Content[i] != nil && node.Content[i].Value == key && node.Content[i+1] != nil {
node.Content[i+1].Value = value
return
}
}
t.Fatalf("plugin raw missing scalar key %q", key)
}
@@ -0,0 +1,13 @@
package management
import (
"os"
"testing"
"github.com/gin-gonic/gin"
)
func TestMain(m *testing.M) {
gin.SetMode(gin.TestMode)
os.Exit(m.Run())
}
@@ -11,7 +11,6 @@ import (
)
func TestGetUsageQueuePopsRequestedRecords(t *testing.T) {
gin.SetMode(gin.TestMode)
withManagementUsageQueue(t, func() {
redisqueue.Enqueue([]byte(`{"id":1}`))
redisqueue.Enqueue([]byte(`{"id":2}`))
@@ -46,7 +45,6 @@ func TestGetUsageQueuePopsRequestedRecords(t *testing.T) {
}
func TestGetUsageQueueInvalidCountDoesNotPop(t *testing.T) {
gin.SetMode(gin.TestMode)
withManagementUsageQueue(t, func() {
redisqueue.Enqueue([]byte(`{"id":1}`))