ui(pool): pick an account's users from its own Edit
Each account row in the Account pool card has a pencil: tick the users whose sessions run on it (one moves off any other account, unticking sends one back to the balancer). PUT /api/accounts/:name/users saves the set in one transaction, audits each change and switches a Claude login into each added user's home at once. The card-level switch button and pinned list are gone.
This commit is contained in:
@@ -5,8 +5,8 @@
|
||||
* admin adds an account only with their own password typed (the token goes
|
||||
* in once and is not shown back) — a saved login moved in (the server's own
|
||||
* included), or a setup-token — and removes one the same way, and an analyst
|
||||
* sees no add or remove control. An admin switches a user to one account and
|
||||
* back to automatic, each with their own password; it shows on the accounts.
|
||||
* sees no add or remove control. An account's Edit ticks the users who run on
|
||||
* it, saved with the admin's own password; the users show on the account.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
@@ -20,7 +20,7 @@ const accountPool = vi.hoisted(() => ({
|
||||
add: vi.fn(),
|
||||
remove: vi.fn(),
|
||||
profiles: vi.fn(),
|
||||
pin: vi.fn(),
|
||||
setUsers: vi.fn(),
|
||||
}));
|
||||
const users = vi.hoisted(() => ({ list: vi.fn() }));
|
||||
vi.mock("../../../lib/api", async (importOriginal) => {
|
||||
@@ -168,53 +168,42 @@ describe("AccountPoolCard", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("pins a user to an account and unpins them, with the admin's own password", async () => {
|
||||
it("sets who runs on an account from its Edit, with the admin's own password", async () => {
|
||||
accountPool.list.mockResolvedValue({
|
||||
...POOL,
|
||||
pins: { claude: [{ userId: 8, username: "leo", account: "team-1" }], codex: [] },
|
||||
pins: {
|
||||
claude: [
|
||||
{ userId: 8, username: "leo", account: "team-1" },
|
||||
{ userId: 7, username: "mia", account: "team-0" },
|
||||
],
|
||||
codex: [],
|
||||
},
|
||||
});
|
||||
accountPool.pin.mockResolvedValue({
|
||||
pin: { userId: 7, username: "mia", account: "team-1" },
|
||||
switched: "switched",
|
||||
accountPool.setUsers.mockResolvedValue({
|
||||
added: [{ userId: 7, username: "mia", switched: "switched" }],
|
||||
removed: [{ userId: 8, username: "leo" }],
|
||||
});
|
||||
const user = userEvent.setup();
|
||||
render(<AccountPoolCard isAdmin />);
|
||||
expect(await screen.findByText("Used by: leo")).toBeInTheDocument();
|
||||
await user.click(screen.getByRole("button", { name: /switch a user's account/i }));
|
||||
expect(screen.queryByRole("button", { name: /switch a user/i })).toBeNull();
|
||||
await user.click(screen.getByRole("button", { name: "Edit team-1" }));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
await waitFor(() => expect(screen.getByRole("option", { name: "mia" })).toBeInTheDocument());
|
||||
await user.selectOptions(screen.getByLabelText("User"), "7");
|
||||
await user.selectOptions(screen.getByLabelText("Account"), "team-1");
|
||||
const confirm = Array.from(dialog.querySelectorAll("button")).find(
|
||||
(b) => b.textContent === "Switch"
|
||||
const leo = await screen.findByRole("checkbox", { name: /leo/ });
|
||||
const mia = screen.getByRole("checkbox", { name: /mia/ });
|
||||
expect(leo).toHaveAttribute("aria-checked", "true");
|
||||
expect(mia).toHaveAttribute("aria-checked", "false");
|
||||
expect(screen.getByText("now on team-0")).toBeInTheDocument();
|
||||
await user.click(mia);
|
||||
await user.click(leo);
|
||||
const save = Array.from(dialog.querySelectorAll("button")).find(
|
||||
(b) => b.textContent === "Save"
|
||||
)!;
|
||||
expect(confirm).toBeDisabled();
|
||||
expect(save).toBeDisabled();
|
||||
await user.type(screen.getByLabelText("Your password"), "boss-pass");
|
||||
await user.click(confirm);
|
||||
await user.click(save);
|
||||
await waitFor(() =>
|
||||
expect(accountPool.pin).toHaveBeenCalledWith("claude", 7, "team-1", "boss-pass")
|
||||
);
|
||||
});
|
||||
|
||||
it("unpins from the pinned list", async () => {
|
||||
accountPool.list.mockResolvedValue({
|
||||
...POOL,
|
||||
pins: { claude: [{ userId: 8, username: "leo", account: "team-1" }], codex: [] },
|
||||
});
|
||||
accountPool.pin.mockResolvedValue({
|
||||
pin: { userId: 8, username: "leo", account: null },
|
||||
switched: null,
|
||||
});
|
||||
const user = userEvent.setup();
|
||||
render(<AccountPoolCard isAdmin />);
|
||||
await user.click(await screen.findByRole("button", { name: "Back to automatic for leo" }));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
await user.type(screen.getByLabelText("Your password"), "boss-pass");
|
||||
await user.click(
|
||||
Array.from(dialog.querySelectorAll("button")).find((b) => b.textContent === "Switch")!
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(accountPool.pin).toHaveBeenCalledWith("claude", 8, null, "boss-pass")
|
||||
expect(accountPool.setUsers).toHaveBeenCalledWith("claude", "team-1", [7], "boss-pass")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user