342 lines
9.9 KiB
JavaScript
342 lines
9.9 KiB
JavaScript
// The Lucia project.
|
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
|
// Authors:
|
|
// imacat.yang@dsp.im (imacat), 2026/03/05
|
|
|
|
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
import { setActivePinia, createPinia } from "pinia";
|
|
import { http, HttpResponse } from "msw";
|
|
import { server } from "@/mocks/node.js";
|
|
import { findRequest, captureRequest } from "@/mocks/request-log.js";
|
|
|
|
vi.mock("@/module/apiError.js", () => ({
|
|
default: vi.fn(),
|
|
}));
|
|
|
|
// Mock login store to avoid its side effects
|
|
vi.mock("@/stores/login", async () => {
|
|
const { defineStore } = await import("pinia");
|
|
return {
|
|
useLoginStore: defineStore("loginStore", {
|
|
state: () => ({
|
|
userData: { username: "currentUser", name: "Current" },
|
|
}),
|
|
actions: {
|
|
getUserData: vi.fn(),
|
|
},
|
|
}),
|
|
};
|
|
});
|
|
|
|
import { useAcctMgmtStore } from "@/stores/acctMgmt";
|
|
|
|
describe("acctMgmtStore", () => {
|
|
let store;
|
|
|
|
beforeEach(() => {
|
|
setActivePinia(createPinia());
|
|
store = useAcctMgmtStore();
|
|
document.cookie = "luciaToken=fake-test-token";
|
|
});
|
|
|
|
it("has correct default state", () => {
|
|
expect(store.allUserAccountList).toEqual([]);
|
|
expect(store.isAcctMenuOpen).toBe(false);
|
|
});
|
|
|
|
describe("menu actions", () => {
|
|
it("openAcctMenu sets true", () => {
|
|
store.openAcctMenu();
|
|
expect(store.isAcctMenuOpen).toBe(true);
|
|
});
|
|
|
|
it("closeAcctMenu sets false", () => {
|
|
store.openAcctMenu();
|
|
store.closeAcctMenu();
|
|
expect(store.isAcctMenuOpen).toBe(false);
|
|
});
|
|
|
|
it("toggleIsAcctMenuOpen toggles", () => {
|
|
store.toggleIsAcctMenuOpen();
|
|
expect(store.isAcctMenuOpen).toBe(true);
|
|
store.toggleIsAcctMenuOpen();
|
|
expect(store.isAcctMenuOpen).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("setCurrentViewingUser", () => {
|
|
it("finds user by username", () => {
|
|
store.allUserAccountList = [
|
|
{ username: "alice", name: "Alice", detail: {} },
|
|
{ username: "bob", name: "Bob", detail: {} },
|
|
];
|
|
store.setCurrentViewingUser("bob");
|
|
expect(store.currentViewingUser.name).toBe("Bob");
|
|
});
|
|
});
|
|
|
|
describe("clearCurrentViewingUser", () => {
|
|
it("resets to empty user", () => {
|
|
store.currentViewingUser = { username: "test", detail: {} };
|
|
store.clearCurrentViewingUser();
|
|
expect(store.currentViewingUser.username).toBe("");
|
|
});
|
|
});
|
|
|
|
describe("createNewAccount", () => {
|
|
it("posts to /api/users and sets flag on success", async () => {
|
|
const randomPassword = crypto.randomUUID();
|
|
const user = { username: "newuser", password: randomPassword };
|
|
server.use(
|
|
http.post("/api/users", async ({ request }) => {
|
|
const body = await request.json();
|
|
captureRequest("POST", new URL(request.url).pathname, body);
|
|
return new HttpResponse(null, { status: 200 });
|
|
}),
|
|
);
|
|
|
|
await store.createNewAccount(user);
|
|
|
|
const reqs = findRequest("POST", "/api/users");
|
|
expect(reqs).toHaveLength(1);
|
|
expect(reqs[0].body).toEqual(user);
|
|
expect(store.isOneAccountJustCreate).toBe(true);
|
|
expect(store.justCreateUsername).toBe("newuser");
|
|
});
|
|
});
|
|
|
|
describe("deleteAccount", () => {
|
|
it("returns true on success", async () => {
|
|
server.use(
|
|
http.delete("/api/users/:username", ({ request }) => {
|
|
captureRequest("DELETE", new URL(request.url).pathname);
|
|
return new HttpResponse(null, { status: 200 });
|
|
}),
|
|
);
|
|
|
|
const result = await store.deleteAccount("alice");
|
|
|
|
const reqs = findRequest("DELETE", "/api/users/alice");
|
|
expect(reqs).toHaveLength(1);
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it("encodes special characters in username", async () => {
|
|
server.use(
|
|
http.delete("/api/users/:username", ({ request }) => {
|
|
captureRequest("DELETE", new URL(request.url).pathname);
|
|
return new HttpResponse(null, { status: 200 });
|
|
}),
|
|
);
|
|
|
|
await store.deleteAccount("user@domain/name");
|
|
|
|
const reqs = findRequest("DELETE",
|
|
"/api/users/user%40domain%2Fname");
|
|
expect(reqs).toHaveLength(1);
|
|
});
|
|
|
|
it("returns false on error", async () => {
|
|
server.use(
|
|
http.delete("/api/users/:username", () =>
|
|
new HttpResponse(null, { status: 500 }),
|
|
),
|
|
);
|
|
|
|
const result = await store.deleteAccount("alice");
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("editAccount", () => {
|
|
it("puts edited data", async () => {
|
|
const randomPassword = crypto.randomUUID();
|
|
const detail = {
|
|
username: "alice",
|
|
password: randomPassword,
|
|
name: "Alice",
|
|
is_active: true,
|
|
};
|
|
server.use(
|
|
http.put("/api/users/:username", async ({ request }) => {
|
|
const body = await request.json();
|
|
captureRequest("PUT", new URL(request.url).pathname, body);
|
|
return new HttpResponse(null, { status: 200 });
|
|
}),
|
|
);
|
|
|
|
const result = await store.editAccount("alice", detail);
|
|
|
|
const reqs = findRequest("PUT", "/api/users/alice");
|
|
expect(reqs).toHaveLength(1);
|
|
expect(reqs[0].body.password).toBe(randomPassword);
|
|
expect(result).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("addRoleToUser", () => {
|
|
it("puts role assignment", async () => {
|
|
server.use(
|
|
http.put("/api/users/:username/roles/:role", ({ request }) => {
|
|
captureRequest("PUT", new URL(request.url).pathname);
|
|
return new HttpResponse(null, { status: 200 });
|
|
}),
|
|
);
|
|
|
|
const result = await store.addRoleToUser("alice", "admin");
|
|
|
|
const reqs = findRequest("PUT", "/api/users/alice/roles/admin");
|
|
expect(reqs).toHaveLength(1);
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it("encodes special characters in username and role", async () => {
|
|
server.use(
|
|
http.put("/api/users/:username/roles/:role", ({ request }) => {
|
|
captureRequest("PUT", new URL(request.url).pathname);
|
|
return new HttpResponse(null, { status: 200 });
|
|
}),
|
|
);
|
|
|
|
await store.addRoleToUser("user@org", "role/special");
|
|
|
|
const reqs = findRequest("PUT",
|
|
"/api/users/user%40org/roles/role%2Fspecial");
|
|
expect(reqs).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe("deleteRoleToUser", () => {
|
|
it("deletes role", async () => {
|
|
server.use(
|
|
http.delete("/api/users/:username/roles/:role", ({ request }) => {
|
|
captureRequest("DELETE", new URL(request.url).pathname);
|
|
return new HttpResponse(null, { status: 200 });
|
|
}),
|
|
);
|
|
|
|
const result = await store.deleteRoleToUser("alice", "admin");
|
|
|
|
const reqs = findRequest("DELETE",
|
|
"/api/users/alice/roles/admin");
|
|
expect(reqs).toHaveLength(1);
|
|
expect(result).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("getUserDetail", () => {
|
|
it("fetches user and sets admin flag", async () => {
|
|
server.use(
|
|
http.get("/api/users/:username", () =>
|
|
HttpResponse.json({
|
|
username: "alice",
|
|
roles: [{ code: "admin" }],
|
|
}),
|
|
),
|
|
);
|
|
|
|
const result = await store.getUserDetail("alice");
|
|
|
|
expect(result).toBe(true);
|
|
expect(store.currentViewingUser.is_admin).toBe(true);
|
|
});
|
|
|
|
it("returns false on error", async () => {
|
|
server.use(
|
|
http.get("/api/users/:username", () =>
|
|
new HttpResponse(null, { status: 500 }),
|
|
),
|
|
);
|
|
|
|
const result = await store.getUserDetail("ghost");
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("hover state actions", () => {
|
|
beforeEach(() => {
|
|
store.allUserAccountList = [
|
|
{
|
|
username: "alice",
|
|
isDeleteHovered: false,
|
|
isRowHovered: false,
|
|
isEditHovered: false,
|
|
isDetailHovered: false,
|
|
},
|
|
];
|
|
});
|
|
|
|
it("changeIsDeleteHoveredByUser", () => {
|
|
store.changeIsDeleteHoveredByUser("alice", true);
|
|
expect(store.allUserAccountList[0].isDeleteHovered).toBe(true);
|
|
});
|
|
|
|
it("changeIsRowHoveredByUser", () => {
|
|
store.changeIsRowHoveredByUser("alice", true);
|
|
expect(store.allUserAccountList[0].isRowHovered).toBe(true);
|
|
});
|
|
|
|
it("changeIsEditHoveredByUser", () => {
|
|
store.changeIsEditHoveredByUser("alice", true);
|
|
expect(store.allUserAccountList[0].isEditHovered).toBe(true);
|
|
});
|
|
|
|
it("changeIsDetailHoveredByUser", () => {
|
|
store.changeIsDetailHoveredByUser("alice", true);
|
|
expect(store.allUserAccountList[0].isDetailHovered).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("moveCurrentLoginUserToFirstRow", () => {
|
|
it("moves logged-in user to first position", async () => {
|
|
const list = [
|
|
{ username: "alice", name: "Alice" },
|
|
{ username: "currentUser", name: "Current" },
|
|
{ username: "bob", name: "Bob" },
|
|
];
|
|
const result = await store.moveCurrentLoginUserToFirstRow(list);
|
|
expect(result.map((u) => u.username)).toEqual([
|
|
"currentUser",
|
|
"alice",
|
|
"bob",
|
|
]);
|
|
});
|
|
|
|
it("preserves list when logged-in user is not found", async () => {
|
|
const list = [
|
|
{ username: "alice", name: "Alice" },
|
|
{ username: "bob", name: "Bob" },
|
|
{ username: "charlie", name: "Charlie" },
|
|
];
|
|
const result = await store.moveCurrentLoginUserToFirstRow(list);
|
|
expect(result.map((u) => u.username)).toEqual([
|
|
"alice",
|
|
"bob",
|
|
"charlie",
|
|
]);
|
|
});
|
|
});
|
|
|
|
it("resetJustCreateFlag resets flag", () => {
|
|
store.isOneAccountJustCreate = true;
|
|
store.resetJustCreateFlag();
|
|
expect(store.isOneAccountJustCreate).toBe(false);
|
|
});
|
|
|
|
it("setShouldUpdateList sets boolean", () => {
|
|
store.setShouldUpdateList(true);
|
|
expect(store.shouldUpdateList).toBe(true);
|
|
});
|
|
|
|
it("updateSingleAccountPiniaState updates user", () => {
|
|
store.allUserAccountList = [{ username: "alice", name: "Old" }];
|
|
store.updateSingleAccountPiniaState({
|
|
username: "alice",
|
|
name: "New",
|
|
});
|
|
expect(store.allUserAccountList[0].name).toBe("New");
|
|
});
|
|
});
|