| ... | @@ -25,7 +25,7 @@ describe("browser target resolver", () => { | ... | @@ -25,7 +25,7 @@ describe("browser target resolver", () => { |
| 25 | }); | 25 | }); |
| 26 | }); | 26 | }); |
| 27 | | 27 | |
| 28 | it("maps localhost URL navigation onto a remote Tailscale IPv4 host", async () => { | 28 | it("preserves explicit loopback URL navigation for a remote Tailscale environment", async () => { |
| 29 | readPreparedConnection.mockReturnValue({ httpBaseUrl: "http://100.65.180.100:3773" }); | 29 | readPreparedConnection.mockReturnValue({ httpBaseUrl: "http://100.65.180.100:3773" }); |
| 30 | const { resolveBrowserNavigationTarget } = await import("./browserTargetResolver"); | 30 | const { resolveBrowserNavigationTarget } = await import("./browserTargetResolver"); |
| 31 | expect( | 31 | expect( |
| ... | @@ -35,13 +35,29 @@ describe("browser target resolver", () => { | ... | @@ -35,13 +35,29 @@ describe("browser target resolver", () => { |
| 35 | }), | 35 | }), |
| 36 | ).toEqual({ | 36 | ).toEqual({ |
| 37 | requestedUrl: "http://localhost:5173/dashboard?mode=test#results", | 37 | requestedUrl: "http://localhost:5173/dashboard?mode=test#results", |
| 38 | resolvedUrl: "http://100.65.180.100:5173/dashboard?mode=test#results", | 38 | resolvedUrl: "http://localhost:5173/dashboard?mode=test#results", |
| 39 | resolutionKind: "direct-private-network", | 39 | resolutionKind: "direct", |
| 40 | environmentId: "environment-1", | 40 | environmentId: "environment-1", |
| 41 | }); | 41 | }); |
| 42 | }); | 42 | }); |
| 43 | | 43 | |
| 44 | it("preserves URL credentials when mapping localhost onto a remote host", async () => { | 44 | it("preserves explicit IPv4 loopback URL navigation for a private network environment", async () => { |
| | 45 | readPreparedConnection.mockReturnValue({ httpBaseUrl: "http://192.168.1.50:3773" }); |
| | 46 | const { resolveBrowserNavigationTarget } = await import("./browserTargetResolver"); |
| | 47 | expect( |
| | 48 | resolveBrowserNavigationTarget(EnvironmentId.make("environment-1"), { |
| | 49 | kind: "url", |
| | 50 | url: "http://127.0.0.1:5999/", |
| | 51 | }), |
| | 52 | ).toEqual({ |
| | 53 | requestedUrl: "http://127.0.0.1:5999/", |
| | 54 | resolvedUrl: "http://127.0.0.1:5999/", |
| | 55 | resolutionKind: "direct", |
| | 56 | environmentId: "environment-1", |
| | 57 | }); |
| | 58 | }); |
| | 59 | |
| | 60 | it("preserves URL credentials on explicit loopback navigation", async () => { |
| 45 | readPreparedConnection.mockReturnValue({ httpBaseUrl: "http://100.65.180.100:3773" }); | 61 | readPreparedConnection.mockReturnValue({ httpBaseUrl: "http://100.65.180.100:3773" }); |
| 46 | const { resolveBrowserNavigationTarget } = await import("./browserTargetResolver"); | 62 | const { resolveBrowserNavigationTarget } = await import("./browserTargetResolver"); |
| 47 | expect( | 63 | expect( |
| ... | @@ -49,10 +65,10 @@ describe("browser target resolver", () => { | ... | @@ -49,10 +65,10 @@ describe("browser target resolver", () => { |
| 49 | kind: "url", | 65 | kind: "url", |
| 50 | url: "http://user:p%40ss@localhost:5173/dashboard", | 66 | url: "http://user:p%40ss@localhost:5173/dashboard", |
| 51 | }).resolvedUrl, | 67 | }).resolvedUrl, |
| 52 | ).toBe("http://user:p%40ss@100.65.180.100:5173/dashboard"); | 68 | ).toBe("http://user:p%40ss@localhost:5173/dashboard"); |
| 53 | }); | 69 | }); |
| 54 | | 70 | |
| 55 | it("maps credentialed localhost URLs onto private IPv6 hosts", async () => { | 71 | it("preserves credentialed loopback URLs for private IPv6 environments", async () => { |
| 56 | readPreparedConnection.mockReturnValue({ | 72 | readPreparedConnection.mockReturnValue({ |
| 57 | httpBaseUrl: "http://[fd7a:115c:a1e0::53]:3773", | 73 | httpBaseUrl: "http://[fd7a:115c:a1e0::53]:3773", |
| 58 | }); | 74 | }); |
| ... | @@ -62,10 +78,10 @@ describe("browser target resolver", () => { | ... | @@ -62,10 +78,10 @@ describe("browser target resolver", () => { |
| 62 | kind: "url", | 78 | kind: "url", |
| 63 | url: "http://user:p%40ss@localhost:5173/dashboard?mode=test#results", | 79 | url: "http://user:p%40ss@localhost:5173/dashboard?mode=test#results", |
| 64 | }).resolvedUrl, | 80 | }).resolvedUrl, |
| 65 | ).toBe("http://user:p%40ss@[fd7a:115c:a1e0::53]:5173/dashboard?mode=test#results"); | 81 | ).toBe("http://user:p%40ss@localhost:5173/dashboard?mode=test#results"); |
| 66 | }); | 82 | }); |
| 67 | | 83 | |
| 68 | it("maps schemeless localhost navigation onto a remote environment host", async () => { | 84 | it("preserves schemeless localhost navigation for a remote environment", async () => { |
| 69 | readPreparedConnection.mockReturnValue({ httpBaseUrl: "http://192.168.1.25:3773" }); | 85 | readPreparedConnection.mockReturnValue({ httpBaseUrl: "http://192.168.1.25:3773" }); |
| 70 | const { resolveBrowserNavigationTarget } = await import("./browserTargetResolver"); | 86 | const { resolveBrowserNavigationTarget } = await import("./browserTargetResolver"); |
| 71 | expect( | 87 | expect( |
| ... | @@ -73,7 +89,7 @@ describe("browser target resolver", () => { | ... | @@ -73,7 +89,7 @@ describe("browser target resolver", () => { |
| 73 | kind: "url", | 89 | kind: "url", |
| 74 | url: "localhost:3000/app", | 90 | url: "localhost:3000/app", |
| 75 | }).resolvedUrl, | 91 | }).resolvedUrl, |
| 76 | ).toBe("http://192.168.1.25:3000/app"); | 92 | ).toBe("localhost:3000/app"); |
| 77 | }); | 93 | }); |
| 78 | | 94 | |
| 79 | it("keeps localhost navigation local for a local environment", async () => { | 95 | it("keeps localhost navigation local for a local environment", async () => { |
| ... | @@ -117,12 +133,12 @@ describe("browser target resolver", () => { | ... | @@ -117,12 +133,12 @@ describe("browser target resolver", () => { |
| 117 | port: 5173, | 133 | port: 5173, |
| 118 | }), | 134 | }), |
| 119 | ).toThrow(/authenticated preview gateway/); | 135 | ).toThrow(/authenticated preview gateway/); |
| 120 | expect(() => | 136 | expect( |
| 121 | resolveBrowserNavigationTarget(EnvironmentId.make("environment-1"), { | 137 | resolveBrowserNavigationTarget(EnvironmentId.make("environment-1"), { |
| 122 | kind: "url", | 138 | kind: "url", |
| 123 | url: "http://localhost:5173", | 139 | url: "http://localhost:5173", |
| 124 | }), | 140 | }), |
| 125 | ).toThrow(/authenticated preview gateway/); | 141 | ).toMatchObject({ resolvedUrl: "http://localhost:5173", resolutionKind: "direct" }); |
| 126 | }); | 142 | }); |
| 127 | | 143 | |
| 128 | it("normalizes schemeless localhost server-picker values", async () => { | 144 | it("normalizes schemeless localhost server-picker values", async () => { |
| ... | @@ -136,6 +152,14 @@ describe("browser target resolver", () => { | ... | @@ -136,6 +152,14 @@ describe("browser target resolver", () => { |
| 136 | ).toBe("http://localhost:3000/app"); | 152 | ).toBe("http://localhost:3000/app"); |
| 137 | }); | 153 | }); |
| 138 | | 154 | |
| | 155 | it("maps discovered loopback servers onto a remote environment host", async () => { |
| | 156 | readPreparedConnection.mockReturnValue({ httpBaseUrl: "http://192.168.1.25:3773" }); |
| | 157 | const { resolveDiscoveredServerUrl } = await import("./browserTargetResolver"); |
| | 158 | expect( |
| | 159 | resolveDiscoveredServerUrl(EnvironmentId.make("environment-1"), "localhost:3000/app"), |
| | 160 | ).toBe("http://192.168.1.25:3000/app"); |
| | 161 | }); |
| | 162 | |
| 139 | it("preserves localhost server-picker values when the prepared base is 127.0.0.1", async () => { | 163 | it("preserves localhost server-picker values when the prepared base is 127.0.0.1", async () => { |
| 140 | readPreparedConnection.mockReturnValue({ httpBaseUrl: "http://127.0.0.1:3773" }); | 164 | readPreparedConnection.mockReturnValue({ httpBaseUrl: "http://127.0.0.1:3773" }); |
| 141 | const { resolveDiscoveredServerUrl } = await import("./browserTargetResolver"); | 165 | const { resolveDiscoveredServerUrl } = await import("./browserTargetResolver"); |