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 ); }); }); });