Skip to content

Commit 5318966

Browse files
committed
Write tests for "Retry-After" header parser
1 parent 9e33dcc commit 5318966

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
@@ -1141,7 +1141,8 @@ fn initHttpResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer:
11411141
const status = response.head.status;
11421142

11431143
if (@intFromEnum(status) >= 500 or status == .too_many_requests or status == .not_found) {
1144-
if (parseRetryAfter(f.io, response) catch null) |delay_sec| {
1144+
var iter = response.head.iterateHeaders();
1145+
if (parseRetryAfter(f.io, &iter) catch null) |delay_sec| {
11451146
// Set max by dividing and multiplying again, because Retry-After
11461147
// header value needs to be u32, and could be obsurdly large, and
11471148
// we do not want to multiply that large number by 1000 in case of
@@ -1170,9 +1171,8 @@ fn initHttpResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer:
11701171
///
11711172
/// For more information, see the MDN documentation:
11721173
/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After
1173-
fn parseRetryAfter(io: Io, response: *const std.http.Client.Response) !?u32 {
1174-
var iter = response.head.iterateHeaders();
1175-
const retry_after: []const u8 = while (iter.next()) |header| {
1174+
fn parseRetryAfter(io: Io, header_iter: *std.http.HeaderIterator) !?u32 {
1175+
const retry_after: []const u8 = while (header_iter.next()) |header| {
11761176
if (ascii.eqlIgnoreCase(header.name, "retry-after")) {
11771177
break header.value;
11781178
}
@@ -2446,12 +2446,60 @@ test "retries the correct number of times" {
24462446
try std.testing.expectEqual(retry.max_retries, retry.cur_retries);
24472447
}
24482448

2449+
test "parse Retry-After header" {
2450+
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
2451+
const alloc = arena.allocator();
2452+
defer arena.deinit();
2453+
{
2454+
var iter = try mockRetryAfterHeaderFactory(alloc, "6");
2455+
const result = try parseRetryAfter(&iter);
2456+
try std.testing.expectEqual(6, result.?);
2457+
}
2458+
{
2459+
var iter = try mockRetryAfterHeaderFactory(alloc, "12345");
2460+
const result = try parseRetryAfter(&iter);
2461+
try std.testing.expectEqual(12345, result.?);
2462+
}
2463+
{
2464+
// anything in the past should return `null`
2465+
var iter = try mockRetryAfterHeaderFactory(alloc, "Wed, 21 Oct 1970 07:28:00 GMT");
2466+
const result = try parseRetryAfter(&iter);
2467+
try std.testing.expectEqual(null, result);
2468+
}
2469+
{
2470+
// anything in the future should return `null` (this will fail in 100 years)
2471+
const future_time = "Wed, 21 Oct 2125 16:19:10 GMT";
2472+
const future_epoc_seconds = 4916737150;
2473+
const seconds_from_now = future_epoc_seconds - std.time.timestamp();
2474+
var iter = try mockRetryAfterHeaderFactory(alloc, future_time);
2475+
const result = try parseRetryAfter(&iter);
2476+
// The time between us calling `std.time.timestamp()` here in this test and when it's called in the
2477+
// `parseRetryAfter` function could be significant enough that they don't match. So we want approx values here.
2478+
try std.testing.expect(result.? + 5 > seconds_from_now);
2479+
try std.testing.expect(result.? - 5 < seconds_from_now);
2480+
}
2481+
{
2482+
// returns error on invalid header
2483+
var iter = try mockRetryAfterHeaderFactory(alloc, "Not a timestamp");
2484+
try std.testing.expectError(error.InvalidHeaderValueLength, parseRetryAfter(&iter));
2485+
}
2486+
{
2487+
// returns null on missing header
2488+
var iter = std.http.HeaderIterator.init("HTTP/1.1 599 NOT-OK\r\n\r\n");
2489+
try std.testing.expectEqual(null, try parseRetryAfter(&iter));
2490+
}
2491+
}
2492+
24492493
fn testFnToCallWithRetries(r: *Retry, is_spurious_error: bool) !void {
24502494
// set to 1 ms so the unit test doesn't take forever
24512495
r.retry_delay_override_ms = 1;
24522496
return if (is_spurious_error) error.MaybeSpurious else error.NonSpurious;
24532497
}
24542498

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

0 commit comments

Comments
 (0)