Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions defaultmodules/weather/node_helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const Log = require("logger");

module.exports = NodeHelper.create({
providers: {},
lastData: {},

start () {
Log.log(`Starting node helper for: ${this.name}`);
Expand Down Expand Up @@ -37,6 +38,10 @@ module.exports = NodeHelper.create({
instanceId,
locationName: this.providers[instanceId].locationName
});
// Push cached data immediately so reconnecting clients don't wait for next scheduled fetch
if (this.lastData[instanceId]) {
this.sendSocketNotification("WEATHER_DATA", this.lastData[instanceId]);
}
return;
}

Expand All @@ -53,11 +58,9 @@ module.exports = NodeHelper.create({
provider.setCallbacks(
(data) => {
// On data received
this.sendSocketNotification("WEATHER_DATA", {
instanceId,
type: config.type,
data
});
const payload = { instanceId, type: config.type, data };
this.lastData[instanceId] = payload;
this.sendSocketNotification("WEATHER_DATA", payload);
},
(errorInfo) => {
// On error
Expand Down Expand Up @@ -101,6 +104,7 @@ module.exports = NodeHelper.create({
Log.log(`Stopping weather provider for instance ${instanceId}`);
provider.stop();
delete this.providers[instanceId];
delete this.lastData[instanceId];
} else {
Log.warn(`No provider found for instance ${instanceId}`);
}
Expand Down
116 changes: 116 additions & 0 deletions tests/unit/modules/default/weather/node_helper_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import Module from "node:module";
import { afterEach, describe, expect, it, vi } from "vitest";

/**
* Creates a fresh weather node helper instance with isolated mocks.
* @returns {Promise<object>} The mocked weather node helper.
*/
async function loadWeatherNodeHelper () {
vi.resetModules();

const loggerMock = {
log: vi.fn(),
warn: vi.fn(),
error: vi.fn()
};
const originalRequire = Module.prototype.require;

Module.prototype.require = function (id) {
if (id === "node_helper") {
return {
create: vi.fn((definition) => definition)
};
}

if (id === "logger") {
return loggerMock;
}

return originalRequire.apply(this, arguments);
};

let helper;
try {
const helperModule = await import("../../../../../defaultmodules/weather/node_helper");
helper = helperModule.default || helperModule;
} finally {
Module.prototype.require = originalRequire;
}

helper.providers = {};
helper.lastData = {};
helper.sendSocketNotification = vi.fn();

return helper;
}

afterEach(() => {
vi.resetAllMocks();
vi.resetModules();
});

describe("weather node_helper reconnect handling", () => {
it("re-sends cached weather data when a client reconnects", async () => {
const helper = await loadWeatherNodeHelper();
const instanceId = "weather-current";
const cachedPayload = {
instanceId,
type: "current",
data: { temperature: 8.5 }
};

helper.providers[instanceId] = { locationName: "Munich, BY" };
helper.lastData[instanceId] = cachedPayload;

await helper.initWeatherProvider({
weatherProvider: "openmeteo",
instanceId,
type: "current"
});

expect(helper.sendSocketNotification).toHaveBeenNthCalledWith(1, "WEATHER_INITIALIZED", {
instanceId,
locationName: "Munich, BY"
});
expect(helper.sendSocketNotification).toHaveBeenNthCalledWith(2, "WEATHER_DATA", cachedPayload);
expect(helper.sendSocketNotification).toHaveBeenCalledTimes(2);
});

it("does not send WEATHER_DATA on reconnect when no cached payload exists", async () => {
const helper = await loadWeatherNodeHelper();
const instanceId = "weather-current";

helper.providers[instanceId] = { locationName: "Munich, BY" };

await helper.initWeatherProvider({
weatherProvider: "openmeteo",
instanceId,
type: "current"
});

expect(helper.sendSocketNotification).toHaveBeenCalledWith("WEATHER_INITIALIZED", {
instanceId,
locationName: "Munich, BY"
});
expect(helper.sendSocketNotification).toHaveBeenCalledTimes(1);
});

it("cleans up provider and cached data when stopping an instance", async () => {
const helper = await loadWeatherNodeHelper();
const instanceId = "weather-current";
const stop = vi.fn();

helper.providers[instanceId] = { stop };
helper.lastData[instanceId] = {
instanceId,
type: "current",
data: { temperature: 8.5 }
};

helper.stopWeatherProvider(instanceId);

expect(stop).toHaveBeenCalledTimes(1);
expect(helper.providers[instanceId]).toBeUndefined();
expect(helper.lastData[instanceId]).toBeUndefined();
});
});
Loading