diff --git a/lua/hurl/lib/hurl_parser.lua b/lua/hurl/lib/hurl_parser.lua index 5eec184..aab4103 100644 --- a/lua/hurl/lib/hurl_parser.lua +++ b/lua/hurl/lib/hurl_parser.lua @@ -76,25 +76,24 @@ function M.parse_hurl_output(stderr, stdout) currentEntry.response.headers[trim(key)] = trim(value) end end + elseif line:find('^%* Response body:') then + -- Skip the response body section elseif line:find('^%* Timings:') then isTimings = true - elseif isTimings and trim(line) ~= '' then - local cleanedLine = line:find('^%* ') and line:sub(3) or line - local key, value = cleanedLine:match('([^:]+):%s*(.+)') + isCaptures = false + elseif line:find('^%* Captures:') then + isTimings = false + isCaptures = true + elseif isTimings and line:find('^%* ') then + local key, value = line:sub(3):match('([^:]+):%s*(.+)') if currentEntry and key and value then currentEntry.timings[key] = value end - elseif isTimings and trim(line) == '' then - isTimings = false - elseif line:find('^%* Captures:') then - isCaptures = true - elseif isCaptures and trim(line) ~= '' then - local key, value = line:match('^%* ([^:]+):%s*(.+)') + elseif isCaptures and line:find('^%* ') then + local key, value = line:sub(3):match('([^:]+):%s*(.+)') if currentEntry and key and value then currentEntry.captures[key] = value end - elseif isCaptures and trim(line) == '' then - isCaptures = false end end diff --git a/lua/hurl/lib/hurl_runner.lua b/lua/hurl/lib/hurl_runner.lua index c4d46a1..5396fc1 100644 --- a/lua/hurl/lib/hurl_runner.lua +++ b/lua/hurl/lib/hurl_runner.lua @@ -173,7 +173,7 @@ function M.run_hurl_in_very_verbose(filePath, fromEntry, toEntry) end -- Only show captures if there are any - if #entry.captures > 0 then + if entry.captures then table.insert(output_lines, '') table.insert(output_lines, '### Captures:') for key, value in pairs(entry.captures) do diff --git a/test/hurl_parser_spec.lua b/test/hurl_parser_spec.lua index ec2bc05..e355089 100644 --- a/test/hurl_parser_spec.lua +++ b/test/hurl_parser_spec.lua @@ -212,17 +212,16 @@ error: Undefined variable local stderr = [[ * Executing entry 1 * Timings: -* begin: 2024-10-24 18:41:33.558372 UTC -* end: 2024-10-24 18:41:35.129397875 UTC -* namelookup: 1446 µs -* connect: 49514 µs -* app_connect: 930193 µs -* pre_transfer: 930448 µs -* start_transfer: 1524375 µs -* total: 1570744 µs +* begin: 2024-10-26 07:39:55.048471 UTC +* end: 2024-10-26 07:39:56.990312125 UTC +* namelookup: 165918 µs +* connect: 467360 µs +* app_connect: 1288249 µs +* pre_transfer: 1288938 µs +* start_transfer: 1904466 µs +* total: 1941484 µs * Captures: * id: 68f47c5a-5115-47cd-9849-e45d3c378f12 -* name: Caucasian Shepherd Dog ]] local stdout = '' @@ -230,10 +229,266 @@ error: Undefined variable assert.are.equal(1, #result.entries) local entry = result.entries[1] - -- __AUTO_GENERATED_PRINT_VAR_START__ - print([==[function#function entry:]==], vim.inspect(entry)) -- __AUTO_GENERATED_PRINT_VAR_END__ assert.are.equal('68f47c5a-5115-47cd-9849-e45d3c378f12', entry.captures['id']) - assert.are.equal('Caucasian Shepherd Dog', entry.captures['name']) + assert.are.equal('1941484 µs', entry.timings['total']) + assert.are.equal('2024-10-26 07:39:55.048471 UTC', entry.timings['begin']) + assert.are.equal('2024-10-26 07:39:56.990312125 UTC', entry.timings['end']) end ) + + it('should parse captures correctly when there is a response body section', function() + local stderr = [[ +* Executing entry 1 +* Response body: +* Bytes <7b2264617461223a5b7b226964223a2236386634376335612d353131352d343763642d393834392d653435643363333738663132222c2274797065223a226272...> +* +* Timings: +* begin: 2024-10-26 07:47:00.610282 UTC +* end: 2024-10-26 07:47:02.170303375 UTC +* namelookup: 1304 µs +* connect: 280119 µs +* app_connect: 989082 µs +* pre_transfer: 989736 µs +* start_transfer: 1523378 µs +* total: 1559741 µs +* Captures: +* id: 68f47c5a-5115-47cd-9849-e45d3c378f12 +* name: Caucasian Shepherd Dog +]] + local stdout = '' + + local result = hurl_parser.parse_hurl_output(stderr, stdout) + + assert.are.equal(1, #result.entries) + local entry = result.entries[1] + assert.are.equal('68f47c5a-5115-47cd-9849-e45d3c378f12', entry.captures['id']) + assert.are.equal('Caucasian Shepherd Dog', entry.captures['name']) + assert.are.equal('1559741 µs', entry.timings['total']) + assert.are.equal('2024-10-26 07:47:00.610282 UTC', entry.timings['begin']) + assert.are.equal('2024-10-26 07:47:02.170303375 UTC', entry.timings['end']) + end) + + it('should parse verbose output correctly', function() + local stderr = [[ +* Variables: +* manga_id: 8b34f37a-0181-4f0b-8ce3-01217e9a602c +* Executing 1/2 entries +* ------------------------------------------------------------------------------ +* Executing entry 1 +* +* Cookie store: +* +* Request: +* GET https://dogapi.dog/api/v2/breeds +* +* Request can be run with the following curl command: +* curl 'https://dogapi.dog/api/v2/breeds' +* +> GET /api/v2/breeds HTTP/2 +> Host: dogapi.dog +> Accept: */* +> User-Agent: hurl/5.0.1 +> +* Request body: +* +* Response: (received 8035 bytes in 1643 ms) +* +< HTTP/2 200 +< cache-control: max-age=0, private, must-revalidate +< content-type: application/vnd.api+json; charset=utf-8 +< etag: W/"6e98619b9e70f8f3f0fe1739d6e7e48f" +< referrer-policy: strict-origin-when-cross-origin +< vary: Accept, Origin +< x-content-type-options: nosniff +< x-download-options: noopen +< x-frame-options: SAMEORIGIN +< x-permitted-cross-domain-policies: none +< x-request-id: 3defaaba-6aa9-4e21-a92a-9b8ffa68722c +< x-runtime: 0.019435 +< x-xss-protection: 0 +< date: Sat, 26 Oct 2024 07:53:12 GMT +< +* Response body: +* Bytes <7b2264617461223a5b7b226964223a2236386634376335612d353131352d343763642d393834392d653435643363333738663132222c2274797065223a226272...> +* +* Timings: +* begin: 2024-10-26 07:53:10.697106 UTC +* end: 2024-10-26 07:53:12.340969625 UTC +* namelookup: 462855 µs +* connect: 685650 µs +* app_connect: 1178810 µs +* pre_transfer: 1179386 µs +* start_transfer: 1605548 µs +* total: 1643543 µs +* Captures: +* id: 68f47c5a-5115-47cd-9849-e45d3c378f12 +* name: Caucasian Shepherd Dog +* +]] + local stdout = + '{"data":[{"id":"68f47c5a-5115-47cd-9849-e45d3c378f12","type":"breed","attributes":{"name":"Caucasian Shepherd Dog"}}]}' + + local result = hurl_parser.parse_hurl_output(stderr, stdout) + + assert.are.equal(1, #result.entries) + local entry = result.entries[1] + assert.are.equal('GET', entry.requestMethod) + assert.are.equal('https://dogapi.dog/api/v2/breeds', entry.requestUrl) + assert.are.equal('HTTP/2 200', entry.response.status) + assert.are.equal( + 'application/vnd.api+json; charset=utf-8', + entry.response.headers['content-type'] + ) + assert.are.equal('68f47c5a-5115-47cd-9849-e45d3c378f12', entry.captures['id']) + assert.are.equal('Caucasian Shepherd Dog', entry.captures['name']) + assert.are.equal('1643543 µs', entry.timings['total']) + assert.are.equal('2024-10-26 07:53:10.697106 UTC', entry.timings['begin']) + assert.are.equal('2024-10-26 07:53:12.340969625 UTC', entry.timings['end']) + assert.is_true(entry.response.body:find('"name":"Caucasian Shepherd Dog"') ~= nil) + end) + + it('should parse verbose output with SSL information correctly', function() + local stderr = [[ +* Variables: +* manga_id: 8b34f37a-0181-4f0b-8ce3-01217e9a602c +* Executing 1/2 entries +* ------------------------------------------------------------------------------ +* Executing entry 1 +* +* Cookie store: +* +* Request: +* GET https://dogapi.dog/api/v2/breeds +* +* Request can be run with the following curl command: +* curl 'https://dogapi.dog/api/v2/breeds' +* +** Host dogapi.dog:443 was resolved. +** IPv6: (none) +** IPv4: 167.71.54.211 +** Trying 167.71.54.211:443... +** Connected to dogapi.dog (167.71.54.211) port 443 +** ALPN: curl offers h2,http/1.1 +** CAfile: /etc/ssl/cert.pem +** CApath: none +** (304) (OUT), TLS handshake, Client hello (1): +** (304) (IN), TLS handshake, Server hello (2): +** TLSv1.2 (IN), TLS handshake, Certificate (11): +** TLSv1.2 (IN), TLS handshake, Server key exchange (12): +** TLSv1.2 (IN), TLS handshake, Server finished (14): +** TLSv1.2 (OUT), TLS handshake, Client key exchange (16): +** TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1): +** TLSv1.2 (OUT), TLS handshake, Finished (20): +** TLSv1.2 (IN), TLS change cipher, Change cipher spec (1): +** TLSv1.2 (IN), TLS handshake, Finished (20): +** SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305 / [blank] / UNDEF +** ALPN: server accepted h2 +** Server certificate: +** subject: CN=dogapi.dog +** start date: Oct 12 00:28:09 2024 GMT +** expire date: Jan 10 00:28:08 2025 GMT +** subjectAltName: host "dogapi.dog" matched cert's "dogapi.dog" +** issuer: C=US; O=Let's Encrypt; CN=R11 +** SSL certificate verify ok. +** using HTTP/2 +** [HTTP/2] [1] OPENED stream for https://dogapi.dog/api/v2/breeds +** [HTTP/2] [1] [:method: GET] +** [HTTP/2] [1] [:scheme: https] +** [HTTP/2] [1] [:authority: dogapi.dog] +** [HTTP/2] [1] [:path: /api/v2/breeds] +** [HTTP/2] [1] [accept: */*] +** [HTTP/2] [1] [user-agent: hurl/5.0.1] +> GET /api/v2/breeds HTTP/2 +> Host: dogapi.dog +> Accept: */* +> User-Agent: hurl/5.0.1 +> +* Request body: +* +** Request completely sent off +** Connection #0 to host dogapi.dog left intact +* Response: (received 8035 bytes in 1135 ms) +* +< HTTP/2 200 +< cache-control: max-age=0, private, must-revalidate +< content-type: application/vnd.api+json; charset=utf-8 +< etag: W/"6e98619b9e70f8f3f0fe1739d6e7e48f" +< referrer-policy: strict-origin-when-cross-origin +< vary: Accept, Origin +< x-content-type-options: nosniff +< x-download-options: noopen +< x-frame-options: SAMEORIGIN +< x-permitted-cross-domain-policies: none +< x-request-id: 068e99e9-bea8-4280-8c7e-d28f00a6ec35 +< x-runtime: 0.035859 +< x-xss-protection: 0 +< date: Sat, 26 Oct 2024 07:55:07 GMT +< +* Response body: +* Bytes <7b2264617461223a5b7b226964223a2236386634376335612d353131352d343763642d393834392d653435643363333738663132222c2274797065223a226272...> +* +* Timings: +* begin: 2024-10-26 07:55:05.994961 UTC +* end: 2024-10-26 07:55:07.130251375 UTC +* namelookup: 1212 µs +* connect: 208799 µs +* app_connect: 690830 µs +* pre_transfer: 691349 µs +* start_transfer: 1097045 µs +* total: 1135005 µs +* Captures: +* id: 68f47c5a-5115-47cd-9849-e45d3c378f12 +* name: Caucasian Shepherd Dog +* +]] + local stdout = + '{"data":[{"id":"68f47c5a-5115-47cd-9849-e45d3c378f12","type":"breed","attributes":{"name":"Caucasian Shepherd Dog"}}]}' + + local result = hurl_parser.parse_hurl_output(stderr, stdout) + + assert.are.equal(1, #result.entries) + local entry = result.entries[1] + assert.are.equal('GET', entry.requestMethod) + assert.are.equal('https://dogapi.dog/api/v2/breeds', entry.requestUrl) + assert.are.equal('HTTP/2 200', entry.response.status) + assert.are.equal( + 'application/vnd.api+json; charset=utf-8', + entry.response.headers['content-type'] + ) + assert.are.equal('68f47c5a-5115-47cd-9849-e45d3c378f12', entry.captures['id']) + assert.are.equal('Caucasian Shepherd Dog', entry.captures['name']) + assert.are.equal('1135005 µs', entry.timings['total']) + assert.are.equal('2024-10-26 07:55:05.994961 UTC', entry.timings['begin']) + assert.are.equal('2024-10-26 07:55:07.130251375 UTC', entry.timings['end']) + assert.is_true(entry.response.body:find('"name":"Caucasian Shepherd Dog"') ~= nil) + end) + + it('should parse timings and captures correctly with Captures: line', function() + local stderr = [[ +* Executing entry 1 +* Timings: +* begin: 2024-10-26 07:58:32.650882 UTC +* end: 2024-10-26 07:58:33.774139 UTC +* namelookup: 1803 µs +* connect: 230174 µs +* app_connect: 712316 µs +* pre_transfer: 712848 µs +* start_transfer: 957261 µs +* total: 1122952 µs +* Captures: +* id: 68f47c5a-5115-47cd-9849-e45d3c378f12 +* name: Caucasian Shepherd Dog +]] + local stdout = '' + + local result = hurl_parser.parse_hurl_output(stderr, stdout) + + assert.are.equal(1, #result.entries) + local entry = result.entries[1] + assert.are.equal('1122952 µs', entry.timings['total']) + assert.are.equal('2024-10-26 07:58:32.650882 UTC', entry.timings['begin']) + assert.are.equal('2024-10-26 07:58:33.774139 UTC', entry.timings['end']) + assert.are.equal('68f47c5a-5115-47cd-9849-e45d3c378f12', entry.captures['id']) + assert.are.equal('Caucasian Shepherd Dog', entry.captures['name']) + end) end)