Adds tests.
This commit is contained in:
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/wild-cloud/wild-central/daemon/internal/instance"
|
||||
"github.com/wild-cloud/wild-central/daemon/internal/operations"
|
||||
"github.com/wild-cloud/wild-central/daemon/internal/secrets"
|
||||
"github.com/wild-cloud/wild-central/daemon/internal/storage"
|
||||
"github.com/wild-cloud/wild-central/daemon/internal/tools"
|
||||
)
|
||||
|
||||
@@ -302,7 +303,7 @@ func (api *API) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// updateYAMLFile updates a YAML file with the provided key-value pairs
|
||||
func (api *API) updateYAMLFile(w http.ResponseWriter, r *http.Request, instanceName, fileType string, updateFunc func(string, string, string) error) {
|
||||
func (api *API) updateYAMLFile(w http.ResponseWriter, r *http.Request, instanceName, fileType string) {
|
||||
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
||||
return
|
||||
@@ -327,13 +328,44 @@ func (api *API) updateYAMLFile(w http.ResponseWriter, r *http.Request, instanceN
|
||||
filePath = api.instance.GetInstanceSecretsPath(instanceName)
|
||||
}
|
||||
|
||||
// Update each key-value pair
|
||||
for key, value := range updates {
|
||||
valueStr := fmt.Sprintf("%v", value)
|
||||
if err := updateFunc(filePath, key, valueStr); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to update %s key %s: %v", fileType, key, err))
|
||||
// Read existing config/secrets file
|
||||
existingContent, err := storage.ReadFile(filePath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to read existing %s: %v", fileType, err))
|
||||
return
|
||||
}
|
||||
|
||||
// Parse existing content or initialize empty map
|
||||
var existingConfig map[string]interface{}
|
||||
if len(existingContent) > 0 {
|
||||
if err := yaml.Unmarshal(existingContent, &existingConfig); err != nil {
|
||||
respondError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse existing %s: %v", fileType, err))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
existingConfig = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// Merge updates into existing config (shallow merge for top-level keys)
|
||||
// This preserves unmodified keys while updating specified ones
|
||||
for key, value := range updates {
|
||||
existingConfig[key] = value
|
||||
}
|
||||
|
||||
// Marshal the merged config back to YAML with proper formatting
|
||||
yamlContent, err := yaml.Marshal(existingConfig)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to marshal YAML: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Write the complete merged YAML content to the file with proper locking
|
||||
lockPath := filePath + ".lock"
|
||||
if err := storage.WithLock(lockPath, func() error {
|
||||
return storage.WriteFile(filePath, yamlContent, 0644)
|
||||
}); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to update %s: %v", fileType, err))
|
||||
return
|
||||
}
|
||||
|
||||
// Capitalize first letter of fileType for message
|
||||
@@ -351,7 +383,7 @@ func (api *API) updateYAMLFile(w http.ResponseWriter, r *http.Request, instanceN
|
||||
func (api *API) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
name := vars["name"]
|
||||
api.updateYAMLFile(w, r, name, "config", api.config.SetConfigValue)
|
||||
api.updateYAMLFile(w, r, name, "config")
|
||||
}
|
||||
|
||||
// GetSecrets retrieves instance secrets (redacted by default)
|
||||
@@ -399,7 +431,7 @@ func (api *API) GetSecrets(w http.ResponseWriter, r *http.Request) {
|
||||
func (api *API) UpdateSecrets(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
name := vars["name"]
|
||||
api.updateYAMLFile(w, r, name, "secrets", api.secrets.SetSecret)
|
||||
api.updateYAMLFile(w, r, name, "secrets")
|
||||
}
|
||||
|
||||
// GetContext retrieves current context
|
||||
|
||||
656
internal/api/v1/handlers_config_test.go
Normal file
656
internal/api/v1/handlers_config_test.go
Normal file
@@ -0,0 +1,656 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/wild-cloud/wild-central/daemon/internal/storage"
|
||||
)
|
||||
|
||||
func setupTestAPI(t *testing.T) (*API, string) {
|
||||
tmpDir := t.TempDir()
|
||||
appsDir := filepath.Join(tmpDir, "apps")
|
||||
|
||||
api, err := NewAPI(tmpDir, appsDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test API: %v", err)
|
||||
}
|
||||
|
||||
return api, tmpDir
|
||||
}
|
||||
|
||||
func createTestInstance(t *testing.T, api *API, name string) {
|
||||
if err := api.instance.CreateInstance(name); err != nil {
|
||||
t.Fatalf("Failed to create test instance: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateYAMLFile_DeltaUpdate(t *testing.T) {
|
||||
api, _ := setupTestAPI(t)
|
||||
instanceName := "test-instance"
|
||||
createTestInstance(t, api, instanceName)
|
||||
|
||||
configPath := api.instance.GetInstanceConfigPath(instanceName)
|
||||
|
||||
// Create initial config
|
||||
initialConfig := map[string]interface{}{
|
||||
"domain": "old.com",
|
||||
"email": "admin@old.com",
|
||||
"cluster": map[string]interface{}{
|
||||
"name": "test-cluster",
|
||||
},
|
||||
}
|
||||
initialYAML, _ := yaml.Marshal(initialConfig)
|
||||
if err := storage.WriteFile(configPath, initialYAML, 0644); err != nil {
|
||||
t.Fatalf("Failed to write initial config: %v", err)
|
||||
}
|
||||
|
||||
// Update only domain
|
||||
updateData := map[string]interface{}{
|
||||
"domain": "new.com",
|
||||
}
|
||||
updateYAML, _ := yaml.Marshal(updateData)
|
||||
|
||||
req := httptest.NewRequest("PUT", "/api/v1/instances/"+instanceName+"/config", bytes.NewBuffer(updateYAML))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
vars := map[string]string{"name": instanceName}
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
api.UpdateConfig(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("Expected status 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Verify merged config
|
||||
resultData, err := storage.ReadFile(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read result: %v", err)
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := yaml.Unmarshal(resultData, &result); err != nil {
|
||||
t.Fatalf("Failed to parse result: %v", err)
|
||||
}
|
||||
|
||||
// Domain should be updated
|
||||
if result["domain"] != "new.com" {
|
||||
t.Errorf("Expected domain='new.com', got %v", result["domain"])
|
||||
}
|
||||
|
||||
// Email should be preserved
|
||||
if result["email"] != "admin@old.com" {
|
||||
t.Errorf("Expected email='admin@old.com', got %v", result["email"])
|
||||
}
|
||||
|
||||
// Cluster should be preserved
|
||||
if cluster, ok := result["cluster"].(map[string]interface{}); !ok {
|
||||
t.Errorf("Cluster not preserved as map")
|
||||
} else if cluster["name"] != "test-cluster" {
|
||||
t.Errorf("Cluster name not preserved")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateYAMLFile_FullReplacement(t *testing.T) {
|
||||
api, _ := setupTestAPI(t)
|
||||
instanceName := "test-instance"
|
||||
createTestInstance(t, api, instanceName)
|
||||
|
||||
configPath := api.instance.GetInstanceConfigPath(instanceName)
|
||||
|
||||
// Create initial config
|
||||
initialConfig := map[string]interface{}{
|
||||
"domain": "old.com",
|
||||
"email": "admin@old.com",
|
||||
"oldKey": "oldValue",
|
||||
}
|
||||
initialYAML, _ := yaml.Marshal(initialConfig)
|
||||
if err := storage.WriteFile(configPath, initialYAML, 0644); err != nil {
|
||||
t.Fatalf("Failed to write initial config: %v", err)
|
||||
}
|
||||
|
||||
// Full replacement
|
||||
newConfig := map[string]interface{}{
|
||||
"domain": "new.com",
|
||||
"email": "new@new.com",
|
||||
"newKey": "newValue",
|
||||
}
|
||||
newYAML, _ := yaml.Marshal(newConfig)
|
||||
|
||||
req := httptest.NewRequest("PUT", "/api/v1/instances/"+instanceName+"/config", bytes.NewBuffer(newYAML))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
vars := map[string]string{"name": instanceName}
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
api.UpdateConfig(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("Expected status 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Verify result
|
||||
resultData, err := storage.ReadFile(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read result: %v", err)
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := yaml.Unmarshal(resultData, &result); err != nil {
|
||||
t.Fatalf("Failed to parse result: %v", err)
|
||||
}
|
||||
|
||||
// All new values should be present
|
||||
if result["domain"] != "new.com" {
|
||||
t.Errorf("Expected domain='new.com', got %v", result["domain"])
|
||||
}
|
||||
if result["email"] != "new@new.com" {
|
||||
t.Errorf("Expected email='new@new.com', got %v", result["email"])
|
||||
}
|
||||
if result["newKey"] != "newValue" {
|
||||
t.Errorf("Expected newKey='newValue', got %v", result["newKey"])
|
||||
}
|
||||
|
||||
// Old key should still be present (shallow merge)
|
||||
if result["oldKey"] != "oldValue" {
|
||||
t.Errorf("Expected oldKey='oldValue', got %v", result["oldKey"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateYAMLFile_NestedStructure(t *testing.T) {
|
||||
api, _ := setupTestAPI(t)
|
||||
instanceName := "test-instance"
|
||||
createTestInstance(t, api, instanceName)
|
||||
|
||||
configPath := api.instance.GetInstanceConfigPath(instanceName)
|
||||
|
||||
// Update with nested structure
|
||||
updateData := map[string]interface{}{
|
||||
"cloud": map[string]interface{}{
|
||||
"domain": "test.com",
|
||||
"dns": map[string]interface{}{
|
||||
"ip": "1.2.3.4",
|
||||
"port": 53,
|
||||
},
|
||||
},
|
||||
}
|
||||
updateYAML, _ := yaml.Marshal(updateData)
|
||||
|
||||
req := httptest.NewRequest("PUT", "/api/v1/instances/"+instanceName+"/config", bytes.NewBuffer(updateYAML))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
vars := map[string]string{"name": instanceName}
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
api.UpdateConfig(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("Expected status 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Verify nested structure preserved
|
||||
resultData, err := storage.ReadFile(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read result: %v", err)
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := yaml.Unmarshal(resultData, &result); err != nil {
|
||||
t.Fatalf("Failed to parse result: %v", err)
|
||||
}
|
||||
|
||||
// Verify nested structure is proper YAML, not Go map notation
|
||||
resultStr := string(resultData)
|
||||
if bytes.Contains(resultData, []byte("map[")) {
|
||||
t.Errorf("Result contains Go map notation: %s", resultStr)
|
||||
}
|
||||
|
||||
// Verify structure is accessible
|
||||
cloud, ok := result["cloud"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("cloud is not a map: %T", result["cloud"])
|
||||
}
|
||||
|
||||
if cloud["domain"] != "test.com" {
|
||||
t.Errorf("Expected cloud.domain='test.com', got %v", cloud["domain"])
|
||||
}
|
||||
|
||||
dns, ok := cloud["dns"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("cloud.dns is not a map: %T", cloud["dns"])
|
||||
}
|
||||
|
||||
if dns["ip"] != "1.2.3.4" {
|
||||
t.Errorf("Expected dns.ip='1.2.3.4', got %v", dns["ip"])
|
||||
}
|
||||
if dns["port"] != 53 {
|
||||
t.Errorf("Expected dns.port=53, got %v", dns["port"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateYAMLFile_EmptyFileCreation(t *testing.T) {
|
||||
api, _ := setupTestAPI(t)
|
||||
instanceName := "test-instance"
|
||||
createTestInstance(t, api, instanceName)
|
||||
|
||||
configPath := api.instance.GetInstanceConfigPath(instanceName)
|
||||
|
||||
// Truncate the config file to make it empty (but still exists)
|
||||
if err := storage.WriteFile(configPath, []byte(""), 0644); err != nil {
|
||||
t.Fatalf("Failed to empty config file: %v", err)
|
||||
}
|
||||
|
||||
// Update should populate empty file
|
||||
updateData := map[string]interface{}{
|
||||
"domain": "new.com",
|
||||
"email": "admin@new.com",
|
||||
}
|
||||
updateYAML, _ := yaml.Marshal(updateData)
|
||||
|
||||
req := httptest.NewRequest("PUT", "/api/v1/instances/"+instanceName+"/config", bytes.NewBuffer(updateYAML))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
vars := map[string]string{"name": instanceName}
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
api.UpdateConfig(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("Expected status 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Verify content
|
||||
resultData, err := storage.ReadFile(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read result: %v", err)
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := yaml.Unmarshal(resultData, &result); err != nil {
|
||||
t.Fatalf("Failed to parse result: %v", err)
|
||||
}
|
||||
|
||||
if result["domain"] != "new.com" {
|
||||
t.Errorf("Expected domain='new.com', got %v", result["domain"])
|
||||
}
|
||||
if result["email"] != "admin@new.com" {
|
||||
t.Errorf("Expected email='admin@new.com', got %v", result["email"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateYAMLFile_EmptyUpdate(t *testing.T) {
|
||||
api, _ := setupTestAPI(t)
|
||||
instanceName := "test-instance"
|
||||
createTestInstance(t, api, instanceName)
|
||||
|
||||
configPath := api.instance.GetInstanceConfigPath(instanceName)
|
||||
|
||||
// Create initial config
|
||||
initialConfig := map[string]interface{}{
|
||||
"domain": "test.com",
|
||||
}
|
||||
initialYAML, _ := yaml.Marshal(initialConfig)
|
||||
if err := storage.WriteFile(configPath, initialYAML, 0644); err != nil {
|
||||
t.Fatalf("Failed to write initial config: %v", err)
|
||||
}
|
||||
|
||||
// Empty update
|
||||
updateData := map[string]interface{}{}
|
||||
updateYAML, _ := yaml.Marshal(updateData)
|
||||
|
||||
req := httptest.NewRequest("PUT", "/api/v1/instances/"+instanceName+"/config", bytes.NewBuffer(updateYAML))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
vars := map[string]string{"name": instanceName}
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
api.UpdateConfig(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("Expected status 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Verify file unchanged
|
||||
resultData, err := storage.ReadFile(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read result: %v", err)
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := yaml.Unmarshal(resultData, &result); err != nil {
|
||||
t.Fatalf("Failed to parse result: %v", err)
|
||||
}
|
||||
|
||||
if result["domain"] != "test.com" {
|
||||
t.Errorf("Expected domain='test.com', got %v", result["domain"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateYAMLFile_YAMLFormatting(t *testing.T) {
|
||||
api, _ := setupTestAPI(t)
|
||||
instanceName := "test-instance"
|
||||
createTestInstance(t, api, instanceName)
|
||||
|
||||
configPath := api.instance.GetInstanceConfigPath(instanceName)
|
||||
|
||||
// Update with complex nested structure
|
||||
updateData := map[string]interface{}{
|
||||
"cloud": map[string]interface{}{
|
||||
"domain": "test.com",
|
||||
"dns": map[string]interface{}{
|
||||
"ip": "1.2.3.4",
|
||||
},
|
||||
},
|
||||
"cluster": map[string]interface{}{
|
||||
"nodes": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "node1",
|
||||
"ip": "10.0.0.1",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"name": "node2",
|
||||
"ip": "10.0.0.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
updateYAML, _ := yaml.Marshal(updateData)
|
||||
|
||||
req := httptest.NewRequest("PUT", "/api/v1/instances/"+instanceName+"/config", bytes.NewBuffer(updateYAML))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
vars := map[string]string{"name": instanceName}
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
api.UpdateConfig(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("Expected status 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Verify YAML formatting
|
||||
resultData, err := storage.ReadFile(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read result: %v", err)
|
||||
}
|
||||
|
||||
resultStr := string(resultData)
|
||||
|
||||
// Should not contain Go map notation
|
||||
if bytes.Contains(resultData, []byte("map[")) {
|
||||
t.Errorf("Result contains Go map notation: %s", resultStr)
|
||||
}
|
||||
|
||||
// Should be valid YAML
|
||||
var result map[string]interface{}
|
||||
if err := yaml.Unmarshal(resultData, &result); err != nil {
|
||||
t.Fatalf("Result is not valid YAML: %v", err)
|
||||
}
|
||||
|
||||
// Should have proper indentation (check for nested structure indicators)
|
||||
if !bytes.Contains(resultData, []byte(" ")) {
|
||||
t.Error("Result appears to lack proper indentation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateYAMLFile_InvalidYAML(t *testing.T) {
|
||||
api, _ := setupTestAPI(t)
|
||||
instanceName := "test-instance"
|
||||
createTestInstance(t, api, instanceName)
|
||||
|
||||
// Send invalid YAML
|
||||
invalidYAML := []byte("invalid: yaml: content: [")
|
||||
|
||||
req := httptest.NewRequest("PUT", "/api/v1/instances/"+instanceName+"/config", bytes.NewBuffer(invalidYAML))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
vars := map[string]string{"name": instanceName}
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
api.UpdateConfig(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("Expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateYAMLFile_InvalidInstance(t *testing.T) {
|
||||
api, _ := setupTestAPI(t)
|
||||
|
||||
updateData := map[string]interface{}{
|
||||
"domain": "test.com",
|
||||
}
|
||||
updateYAML, _ := yaml.Marshal(updateData)
|
||||
|
||||
req := httptest.NewRequest("PUT", "/api/v1/instances/nonexistent/config", bytes.NewBuffer(updateYAML))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
vars := map[string]string{"name": "nonexistent"}
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
api.UpdateConfig(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("Expected status 404, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateYAMLFile_FilePermissions(t *testing.T) {
|
||||
api, _ := setupTestAPI(t)
|
||||
instanceName := "test-instance"
|
||||
createTestInstance(t, api, instanceName)
|
||||
|
||||
configPath := api.instance.GetInstanceConfigPath(instanceName)
|
||||
|
||||
updateData := map[string]interface{}{
|
||||
"domain": "test.com",
|
||||
}
|
||||
updateYAML, _ := yaml.Marshal(updateData)
|
||||
|
||||
req := httptest.NewRequest("PUT", "/api/v1/instances/"+instanceName+"/config", bytes.NewBuffer(updateYAML))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
vars := map[string]string{"name": instanceName}
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
api.UpdateConfig(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("Expected status 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Check file permissions
|
||||
info, err := os.Stat(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to stat config file: %v", err)
|
||||
}
|
||||
|
||||
expectedPerm := os.FileMode(0644)
|
||||
if info.Mode().Perm() != expectedPerm {
|
||||
t.Errorf("Expected permissions %v, got %v", expectedPerm, info.Mode().Perm())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateYAMLFile_UpdateSecrets(t *testing.T) {
|
||||
api, _ := setupTestAPI(t)
|
||||
instanceName := "test-instance"
|
||||
createTestInstance(t, api, instanceName)
|
||||
|
||||
secretsPath := api.instance.GetInstanceSecretsPath(instanceName)
|
||||
|
||||
// Update secrets
|
||||
updateData := map[string]interface{}{
|
||||
"dbPassword": "secret123",
|
||||
"apiKey": "key456",
|
||||
}
|
||||
updateYAML, _ := yaml.Marshal(updateData)
|
||||
|
||||
req := httptest.NewRequest("PUT", "/api/v1/instances/"+instanceName+"/secrets", bytes.NewBuffer(updateYAML))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
vars := map[string]string{"name": instanceName}
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
api.UpdateSecrets(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("Expected status 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Verify secrets file created and contains data
|
||||
resultData, err := storage.ReadFile(secretsPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read secrets: %v", err)
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := yaml.Unmarshal(resultData, &result); err != nil {
|
||||
t.Fatalf("Failed to parse secrets: %v", err)
|
||||
}
|
||||
|
||||
if result["dbPassword"] != "secret123" {
|
||||
t.Errorf("Expected dbPassword='secret123', got %v", result["dbPassword"])
|
||||
}
|
||||
if result["apiKey"] != "key456" {
|
||||
t.Errorf("Expected apiKey='key456', got %v", result["apiKey"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateYAMLFile_ConcurrentUpdates(t *testing.T) {
|
||||
api, _ := setupTestAPI(t)
|
||||
instanceName := "test-instance"
|
||||
createTestInstance(t, api, instanceName)
|
||||
|
||||
// This test verifies that file locking prevents race conditions
|
||||
// We'll simulate concurrent updates and verify data integrity
|
||||
|
||||
numUpdates := 10
|
||||
done := make(chan bool, numUpdates)
|
||||
|
||||
for i := 0; i < numUpdates; i++ {
|
||||
go func(index int) {
|
||||
updateData := map[string]interface{}{
|
||||
"counter": index,
|
||||
}
|
||||
updateYAML, _ := yaml.Marshal(updateData)
|
||||
|
||||
req := httptest.NewRequest("PUT", "/api/v1/instances/"+instanceName+"/config", bytes.NewBuffer(updateYAML))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
vars := map[string]string{"name": instanceName}
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
api.UpdateConfig(w, req)
|
||||
|
||||
done <- w.Code == http.StatusOK
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Wait for all updates to complete
|
||||
successCount := 0
|
||||
for i := 0; i < numUpdates; i++ {
|
||||
if <-done {
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
|
||||
if successCount != numUpdates {
|
||||
t.Errorf("Expected %d successful updates, got %d", numUpdates, successCount)
|
||||
}
|
||||
|
||||
// Verify file is still valid YAML
|
||||
configPath := api.instance.GetInstanceConfigPath(instanceName)
|
||||
resultData, err := storage.ReadFile(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read final config: %v", err)
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := yaml.Unmarshal(resultData, &result); err != nil {
|
||||
t.Fatalf("Final config is not valid YAML: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateYAMLFile_PreservesComplexTypes(t *testing.T) {
|
||||
api, _ := setupTestAPI(t)
|
||||
instanceName := "test-instance"
|
||||
createTestInstance(t, api, instanceName)
|
||||
|
||||
configPath := api.instance.GetInstanceConfigPath(instanceName)
|
||||
|
||||
// Create config with various types
|
||||
updateData := map[string]interface{}{
|
||||
"stringValue": "text",
|
||||
"intValue": 42,
|
||||
"floatValue": 3.14,
|
||||
"boolValue": true,
|
||||
"arrayValue": []interface{}{"a", "b", "c"},
|
||||
"mapValue": map[string]interface{}{
|
||||
"nested": "value",
|
||||
},
|
||||
"nullValue": nil,
|
||||
}
|
||||
updateYAML, _ := yaml.Marshal(updateData)
|
||||
|
||||
req := httptest.NewRequest("PUT", "/api/v1/instances/"+instanceName+"/config", bytes.NewBuffer(updateYAML))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
vars := map[string]string{"name": instanceName}
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
api.UpdateConfig(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("Expected status 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Verify types preserved
|
||||
resultData, err := storage.ReadFile(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read result: %v", err)
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := yaml.Unmarshal(resultData, &result); err != nil {
|
||||
t.Fatalf("Failed to parse result: %v", err)
|
||||
}
|
||||
|
||||
if result["stringValue"] != "text" {
|
||||
t.Errorf("String value not preserved: %v", result["stringValue"])
|
||||
}
|
||||
if result["intValue"] != 42 {
|
||||
t.Errorf("Int value not preserved: %v", result["intValue"])
|
||||
}
|
||||
if result["floatValue"] != 3.14 {
|
||||
t.Errorf("Float value not preserved: %v", result["floatValue"])
|
||||
}
|
||||
if result["boolValue"] != true {
|
||||
t.Errorf("Bool value not preserved: %v", result["boolValue"])
|
||||
}
|
||||
|
||||
arrayValue, ok := result["arrayValue"].([]interface{})
|
||||
if !ok {
|
||||
t.Errorf("Array not preserved as slice: %T", result["arrayValue"])
|
||||
} else if len(arrayValue) != 3 {
|
||||
t.Errorf("Array length not preserved: %d", len(arrayValue))
|
||||
}
|
||||
|
||||
mapValue, ok := result["mapValue"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Errorf("Map not preserved: %T", result["mapValue"])
|
||||
} else if mapValue["nested"] != "value" {
|
||||
t.Errorf("Nested map value not preserved: %v", mapValue["nested"])
|
||||
}
|
||||
|
||||
if result["nullValue"] != nil {
|
||||
t.Errorf("Null value not preserved: %v", result["nullValue"])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user