Pulse/internal/config/persistence_relay_test.go
rcourtman b69c8c8007 Wire PULSE_RELAY_ENABLED and PULSE_RELAY_SERVER as real env overrides
These two env vars were documented as relay overrides in v6 docs since
March 18 (CONFIGURATION.md, RELAY.md, and the frontend-served doc copy)
but no code ever read them. Operators trying to bootstrap relay headlessly
saw no effect.

Implement them rather than remove the documentation. Headless and
container deployments now have a real path to enable relay and point it
at a private endpoint without going through Settings → Relay.

internal/relay/config_env.go:
  - ApplyEnvOverrides(*Config) mutates relay.Config in place.
  - PULSE_RELAY_ENABLED accepts true/false/yes/no/1/0/on/off (case-
    insensitive). Unrecognized values log a warning and leave the file
    value untouched — important so "unset" reads differently from
    "explicit false."
  - PULSE_RELAY_SERVER goes through the existing validateRelayServerURL
    check; invalid URLs log a warning and fall through.

internal/config/persistence_relay.go:
  LoadRelayConfig calls ApplyEnvOverrides after the file load and after
  the default-fallback when relay.enc is absent, so the env override
  applies on every load.

Tests cover unset / true / false / garbage-bool / valid-URL / invalid-URL
/ both-together / nil-config paths in the relay package, plus two
end-to-end tests in internal/config that prove the override flows through
LoadRelayConfig against a real persisted file and against the
missing-file default branch.

Restore the env-var docs with the correct default URL (the full
wss://relay.pulserelay.pro/ws/instance, not the bare hostname the
original aspirational table claimed) and add an explicit precedence note:
saving from the UI after an env override persists the env-effective state
to disk, so clearing the env alone does not revert.

Add internal/relay/config_env_test.go to the relay-runtime registry's
desktop-relay-runtime exact_files so the new code surface is proof-tracked.
Update the matching pin in subsystem_lookup_test.py. Extend the
relay-runtime contract Extension Point 3 to document the override
semantics LoadRelayConfig must satisfy.
2026-05-12 11:18:31 +01:00

117 lines
3.5 KiB
Go

package config
import (
"bytes"
"encoding/json"
"os"
"path/filepath"
"testing"
"github.com/rcourtman/pulse-go-rewrite/internal/relay"
)
func TestConfigPersistenceLoadRelayConfigMissingFileReturnsDefault(t *testing.T) {
cp := NewConfigPersistence(t.TempDir())
cfg, err := cp.LoadRelayConfig()
if err != nil {
t.Fatalf("LoadRelayConfig() error = %v", err)
}
if cfg == nil {
t.Fatal("LoadRelayConfig() returned nil config")
}
if cfg.Enabled {
t.Fatalf("LoadRelayConfig() enabled = true, want false")
}
if cfg.ServerURL != relay.DefaultServerURL {
t.Fatalf("LoadRelayConfig() server_url = %q, want %q", cfg.ServerURL, relay.DefaultServerURL)
}
}
func TestConfigPersistenceLoadRelayConfigMigratesPlaintextFile(t *testing.T) {
tempDir := t.TempDir()
cp := NewConfigPersistence(tempDir)
plaintext := &relay.Config{
Enabled: true,
ServerURL: "wss://relay.example.com/ws/instance",
InstanceSecret: "relay-instance-secret",
IdentityPrivateKey: "private-key",
IdentityPublicKey: "public-key",
IdentityFingerprint: "fingerprint",
}
raw, err := json.Marshal(plaintext)
if err != nil {
t.Fatalf("marshal plaintext relay config: %v", err)
}
filePath := filepath.Join(tempDir, "relay.enc")
if err := os.WriteFile(filePath, raw, 0o600); err != nil {
t.Fatalf("write plaintext relay config: %v", err)
}
cfg, err := cp.LoadRelayConfig()
if err != nil {
t.Fatalf("LoadRelayConfig() error = %v", err)
}
if cfg == nil {
t.Fatal("LoadRelayConfig() returned nil config")
}
if cfg.InstanceSecret != plaintext.InstanceSecret {
t.Fatalf("LoadRelayConfig() instance_secret = %q, want %q", cfg.InstanceSecret, plaintext.InstanceSecret)
}
if cfg.IdentityPrivateKey != plaintext.IdentityPrivateKey {
t.Fatalf("LoadRelayConfig() identity_private_key = %q, want %q", cfg.IdentityPrivateKey, plaintext.IdentityPrivateKey)
}
rewritten, err := os.ReadFile(filePath)
if err != nil {
t.Fatalf("read rewritten relay config: %v", err)
}
if bytes.Equal(rewritten, raw) {
t.Fatal("expected plaintext relay config to be rewritten encrypted")
}
}
func TestConfigPersistenceLoadRelayConfigAppliesEnvOverrideOverFile(t *testing.T) {
tempDir := t.TempDir()
cp := NewConfigPersistence(tempDir)
// File says disabled with the default URL.
if err := cp.SaveRelayConfig(*relay.DefaultConfig()); err != nil {
t.Fatalf("SaveRelayConfig() error = %v", err)
}
// Env says enable + redirect to a private endpoint.
t.Setenv(relay.EnvRelayEnabled, "true")
t.Setenv(relay.EnvRelayServerURL, "wss://relay.internal.example/ws/instance")
cfg, err := cp.LoadRelayConfig()
if err != nil {
t.Fatalf("LoadRelayConfig() error = %v", err)
}
if !cfg.Enabled {
t.Fatalf("LoadRelayConfig() enabled = false, want true (env override should beat file)")
}
if cfg.ServerURL != "wss://relay.internal.example/ws/instance" {
t.Fatalf("LoadRelayConfig() server_url = %q, want env override", cfg.ServerURL)
}
}
func TestConfigPersistenceLoadRelayConfigAppliesEnvOverrideWithNoFile(t *testing.T) {
cp := NewConfigPersistence(t.TempDir())
t.Setenv(relay.EnvRelayEnabled, "yes")
t.Setenv(relay.EnvRelayServerURL, "wss://relay.internal.example/ws/instance")
cfg, err := cp.LoadRelayConfig()
if err != nil {
t.Fatalf("LoadRelayConfig() error = %v", err)
}
if !cfg.Enabled {
t.Fatalf("LoadRelayConfig() enabled = false, want true (env override on default-when-missing)")
}
if cfg.ServerURL != "wss://relay.internal.example/ws/instance" {
t.Fatalf("LoadRelayConfig() server_url = %q, want env override", cfg.ServerURL)
}
}