Skip to content

Commit 125c092

Browse files
authored
Merge pull request router-for-me#3872 from router-for-me/codex/pluginhost-async-reload
Fix async plugin reload races
2 parents a5cb883 + 09596d2 commit 125c092

25 files changed

Lines changed: 1102 additions & 123 deletions

internal/api/handlers/management/api_key_usage_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ func sumRecentRequestBuckets(buckets []coreauth.RecentRequestBucket) (int64, int
2424

2525
func TestGetAPIKeyUsage_GroupsByProviderAndAPIKey(t *testing.T) {
2626
t.Setenv("MANAGEMENT_PASSWORD", "")
27-
gin.SetMode(gin.TestMode)
2827

2928
manager := coreauth.NewManager(nil, nil, nil)
3029
if _, err := manager.Register(context.Background(), &coreauth.Auth{

internal/api/handlers/management/auth_files.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
geminiAuth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/gemini"
2929
"github.com/router-for-me/CLIProxyAPI/v7/internal/auth/kimi"
3030
xaiauth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/xai"
31-
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
3231
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
3332
"github.com/router-for-me/CLIProxyAPI/v7/internal/misc"
3433
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
@@ -1267,13 +1266,11 @@ func (h *Handler) PatchAuthFileStatus(c *gin.Context) {
12671266
c.JSON(http.StatusNotFound, gin.H{"error": "config api key entry not found"})
12681267
return
12691268
}
1270-
if errSave := config.SaveConfigPreserveComments(h.configFilePath, h.cfg); errSave != nil {
1271-
h.mu.Unlock()
1272-
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to save config: %v", errSave)})
1269+
cfgSnapshot, okSnapshot := h.saveConfigAndSnapshotLocked(c)
1270+
h.mu.Unlock()
1271+
if !okSnapshot {
12731272
return
12741273
}
1275-
cfgSnapshot := h.cfg
1276-
h.mu.Unlock()
12771274
h.reloadConfigAfterManagementSave(ctx, cfgSnapshot)
12781275
if h.tokenStore != nil {
12791276
_ = h.tokenStore.Delete(ctx, targetAuth.ID)

internal/api/handlers/management/auth_files_batch_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818

1919
func TestUploadAuthFile_BatchMultipart(t *testing.T) {
2020
t.Setenv("MANAGEMENT_PASSWORD", "")
21-
gin.SetMode(gin.TestMode)
2221

2322
authDir := t.TempDir()
2423
manager := coreauth.NewManager(nil, nil, nil)
@@ -86,7 +85,6 @@ func TestUploadAuthFile_BatchMultipart(t *testing.T) {
8685

8786
func TestUploadAuthFile_BatchMultipart_InvalidJSONDoesNotOverwriteExistingFile(t *testing.T) {
8887
t.Setenv("MANAGEMENT_PASSWORD", "")
89-
gin.SetMode(gin.TestMode)
9088

9189
authDir := t.TempDir()
9290
manager := coreauth.NewManager(nil, nil, nil)
@@ -152,7 +150,6 @@ func TestUploadAuthFile_BatchMultipart_InvalidJSONDoesNotOverwriteExistingFile(t
152150

153151
func TestDeleteAuthFile_BatchQuery(t *testing.T) {
154152
t.Setenv("MANAGEMENT_PASSWORD", "")
155-
gin.SetMode(gin.TestMode)
156153

157154
authDir := t.TempDir()
158155
files := []string{"alpha.json", "beta.json"}

internal/api/handlers/management/auth_files_delete_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717

1818
func TestDeleteAuthFile_UsesAuthPathFromManager(t *testing.T) {
1919
t.Setenv("MANAGEMENT_PASSWORD", "")
20-
gin.SetMode(gin.TestMode)
2120

2221
tempDir := t.TempDir()
2322
authDir := filepath.Join(tempDir, "auth")
@@ -101,7 +100,6 @@ func TestDeleteAuthFile_UsesAuthPathFromManager(t *testing.T) {
101100

102101
func TestDeleteAuthFile_FallbackToAuthDirPath(t *testing.T) {
103102
t.Setenv("MANAGEMENT_PASSWORD", "")
104-
gin.SetMode(gin.TestMode)
105103

106104
authDir := t.TempDir()
107105
fileName := "fallback-user.json"
@@ -130,7 +128,6 @@ func TestDeleteAuthFile_FallbackToAuthDirPath(t *testing.T) {
130128

131129
func TestDeleteAuthFile_RemovesRuntimeAuth(t *testing.T) {
132130
t.Setenv("MANAGEMENT_PASSWORD", "")
133-
gin.SetMode(gin.TestMode)
134131

135132
authDir := t.TempDir()
136133
fileName := "runtime-remove-user.json"

internal/api/handlers/management/auth_files_download_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414

1515
func TestDownloadAuthFile_ReturnsFile(t *testing.T) {
1616
t.Setenv("MANAGEMENT_PASSWORD", "")
17-
gin.SetMode(gin.TestMode)
1817

1918
authDir := t.TempDir()
2019
fileName := "download-user.json"
@@ -40,7 +39,6 @@ func TestDownloadAuthFile_ReturnsFile(t *testing.T) {
4039

4140
func TestDownloadAuthFile_RejectsPathSeparators(t *testing.T) {
4241
t.Setenv("MANAGEMENT_PASSWORD", "")
43-
gin.SetMode(gin.TestMode)
4442

4543
h := NewHandlerWithoutConfigFilePath(&config.Config{AuthDir: t.TempDir()}, nil)
4644

internal/api/handlers/management/auth_files_download_windows_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616

1717
func TestDownloadAuthFile_PreventsWindowsSlashTraversal(t *testing.T) {
1818
t.Setenv("MANAGEMENT_PASSWORD", "")
19-
gin.SetMode(gin.TestMode)
2019

2120
tempDir := t.TempDir()
2221
authDir := filepath.Join(tempDir, "auth")

internal/api/handlers/management/auth_files_patch_fields_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818

1919
func TestPatchAuthFileFields_MergeHeadersAndDeleteEmptyValues(t *testing.T) {
2020
t.Setenv("MANAGEMENT_PASSWORD", "")
21-
gin.SetMode(gin.TestMode)
2221

2322
store := &memoryAuthStore{}
2423
manager := coreauth.NewManager(store, nil, nil)
@@ -113,7 +112,6 @@ func TestPatchAuthFileFields_MergeHeadersAndDeleteEmptyValues(t *testing.T) {
113112

114113
func TestPatchAuthFileFields_HeadersEmptyMapIsNoop(t *testing.T) {
115114
t.Setenv("MANAGEMENT_PASSWORD", "")
116-
gin.SetMode(gin.TestMode)
117115

118116
store := &memoryAuthStore{}
119117
manager := coreauth.NewManager(store, nil, nil)
@@ -168,7 +166,6 @@ func TestPatchAuthFileFields_HeadersEmptyMapIsNoop(t *testing.T) {
168166

169167
func TestPatchAuthFileFields_WebsocketsFalseIsUpdate(t *testing.T) {
170168
t.Setenv("MANAGEMENT_PASSWORD", "")
171-
gin.SetMode(gin.TestMode)
172169

173170
store := &memoryAuthStore{}
174171
manager := coreauth.NewManager(store, nil, nil)
@@ -217,7 +214,6 @@ func TestPatchAuthFileFields_WebsocketsFalseIsUpdate(t *testing.T) {
217214

218215
func TestPatchAuthFileFields_ArbitraryFieldsPersistToFile(t *testing.T) {
219216
t.Setenv("MANAGEMENT_PASSWORD", "")
220-
gin.SetMode(gin.TestMode)
221217

222218
authDir := t.TempDir()
223219
fileName := "generic.json"

internal/api/handlers/management/auth_files_project_id_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616

1717
func TestListAuthFiles_IncludesProjectIDFromManager(t *testing.T) {
1818
t.Setenv("MANAGEMENT_PASSWORD", "")
19-
gin.SetMode(gin.TestMode)
2019

2120
authDir := t.TempDir()
2221
fileName := "gemini-user@example.com-project-a.json"
@@ -55,7 +54,6 @@ func TestListAuthFiles_IncludesProjectIDFromManager(t *testing.T) {
5554

5655
func TestListAuthFilesFromDisk_IncludesProjectID(t *testing.T) {
5756
t.Setenv("MANAGEMENT_PASSWORD", "")
58-
gin.SetMode(gin.TestMode)
5957

6058
authDir := t.TempDir()
6159
filePath := filepath.Join(authDir, "gemini-user@example.com-project-a.json")
@@ -73,7 +71,6 @@ func TestListAuthFilesFromDisk_IncludesProjectID(t *testing.T) {
7371

7472
func TestListAuthFiles_IncludesWebsocketsFromManager(t *testing.T) {
7573
t.Setenv("MANAGEMENT_PASSWORD", "")
76-
gin.SetMode(gin.TestMode)
7774

7875
authDir := t.TempDir()
7976
fileName := "codex-user@example.com-pro.json"
@@ -111,7 +108,6 @@ func TestListAuthFiles_IncludesWebsocketsFromManager(t *testing.T) {
111108

112109
func TestListAuthFilesFromDisk_IncludesWebsockets(t *testing.T) {
113110
t.Setenv("MANAGEMENT_PASSWORD", "")
114-
gin.SetMode(gin.TestMode)
115111

116112
authDir := t.TempDir()
117113
filePath := filepath.Join(authDir, "codex-user@example.com-pro.json")

internal/api/handlers/management/auth_files_recent_requests_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414

1515
func TestListAuthFiles_IncludesRecentRequestsBuckets(t *testing.T) {
1616
t.Setenv("MANAGEMENT_PASSWORD", "")
17-
gin.SetMode(gin.TestMode)
1817

1918
manager := coreauth.NewManager(nil, nil, nil)
2019
record := &coreauth.Auth{

internal/api/handlers/management/config_lists_delete_keys_test.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ func writeTestConfigFile(t *testing.T) string {
2424

2525
func TestDeleteGeminiKey_RequiresBaseURLWhenAPIKeyDuplicated(t *testing.T) {
2626
t.Parallel()
27-
gin.SetMode(gin.TestMode)
2827

2928
h := &Handler{
3029
cfg: &config.Config{
@@ -52,7 +51,6 @@ func TestDeleteGeminiKey_RequiresBaseURLWhenAPIKeyDuplicated(t *testing.T) {
5251

5352
func TestDeleteGeminiKey_DeletesOnlyMatchingBaseURL(t *testing.T) {
5453
t.Parallel()
55-
gin.SetMode(gin.TestMode)
5654

5755
h := &Handler{
5856
cfg: &config.Config{
@@ -83,7 +81,6 @@ func TestDeleteGeminiKey_DeletesOnlyMatchingBaseURL(t *testing.T) {
8381

8482
func TestDeleteClaudeKey_DeletesEmptyBaseURLWhenExplicitlyProvided(t *testing.T) {
8583
t.Parallel()
86-
gin.SetMode(gin.TestMode)
8784

8885
h := &Handler{
8986
cfg: &config.Config{
@@ -114,7 +111,6 @@ func TestDeleteClaudeKey_DeletesEmptyBaseURLWhenExplicitlyProvided(t *testing.T)
114111

115112
func TestDeleteVertexCompatKey_DeletesOnlyMatchingBaseURL(t *testing.T) {
116113
t.Parallel()
117-
gin.SetMode(gin.TestMode)
118114

119115
h := &Handler{
120116
cfg: &config.Config{
@@ -145,7 +141,6 @@ func TestDeleteVertexCompatKey_DeletesOnlyMatchingBaseURL(t *testing.T) {
145141

146142
func TestDeleteCodexKey_RequiresBaseURLWhenAPIKeyDuplicated(t *testing.T) {
147143
t.Parallel()
148-
gin.SetMode(gin.TestMode)
149144

150145
h := &Handler{
151146
cfg: &config.Config{

0 commit comments

Comments
 (0)