
ConnectTest.pudu
Pudu70 lines4.7 KB
1/** @Test.Transport.Connect.Suite — routes chosen and connections opened through a proxy */2module PuduLangHttpClient.Transport.ConnectTest34import Std.Concurrent.Cancel as Cancel5import Std.Io as Io6import Std.Net as Net7import Std.Result as Result8import Std.Sync as Sync9import Std.Test as Test10import PuduLangHttpClient.Domain.Uri as Uri11import PuduLangHttpClient as HttpClient12import PuduLangHttpClient.Proxy as Proxy13import PuduLangHttpClient.Request as Request14import PuduLangHttpClient.Response as Response15import PuduLangHttpClient.Transport.Connect as Connect16import PuduLangHttpClient.Transport as Transport17import Support.Loopback as Loopback181920fn endpoint(uri: Str) -> Uri.Endpoint {21 match Uri.endpointOf(uri) {22 case Some(found) => found23 case None => panic("no endpoint: " + uri)24 }25}262728fn main() -> Int {29 let seen = Sync.cell([])30 let proxyServer = Loopback.scripted(fn(connection: Net.Connection, _number: Int) -> () {31 var going = true32 while going {33 let request = Loopback.readRequest(&connection)34 if request.isEmpty() {35 going = false36 } else {37 let _kept = Sync.set(&seen, Result.unwrapOr(Sync.get(&seen), []).push(request))38 if request.startsWith("CONNECT ") { Loopback.write(&connection, "HTTP/1.1 403 Forbidden\r\ncontent-length: 0\r\n\r\n") } else { Loopback.write(&connection, "HTTP/1.1 200 OK\r\ncontent-length: 7\r\n\r\nproxied") }39 }40 }41 })42 let proxy = Proxy.at(proxyServer.base).withCredentials("user", "secret").bypassing(&["*.internal.test", "direct.test"])43 let through = Transport.create(Transport.defaults().withProxy(proxy))44 let plain = Transport.send(&through)(Request.get("http://catalog.test/items?page=2"), Cancel.expiring(2000))45 let secured = Transport.send(&through)(Request.get("https://secure.test/"), Cancel.expiring(2000))46 let requests = Result.unwrapOr(Sync.get(&seen), [])47 Loopback.close(&proxyServer)48 let direct = Connect.routeTo(&endpoint("http://a.internal.test/"), &Some(proxy))49 let checks = Test.suite("Transport.Connect", &[50 Test.equals("a plain request through a proxy answers the proxy's response", &Result.map(plain, |r: Response.Response| Result.unwrapOr(Response.text(&r), "")), &Ok("proxied")),51 Test.that("a plain request names its absolute target", requests.length() > 0 && requests[0].startsWith("GET http://catalog.test/items?page=2 HTTP/1.1\r\n")),52 Test.that("a plain request carries the proxy's credentials", requests.length() > 0 && requests[0].contains("proxy-authorization: Basic dXNlcjpzZWNyZXQ=")),53 Test.that("a secured request asks the proxy for a tunnel with credentials", requests.length() > 1 && requests[1].startsWith("CONNECT secure.test:443 HTTP/1.1\r\n") && requests[1].contains("proxy-authorization: Basic")),54 Test.equals("a refused tunnel is a tunnel failure", &Result.map(secured, |r: Response.Response| r.status.code), &Err(HttpClient.ProxyTunnel("status 403"))),55 Test.equals("a listed suffix goes direct", &(direct.via == None, direct.key), &(true, "direct http://a.internal.test")),56 Test.equals("a secured target goes through a tunnel keyed by its origin", &Connect.routeTo(&endpoint("https://s.test/"), &Some(Proxy.at("http://p:3128"))).key, &"tunnel http://p:3128 https://s.test"),57 Test.equals("a plain target shares the proxy's connections", &Connect.routeTo(&endpoint("http://s.test/"), &Some(Proxy.at("http://p:3128"))).key, &"proxy http://p:3128"),58 Test.that("only a plain target through a proxy is written in absolute form", Connect.isProxied(&Connect.routeTo(&endpoint("http://s.test/"), &Some(Proxy.at("http://p")))) && !Connect.isProxied(&Connect.routeTo(&endpoint("https://s.test/"), &Some(Proxy.at("http://p")))) && !Connect.isProxied(&Connect.routeTo(&endpoint("http://s.test/"), &None))),59 Test.that("local hosts bypass when asked", Proxy.bypasses(&Proxy.at("http://p").bypassingLocal(), "localhost") && Proxy.bypasses(&Proxy.at("http://p").bypassingLocal(), "127.0.0.2") && Proxy.bypasses(&Proxy.at("http://p").bypassingLocal(), "intranet")),60 Test.not("other hosts do not bypass", Proxy.bypasses(&Proxy.at("http://p").bypassingLocal(), "example.com") || Proxy.bypasses(&Proxy.at("http://p"), "localhost")),61 Test.equals("a proxy must be a plain http address", &[Proxy.validate(&Proxy.at("https://p")).length(), Proxy.validate(&Proxy.at("p:80")).length(), Proxy.validate(&Proxy.at("http://p:3128")).length()], &[1, 1, 0]),62 Test.equals("a proxy without credentials sends none", &Proxy.authorization(&Proxy.at("http://p")), &None)63 ])64 let ran = Test.run(&checks)65 for failure in Test.failuresOf(&ran) {66 let _reported = Io.writeErrorLine(failure)67 }68 Test.report(&ran)69}70