package management import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "sort" "strings" "testing" "github.com/gin-gonic/gin" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" "github.com/stretchr/testify/assert" ) // setupTestHandler creates a temporary config file and a Handler for testing. // It returns the handler, the config object, the cleanup function, and an error if any. func setupTestHandler(t *testing.T) (*Handler, *config.Config, func(), error) { // Create a temporary file for config tmpFile, err := os.CreateTemp("", "config_test_*.yaml") if err != nil { return nil, nil, nil, err } // Write initial valid YAML content (empty object) if _, err := tmpFile.WriteString("{}\n"); err != nil { tmpFile.Close() os.Remove(tmpFile.Name()) return nil, nil, nil, err } tmpFile.Close() cfg := &config.Config{ SDKConfig: config.SDKConfig{ Access: config.AccessConfig{ Providers: nil, }, }, } h := NewHandler(cfg, tmpFile.Name(), nil) cleanup := func() { os.Remove(tmpFile.Name()) } return h, cfg, cleanup, nil } func TestPutAPIKeys(t *testing.T) { gin.SetMode(gin.TestMode) h, cfg, cleanup, err := setupTestHandler(t) assert.NoError(t, err) defer cleanup() // Initial state cfg.APIKeys = []string{"initial-key"} // Test case: Put a new list of keys newKeys := []string{"key1", "key2", "key3"} body, _ := json.Marshal(newKeys) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request, _ = http.NewRequest(http.MethodPut, "/api-keys", bytes.NewBuffer(body)) h.PutAPIKeys(c) assert.Equal(t, http.StatusOK, w.Code) // Verify memory update assert.Equal(t, newKeys, cfg.APIKeys) // Verify Access.Providers is cleared (as per PutAPIKeys logic) assert.Nil(t, cfg.Access.Providers) // Test case: Put using object wrapper { "items": [...] } wrapperBody := `{"items": ["wrapped-key-1", "wrapped-key-2"]}` w = httptest.NewRecorder() c, _ = gin.CreateTestContext(w) c.Request, _ = http.NewRequest(http.MethodPut, "/api-keys", bytes.NewBufferString(wrapperBody)) h.PutAPIKeys(c) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, []string{"wrapped-key-1", "wrapped-key-2"}, cfg.APIKeys) } func TestDeleteAPIKeys(t *testing.T) { gin.SetMode(gin.TestMode) h, cfg, cleanup, err := setupTestHandler(t) assert.NoError(t, err) defer cleanup() // Initial state cfg.APIKeys = []string{"key1", "key2", "key3", "key4"} // Test case: Delete by value (query param "value") w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request, _ = http.NewRequest(http.MethodDelete, "/api-keys?value=key2", nil) h.DeleteAPIKeys(c) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, []string{"key1", "key3", "key4"}, cfg.APIKeys) // Test case: Delete by index w = httptest.NewRecorder() c, _ = gin.CreateTestContext(w) c.Request, _ = http.NewRequest(http.MethodDelete, "/api-keys?index=1", nil) // Deleting index 1 (key3 now, since key2 is gone) h.DeleteAPIKeys(c) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, []string{"key1", "key4"}, cfg.APIKeys) // Test case: Delete non-existent value w = httptest.NewRecorder() c, _ = gin.CreateTestContext(w) c.Request, _ = http.NewRequest(http.MethodDelete, "/api-keys?value=non-existent", nil) h.DeleteAPIKeys(c) assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, []string{"key1", "key4"}, cfg.APIKeys) // Test case: Delete invalid index w = httptest.NewRecorder() c, _ = gin.CreateTestContext(w) c.Request, _ = http.NewRequest(http.MethodDelete, "/api-keys?index=99", nil) h.DeleteAPIKeys(c) // GenericDeleteList returns 400 if index is out of bounds but parsed correctly? // The code says: if _, err := fmt.Sscanf(idxStr, "%d", &idx); err == nil && idx >= 0 && idx < len(list) // If that condition fails, it falls through to c.JSON(400, "missing ... or index") assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, []string{"key1", "key4"}, cfg.APIKeys) } func TestGenericUpdateField(t *testing.T) { gin.SetMode(gin.TestMode) h, cfg, cleanup, err := setupTestHandler(t) assert.NoError(t, err) defer cleanup() // Test updating a bool field (Debug) cfg.Debug = false body := `{"value": true}` w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request, _ = http.NewRequest(http.MethodPut, "/debug", bytes.NewBufferString(body)) // Manually call genericUpdateField with a setter that updates cfg.Debug genericUpdateField(h, c, func(v bool) { cfg.Debug = v }) assert.Equal(t, http.StatusOK, w.Code) assert.True(t, cfg.Debug) // Verify persistence (load from file) loaded, err := config.LoadConfig(h.configFilePath) assert.NoError(t, err) assert.True(t, loaded.Debug) } func TestGenericListFiles(t *testing.T) { gin.SetMode(gin.TestMode) // Create temp dir tmpDir, err := os.MkdirTemp("", "test_files") assert.NoError(t, err) defer os.RemoveAll(tmpDir) // Create some files _ = os.WriteFile(filepath.Join(tmpDir, "file1.txt"), []byte("content"), 0644) _ = os.WriteFile(filepath.Join(tmpDir, "file2.json"), []byte("content"), 0644) _ = os.WriteFile(filepath.Join(tmpDir, "file3.txt"), []byte("content"), 0644) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) genericListFiles(c, tmpDir, func(e os.DirEntry) (string, bool) { if strings.HasSuffix(e.Name(), ".txt") { return e.Name(), true } return "", false }, func(files []string) { // Sort sort.Strings(files) }) assert.Equal(t, http.StatusOK, w.Code) var resp struct { Files []string `json:"files"` } err = json.Unmarshal(w.Body.Bytes(), &resp) assert.NoError(t, err) assert.Equal(t, []string{"file1.txt", "file3.txt"}, resp.Files) }