From 6f3e36fa4b670e8207d1516f83ad49496a4d0f30 Mon Sep 17 00:00:00 2001 From: Vernon Stinebaker Date: Thu, 7 May 2026 11:40:39 +0800 Subject: [PATCH 1/2] test(integration): cover wizard failure contracts --- src/integration_tests.zig | 49 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/integration_tests.zig b/src/integration_tests.zig index 6f57b15..f548067 100644 --- a/src/integration_tests.zig +++ b/src/integration_tests.zig @@ -317,3 +317,52 @@ test "integration harness covers orchestration proxy not configured" { try std.testing.expectEqual(std.http.Status.service_unavailable, resp.status); try std.testing.expect(std.mem.indexOf(u8, resp.body, "NullBoiler not configured") != null); } + +test "integration harness covers wizard failure contracts" { + var server = try IntegrationServer.start(std.testing.allocator); + defer server.deinit(); + + { + const resp = try server.fetch(.{ + .path = "/api/wizard/nullclaw", + .method = .POST, + .body = "{", + }); + defer resp.deinit(std.testing.allocator); + try std.testing.expectEqual(std.http.Status.bad_request, resp.status); + try std.testing.expect(std.mem.indexOf(u8, resp.body, "invalid JSON body") != null); + } + + { + const resp = try server.fetch(.{ + .path = "/api/wizard/missing-component", + .method = .POST, + .body = "{\"instance_name\":\"demo\"}", + }); + defer resp.deinit(std.testing.allocator); + try std.testing.expectEqual(std.http.Status.not_found, resp.status); + try std.testing.expect(std.mem.indexOf(u8, resp.body, "component not found") != null); + } + + { + const resp = try server.fetch(.{ + .path = "/api/wizard/nullclaw/validate-providers", + .method = .POST, + .body = "{", + }); + defer resp.deinit(std.testing.allocator); + try std.testing.expectEqual(std.http.Status.bad_request, resp.status); + try std.testing.expect(std.mem.indexOf(u8, resp.body, "invalid JSON body") != null); + } + + { + const resp = try server.fetch(.{ + .path = "/api/wizard/missing-component/validate-providers", + .method = .POST, + .body = "{\"providers\":[]}", + }); + defer resp.deinit(std.testing.allocator); + try std.testing.expectEqual(std.http.Status.not_found, resp.status); + try std.testing.expect(std.mem.indexOf(u8, resp.body, "component not found") != null); + } +} From 08ee0d22aed3fc392af5d99a20e05a3247f19fb9 Mon Sep 17 00:00:00 2001 From: Igor Somov Date: Sat, 16 May 2026 19:59:55 -0300 Subject: [PATCH 2/2] fix(wizard): cover remaining failure contracts --- src/api/wizard.zig | 29 +++++++++++++------------- src/integration_tests.zig | 44 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/api/wizard.zig b/src/api/wizard.zig index bf620e3..1764f9e 100644 --- a/src/api/wizard.zig +++ b/src/api/wizard.zig @@ -221,6 +221,8 @@ pub fn handlePostModels( paths: paths_mod.Paths, body: []const u8, ) ?[]const u8 { + if (registry.findKnownComponent(component_name) == null) return null; + const parsed = std.json.parseFromSlice(struct { provider: []const u8, api_key: []const u8 = "", @@ -1118,6 +1120,19 @@ pub fn handleValidateChannels( ) ?[]const u8 { if (registry.findKnownComponent(component_name) == null) return null; + var tree = std.json.parseFromSlice(std.json.Value, allocator, body, .{ .allocate = .alloc_always }) catch + return allocator.dupe(u8, "{\"error\":\"invalid JSON body\"}") catch null; + defer tree.deinit(); + + const channels_val = switch (tree.value) { + .object => |obj| obj.get("channels") orelse return allocator.dupe(u8, "{\"error\":\"missing channels field\"}") catch null, + else => return allocator.dupe(u8, "{\"error\":\"invalid JSON body\"}") catch null, + }; + const channels_map = switch (channels_val) { + .object => |obj| obj, + else => return allocator.dupe(u8, "{\"error\":\"channels must be an object\"}") catch null, + }; + const bin_path = findOrFetchComponentBinary(allocator, component_name, paths) orelse return allocator.dupe(u8, "{\"error\":\"component binary not found\"}") catch null; defer allocator.free(bin_path); @@ -1138,20 +1153,6 @@ pub fn handleValidateChannels( file.writeAll(body) catch return null; } - // Parse body to iterate channels and accounts - var tree = std.json.parseFromSlice(std.json.Value, allocator, body, .{ .allocate = .alloc_always }) catch - return allocator.dupe(u8, "{\"error\":\"invalid JSON body\"}") catch null; - defer tree.deinit(); - - const channels_val = switch (tree.value) { - .object => |obj| obj.get("channels") orelse return allocator.dupe(u8, "{\"error\":\"missing channels field\"}") catch null, - else => return allocator.dupe(u8, "{\"error\":\"invalid JSON body\"}") catch null, - }; - const channels_map = switch (channels_val) { - .object => |obj| obj, - else => return allocator.dupe(u8, "{\"error\":\"channels must be an object\"}") catch null, - }; - var buf = std.array_list.Managed(u8).init(allocator); errdefer buf.deinit(); buf.appendSlice("{\"results\":[") catch return null; diff --git a/src/integration_tests.zig b/src/integration_tests.zig index cf139f2..160cc44 100644 --- a/src/integration_tests.zig +++ b/src/integration_tests.zig @@ -624,4 +624,48 @@ test "integration harness covers wizard failure contracts" { try std.testing.expectEqual(std.http.Status.not_found, resp.status); try std.testing.expect(std.mem.indexOf(u8, resp.body, "component not found") != null); } + + { + const resp = try server.fetch(.{ + .path = "/api/wizard/nullclaw/validate-channels", + .method = .POST, + .body = "{", + }); + defer resp.deinit(std.testing.allocator); + try std.testing.expectEqual(std.http.Status.bad_request, resp.status); + try std.testing.expect(std.mem.indexOf(u8, resp.body, "invalid JSON body") != null); + } + + { + const resp = try server.fetch(.{ + .path = "/api/wizard/missing-component/validate-channels", + .method = .POST, + .body = "{\"channels\":{}}", + }); + defer resp.deinit(std.testing.allocator); + try std.testing.expectEqual(std.http.Status.not_found, resp.status); + try std.testing.expect(std.mem.indexOf(u8, resp.body, "component not found") != null); + } + + { + const resp = try server.fetch(.{ + .path = "/api/wizard/nullclaw/models", + .method = .POST, + .body = "{", + }); + defer resp.deinit(std.testing.allocator); + try std.testing.expectEqual(std.http.Status.bad_request, resp.status); + try std.testing.expect(std.mem.indexOf(u8, resp.body, "invalid JSON body") != null); + } + + { + const resp = try server.fetch(.{ + .path = "/api/wizard/missing-component/models", + .method = .POST, + .body = "{\"provider\":\"openrouter\"}", + }); + defer resp.deinit(std.testing.allocator); + try std.testing.expectEqual(std.http.Status.not_found, resp.status); + try std.testing.expect(std.mem.indexOf(u8, resp.body, "component not found") != null); + } }