40 lines
3.1 KiB
JavaScript
40 lines
3.1 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { createWeatherClient } from '../server/weather.mjs';
|
|
import { weatherAvailable, weatherCondition } from '../src/lib/weather.mjs';
|
|
|
|
const now = Date.parse('2026-10-08T12:00:00+03:00');
|
|
const raw = { current: { time: now / 1000, temperature_2m: 12.2, apparent_temperature: 10.5, weather_code: 61, is_day: 1, wind_speed_10m: 3 },
|
|
hourly: { time: [now / 1000 + 3600], temperature_2m: [13], weather_code: [3], precipitation_probability: [20] } };
|
|
test('weather requests are coalesced and exclude transit credentials; Unix times retain Minsk instant', async () => {
|
|
let calls = 0;
|
|
const client = createWeatherClient({ now: () => now, fetchData: async (url, init) => {
|
|
calls++; assert.equal(url.searchParams.get('wind_speed_unit'), 'ms'); assert.equal(url.searchParams.get('timeformat'), 'unixtime');
|
|
assert.deepEqual(init.headers, { Accept: 'application/json' }); return Response.json(raw);
|
|
} });
|
|
const [first, second] = await Promise.all([client.weather(1, 1, { latitude: 55.5, longitude: 28.7 }), client.weather(1, 1, { latitude: 55.5, longitude: 28.7 })]);
|
|
assert.deepEqual(first, second); assert.equal(calls, 1); assert.equal(first.observedAt, new Date(now).toISOString());
|
|
assert.equal(weatherAvailable(first, now + 30 * 60000), true); assert.equal(weatherAvailable(first, now + 61 * 60000), false);
|
|
assert.equal(weatherCondition(first.code).label, 'Дождь');
|
|
});
|
|
test('missing coordinates and null weather values never produce a fabricated 0 degree forecast', async () => {
|
|
const client = createWeatherClient({ now: () => now, fetchData: async () => Response.json({ ...raw, current: { ...raw.current, temperature_2m: null } }) });
|
|
await assert.rejects(client.weather(1, 1, { latitude: null, longitude: null }), error => error.code === 'coordinates_unavailable');
|
|
await assert.rejects(client.weather(1, 1, { latitude: 55, longitude: 28 }), error => error.code === 'weather_unavailable');
|
|
});
|
|
test('commercial provider key selects its endpoint and is never exposed in response or errors', async () => {
|
|
const client = createWeatherClient({ apiKey: 'private-weather-key', now: () => now, fetchData: async url => {
|
|
assert.equal(url.host, 'customer-api.open-meteo.com'); assert.equal(url.searchParams.get('apikey'), 'private-weather-key'); return Response.json(raw);
|
|
} });
|
|
assert.ok(!JSON.stringify(await client.weather(1, 1, { latitude: 55, longitude: 28 })).includes('private-weather-key'));
|
|
});
|
|
test('provider outage backs off for a minute without extending the old forecast timestamp', async () => {
|
|
let current = now, calls = 0;
|
|
const client = createWeatherClient({ now: () => current, fetchData: async () => { calls++; return calls === 1 ? Response.json(raw) : new Response('', { status: 503 }); } });
|
|
const coords = { latitude: 55, longitude: 28 };
|
|
const saved = await client.weather(1, 1, coords);
|
|
current += 11 * 60000;
|
|
await assert.rejects(client.weather(1, 1, coords)); await assert.rejects(client.weather(1, 1, coords));
|
|
assert.equal(calls, 2); assert.equal(saved.updatedAt, new Date(now).toISOString());
|
|
});
|