Files
frontend/test/data/zwave_js-credentials.test.ts
AlCalzone 211579eade Add Z-Wave credential mangement (#51591)
* first rough draft of Z-Wave credential mangement

* separate user and credentials, error handling, dialog tweaks

* align with upstream API changes, improve error handling

* align more with Matter, use lock entity for services

* remove get_credential_status service

* address review feedback, clarify user types

* user_index -> user_id, fix some pending states

* address review feedback

* clean up unused code, strongly type credential types

* Clear -> Delete, drop icons

* Simplify flow to 1 PIN/Password credential per user

* cleanup, comments, etc.

* address review feedback

* do not show existing credential data

* fix lint errors after branch update

* ignore non-enterable credential types when editing user
2026-05-20 12:05:13 +03:00

304 lines
8.1 KiB
TypeScript

import { describe, it, expect, vi, afterEach } from "vitest";
import {
getZwaveCredentialCapabilities,
getZwaveUsers,
setZwaveUser,
deleteZwaveUser,
deleteZwaveAllUsers,
setZwaveCredential,
deleteZwaveCredential,
} from "../../src/data/zwave_js-credentials";
import type { HomeAssistant } from "../../src/types";
const ENTITY_ID = "lock.zwave_front_door";
const mockHass = (response?: unknown) =>
({
callService: vi
.fn()
.mockResolvedValue(
response !== undefined ? { response } : { response: undefined }
),
}) as unknown as HomeAssistant;
describe("zwave_js-credentials", () => {
afterEach(() => {
vi.restoreAllMocks();
});
describe("getZwaveCredentialCapabilities", () => {
it("calls the correct service with entity_id target and returnResponse", async () => {
const capabilities = {
supports_user_management: true,
max_users: 20,
supported_user_types: ["general", "programming", "remote_only"],
max_user_name_length: 20,
supported_credential_rules: ["single", "dual", "triple"],
supported_credential_types: {
pin_code: {
num_slots: 10,
min_length: 4,
max_length: 10,
supports_learn: false,
},
rfid_code: {
num_slots: 5,
min_length: 1,
max_length: 32,
supports_learn: true,
},
},
};
const hass = mockHass({ [ENTITY_ID]: capabilities });
const result = await getZwaveCredentialCapabilities(hass, ENTITY_ID);
expect(hass.callService).toHaveBeenCalledWith(
"zwave_js",
"get_credential_capabilities",
{},
{ entity_id: ENTITY_ID },
false,
true
);
expect(result).toEqual(capabilities);
});
it("propagates errors from callService", async () => {
const hass = {
callService: vi
.fn()
.mockRejectedValue(new Error("Service unavailable")),
} as unknown as HomeAssistant;
await expect(
getZwaveCredentialCapabilities(hass, ENTITY_ID)
).rejects.toThrow("Service unavailable");
});
});
describe("getZwaveUsers", () => {
it("calls the correct service with entity_id target and returnResponse", async () => {
const usersResponse = {
max_users: 20,
users: [
{
user_id: 1,
user_name: "Alice",
active: true,
user_type: "general",
credential_rule: "single",
credentials: [
{ type: "pin_code", slot: 1 },
{ type: "finger_biometric", slot: 1 },
],
},
],
};
const hass = mockHass({ [ENTITY_ID]: usersResponse });
const result = await getZwaveUsers(hass, ENTITY_ID);
expect(hass.callService).toHaveBeenCalledWith(
"zwave_js",
"get_users",
{},
{ entity_id: ENTITY_ID },
false,
true
);
expect(result).toEqual(usersResponse);
expect(result.users).toHaveLength(1);
expect(result.users[0].user_name).toBe("Alice");
expect(result.users[0].credentials).toHaveLength(2);
});
it("propagates errors from callService", async () => {
const hass = {
callService: vi.fn().mockRejectedValue(new Error("Not supported")),
} as unknown as HomeAssistant;
await expect(getZwaveUsers(hass, ENTITY_ID)).rejects.toThrow(
"Not supported"
);
});
});
describe("setZwaveUser", () => {
const setUserResponse = (inner: unknown) =>
({
callService: vi
.fn()
.mockResolvedValue({ response: { [ENTITY_ID]: inner } }),
}) as unknown as HomeAssistant;
it("unwraps the entity_id-keyed response and returns user_id", async () => {
const hass = setUserResponse({ user_id: 5 });
const result = await setZwaveUser(hass, ENTITY_ID, {
user_id: 5,
user_name: "Bob",
user_type: "general",
});
expect(hass.callService).toHaveBeenCalledWith(
"zwave_js",
"set_user",
{ user_id: 5, user_name: "Bob", user_type: "general" },
{ entity_id: ENTITY_ID },
false,
true
);
expect(result.user_id).toBe(5);
});
it("returns the allocated user_id when auto-finding", async () => {
const hass = setUserResponse({ user_id: 1 });
const result = await setZwaveUser(hass, ENTITY_ID, {
user_name: "Carol",
});
expect(hass.callService).toHaveBeenCalledWith(
"zwave_js",
"set_user",
{ user_name: "Carol" },
{ entity_id: ENTITY_ID },
false,
true
);
expect(result.user_id).toBe(1);
});
});
describe("deleteZwaveUser", () => {
it("calls the correct service with user_id and entity_id target", async () => {
const hass = mockHass();
await deleteZwaveUser(hass, ENTITY_ID, 2);
expect(hass.callService).toHaveBeenCalledWith(
"zwave_js",
"delete_user",
{ user_id: 2 },
{ entity_id: ENTITY_ID },
false
);
});
});
describe("deleteZwaveAllUsers", () => {
it("calls delete_all_users with entity_id target and no params", async () => {
const hass = mockHass();
await deleteZwaveAllUsers(hass, ENTITY_ID);
expect(hass.callService).toHaveBeenCalledWith(
"zwave_js",
"delete_all_users",
{},
{ entity_id: ENTITY_ID },
false
);
});
});
describe("setZwaveCredential", () => {
const setCredResponse = (inner: unknown) =>
({
callService: vi
.fn()
.mockResolvedValue({ response: { [ENTITY_ID]: inner } }),
}) as unknown as HomeAssistant;
it("unwraps the entity_id-keyed response", async () => {
const credentialResult = {
credential_slot: 1,
user_id: 3,
};
const hass = setCredResponse(credentialResult);
const result = await setZwaveCredential(hass, ENTITY_ID, {
user_id: 3,
credential_type: "pin_code",
credential_data: "1234",
});
expect(hass.callService).toHaveBeenCalledWith(
"zwave_js",
"set_credential",
{
user_id: 3,
credential_type: "pin_code",
credential_data: "1234",
},
{ entity_id: ENTITY_ID },
false,
true
);
expect(result).toEqual(credentialResult);
expect(result.credential_slot).toBe(1);
expect(result.user_id).toBe(3);
});
it("forwards explicit credential_slot when provided", async () => {
const hass = setCredResponse({ credential_slot: 5, user_id: 3 });
await setZwaveCredential(hass, ENTITY_ID, {
user_id: 3,
credential_type: "pin_code",
credential_data: "1234",
credential_slot: 5,
});
expect(hass.callService).toHaveBeenCalledWith(
"zwave_js",
"set_credential",
{
user_id: 3,
credential_type: "pin_code",
credential_data: "1234",
credential_slot: 5,
},
{ entity_id: ENTITY_ID },
false,
true
);
});
it("propagates errors from callService", async () => {
const hass = {
callService: vi.fn().mockRejectedValue(new Error("Slot full")),
} as unknown as HomeAssistant;
await expect(
setZwaveCredential(hass, ENTITY_ID, {
user_id: 1,
credential_type: "pin_code",
credential_data: "1234",
})
).rejects.toThrow("Slot full");
});
});
describe("deleteZwaveCredential", () => {
it("calls the correct service with params and entity_id target", async () => {
const hass = mockHass();
await deleteZwaveCredential(hass, ENTITY_ID, {
user_id: 2,
credential_type: "pin_code",
credential_slot: 1,
});
expect(hass.callService).toHaveBeenCalledWith(
"zwave_js",
"delete_credential",
{ user_id: 2, credential_type: "pin_code", credential_slot: 1 },
{ entity_id: ENTITY_ID },
false
);
});
});
});