complete mock implementation, add tests/fix forecast calc (part 1)
This commit is contained in:
parent
06d25df997
commit
62bae1fb99
4 changed files with 75 additions and 30 deletions
1
src/tests/metno_test_data.json
Normal file
1
src/tests/metno_test_data.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -116,7 +116,7 @@ fn fetchRaw(ptr: *anyopaque, allocator: std.mem.Allocator, coords: Coordinates)
|
|||
return try allocator.dupe(u8, response_body);
|
||||
}
|
||||
|
||||
fn parse(ptr: *anyopaque, allocator: std.mem.Allocator, raw: []const u8) !types.WeatherData {
|
||||
pub fn parse(ptr: *anyopaque, allocator: std.mem.Allocator, raw: []const u8) !types.WeatherData {
|
||||
_ = ptr;
|
||||
|
||||
// Parse JSON response
|
||||
|
|
@ -268,31 +268,29 @@ fn parseForecastDays(allocator: std.mem.Allocator, timeseries: []std.json.Value)
|
|||
break :blk hrs;
|
||||
}
|
||||
|
||||
clearHourlyForecast(allocator, &day_all_hours);
|
||||
|
||||
if (day_all_hours.items.len < 4)
|
||||
if (day_all_hours.items.len < 4) {
|
||||
clearHourlyForecast(allocator, &day_hourly);
|
||||
break :blk try day_all_hours.toOwnedSlice(allocator);
|
||||
// Pick 4 evenly spaced entries from day_all_hours
|
||||
if (day_all_hours.items.len >= 4) {
|
||||
const step = day_all_hours.items.len / 4;
|
||||
var selected: std.ArrayList(types.HourlyForecast) = .empty;
|
||||
try selected.append(allocator, day_all_hours.items[0]);
|
||||
try selected.append(allocator, day_all_hours.items[step]);
|
||||
try selected.append(allocator, day_all_hours.items[step * 2]);
|
||||
try selected.append(allocator, day_all_hours.items[step * 3]);
|
||||
const hrs = try selected.toOwnedSlice(allocator);
|
||||
|
||||
// Free the rest
|
||||
for (day_all_hours.items, 0..) |h, i| {
|
||||
if (i != 0 and i != step and i != step * 2 and i != step * 3) {
|
||||
allocator.free(h.time);
|
||||
allocator.free(h.condition);
|
||||
}
|
||||
}
|
||||
day_all_hours.clearRetainingCapacity();
|
||||
break :blk hrs;
|
||||
}
|
||||
break :blk try day_all_hours.toOwnedSlice(allocator);
|
||||
// Pick 4 evenly spaced entries from day_all_hours
|
||||
const step = day_all_hours.items.len / 4;
|
||||
var selected: std.ArrayList(types.HourlyForecast) = .empty;
|
||||
try selected.append(allocator, day_all_hours.items[0]);
|
||||
try selected.append(allocator, day_all_hours.items[step]);
|
||||
try selected.append(allocator, day_all_hours.items[step * 2]);
|
||||
try selected.append(allocator, day_all_hours.items[step * 3]);
|
||||
const hrs = try selected.toOwnedSlice(allocator);
|
||||
|
||||
// Free the rest
|
||||
for (day_all_hours.items, 0..) |h, i| {
|
||||
if (i != 0 and i != step and i != step * 2 and i != step * 3) {
|
||||
allocator.free(h.time);
|
||||
allocator.free(h.condition);
|
||||
}
|
||||
}
|
||||
day_all_hours.clearRetainingCapacity();
|
||||
clearHourlyForecast(allocator, &day_hourly);
|
||||
break :blk hrs;
|
||||
};
|
||||
|
||||
try days.append(allocator, .{
|
||||
|
|
@ -310,6 +308,7 @@ fn parseForecastDays(allocator: std.mem.Allocator, timeseries: []std.json.Value)
|
|||
// Start new day
|
||||
current_date = date;
|
||||
day_temps.clearRetainingCapacity();
|
||||
day_hourly.clearRetainingCapacity();
|
||||
day_all_hours.clearRetainingCapacity();
|
||||
day_symbol = null;
|
||||
}
|
||||
|
|
@ -476,3 +475,29 @@ test "parseForecastDays handles empty timeseries" {
|
|||
defer allocator.free(forecast);
|
||||
try std.testing.expectEqual(@as(usize, 0), forecast.len);
|
||||
}
|
||||
|
||||
test "hourly forecasts should have 4 entries per day" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
const json_data = @embedFile("../tests/metno_test_data.json");
|
||||
const weather_data = try parse(undefined, allocator, json_data);
|
||||
defer weather_data.deinit();
|
||||
|
||||
// Skip first day if incomplete, check remaining days have 4 hourly entries
|
||||
var checked: usize = 0;
|
||||
for (weather_data.forecast) |day| {
|
||||
if (day.hourly.len < 4) continue; // Skip incomplete days
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 4), day.hourly.len);
|
||||
|
||||
// None should be "Unknown"
|
||||
for (day.hourly) |hour| {
|
||||
try std.testing.expect(!std.mem.eql(u8, hour.condition, "Unknown"));
|
||||
}
|
||||
|
||||
checked += 1;
|
||||
if (checked >= 3) break; // Check 3 complete days
|
||||
}
|
||||
|
||||
try std.testing.expect(checked >= 2); // At least 2 complete days
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ const Mock = @This();
|
|||
|
||||
allocator: std.mem.Allocator,
|
||||
responses: std.StringHashMap([]const u8),
|
||||
parse_fn: ?*const fn (ptr: *anyopaque, allocator: std.mem.Allocator, raw: []const u8) anyerror!types.WeatherData = null,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) !Mock {
|
||||
return Mock{
|
||||
.allocator = allocator,
|
||||
.responses = std.StringHashMap([]const u8).init(allocator),
|
||||
.parse_fn = null,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -43,9 +45,10 @@ fn fetchRaw(ptr: *anyopaque, allocator: std.mem.Allocator, coords: Coordinates)
|
|||
}
|
||||
|
||||
fn parse(ptr: *anyopaque, allocator: std.mem.Allocator, raw: []const u8) !types.WeatherData {
|
||||
_ = ptr;
|
||||
_ = allocator;
|
||||
_ = raw;
|
||||
const self: *Mock = @ptrCast(@alignCast(ptr));
|
||||
if (self.parse_fn) |parse_fn| {
|
||||
return parse_fn(ptr, allocator, raw);
|
||||
}
|
||||
return error.NotImplemented;
|
||||
}
|
||||
|
||||
|
|
@ -63,7 +66,21 @@ pub fn deinit(self: *Mock) void {
|
|||
self.responses.deinit();
|
||||
}
|
||||
|
||||
test "mock weather provider" {
|
||||
// TODO: Implement Mock.parse to enable this test
|
||||
return error.SkipZigTest;
|
||||
test "mock weather provider with MetNo parse" {
|
||||
const MetNo = @import("MetNo.zig");
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
const test_data = @embedFile("../tests/metno_test_data.json");
|
||||
|
||||
var mock = try Mock.init(allocator);
|
||||
defer mock.deinit();
|
||||
|
||||
mock.parse_fn = MetNo.parse;
|
||||
|
||||
// Parse directly - no fetching
|
||||
const weather = try MetNo.parse(&mock, allocator, test_data);
|
||||
defer weather.deinit();
|
||||
|
||||
// Verify we got valid weather data
|
||||
try std.testing.expect(weather.forecast.len > 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,6 +89,8 @@ pub const WeatherData = struct {
|
|||
|
||||
pub fn deinit(self: WeatherData) void {
|
||||
self.allocator.free(self.location);
|
||||
self.allocator.free(self.current.condition);
|
||||
self.allocator.free(self.current.wind_dir);
|
||||
for (self.forecast) |day| {
|
||||
self.allocator.free(day.date);
|
||||
self.allocator.free(day.condition);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue