-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1aa1113
commit 54bddc0
Showing
12 changed files
with
810 additions
and
540 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
MQTT_BROKER=mqtt://localhost | ||
MQTT_USERNAME=username | ||
MQTT_PASSWORD=password | ||
LOG_LEVEL=info | ||
MQTT_TASK_INTERVAL=100 | ||
HA_DISCOVERY_PREFIX=homeassistant | ||
PORT=3000 | ||
AUTO_REQUEST_INTERVAL=100 | ||
WISUN_CONNECTOR_MODEL=BP35C2 | ||
ROUTE_B_ID=id | ||
ROUTE_B_PASSWORD=password |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { Entity } from "@/entity"; | ||
import { setupAvailability } from "@/manager/availabilityManager"; | ||
import { MqttClient } from "@/service/mqtt"; | ||
|
||
describe("setupAvailability", () => { | ||
const deviceId = "deviceId1"; | ||
let mockMqttClient: jest.Mocked<MqttClient>; | ||
let entities: Entity[]; | ||
|
||
beforeEach(() => { | ||
mockMqttClient = { | ||
publish: jest.fn(), | ||
} as unknown as jest.Mocked<MqttClient>; | ||
|
||
entities = [ | ||
{ id: "entity1", name: "Entity 1" }, | ||
{ id: "entity2", name: "Entity 2" }, | ||
] as Entity[]; | ||
}); | ||
|
||
afterEach(() => { | ||
jest.clearAllTimers(); | ||
}); | ||
|
||
it("pushOnline を呼び出すと全てのエンティティにオンライン状態を送信する", () => { | ||
const { pushOnline, close } = setupAvailability( | ||
deviceId, | ||
entities, | ||
mockMqttClient, | ||
); | ||
|
||
pushOnline(); | ||
|
||
expect(mockMqttClient.publish).toHaveBeenCalledTimes(entities.length); | ||
entities.forEach((entity) => { | ||
expect(mockMqttClient.publish).toHaveBeenCalledWith( | ||
expect.stringContaining(entity.id), | ||
"online", | ||
); | ||
}); | ||
|
||
close(); | ||
}); | ||
|
||
it("close を呼び出すと全てのエンティティにオフライン状態を送信する", () => { | ||
jest.useFakeTimers(); | ||
const { close } = setupAvailability(deviceId, entities, mockMqttClient); | ||
|
||
close(); | ||
|
||
expect(mockMqttClient.publish).toHaveBeenCalledTimes(entities.length); | ||
entities.forEach((entity) => { | ||
expect(mockMqttClient.publish).toHaveBeenCalledWith( | ||
expect.stringContaining(entity.id), | ||
"offline", | ||
); | ||
}); | ||
}); | ||
|
||
it("定期的にオンライン状態を送信する", () => { | ||
jest.useFakeTimers(); | ||
const { close } = setupAvailability(deviceId, entities, mockMqttClient); | ||
|
||
jest.advanceTimersByTime(10000); // Assume AVAILABILITY_INTERVAL is 10000ms | ||
|
||
expect(mockMqttClient.publish).toHaveBeenCalledTimes(entities.length); | ||
entities.forEach((entity) => { | ||
expect(mockMqttClient.publish).toHaveBeenCalledWith( | ||
expect.stringContaining(entity.id), | ||
"online", | ||
); | ||
}); | ||
|
||
jest.advanceTimersByTime(10000); | ||
|
||
expect(mockMqttClient.publish).toHaveBeenCalledTimes(entities.length * 2); | ||
|
||
close(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import initializeMqttClient from "@/service/mqtt"; | ||
import mqttjs, { MqttClient } from "mqtt"; | ||
import { setTimeout } from "timers/promises"; | ||
|
||
// 必要なモック関数 | ||
const mockSubscribeAsync = jest.fn(); | ||
const mockPublishAsync = jest.fn(); | ||
const mockEndAsync = jest.fn(); | ||
const mockOn = jest.fn< | ||
ReturnType<MqttClient["on"]>, | ||
Parameters<MqttClient["on"]> | ||
>(); | ||
|
||
jest.mock("mqtt", () => { | ||
return { | ||
connectAsync: jest.fn(), | ||
}; | ||
}); | ||
|
||
beforeEach(() => { | ||
jest.resetModules(); | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
describe("initializeMqttClient", () => { | ||
test("MQTTクライアントが正常に接続される", async () => { | ||
const mockConnectAsync = mqttjs.connectAsync as jest.Mock; | ||
mockConnectAsync.mockResolvedValue({ | ||
subscribeAsync: mockSubscribeAsync, | ||
publishAsync: mockPublishAsync, | ||
endAsync: mockEndAsync, | ||
on: mockOn, | ||
}); | ||
|
||
const mqtt = await initializeMqttClient(); | ||
|
||
await mqtt.close(); | ||
|
||
// MQTTクライアントの接続確認 | ||
expect(mockConnectAsync).toHaveBeenCalledWith( | ||
process.env.MQTT_BROKER, | ||
expect.objectContaining({ | ||
username: process.env.MQTT_USERNAME, | ||
password: process.env.MQTT_PASSWORD, | ||
}), | ||
); | ||
}); | ||
|
||
test("publishがタスクキューに追加される", async () => { | ||
const mockConnectAsync = mqttjs.connectAsync as jest.Mock; | ||
mockConnectAsync.mockResolvedValue({ | ||
subscribeAsync: mockSubscribeAsync, | ||
publishAsync: mockPublishAsync, | ||
endAsync: mockEndAsync, | ||
on: mockOn, | ||
}); | ||
|
||
const mqtt = await initializeMqttClient(); | ||
|
||
// publishを呼び出す | ||
mqtt.publish("topic/publish", "test message", { retain: true }); | ||
|
||
// タスクキューの状態を確認 | ||
expect(mqtt.taskQueueSize).toBe(1); | ||
|
||
await mqtt.close(true); | ||
}); | ||
|
||
test("close(true)を呼び出すとタスクキューが空になりクライアントが終了する", async () => { | ||
const mockConnectAsync = mqttjs.connectAsync as jest.Mock; | ||
mockConnectAsync.mockResolvedValue({ | ||
subscribeAsync: mockSubscribeAsync, | ||
publishAsync: mockPublishAsync, | ||
endAsync: mockEndAsync, | ||
on: mockOn, | ||
}); | ||
mockPublishAsync.mockImplementation(async () => { | ||
await setTimeout(100); | ||
return Promise.resolve(); | ||
}); | ||
|
||
const mqtt = await initializeMqttClient(); | ||
|
||
mqtt.publish("topic", "message"); | ||
|
||
// closeを呼び出す | ||
await mqtt.close(true); | ||
|
||
// タスクキューが空になっていることを確認 | ||
expect(mqtt.taskQueueSize).toBe(0); | ||
|
||
// MQTTクライアントの終了を確認 | ||
expect(mockEndAsync).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
test("close()を呼び出すとタスクキューが残っていてもクライアントが終了する", async () => { | ||
const mockConnectAsync = mqttjs.connectAsync as jest.Mock; | ||
mockConnectAsync.mockResolvedValue({ | ||
subscribeAsync: mockSubscribeAsync, | ||
publishAsync: mockPublishAsync, | ||
endAsync: mockEndAsync, | ||
on: mockOn, | ||
}); | ||
mockPublishAsync.mockImplementation(async () => { | ||
await setTimeout(100); | ||
return Promise.resolve(); | ||
}); | ||
|
||
const mqtt = await initializeMqttClient(); | ||
|
||
mqtt.publish("topic", "message"); | ||
|
||
// closeを呼び出す | ||
await mqtt.close(); | ||
|
||
// タスクキューが空になっていないことを確認 | ||
expect(mqtt.taskQueueSize).toBe(1); | ||
|
||
// MQTTクライアントの終了を確認 | ||
expect(mockEndAsync).toHaveBeenCalledTimes(1); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import * as fs from "fs"; | ||
import { pathsToModuleNameMapper } from "ts-jest"; | ||
|
||
const tsconfig = JSON.parse(fs.readFileSync("./tsconfig.json", "utf-8")); | ||
|
||
/** @type {import('ts-jest').JestConfigWithTsJest} **/ | ||
export default { | ||
testEnvironment: "node", | ||
moduleNameMapper: pathsToModuleNameMapper(tsconfig.compilerOptions.paths, { | ||
prefix: "<rootDir>/", | ||
}), | ||
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"], | ||
transform: { | ||
"^.+\\.(t|j)sx?$": "@swc/jest", | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import env from "@/env"; | ||
import { config } from "dotenv"; | ||
import { Writable } from "type-fest"; | ||
|
||
config({ path: "./.env.test" }); | ||
|
||
export type MutableEnv = Partial<Writable<typeof env>>; | ||
|
||
let overrideEnv: MutableEnv = {}; | ||
|
||
beforeEach(() => { | ||
overrideEnv = {}; | ||
}); | ||
|
||
jest.mock("@/env", () => { | ||
const { default: defaultEnv } = jest.requireActual<{ default: typeof env }>( | ||
"@/env", | ||
); | ||
return new Proxy( | ||
{ ...defaultEnv }, | ||
{ | ||
get(target, prop: keyof typeof env) { | ||
if (prop in overrideEnv) { | ||
return overrideEnv[prop]; | ||
} | ||
return target[prop]; | ||
}, | ||
set( | ||
target, | ||
prop: keyof typeof env, | ||
value: (typeof env)[keyof typeof env], | ||
) { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any | ||
(overrideEnv as any)[prop] = value; | ||
return true; | ||
}, | ||
}, | ||
); | ||
}); |
Oops, something went wrong.