const std = @import("std"); const weather_provider = @import("provider.zig"); const types = @import("types.zig"); const Mock = @This(); allocator: std.mem.Allocator, responses: std.StringHashMap(types.WeatherData), pub fn init(allocator: std.mem.Allocator) !Mock { return Mock{ .allocator = allocator, .responses = std.StringHashMap(types.WeatherData).init(allocator), }; } pub fn provider(self: *Mock) weather_provider.WeatherProvider { return .{ .ptr = self, .vtable = &.{ .fetch = fetch, .deinit = deinitProvider, }, }; } pub fn addResponse(self: *Mock, location: []const u8, data: types.WeatherData) !void { const key = try self.allocator.dupe(u8, location); try self.responses.put(key, data); } fn fetch(ptr: *anyopaque, allocator: std.mem.Allocator, location: []const u8) !types.WeatherData { const self: *Mock = @ptrCast(@alignCast(ptr)); const data = self.responses.get(location) orelse return error.LocationNotFound; return types.WeatherData{ .location = try allocator.dupe(u8, data.location), .current = data.current, .forecast = try allocator.dupe(types.ForecastDay, data.forecast), .allocator = allocator, }; } fn deinitProvider(ptr: *anyopaque) void { const self: *Mock = @ptrCast(@alignCast(ptr)); self.deinit(); } pub fn deinit(self: *Mock) void { var it = self.responses.iterator(); while (it.next()) |entry| { self.allocator.free(entry.key_ptr.*); } self.responses.deinit(); } test "mock weather provider" { var mock = try Mock.init(std.testing.allocator); defer mock.deinit(); const data = types.WeatherData{ .location = "London", .current = .{ .temp_c = 15.0, .temp_f = 59.0, .condition = "Clear", .weather_code = 113, .humidity = 65, .wind_kph = 10.0, .wind_dir = "N", .pressure_mb = 1013.0, .precip_mm = 0.0, }, .forecast = &.{}, .allocator = std.testing.allocator, }; try mock.addResponse("London", data); const p = mock.provider(); const result = try p.fetch(std.testing.allocator, "London"); defer result.deinit(); try std.testing.expectEqual(@as(f32, 15.0), result.current.temp_c); }