Skip to content

Commit 1cd4179

Browse files
authored
Merge pull request #16 from bradcypert/bradcypert/add-starts-with
Add support for `startsWith` assertion in `assertion_checker.zig`
2 parents 194897e + 0f8f235 commit 1cd4179

1 file changed

Lines changed: 86 additions & 22 deletions

File tree

src/httpfile/assertion_checker.zig

Lines changed: 86 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ const http = std.http;
4646
const HttpParser = @import("./parser.zig");
4747
const Client = @import("./http_client.zig");
4848

49+
fn extractHeaderName(key: []const u8) ![]const u8 {
50+
// Expects key in the form header["..."]
51+
const start_quote = std.mem.indexOfScalar(u8, key, '"') orelse return error.InvalidAssertionKey;
52+
const end_quote = std.mem.lastIndexOfScalar(u8, key, '"') orelse return error.InvalidAssertionKey;
53+
if (end_quote <= start_quote) return error.InvalidAssertionKey;
54+
return key[start_quote + 1 .. end_quote];
55+
}
56+
4957
pub fn check(request: *HttpParser.HttpRequest, response: Client.HttpResponse) !void {
5058
const stderr = std.io.getStdErr().writer();
5159
for (request.assertions.items) |assertion| {
@@ -63,8 +71,7 @@ pub fn check(request: *HttpParser.HttpRequest, response: Client.HttpResponse) !v
6371
return error.BodyContentMismatch;
6472
}
6573
} else if (std.mem.startsWith(u8, assertion.key, "header[\"")) {
66-
// Extract the header name from the assertion key
67-
const header_name = assertion.key[8 .. assertion.key.len - 2];
74+
const header_name = try extractHeaderName(assertion.key);
6875
const actual_value = response.headers.get(header_name);
6976
if (actual_value == null or !std.ascii.eqlIgnoreCase(actual_value.?, assertion.value)) {
7077
stderr.print("[Fail] Expected header \"{s}\" to be \"{s}\", got \"{s}\"\n", .{ header_name, assertion.value, actual_value orelse "null" }) catch {};
@@ -88,8 +95,7 @@ pub fn check(request: *HttpParser.HttpRequest, response: Client.HttpResponse) !v
8895
return error.BodyContentMatchesButShouldnt;
8996
}
9097
} else if (std.mem.startsWith(u8, assertion.key, "header[\"")) {
91-
// Extract the header name from the assertion key
92-
const header_name = assertion.key[8 .. assertion.key.len - 2];
98+
const header_name = try extractHeaderName(assertion.key);
9399
const actual_value = response.headers.get(header_name);
94100
if (actual_value != null and std.ascii.eqlIgnoreCase(actual_value.?, assertion.value)) {
95101
stderr.print("[Fail] Expected header \"{s}\" to NOT equal \"{s}\", got \"{s}\"\n", .{ header_name, assertion.value, actual_value orelse "null" }) catch {};
@@ -100,20 +106,6 @@ pub fn check(request: *HttpParser.HttpRequest, response: Client.HttpResponse) !v
100106
return error.InvalidAssertionKey;
101107
}
102108
},
103-
104-
// .header => {
105-
// // assertion.key is header[""] so we need to
106-
// // parse it out of the quotes
107-
// const tokens = std.mem.splitScalar(u8, assertion.key, '\"');
108-
// const expected_header = tokens.next() orelse return error.InvalidHeaderFormat;
109-
// if (expected_header.len != 2) {
110-
// return error.InvalidHeaderFormat;
111-
// }
112-
// const actual_value = response.headers.get(expected_header);
113-
// if (actual_value == null or actual_value.* != expected_header.value) {
114-
// return error.HeaderMismatch;
115-
// }
116-
// },
117109
.contains => {
118110
if (std.ascii.eqlIgnoreCase(assertion.key, "status")) {
119111
var status_buf: [3]u8 = undefined;
@@ -129,8 +121,7 @@ pub fn check(request: *HttpParser.HttpRequest, response: Client.HttpResponse) !v
129121
return error.BodyContentNotContains;
130122
}
131123
} else if (std.mem.startsWith(u8, assertion.key, "header[\"")) {
132-
// Extract the header name from the assertion key
133-
const header_name = assertion.key[8 .. assertion.key.len - 2];
124+
const header_name = try extractHeaderName(assertion.key);
134125
const actual_value = response.headers.get(header_name);
135126
if (actual_value == null or std.mem.indexOf(u8, actual_value.?, assertion.value) == null) {
136127
stderr.print("[Fail] Expected header \"{s}\" to contain \"{s}\", got \"{s}\"\n", .{ header_name, assertion.value, actual_value orelse "null" }) catch {};
@@ -156,8 +147,7 @@ pub fn check(request: *HttpParser.HttpRequest, response: Client.HttpResponse) !v
156147
return error.BodyContentContainsButShouldnt;
157148
}
158149
} else if (std.mem.startsWith(u8, assertion.key, "header[\"")) {
159-
// Extract the header name from the assertion key
160-
const header_name = assertion.key[8 .. assertion.key.len - 2];
150+
const header_name = try extractHeaderName(assertion.key);
161151
const actual_value = response.headers.get(header_name);
162152
if (actual_value != null and std.mem.indexOf(u8, actual_value.?, assertion.value) != null) {
163153
stderr.print("[Fail] Expected header \"{s}\" to NOT contain \"{s}\", got \"{s}\"\n", .{ header_name, assertion.value, actual_value orelse "null" }) catch {};
@@ -168,6 +158,32 @@ pub fn check(request: *HttpParser.HttpRequest, response: Client.HttpResponse) !v
168158
return error.InvalidAssertionKey;
169159
}
170160
},
161+
.starts_with => {
162+
if (std.ascii.eqlIgnoreCase(assertion.key, "status")) {
163+
var status_buf: [3]u8 = undefined;
164+
const status_code = @intFromEnum(response.status.?);
165+
const status_str = std.fmt.bufPrint(&status_buf, "{}", .{status_code}) catch return error.StatusCodeFormat;
166+
if (!std.mem.startsWith(u8, status_str, assertion.value)) {
167+
stderr.print("[Fail] Expected status code to start with \"{s}\", got \"{s}\"\n", .{ assertion.value, status_str }) catch {};
168+
return error.StatusCodeNotStartsWith;
169+
}
170+
} else if (std.ascii.eqlIgnoreCase(assertion.key, "body")) {
171+
if (!std.mem.startsWith(u8, response.body, assertion.value)) {
172+
stderr.print("[Fail] Expected body content to start with \"{s}\", got \"{s}\"\n", .{ assertion.value, response.body }) catch {};
173+
return error.BodyContentNotStartsWith;
174+
}
175+
} else if (std.mem.startsWith(u8, assertion.key, "header[\"")) {
176+
const header_name = try extractHeaderName(assertion.key);
177+
const actual_value = response.headers.get(header_name);
178+
if (actual_value == null or !std.mem.startsWith(u8, actual_value.?, assertion.value)) {
179+
stderr.print("[Fail] Expected header \"{s}\" to start with \"{s}\", got \"{s}\"\n", .{ header_name, assertion.value, actual_value orelse "null" }) catch {};
180+
return error.HeaderNotStartsWith;
181+
}
182+
} else {
183+
stderr.print("[Fail] Invalid assertion key for starts_with: {s}\n", .{assertion.key}) catch {};
184+
return error.InvalidAssertionKey;
185+
}
186+
},
171187
else => {},
172188
}
173189
}
@@ -276,3 +292,51 @@ test "HttpParser handles NotEquals" {
276292

277293
try check(&request, response);
278294
}
295+
296+
test "HttpParser supports starts_with for status, body, and header" {
297+
const allocator = std.testing.allocator;
298+
var assertions = std.ArrayList(HttpParser.Assertion).init(allocator);
299+
defer assertions.deinit();
300+
301+
// Status starts with "2"
302+
try assertions.append(HttpParser.Assertion{
303+
.key = "status",
304+
.value = "2",
305+
.assertion_type = .starts_with,
306+
});
307+
// Body starts with "Hello"
308+
try assertions.append(HttpParser.Assertion{
309+
.key = "body",
310+
.value = "Hello",
311+
.assertion_type = .starts_with,
312+
});
313+
// Header starts with "application"
314+
try assertions.append(HttpParser.Assertion{
315+
.key = "header[\"content-type\"]",
316+
.value = "application",
317+
.assertion_type = .starts_with,
318+
});
319+
320+
var request = HttpParser.HttpRequest{
321+
.method = .GET,
322+
.url = "https://api.example.com",
323+
.headers = std.ArrayList(http.Header).init(allocator),
324+
.assertions = assertions,
325+
.body = null,
326+
};
327+
328+
var response_headers = std.StringHashMap([]const u8).init(allocator);
329+
try response_headers.put("content-type", "application/json");
330+
defer response_headers.deinit();
331+
332+
const body = try allocator.dupe(u8, "Hello world!");
333+
defer allocator.free(body);
334+
const response = Client.HttpResponse{
335+
.status = http.Status.ok,
336+
.headers = response_headers,
337+
.body = body,
338+
.allocator = allocator,
339+
};
340+
341+
try check(&request, response);
342+
}

0 commit comments

Comments
 (0)