102 lines
3.5 KiB
TypeScript
102 lines
3.5 KiB
TypeScript
import { Response } from "undici";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
|
|
|
import { airQualityRequestHandler } from "./air-quality";
|
|
|
|
vi.mock("@homarr/core/infrastructure/http", () => ({
|
|
fetchWithTrustedCertificatesAsync: vi.fn(),
|
|
}));
|
|
|
|
const mockFetch = vi.mocked(fetchWithTrustedCertificatesAsync);
|
|
|
|
const makeAirQualityResponse = () => {
|
|
const times = ["2026-07-01T00:00", "2026-07-01T01:00", "2026-07-02T00:00"];
|
|
const values = [10, 20, 30];
|
|
const fields = [
|
|
"european_aqi",
|
|
"us_aqi",
|
|
"uv_index",
|
|
"pm10",
|
|
"pm2_5",
|
|
"carbon_monoxide",
|
|
"nitrogen_dioxide",
|
|
"sulphur_dioxide",
|
|
"ozone",
|
|
"european_aqi_pm2_5",
|
|
"european_aqi_pm10",
|
|
"european_aqi_nitrogen_dioxide",
|
|
"european_aqi_ozone",
|
|
"european_aqi_sulphur_dioxide",
|
|
"us_aqi_pm2_5",
|
|
"us_aqi_pm10",
|
|
"us_aqi_nitrogen_dioxide",
|
|
"us_aqi_carbon_monoxide",
|
|
"us_aqi_ozone",
|
|
"us_aqi_sulphur_dioxide",
|
|
"alder_pollen",
|
|
"birch_pollen",
|
|
"grass_pollen",
|
|
"mugwort_pollen",
|
|
"olive_pollen",
|
|
"ragweed_pollen",
|
|
] as const;
|
|
|
|
return {
|
|
timezone: "Europe/Berlin",
|
|
utc_offset_seconds: 7200,
|
|
current: {
|
|
time: "2026-07-01T12:00",
|
|
...Object.fromEntries(fields.map((field) => [field, field === "ragweed_pollen" ? null : 12])),
|
|
},
|
|
hourly: {
|
|
time: times,
|
|
...Object.fromEntries(fields.map((field) => [field, field === "ragweed_pollen" ? [null, null, null] : values])),
|
|
},
|
|
fields,
|
|
};
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
airQualityRequestHandler.invalidateCache();
|
|
});
|
|
|
|
describe("air quality request handler", () => {
|
|
it("requests and normalizes the full useful forecast field set", async () => {
|
|
const fixture = makeAirQualityResponse();
|
|
mockFetch.mockResolvedValue(new Response(JSON.stringify(fixture)));
|
|
|
|
const first = await airQualityRequestHandler.handler({ latitude: 48.85, longitude: 2.35 }).getDataAsync();
|
|
const second = await airQualityRequestHandler.handler({ latitude: 48.85, longitude: 2.35 }).getDataAsync();
|
|
|
|
expect(first.data.current.observedAt).toBe("2026-07-01T10:00:00.000Z");
|
|
expect(first.data.current.pollutants.pm2_5).toBe(12);
|
|
expect(first.data.current.pollen.ragweed).toBeNull();
|
|
expect(first.data.hourly[0]?.observedAt).toBe("2026-06-30T22:00:00.000Z");
|
|
expect(first.data.daily).toEqual([
|
|
{ date: "2026-07-01", europeanAqiMax: 20, usAqiMax: 20, uvIndexMax: 20, pm2_5Max: 20, pm10Max: 20 },
|
|
{ date: "2026-07-02", europeanAqiMax: 30, usAqiMax: 30, uvIndexMax: 30, pm2_5Max: 30, pm10Max: 30 },
|
|
]);
|
|
expect(second.data).toStrictEqual(first.data);
|
|
expect(mockFetch).toHaveBeenCalledOnce();
|
|
|
|
const requestUrl = new URL(String(mockFetch.mock.calls[0]?.[0]));
|
|
expect(requestUrl.origin).toBe("https://air-quality-api.open-meteo.com");
|
|
expect(requestUrl.searchParams.get("forecast_days")).toBe("7");
|
|
expect(requestUrl.searchParams.get("timezone")).toBe("auto");
|
|
expect(requestUrl.searchParams.get("current")).toContain("european_aqi_pm2_5");
|
|
expect(requestUrl.searchParams.get("hourly")).toContain("us_aqi_carbon_monoxide");
|
|
expect(requestUrl.searchParams.get("hourly")).toContain("ragweed_pollen");
|
|
});
|
|
|
|
it("rejects unsuccessful Open-Meteo responses", async () => {
|
|
mockFetch.mockResolvedValue(new Response("unavailable", { status: 503 }));
|
|
|
|
await expect(
|
|
airQualityRequestHandler.handler({ latitude: 48.85, longitude: 2.35 }).getDataAsync(),
|
|
).rejects.toThrow();
|
|
});
|
|
});
|