Skip to content

Commit dee31f7

Browse files
committed
Write tests for "Retry-After" header parser
1 parent b114de2 commit dee31f7

File tree

1 file changed

+52
-4
lines changed

1 file changed

+52
-4
lines changed

src/Package/Fetch.zig

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,7 +1133,8 @@ fn initHttpResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer:
11331133
const status = response.head.status;
11341134

11351135
if (@intFromEnum(status) >= 500 or status == .too_many_requests or status == .not_found) {
1136-
if (parseRetryAfter(response) catch null) |delay_sec| {
1136+
var iter = response.head.iterateHeaders();
1137+
if (parseRetryAfter(&iter) catch null) |delay_sec| {
11371138
// Set max by dividing and multiplying again, because Retry-After
11381139
// header value needs to be u32, and could be obsurdly large, and
11391140
// we do not want to multiply that large number by 1000 in case of
@@ -1162,9 +1163,8 @@ fn initHttpResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer:
11621163
///
11631164
/// For more information, see the MDN documentation:
11641165
/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After
1165-
fn parseRetryAfter(response: *const std.http.Client.Response) !?u32 {
1166-
var iter = response.head.iterateHeaders();
1167-
const retry_after: []const u8 = while (iter.next()) |header| {
1166+
fn parseRetryAfter(header_iter: *std.http.HeaderIterator) !?u32 {
1167+
const retry_after: []const u8 = while (header_iter.next()) |header| {
11681168
if (ascii.eqlIgnoreCase(header.name, "retry-after")) {
11691169
break header.value;
11701170
}
@@ -2428,12 +2428,60 @@ test "retries the correct number of times" {
24282428
try std.testing.expectEqual(retry.max_retries, retry.cur_retries);
24292429
}
24302430

2431+
test "parse Retry-After header" {
2432+
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
2433+
const alloc = arena.allocator();
2434+
defer arena.deinit();
2435+
{
2436+
var iter = try mockRetryAfterHeaderFactory(alloc, "6");
2437+
const result = try parseRetryAfter(&iter);
2438+
try std.testing.expectEqual(6, result.?);
2439+
}
2440+
{
2441+
var iter = try mockRetryAfterHeaderFactory(alloc, "12345");
2442+
const result = try parseRetryAfter(&iter);
2443+
try std.testing.expectEqual(12345, result.?);
2444+
}
2445+
{
2446+
// anything in the past should return `null`
2447+
var iter = try mockRetryAfterHeaderFactory(alloc, "Wed, 21 Oct 1970 07:28:00 GMT");
2448+
const result = try parseRetryAfter(&iter);
2449+
try std.testing.expectEqual(null, result);
2450+
}
2451+
{
2452+
// anything in the future should return `null` (this will fail in 100 years)
2453+
const future_time = "Wed, 21 Oct 2125 16:19:10 GMT";
2454+
const future_epoc_seconds = 4916737150;
2455+
const seconds_from_now = future_epoc_seconds - std.time.timestamp();
2456+
var iter = try mockRetryAfterHeaderFactory(alloc, future_time);
2457+
const result = try parseRetryAfter(&iter);
2458+
// The time between us calling `std.time.timestamp()` here in this test and when it's called in the
2459+
// `parseRetryAfter` function could be significant enough that they don't match. So we want approx values here.
2460+
try std.testing.expect(result.? + 5 > seconds_from_now);
2461+
try std.testing.expect(result.? - 5 < seconds_from_now);
2462+
}
2463+
{
2464+
// returns error on invalid header
2465+
var iter = try mockRetryAfterHeaderFactory(alloc, "Not a timestamp");
2466+
try std.testing.expectError(error.InvalidHeaderValueLength, parseRetryAfter(&iter));
2467+
}
2468+
{
2469+
// returns null on missing header
2470+
var iter = std.http.HeaderIterator.init("HTTP/1.1 599 NOT-OK\r\n\r\n");
2471+
try std.testing.expectEqual(null, try parseRetryAfter(&iter));
2472+
}
2473+
}
2474+
24312475
fn testFnToCallWithRetries(r: *Retry, is_spurious_error: bool) !void {
24322476
// set to 1 ms so the unit test doesn't take forever
24332477
r.retry_delay_override_ms = 1;
24342478
return if (is_spurious_error) error.MaybeSpurious else error.NonSpurious;
24352479
}
24362480

2481+
fn mockRetryAfterHeaderFactory(alloc: std.mem.Allocator, time: []const u8) !std.http.HeaderIterator {
2482+
return std.http.HeaderIterator.init(try std.fmt.allocPrint(alloc, "HTTP/1.1 599 NOT-OK\r\nRetry-After:{s}\r\n", .{time}));
2483+
}
2484+
24372485
fn saveEmbedFile(comptime tarball_name: []const u8, dir: fs.Dir) !void {
24382486
//const tarball_name = "duplicate_paths_excluded.tar.gz";
24392487
const tarball_content = @embedFile("Fetch/testdata/" ++ tarball_name);

0 commit comments

Comments
 (0)