Files
matrix-backend/tests/services/weatherPollingService.test.ts
T
2025-09-27 01:21:57 +02:00

158 lines
5.9 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach, Mocked } from "vitest";
import { appEventBus, USER_UPDATED_EVENT, WEATHER_STATE_UPDATED_EVENT } from "../../src/utils/eventBus";
import { WeatherPollingService } from "../../src/services/weatherPollingService";
import { IUser } from "../../src/db/models/user";
import { getCurrentWeather } from "../../src/services/owmApiService";
vi.mock("../../src/services/owmApiService");
vi.mock("../../src/utils/eventBus", () => ({
appEventBus: {
on: vi.fn(),
emit: vi.fn(),
},
WEATHER_STATE_UPDATED_EVENT: "weather:state-updated",
USER_UPDATED_EVENT: "user:updated",
}));
vi.mock("../../src/services/owmApiService", () => ({
getCurrentWeather: vi.fn(),
}));
describe("WeatherPollingService", () => {
let mockedAppEventBus: Mocked<typeof appEventBus>;
const mockedGetCurrentWeather = vi.mocked(getCurrentWeather);
let pollingService: WeatherPollingService;
const mockUser: IUser = {
uuid: "user-123",
location: "Berlin",
} as any;
beforeEach(() => {
vi.clearAllMocks();
vi.useFakeTimers();
mockedAppEventBus = appEventBus as Mocked<typeof appEventBus>;
pollingService = new WeatherPollingService();
});
afterEach(() => {
vi.useRealTimers();
});
describe("Subscription Management", () => {
it("should start a new poll when the first user subscribes to a location", async () => {
mockedGetCurrentWeather.mockResolvedValue({ temp: 10 } as any);
pollingService.subscribeUser(mockUser.uuid, mockUser.location);
await vi.advanceTimersByTimeAsync(0);
expect(vi.getTimerCount()).toBe(1);
expect(mockedGetCurrentWeather).toHaveBeenCalledOnce();
});
it("should NOT start a new poll if another user subscribes to the same location", async () => {
pollingService.subscribeUser("user-1", "Berlin");
pollingService.subscribeUser("user-2", "Berlin");
await vi.advanceTimersByTimeAsync(0);
expect(vi.getTimerCount()).toBe(1);
expect(mockedGetCurrentWeather).toHaveBeenCalledTimes(1);
});
it("should stop the poll when the last user unsubscribes from a location", async () => {
// @ts-ignore - access to private property for test purposes
const activePolls = (pollingService as any).activeLocationPolls;
pollingService.subscribeUser("user-1", "Berlin");
pollingService.subscribeUser("user-2", "Berlin");
await vi.advanceTimersByTimeAsync(0);
expect(activePolls.has("Berlin")).toBe(true);
pollingService.unsubscribeUser("user-1", "Berlin");
expect(activePolls.has("Berlin")).toBe(true);
// @ts-ignore - access to private property for test purposes
const stopPollingSpy = vi.spyOn(pollingService as any, "_stopPollingForLocation");
pollingService.unsubscribeUser("user-2", "Berlin");
expect(stopPollingSpy).toHaveBeenCalledWith("Berlin");
expect(activePolls.has("Berlin")).toBe(false);
});
});
describe("Polling and Event Emission", () => {
it("should periodically poll the API and emit an event for all subscribers of that location", async () => {
const weatherData = { temp: 12, city: "London" };
mockedGetCurrentWeather.mockResolvedValue(weatherData as any);
pollingService.subscribeUser("user-london-1", "London");
pollingService.subscribeUser("user-london-2", "London");
await vi.advanceTimersByTimeAsync(0);
expect(mockedAppEventBus.emit).toHaveBeenCalledTimes(1);
expect(mockedAppEventBus.emit).toHaveBeenCalledWith(WEATHER_STATE_UPDATED_EVENT, {
weatherData,
subscribers: ["user-london-1", "user-london-2"],
});
await vi.advanceTimersByTimeAsync(10 * 60 * 1000);
expect(mockedAppEventBus.emit).toHaveBeenCalledTimes(2);
await vi.advanceTimersByTimeAsync(10 * 60 * 1000);
expect(mockedAppEventBus.emit).toHaveBeenCalledTimes(3);
});
});
describe("Automatic Location Change Handling (via USER_UPDATED_EVENT)", () => {
let userUpdateListener: (user: IUser) => void;
beforeEach(() => {
const onCall = mockedAppEventBus.on.mock.calls.find((call) => call[0] === USER_UPDATED_EVENT);
if (onCall) {
userUpdateListener = onCall[1];
}
});
it("should be listening for USER_UPDATED_EVENT", () => {
expect(userUpdateListener).toBeDefined();
expect(typeof userUpdateListener).toBe("function");
});
it("should automatically move a user's subscription when their location changes", () => {
const unsubscribeSpy = vi.spyOn(pollingService, "unsubscribeUser");
const subscribeSpy = vi.spyOn(pollingService, "subscribeUser");
pollingService.subscribeUser("user-moving", "Berlin");
const updatedUser = { uuid: "user-moving", location: "London" } as IUser;
userUpdateListener(updatedUser);
expect(unsubscribeSpy).toHaveBeenCalledOnce();
expect(unsubscribeSpy).toHaveBeenCalledWith("user-moving", "Berlin");
expect(subscribeSpy).toHaveBeenCalledTimes(2); // Once for Berlin, once for London
expect(subscribeSpy).toHaveBeenCalledWith("user-moving", "London");
});
it("should do nothing if the user's location has not changed", () => {
const unsubscribeSpy = vi.spyOn(pollingService, "unsubscribeUser");
pollingService.subscribeUser("user-staying", "Berlin");
const updatedUser = { uuid: "user-staying", location: "Berlin", name: "New Name" } as IUser;
userUpdateListener(updatedUser);
expect(unsubscribeSpy).not.toHaveBeenCalled();
});
});
});