diff --git a/src/http/Server.zig b/src/http/Server.zig index 0a63ecf..f71bb1c 100644 --- a/src/http/Server.zig +++ b/src/http/Server.zig @@ -76,7 +76,7 @@ fn handleWeather(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void try handler.handleWeather(&ctx.options, req, res, client_ip); } -fn getClientIp(req: *httpz.Request, buf: []u8) ![]const u8 { +pub fn getClientIp(req: *httpz.Request, buf: []u8) ![]const u8 { // Check X-Forwarded-For header first (for proxies) if (req.header("x-forwarded-for")) |xff| { return parseXForwardedFor(xff); @@ -114,7 +114,7 @@ pub fn deinit(self: *Server) void { self.httpz_server.deinit(); } -const MockHarness = struct { +pub const MockHarness = struct { allocator: std.mem.Allocator, config: Config, geoip: *GeoIp, @@ -241,6 +241,16 @@ test "handleWeather: default endpoint uses IP address" { try handler.handleWeather(&harness.opts, ht.req, ht.res, client_ip); try ht.expectStatus(200); + try ht.expectBody( + \\
Weather report: 73.158.64.1
+        \\
+        \\    \   /         Clear
+        \\     .-.          +68(+68) °F
+        \\  ― (   ) ―  3 mph
+        \\     `-'          6 mi
+        \\    /   \         0.0 in
+        \\
+ ); } test "handleWeather: x-forwarded-for with multiple IPs" { diff --git a/src/http/handler.zig b/src/http/handler.zig index 16797e0..2938f73 100644 --- a/src/http/handler.zig +++ b/src/http/handler.zig @@ -198,3 +198,256 @@ fn determineFormat(params: QueryParams, user_agent: ?[]const u8) Formatted.Forma return .ansi; return .html; } + +test "handler: help page" { + const allocator = std.testing.allocator; + const MockHarness = @import("Server.zig").MockHarness; + + var harness = try MockHarness.init(allocator); + defer harness.deinit(); + + var ht = httpz.testing.init(.{}); + defer ht.deinit(); + + ht.url("/:help"); + ht.param("location", ":help"); + + try handleWeather(&harness.opts, ht.req, ht.res, "127.0.0.1"); + + try ht.expectStatus(200); +} + +test "handler: translation page" { + const allocator = std.testing.allocator; + const MockHarness = @import("Server.zig").MockHarness; + + var harness = try MockHarness.init(allocator); + defer harness.deinit(); + + var ht = httpz.testing.init(.{}); + defer ht.deinit(); + + ht.url("/:translation"); + ht.param("location", ":translation"); + + try handleWeather(&harness.opts, ht.req, ht.res, "127.0.0.1"); + + try ht.expectStatus(200); +} + +test "handler: favicon" { + const allocator = std.testing.allocator; + const MockHarness = @import("Server.zig").MockHarness; + + var harness = try MockHarness.init(allocator); + defer harness.deinit(); + + var ht = httpz.testing.init(.{}); + defer ht.deinit(); + + ht.url("/favicon.ico"); + ht.param("location", "favicon.ico"); + + try handleWeather(&harness.opts, ht.req, ht.res, "127.0.0.1"); + + try ht.expectStatus(200); +} + +test "handler: format j1 (json)" { + const allocator = std.testing.allocator; + const MockHarness = @import("Server.zig").MockHarness; + + var harness = try MockHarness.init(allocator); + defer harness.deinit(); + + var ht = httpz.testing.init(.{}); + defer ht.deinit(); + + ht.url("/73.158.64.1?format=j1"); + ht.param("location", "73.158.64.1"); + + var client_ip_buf: [47]u8 = undefined; + const client_ip = try @import("Server.zig").getClientIp(ht.req, &client_ip_buf); + try handleWeather(&harness.opts, ht.req, ht.res, client_ip); + + try ht.expectStatus(200); + try ht.expectHeader("Content-Type", "application/json; charset=UTF-8"); + try ht.expectBody( + \\{"current_condition":{"temp_C":20,"weatherCode":"clear","weatherDesc":[{"value":"Clear"}],"humidity":50,"windspeedKmph":5,"winddirDegree":0,"pressure":1013,"precipMM":0},"weather":[]} + ); +} + +test "handler: format p1 (prometheus)" { + const allocator = std.testing.allocator; + const MockHarness = @import("Server.zig").MockHarness; + + var harness = try MockHarness.init(allocator); + defer harness.deinit(); + + var ht = httpz.testing.init(.{}); + defer ht.deinit(); + + ht.url("/73.158.64.1?format=p1"); + ht.param("location", "73.158.64.1"); + + var client_ip_buf: [47]u8 = undefined; + const client_ip = try @import("Server.zig").getClientIp(ht.req, &client_ip_buf); + try handleWeather(&harness.opts, ht.req, ht.res, client_ip); + + try ht.expectStatus(200); + try ht.expectBody( + \\# HELP temperature_feels_like_celsius Feels Like Temperature in Celsius + \\temperature_feels_like_celsius{forecast="current"} 20 + \\# HELP temperature_feels_like_fahrenheit Feels Like Temperature in Fahrenheit + \\temperature_feels_like_fahrenheit{forecast="current"} 68 + \\# HELP cloudcover_percentage Cloud Coverage in Percent + \\cloudcover_percentage{forecast="current"} 0 + \\# HELP humidity_percentage Humidity in Percent + \\humidity_percentage{forecast="current"} 50 + \\# HELP precipitation_mm Precipitation (Rainfall) in mm + \\precipitation_mm{forecast="current"} 0.0 + \\# HELP pressure_hpa Air pressure in hPa + \\pressure_hpa{forecast="current"} 1013 + \\# HELP temperature_celsius Temperature in Celsius + \\temperature_celsius{forecast="current"} 20 + \\# HELP temperature_fahrenheit Temperature in Fahrenheit + \\temperature_fahrenheit{forecast="current"} 68 + \\# HELP uv_index Ultraviolet Radiation Index + \\uv_index{forecast="current"} 0 + \\# HELP visibility Visible Distance in Kilometres + \\visibility{forecast="current"} 10 + \\# HELP weather_code Code to describe Weather Condition + \\weather_code{forecast="current"} 800 + \\# HELP winddir_degree Wind Direction in Degree + \\winddir_degree{forecast="current"} 0 + \\# HELP windspeed_kmph Wind Speed in Kilometres per Hour + \\windspeed_kmph{forecast="current"} 5 + \\# HELP windspeed_mph Wind Speed in Miles per Hour + \\windspeed_mph{forecast="current"} 3.106856 + \\# HELP observation_time Minutes since start of the day the observation happened + \\observation_time{forecast="current"} 0 + \\# HELP weather_desc Weather Description + \\weather_desc{forecast="current", description="Clear"} 1 + \\# HELP winddir_16_point Wind Direction on a 16-wind compass rose + \\winddir_16_point{forecast="current", description="N"} 1 + \\ + ); +} + +test "handler: format v2" { + const allocator = std.testing.allocator; + const MockHarness = @import("Server.zig").MockHarness; + + var harness = try MockHarness.init(allocator); + defer harness.deinit(); + + var ht = httpz.testing.init(.{}); + defer ht.deinit(); + + ht.url("/73.158.64.1?format=v2"); + ht.param("location", "73.158.64.1"); + + var client_ip_buf: [47]u8 = undefined; + const client_ip = try @import("Server.zig").getClientIp(ht.req, &client_ip_buf); + try handleWeather(&harness.opts, ht.req, ht.res, client_ip); + + try ht.expectStatus(200); + // Should we have 2 empty lines? + try ht.expectBody( + \\Weather report: 73.158.64.1 + \\ + \\ Current conditions + \\ Clear + \\ 🌡️ 20.0°C (68.0°F) + \\ 💧 50% + \\ 🌬️ 5.0 km/h N + \\ 🔽 1013.0 hPa + \\ 💦 0.0 mm + \\ + \\ + ); +} + +test "handler: format custom (%c)" { + const allocator = std.testing.allocator; + const MockHarness = @import("Server.zig").MockHarness; + + var harness = try MockHarness.init(allocator); + defer harness.deinit(); + + var ht = httpz.testing.init(.{}); + defer ht.deinit(); + + ht.url("/73.158.64.1?format=%c"); + ht.param("location", "73.158.64.1"); + + var client_ip_buf: [47]u8 = undefined; + const client_ip = try @import("Server.zig").getClientIp(ht.req, &client_ip_buf); + try handleWeather(&harness.opts, ht.req, ht.res, client_ip); + + try ht.expectStatus(200); + try ht.expectBody("☀️"); +} + +test "handler: format line 1" { + const allocator = std.testing.allocator; + const MockHarness = @import("Server.zig").MockHarness; + + var harness = try MockHarness.init(allocator); + defer harness.deinit(); + + var ht = httpz.testing.init(.{}); + defer ht.deinit(); + + ht.url("/73.158.64.1?format=1"); + ht.param("location", "73.158.64.1"); + + var client_ip_buf: [47]u8 = undefined; + const client_ip = try @import("Server.zig").getClientIp(ht.req, &client_ip_buf); + try handleWeather(&harness.opts, ht.req, ht.res, client_ip); + + try ht.expectStatus(200); + try ht.expectBody("Test: ☀️ 20°C"); +} + +test "handler: format line 2" { + const allocator = std.testing.allocator; + const MockHarness = @import("Server.zig").MockHarness; + + var harness = try MockHarness.init(allocator); + defer harness.deinit(); + + var ht = httpz.testing.init(.{}); + defer ht.deinit(); + + ht.url("/73.158.64.1?format=2"); + ht.param("location", "73.158.64.1"); + + var client_ip_buf: [47]u8 = undefined; + const client_ip = try @import("Server.zig").getClientIp(ht.req, &client_ip_buf); + try handleWeather(&harness.opts, ht.req, ht.res, client_ip); + + try ht.expectStatus(200); + try ht.expectBody("Test: ☀️ 20°C 🌬️N5km/h"); +} + +test "handler: format line 3" { + const allocator = std.testing.allocator; + const MockHarness = @import("Server.zig").MockHarness; + + var harness = try MockHarness.init(allocator); + defer harness.deinit(); + + var ht = httpz.testing.init(.{}); + defer ht.deinit(); + + ht.url("/73.158.64.1?format=3"); + ht.param("location", "73.158.64.1"); + + var client_ip_buf: [47]u8 = undefined; + const client_ip = try @import("Server.zig").getClientIp(ht.req, &client_ip_buf); + try handleWeather(&harness.opts, ht.req, ht.res, client_ip); + + try ht.expectStatus(200); + try ht.expectBody("Test: ☀️ 20°C 🌬️N5km/h 💧50%%"); +}