mirror of
https://github.com/lukaszraczylo/kportal.git
synced 2026-06-11 00:09:31 +00:00
96ae1d45e0
- [x] Add golangci-lint configuration with gocritic ifElseChain disabled
- [x] Rename error variables to avoid shadowing (createErr, watcherErr, watchErr, etc.)
- [x] Replace `interface{}` with `any` type alias throughout codebase
- [x] Add package-level documentation comments to all internal packages
- [x] Reorder struct fields alphabetically for consistency
- [x] Extract UI constants (terminal dimensions, column widths, colors) to constants.go
- [x] Refactor BubbleTeaUI main view rendering into smaller helper functions
- [x] Simplify nested conditionals and improve code clarity
- [x] Add `isForwardDisabled()` helper method to BubbleTeaUI
- [x] Update file permissions from 0644 to 0600 in config tests
- [x] Add `#nosec` comments and error suppression where appropriate
- [x] Improve test table struct field ordering for readability
- [x] Fix resource parsing in AddForward using strings.SplitN
- [x] Add comprehensive tests for new UI helper functions and constants
665 lines
17 KiB
Go
665 lines
17 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestNewMutator tests mutator creation
|
|
func TestNewMutator(t *testing.T) {
|
|
mutator := NewMutator("/path/to/config.yaml")
|
|
assert.NotNil(t, mutator)
|
|
assert.Equal(t, "/path/to/config.yaml", mutator.configPath)
|
|
}
|
|
|
|
// TestMutator_AddForward_NewFile tests adding a forward to a new file
|
|
// Note: Due to how LoadConfig wraps errors, os.IsNotExist check in AddForward
|
|
// doesn't work with wrapped errors. This documents the current behavior.
|
|
func TestMutator_AddForward_NewFile(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
|
|
|
mutator := NewMutator(configPath)
|
|
|
|
fwd := Forward{
|
|
Resource: "pod/my-app",
|
|
Protocol: "tcp",
|
|
Port: 8080,
|
|
LocalPort: 8080,
|
|
}
|
|
|
|
// Currently fails because LoadConfig wraps the error and os.IsNotExist doesn't match
|
|
err := mutator.AddForward("dev-cluster", "default", fwd)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to load config")
|
|
}
|
|
|
|
// TestMutator_AddForward_EmptyFile tests adding a forward to an empty file
|
|
func TestMutator_AddForward_EmptyFile(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
|
|
|
// Create empty config file with minimal valid structure
|
|
initial := `contexts: []
|
|
`
|
|
err := os.WriteFile(configPath, []byte(initial), 0600)
|
|
require.NoError(t, err)
|
|
|
|
mutator := NewMutator(configPath)
|
|
|
|
fwd := Forward{
|
|
Resource: "pod/my-app",
|
|
Protocol: "tcp",
|
|
Port: 8080,
|
|
LocalPort: 8080,
|
|
}
|
|
|
|
err = mutator.AddForward("dev-cluster", "default", fwd)
|
|
require.NoError(t, err)
|
|
|
|
// Verify file was updated and contains the forward
|
|
cfg, err := LoadConfig(configPath)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, cfg)
|
|
|
|
assert.Len(t, cfg.Contexts, 1)
|
|
assert.Equal(t, "dev-cluster", cfg.Contexts[0].Name)
|
|
assert.Len(t, cfg.Contexts[0].Namespaces, 1)
|
|
assert.Equal(t, "default", cfg.Contexts[0].Namespaces[0].Name)
|
|
assert.Len(t, cfg.Contexts[0].Namespaces[0].Forwards, 1)
|
|
assert.Equal(t, "pod/my-app", cfg.Contexts[0].Namespaces[0].Forwards[0].Resource)
|
|
}
|
|
|
|
// TestMutator_AddForward_ExistingFile tests adding to existing config
|
|
func TestMutator_AddForward_ExistingFile(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
|
|
|
// Create initial config
|
|
initial := `contexts:
|
|
- name: dev-cluster
|
|
namespaces:
|
|
- name: default
|
|
forwards:
|
|
- resource: pod/existing-app
|
|
protocol: tcp
|
|
port: 8080
|
|
localPort: 8080
|
|
`
|
|
err := os.WriteFile(configPath, []byte(initial), 0600)
|
|
require.NoError(t, err)
|
|
|
|
mutator := NewMutator(configPath)
|
|
|
|
fwd := Forward{
|
|
Resource: "service/postgres",
|
|
Protocol: "tcp",
|
|
Port: 5432,
|
|
LocalPort: 5432,
|
|
}
|
|
|
|
err = mutator.AddForward("dev-cluster", "default", fwd)
|
|
require.NoError(t, err)
|
|
|
|
// Verify both forwards exist
|
|
cfg, err := LoadConfig(configPath)
|
|
require.NoError(t, err)
|
|
|
|
assert.Len(t, cfg.Contexts[0].Namespaces[0].Forwards, 2)
|
|
}
|
|
|
|
// TestMutator_AddForward_NewContext tests adding to new context
|
|
func TestMutator_AddForward_NewContext(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
|
|
|
// Create initial config
|
|
initial := `contexts:
|
|
- name: dev-cluster
|
|
namespaces:
|
|
- name: default
|
|
forwards:
|
|
- resource: pod/app
|
|
protocol: tcp
|
|
port: 8080
|
|
localPort: 8080
|
|
`
|
|
err := os.WriteFile(configPath, []byte(initial), 0600)
|
|
require.NoError(t, err)
|
|
|
|
mutator := NewMutator(configPath)
|
|
|
|
fwd := Forward{
|
|
Resource: "pod/prod-app",
|
|
Protocol: "tcp",
|
|
Port: 80,
|
|
LocalPort: 8081,
|
|
}
|
|
|
|
err = mutator.AddForward("prod-cluster", "production", fwd)
|
|
require.NoError(t, err)
|
|
|
|
// Verify new context was created
|
|
cfg, err := LoadConfig(configPath)
|
|
require.NoError(t, err)
|
|
|
|
assert.Len(t, cfg.Contexts, 2)
|
|
assert.Equal(t, "prod-cluster", cfg.Contexts[1].Name)
|
|
}
|
|
|
|
// TestMutator_AddForward_DuplicatePort tests rejecting duplicate ports
|
|
func TestMutator_AddForward_DuplicatePort(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
|
|
|
// Create initial config
|
|
initial := `contexts:
|
|
- name: dev-cluster
|
|
namespaces:
|
|
- name: default
|
|
forwards:
|
|
- resource: pod/app
|
|
protocol: tcp
|
|
port: 8080
|
|
localPort: 8080
|
|
`
|
|
err := os.WriteFile(configPath, []byte(initial), 0600)
|
|
require.NoError(t, err)
|
|
|
|
mutator := NewMutator(configPath)
|
|
|
|
fwd := Forward{
|
|
Resource: "pod/another-app",
|
|
Protocol: "tcp",
|
|
Port: 9090,
|
|
LocalPort: 8080, // Duplicate local port
|
|
}
|
|
|
|
err = mutator.AddForward("dev-cluster", "default", fwd)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "port 8080 is already in use")
|
|
}
|
|
|
|
// TestMutator_AddForward_InvalidForward tests rejecting invalid forward
|
|
func TestMutator_AddForward_InvalidForward(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
|
|
|
// Create initial config file
|
|
initial := `contexts:
|
|
- name: dev-cluster
|
|
namespaces:
|
|
- name: default
|
|
forwards:
|
|
- resource: pod/existing-app
|
|
protocol: tcp
|
|
port: 8080
|
|
localPort: 8080
|
|
`
|
|
err := os.WriteFile(configPath, []byte(initial), 0600)
|
|
require.NoError(t, err)
|
|
|
|
mutator := NewMutator(configPath)
|
|
|
|
fwd := Forward{
|
|
Resource: "invalid/type/resource", // Invalid resource
|
|
Protocol: "tcp",
|
|
Port: 9090,
|
|
LocalPort: 9090,
|
|
}
|
|
|
|
err = mutator.AddForward("dev-cluster", "default", fwd)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "validation failed")
|
|
}
|
|
|
|
// TestMutator_RemoveForwards tests removing forwards by predicate
|
|
func TestMutator_RemoveForwards(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
|
|
|
// Create initial config with multiple forwards
|
|
initial := `contexts:
|
|
- name: dev-cluster
|
|
namespaces:
|
|
- name: default
|
|
forwards:
|
|
- resource: pod/app1
|
|
protocol: tcp
|
|
port: 8080
|
|
localPort: 8080
|
|
- resource: pod/app2
|
|
protocol: tcp
|
|
port: 8081
|
|
localPort: 8081
|
|
- resource: service/postgres
|
|
protocol: tcp
|
|
port: 5432
|
|
localPort: 5432
|
|
`
|
|
err := os.WriteFile(configPath, []byte(initial), 0600)
|
|
require.NoError(t, err)
|
|
|
|
mutator := NewMutator(configPath)
|
|
|
|
// Remove all pod resources
|
|
err = mutator.RemoveForwards(func(ctx, ns string, fwd Forward) bool {
|
|
return fwd.Resource == "pod/app1"
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Verify the forward was removed
|
|
cfg, err := LoadConfig(configPath)
|
|
require.NoError(t, err)
|
|
|
|
assert.Len(t, cfg.Contexts[0].Namespaces[0].Forwards, 2)
|
|
for _, fwd := range cfg.Contexts[0].Namespaces[0].Forwards {
|
|
assert.NotEqual(t, "pod/app1", fwd.Resource)
|
|
}
|
|
}
|
|
|
|
// TestMutator_RemoveForwards_RemovesEmptyNamespaces tests that empty namespaces are removed
|
|
func TestMutator_RemoveForwards_RemovesEmptyNamespaces(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
|
|
|
// Create config with two namespaces
|
|
initial := `contexts:
|
|
- name: dev-cluster
|
|
namespaces:
|
|
- name: ns1
|
|
forwards:
|
|
- resource: pod/app1
|
|
protocol: tcp
|
|
port: 8080
|
|
localPort: 8080
|
|
- name: ns2
|
|
forwards:
|
|
- resource: pod/app2
|
|
protocol: tcp
|
|
port: 8081
|
|
localPort: 8081
|
|
`
|
|
err := os.WriteFile(configPath, []byte(initial), 0600)
|
|
require.NoError(t, err)
|
|
|
|
mutator := NewMutator(configPath)
|
|
|
|
// Remove all forwards from ns1
|
|
err = mutator.RemoveForwards(func(ctx, ns string, fwd Forward) bool {
|
|
return ns == "ns1"
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Verify ns1 was removed (has no forwards left)
|
|
cfg, err := LoadConfig(configPath)
|
|
require.NoError(t, err)
|
|
|
|
assert.Len(t, cfg.Contexts[0].Namespaces, 1)
|
|
assert.Equal(t, "ns2", cfg.Contexts[0].Namespaces[0].Name)
|
|
}
|
|
|
|
// TestMutator_RemoveForwards_NonExistentFile tests removing from non-existent file
|
|
func TestMutator_RemoveForwards_NonExistentFile(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
|
|
|
mutator := NewMutator(configPath)
|
|
|
|
err := mutator.RemoveForwards(func(ctx, ns string, fwd Forward) bool {
|
|
return true
|
|
})
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to load config")
|
|
}
|
|
|
|
// TestMutator_RemoveForwardByID tests removing a specific forward by ID
|
|
func TestMutator_RemoveForwardByID(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
|
|
|
// Create initial config
|
|
initial := `contexts:
|
|
- name: dev-cluster
|
|
namespaces:
|
|
- name: default
|
|
forwards:
|
|
- resource: pod/app1
|
|
protocol: tcp
|
|
port: 8080
|
|
localPort: 8080
|
|
- resource: pod/app2
|
|
protocol: tcp
|
|
port: 8081
|
|
localPort: 8081
|
|
`
|
|
err := os.WriteFile(configPath, []byte(initial), 0600)
|
|
require.NoError(t, err)
|
|
|
|
mutator := NewMutator(configPath)
|
|
|
|
// Remove by ID
|
|
err = mutator.RemoveForwardByID("dev-cluster/default/pod/app1:8080")
|
|
require.NoError(t, err)
|
|
|
|
// Verify the forward was removed
|
|
cfg, err := LoadConfig(configPath)
|
|
require.NoError(t, err)
|
|
|
|
assert.Len(t, cfg.Contexts[0].Namespaces[0].Forwards, 1)
|
|
assert.Equal(t, "pod/app2", cfg.Contexts[0].Namespaces[0].Forwards[0].Resource)
|
|
}
|
|
|
|
// TestMutator_UpdateForward tests updating an existing forward
|
|
func TestMutator_UpdateForward(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
|
|
|
// Create initial config
|
|
initial := `contexts:
|
|
- name: dev-cluster
|
|
namespaces:
|
|
- name: default
|
|
forwards:
|
|
- resource: pod/app1
|
|
protocol: tcp
|
|
port: 8080
|
|
localPort: 8080
|
|
`
|
|
err := os.WriteFile(configPath, []byte(initial), 0600)
|
|
require.NoError(t, err)
|
|
|
|
mutator := NewMutator(configPath)
|
|
|
|
newFwd := Forward{
|
|
Resource: "pod/app1-updated",
|
|
Protocol: "tcp",
|
|
Port: 9090,
|
|
LocalPort: 9090,
|
|
}
|
|
|
|
err = mutator.UpdateForward("dev-cluster/default/pod/app1:8080", "dev-cluster", "default", newFwd)
|
|
require.NoError(t, err)
|
|
|
|
// Verify the forward was updated
|
|
cfg, err := LoadConfig(configPath)
|
|
require.NoError(t, err)
|
|
|
|
assert.Len(t, cfg.Contexts[0].Namespaces[0].Forwards, 1)
|
|
assert.Equal(t, "pod/app1-updated", cfg.Contexts[0].Namespaces[0].Forwards[0].Resource)
|
|
assert.Equal(t, 9090, cfg.Contexts[0].Namespaces[0].Forwards[0].LocalPort)
|
|
}
|
|
|
|
// TestMutator_UpdateForward_MoveToNewContext tests moving forward to new context
|
|
func TestMutator_UpdateForward_MoveToNewContext(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
|
|
|
// Create initial config with multiple forwards (so removing one doesn't leave empty namespace)
|
|
initial := `contexts:
|
|
- name: dev-cluster
|
|
namespaces:
|
|
- name: default
|
|
forwards:
|
|
- resource: pod/app1
|
|
protocol: tcp
|
|
port: 8080
|
|
localPort: 8080
|
|
- resource: pod/app2
|
|
protocol: tcp
|
|
port: 9090
|
|
localPort: 9090
|
|
`
|
|
err := os.WriteFile(configPath, []byte(initial), 0600)
|
|
require.NoError(t, err)
|
|
|
|
mutator := NewMutator(configPath)
|
|
|
|
newFwd := Forward{
|
|
Resource: "pod/app1-moved",
|
|
Protocol: "tcp",
|
|
Port: 8080,
|
|
LocalPort: 8080,
|
|
}
|
|
|
|
err = mutator.UpdateForward("dev-cluster/default/pod/app1:8080", "prod-cluster", "production", newFwd)
|
|
require.NoError(t, err)
|
|
|
|
// Verify the forward was moved
|
|
cfg, err := LoadConfig(configPath)
|
|
require.NoError(t, err)
|
|
|
|
// New context should exist with the forward
|
|
assert.Len(t, cfg.Contexts, 2)
|
|
|
|
// Original namespace should still have one forward
|
|
assert.Len(t, cfg.Contexts[0].Namespaces, 1)
|
|
assert.Len(t, cfg.Contexts[0].Namespaces[0].Forwards, 1)
|
|
|
|
// New context should have the moved forward
|
|
assert.Equal(t, "prod-cluster", cfg.Contexts[1].Name)
|
|
assert.Len(t, cfg.Contexts[1].Namespaces, 1)
|
|
assert.Equal(t, "production", cfg.Contexts[1].Namespaces[0].Name)
|
|
}
|
|
|
|
// TestMutator_UpdateForward_NotFound tests updating non-existent forward
|
|
func TestMutator_UpdateForward_NotFound(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
|
|
|
// Create initial config
|
|
initial := `contexts:
|
|
- name: dev-cluster
|
|
namespaces:
|
|
- name: default
|
|
forwards:
|
|
- resource: pod/app
|
|
protocol: tcp
|
|
port: 8080
|
|
localPort: 8080
|
|
`
|
|
err := os.WriteFile(configPath, []byte(initial), 0600)
|
|
require.NoError(t, err)
|
|
|
|
mutator := NewMutator(configPath)
|
|
|
|
newFwd := Forward{
|
|
Resource: "pod/app",
|
|
Protocol: "tcp",
|
|
Port: 8080,
|
|
LocalPort: 8080,
|
|
}
|
|
|
|
err = mutator.UpdateForward("non-existent-id", "dev-cluster", "default", newFwd)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "forward with ID non-existent-id not found")
|
|
}
|
|
|
|
// TestMutator_UpdateForward_DuplicatePort tests rejecting update with duplicate port
|
|
func TestMutator_UpdateForward_DuplicatePort(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
|
|
|
// Create initial config with two forwards
|
|
initial := `contexts:
|
|
- name: dev-cluster
|
|
namespaces:
|
|
- name: default
|
|
forwards:
|
|
- resource: pod/app1
|
|
protocol: tcp
|
|
port: 8080
|
|
localPort: 8080
|
|
- resource: pod/app2
|
|
protocol: tcp
|
|
port: 9090
|
|
localPort: 9090
|
|
`
|
|
err := os.WriteFile(configPath, []byte(initial), 0600)
|
|
require.NoError(t, err)
|
|
|
|
mutator := NewMutator(configPath)
|
|
|
|
// Try to update app1 to use the same port as app2
|
|
newFwd := Forward{
|
|
Resource: "pod/app1-updated",
|
|
Protocol: "tcp",
|
|
Port: 9090,
|
|
LocalPort: 9090, // Duplicate with app2
|
|
}
|
|
|
|
err = mutator.UpdateForward("dev-cluster/default/pod/app1:8080", "dev-cluster", "default", newFwd)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "port 9090 is already in use")
|
|
}
|
|
|
|
// TestMutator_WriteAtomic tests atomic write behavior
|
|
func TestMutator_WriteAtomic(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
|
|
|
mutator := NewMutator(configPath)
|
|
|
|
cfg := &Config{
|
|
Contexts: []Context{
|
|
{
|
|
Name: "test",
|
|
Namespaces: []Namespace{
|
|
{
|
|
Name: "default",
|
|
Forwards: []Forward{
|
|
{Resource: "pod/app", Protocol: "tcp", Port: 8080, LocalPort: 8080},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
err := mutator.writeAtomic(cfg)
|
|
require.NoError(t, err)
|
|
|
|
// Verify file was created with correct permissions
|
|
info, err := os.Stat(configPath)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, os.FileMode(0600), info.Mode().Perm())
|
|
|
|
// Verify temp file was cleaned up
|
|
tmpFile := filepath.Join(tmpDir, ".kportal.yaml.tmp")
|
|
_, err = os.Stat(tmpFile)
|
|
assert.True(t, os.IsNotExist(err))
|
|
}
|
|
|
|
// TestMutator_FindOrCreateContext tests context finding/creation
|
|
func TestMutator_FindOrCreateContext(t *testing.T) {
|
|
mutator := NewMutator("/fake/path")
|
|
|
|
t.Run("find existing context", func(t *testing.T) {
|
|
cfg := &Config{
|
|
Contexts: []Context{
|
|
{Name: "existing"},
|
|
},
|
|
}
|
|
|
|
ctx := mutator.findOrCreateContext(cfg, "existing")
|
|
assert.Equal(t, "existing", ctx.Name)
|
|
assert.Len(t, cfg.Contexts, 1)
|
|
})
|
|
|
|
t.Run("create new context", func(t *testing.T) {
|
|
cfg := &Config{
|
|
Contexts: []Context{
|
|
{Name: "existing"},
|
|
},
|
|
}
|
|
|
|
ctx := mutator.findOrCreateContext(cfg, "new-context")
|
|
assert.Equal(t, "new-context", ctx.Name)
|
|
assert.Len(t, cfg.Contexts, 2)
|
|
})
|
|
}
|
|
|
|
// TestMutator_FindOrCreateNamespace tests namespace finding/creation
|
|
func TestMutator_FindOrCreateNamespace(t *testing.T) {
|
|
mutator := NewMutator("/fake/path")
|
|
|
|
t.Run("find existing namespace", func(t *testing.T) {
|
|
ctx := &Context{
|
|
Name: "test",
|
|
Namespaces: []Namespace{
|
|
{Name: "existing"},
|
|
},
|
|
}
|
|
|
|
ns := mutator.findOrCreateNamespace(ctx, "existing")
|
|
assert.Equal(t, "existing", ns.Name)
|
|
assert.Len(t, ctx.Namespaces, 1)
|
|
})
|
|
|
|
t.Run("create new namespace", func(t *testing.T) {
|
|
ctx := &Context{
|
|
Name: "test",
|
|
Namespaces: []Namespace{
|
|
{Name: "existing"},
|
|
},
|
|
}
|
|
|
|
ns := mutator.findOrCreateNamespace(ctx, "new-namespace")
|
|
assert.Equal(t, "new-namespace", ns.Name)
|
|
assert.Len(t, ctx.Namespaces, 2)
|
|
})
|
|
}
|
|
|
|
// TestMutator_Concurrent tests mutex protection
|
|
func TestMutator_Concurrent(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
|
|
|
// Create initial config file
|
|
initial := `contexts:
|
|
- name: dev
|
|
namespaces:
|
|
- name: default
|
|
forwards:
|
|
- resource: pod/app
|
|
protocol: tcp
|
|
port: 8080
|
|
localPort: 8080
|
|
`
|
|
err := os.WriteFile(configPath, []byte(initial), 0600)
|
|
require.NoError(t, err)
|
|
|
|
mutator := NewMutator(configPath)
|
|
|
|
// Run concurrent operations
|
|
done := make(chan bool, 10)
|
|
for i := 0; i < 10; i++ {
|
|
go func(port int) {
|
|
defer func() { done <- true }()
|
|
fwd := Forward{
|
|
Resource: "pod/app",
|
|
Protocol: "tcp",
|
|
Port: port + 9000,
|
|
LocalPort: port + 9000,
|
|
}
|
|
// Some will succeed, some will fail due to validation
|
|
// The important thing is no race condition
|
|
_ = mutator.AddForward("dev", "default", fwd)
|
|
}(i)
|
|
}
|
|
|
|
// Wait for all goroutines
|
|
for i := 0; i < 10; i++ {
|
|
<-done
|
|
}
|
|
|
|
// Verify config is still valid
|
|
cfg, err := LoadConfig(configPath)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, cfg)
|
|
}
|