|
| 1 | +class Httptap < Formula |
| 2 | + include Language::Python::Virtualenv |
| 3 | + |
| 4 | + desc "HTTP request visualizer with phase-by-phase timing breakdown" |
| 5 | + homepage "https://httptap.dev" |
| 6 | + url "https://files.pythonhosted.org/packages/7a/6a/f0da6ea52f95d4a4550e681223100e4a9171394dbaaecaa2c1588be55280/httptap-0.1.1.tar.gz" |
| 7 | + sha256 "c365dbd7de5ad50caadd2eb827ea3e205a2985f437f3c472fd9fbb324fa01a38" |
| 8 | + |
| 9 | + license "Apache-2.0" |
| 10 | + |
| 11 | + |
| 12 | + |
| 13 | + resource "anyio" do |
| 14 | + url "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz" |
| 15 | + sha256 "82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4" |
| 16 | + end |
| 17 | + |
| 18 | + resource "certifi" do |
| 19 | + url "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz" |
| 20 | + sha256 "47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43" |
| 21 | + end |
| 22 | + |
| 23 | + resource "dnspython" do |
| 24 | + url "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz" |
| 25 | + sha256 "181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f" |
| 26 | + end |
| 27 | + |
| 28 | + resource "h11" do |
| 29 | + url "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz" |
| 30 | + sha256 "4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1" |
| 31 | + end |
| 32 | + |
| 33 | + resource "h2" do |
| 34 | + url "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz" |
| 35 | + sha256 "6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1" |
| 36 | + end |
| 37 | + |
| 38 | + resource "hpack" do |
| 39 | + url "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz" |
| 40 | + sha256 "ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca" |
| 41 | + end |
| 42 | + |
| 43 | + resource "httpcore" do |
| 44 | + url "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz" |
| 45 | + sha256 "6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8" |
| 46 | + end |
| 47 | + |
| 48 | + resource "httpx" do |
| 49 | + url "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz" |
| 50 | + sha256 "75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc" |
| 51 | + end |
| 52 | + |
| 53 | + resource "hyperframe" do |
| 54 | + url "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz" |
| 55 | + sha256 "f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08" |
| 56 | + end |
| 57 | + |
| 58 | + resource "idna" do |
| 59 | + url "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz" |
| 60 | + sha256 "795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" |
| 61 | + end |
| 62 | + |
| 63 | + resource "markdown-it-py" do |
| 64 | + url "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz" |
| 65 | + sha256 "cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3" |
| 66 | + end |
| 67 | + |
| 68 | + resource "mdurl" do |
| 69 | + url "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz" |
| 70 | + sha256 "bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" |
| 71 | + end |
| 72 | + |
| 73 | + resource "pygments" do |
| 74 | + url "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz" |
| 75 | + sha256 "636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887" |
| 76 | + end |
| 77 | + |
| 78 | + resource "rich" do |
| 79 | + url "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz" |
| 80 | + sha256 "73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4" |
| 81 | + end |
| 82 | + |
| 83 | + resource "sniffio" do |
| 84 | + url "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz" |
| 85 | + sha256 "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" |
| 86 | + end |
| 87 | + |
| 88 | + def install |
| 89 | + virtualenv_install_with_resources |
| 90 | + end |
| 91 | + |
| 92 | + test do |
| 93 | + ENV["NO_COLOR"] = "1" |
| 94 | + |
| 95 | + # Start a minimal local HTTP server that returns 200 OK. |
| 96 | + port = free_port |
| 97 | + server = TCPServer.new("127.0.0.1", port) |
| 98 | + |
| 99 | + server_thread = Thread.new do |
| 100 | + client = server.accept |
| 101 | + client.write <<~HTTP_RESPONSE |
| 102 | + HTTP/1.1 200 OK\r |
| 103 | + Content-Length: 5\r |
| 104 | + Content-Type: text/plain\r |
| 105 | + \r |
| 106 | + hello |
| 107 | + HTTP_RESPONSE |
| 108 | + client.close |
| 109 | + end |
| 110 | + |
| 111 | + json_path = testpath/"report.json" |
| 112 | + test_url = "http://127.0.0.1:#{port}" |
| 113 | + |
| 114 | + # Run httptap in non-interactive mode with JSON output. |
| 115 | + system bin/"httptap", "--metrics-only", "--json", json_path, test_url |
| 116 | + |
| 117 | + server_thread.join |
| 118 | + server.close |
| 119 | + |
| 120 | + # Verify JSON output was created. |
| 121 | + assert_path_exists json_path, "JSON report was not created" |
| 122 | + json_data = JSON.parse(json_path.read) |
| 123 | + |
| 124 | + # Verify request metadata. |
| 125 | + assert_equal test_url, json_data["initial_url"] |
| 126 | + assert_equal 200, json_data.dig("summary", "final_status") |
| 127 | + |
| 128 | + # Verify steps array structure. |
| 129 | + steps = json_data["steps"] |
| 130 | + assert_kind_of Array, steps |
| 131 | + refute_empty steps, "Steps array should not be empty" |
| 132 | + |
| 133 | + # Verify first step contains expected data. |
| 134 | + first_step = steps.first |
| 135 | + assert_equal 1, first_step["step_number"] |
| 136 | + assert_equal 200, first_step.dig("response", "status") |
| 137 | + |
| 138 | + # Verify timing metrics are present. |
| 139 | + timing = first_step["timing"] |
| 140 | + assert timing, "Timing data missing" |
| 141 | + assert timing["total_ms"], "total_ms missing from timing data" |
| 142 | + assert_operator timing["total_ms"], :>=, 0, "total_ms should be non-negative" |
| 143 | + |
| 144 | + # Verify response body if present. |
| 145 | + response_body = first_step.dig("response", "body") |
| 146 | + assert_match "hello", response_body if response_body |
| 147 | + end |
| 148 | +end |
0 commit comments