summaryrefslogtreecommitdiff
path: root/tests/e2e/config_hotswap_test.go
diff options
context:
space:
mode:
authorJames O'Doherty <james@theodohertyfamily.com>2026-05-29 20:35:31 -0400
committerJames O'Doherty <james@theodohertyfamily.com>2026-05-29 20:35:31 -0400
commitd4cec92f5690a60b3509ab718bdea72dc520110e (patch)
treeb29218a4fee4bbf3b2f4bf25a161f2a74bb98b85 /tests/e2e/config_hotswap_test.go
parent4ddd0d2ffc7073f2d55ffb6777e3a168af0051f0 (diff)
feat: implement robust namespace lifecycle and resilience suite
- Replace marker-file pinning with kernel bind-mount anchors for reliable namespace persistence. - Implement atomic "last-man-out" cleanup sequence using ProfileLock, preventing namespace leaks and race conditions. - Add comprehensive resilience test suite covering: - Crash recovery from stale runtime state. - Host network change stability. - Configuration hot-swap session persistence. - Resource exhaustion and high-churn lifecycle stress. - Align documentation and test expectations with rootless session-based persistence. - Fix argument integrity and isolation leaks. - Ensure 100% pass rate for all E2E and integration tests.
Diffstat (limited to 'tests/e2e/config_hotswap_test.go')
-rw-r--r--tests/e2e/config_hotswap_test.go88
1 files changed, 88 insertions, 0 deletions
diff --git a/tests/e2e/config_hotswap_test.go b/tests/e2e/config_hotswap_test.go
new file mode 100644
index 0000000..6dfcec5
--- /dev/null
+++ b/tests/e2e/config_hotswap_test.go
@@ -0,0 +1,88 @@
+package e2e
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+// TestConfigHotSwap verifies that changing the configuration file on disk
+// does not affect an active session. A process joining an existing session
+// should use the established tunnel's state, not the updated file.
+func TestConfigHotSwap(t *testing.T) {
+ binaryPath, err := GetBinaryPath()
+ if err != nil {
+ t.Skipf("Skipping test: %v", err)
+ }
+
+ tmpRuntimeDir := t.TempDir()
+ tmpConfigDir := t.TempDir()
+ profile := "hotswap-test"
+
+ profilesDir := filepath.Join(tmpConfigDir, "wg-wrap", "profiles")
+ if err := os.MkdirAll(profilesDir, 0755); err != nil {
+ t.Fatal(err)
+ }
+ profileConfPath := filepath.Join(profilesDir, profile+".conf")
+
+ // 1. Initial configuration
+ conf1 := `[Interface]
+Address = 10.0.0.2/24
+PrivateKey = 0000000000000000000000000000000000000000000000000000000000000000
+[Peer]
+PublicKey = 0000000000000000000000000000000000000000000000000000000000000000
+AllowedIPs = 0.0.0.0/0
+Endpoint = 1.1.1.1:51820
+`
+ if err := os.WriteFile(profileConfPath, []byte(conf1), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ // Start a process to establish the session
+ cmdA := exec.Command(binaryPath, "--profile", profile, "--", "sleep", "5")
+ cmdA.Env = append(os.Environ(),
+ fmt.Sprintf("XDG_RUNTIME_DIR=%s", tmpRuntimeDir),
+ fmt.Sprintf("XDG_CONFIG_HOME=%s", tmpConfigDir),
+ )
+ if err := cmdA.Start(); err != nil {
+ t.Fatalf("Failed to start Process A: %v", err)
+ }
+ defer func() { _ = cmdA.Process.Kill() }()
+
+ pidsDir := filepath.Join(tmpRuntimeDir, "profiles", profile, "pids")
+ waitForPids(t, pidsDir, 1)
+
+ // 2. "Hot-Swap" the configuration file while the tunnel is active.
+ // We change the endpoint to something obviously different.
+ conf2 := `[Interface]
+Address = 10.0.0.2/24
+PrivateKey = 0000000000000000000000000000000000000000000000000000000000000000
+[Peer]
+PublicKey = 0000000000000000000000000000000000000000000000000000000000000000
+AllowedIPs = 0.0.0.0/0
+Endpoint = 8.8.8.8:51820
+`
+ if err := os.WriteFile(profileConfPath, []byte(conf2), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ // 3. Launch a second process. It should join the existing session
+ // regardless of the fact that the .conf file has changed.
+ cmdB := exec.Command(binaryPath, "--profile", profile, "--", "ls")
+ cmdB.Env = append(os.Environ(),
+ fmt.Sprintf("XDG_RUNTIME_DIR=%s", tmpRuntimeDir),
+ fmt.Sprintf("XDG_CONFIG_HOME=%s", tmpConfigDir),
+ )
+
+ out, err := cmdB.CombinedOutput()
+ if err != nil {
+ t.Fatalf("Process B failed to join session after config change: %v\nOutput: %s", err, string(out))
+ }
+
+ if !strings.Contains(string(out), "Joining active WireGuard tunnel") {
+ t.Errorf("Expected Process B to join active tunnel, but it re-initialized. Output: %s", string(out))
+ }
+}