package main import ( "net/url" "os" "os/exec" "strings" "testing" ) // Closes R-005 for gateway: cmd-level test for mustParseUpstream. // The proxy mounts themselves are exercised end-to-end by the // proof harness's GOLAKE-003 case (gateway proxy passthrough). // // mustParseUpstream calls os.Exit on bad input — testing it directly // would kill the test process. The standard Go pattern for testing // os.Exit-calling code: re-exec the test binary with a flag and // observe the subprocess exit status. We exercise the helper that // way for the failure paths and inline-check the success path. func TestMustParseUpstream_HappyPaths(t *testing.T) { // Success paths can be exercised inline — only failure exits. cases := []string{ "http://127.0.0.1:3211", "https://example.com:443", "http://catalogd:3212", } for _, raw := range cases { t.Run(raw, func(t *testing.T) { u := mustParseUpstream("test", raw) if u.Scheme == "" || u.Host == "" { t.Errorf("mustParseUpstream(%q) returned empty scheme/host: %+v", raw, u) } }) } } func TestMustParseUpstream_FailureExits(t *testing.T) { if os.Getenv("GATEWAY_TEST_EXIT") == "1" { // Subprocess: invoke mustParseUpstream with a bad value; // expect os.Exit(1). url.Parse is permissive — schemes can // be missing without a parse error — so the assertion in // mustParseUpstream catches the empty-Host case. mustParseUpstream("storaged_url", "127.0.0.1:3211") // If we reach here, the function failed to fail. os.Exit(0) } cmd := exec.Command(os.Args[0], "-test.run=TestMustParseUpstream_FailureExits") cmd.Env = append(os.Environ(), "GATEWAY_TEST_EXIT=1") err := cmd.Run() if err == nil { t.Fatal("expected subprocess to exit non-zero on bad upstream URL") } exitErr, ok := err.(*exec.ExitError) if !ok { t.Fatalf("expected ExitError, got %T: %v", err, err) } if exitErr.ExitCode() == 0 { t.Fatal("subprocess returned 0 — mustParseUpstream did not fail") } } func TestMustParseUpstream_GarbageInput_Exits(t *testing.T) { if os.Getenv("GATEWAY_TEST_EXIT_GARBAGE") == "1" { mustParseUpstream("queryd_url", "https://%zz") os.Exit(0) } cmd := exec.Command(os.Args[0], "-test.run=TestMustParseUpstream_GarbageInput_Exits") cmd.Env = append(os.Environ(), "GATEWAY_TEST_EXIT_GARBAGE=1") err := cmd.Run() if err == nil { t.Fatal("expected subprocess to exit non-zero on garbage URL") } } // TestUpstreamConfigKeys documents the upstream config field names // the gateway expects. A future refactor that renames a field would // fail this test; operators eyeballing systemctl status see the // failure before traffic does. func TestUpstreamConfigKeys_DocumentedShape(t *testing.T) { // This test is shape documentation. main() iterates a map with // these exact keys; if any are renamed, all gateway deployments // silently break. expected := []string{ "storaged_url", "catalogd_url", "ingestd_url", "queryd_url", "vectord_url", "embedd_url", } for _, k := range expected { if !strings.HasSuffix(k, "_url") { t.Errorf("upstream key %q does not end in _url — convention break", k) } if _, err := url.Parse("http://" + k); err != nil { t.Errorf("key %q failed url-test parse: %v", k, err) } } }