feat(api): add workspace member management endpoints (#556)
Some checks are pending
ci/woodpecker/push/api Pipeline is running

Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #556.
This commit is contained in:
2026-02-28 18:01:05 +00:00
committed by jason.woltje
parent 20f914ea85
commit 8388d49786
9 changed files with 718 additions and 8 deletions

View File

@@ -3,6 +3,7 @@ import { Test, TestingModule } from "@nestjs/testing";
import { WorkspacesController } from "./workspaces.controller";
import { WorkspacesService } from "./workspaces.service";
import { AuthGuard } from "../auth/guards/auth.guard";
import { WorkspaceGuard, PermissionGuard } from "../common/guards";
import { WorkspaceMemberRole } from "@prisma/client";
import type { AuthUser } from "@mosaic/shared";
@@ -12,6 +13,9 @@ describe("WorkspacesController", () => {
const mockWorkspacesService = {
getUserWorkspaces: vi.fn(),
addMember: vi.fn(),
updateMemberRole: vi.fn(),
removeMember: vi.fn(),
};
const mockUser: AuthUser = {
@@ -32,6 +36,10 @@ describe("WorkspacesController", () => {
})
.overrideGuard(AuthGuard)
.useValue({ canActivate: () => true })
.overrideGuard(WorkspaceGuard)
.useValue({ canActivate: () => true })
.overrideGuard(PermissionGuard)
.useValue({ canActivate: () => true })
.compile();
controller = module.get<WorkspacesController>(WorkspacesController);
@@ -72,4 +80,70 @@ describe("WorkspacesController", () => {
await expect(controller.getUserWorkspaces(mockUser)).rejects.toThrow("Database error");
});
});
describe("POST /api/workspaces/:id/members", () => {
it("should call service with workspace id, actor id, and add member dto", async () => {
const workspaceId = "ws-1";
const addMemberDto = {
userId: "user-2",
role: WorkspaceMemberRole.MEMBER,
};
const mockMember = {
workspaceId,
userId: "user-2",
role: WorkspaceMemberRole.MEMBER,
joinedAt: new Date("2026-02-01"),
};
mockWorkspacesService.addMember.mockResolvedValueOnce(mockMember);
const result = await controller.addMember(workspaceId, addMemberDto, mockUser);
expect(result).toEqual(mockMember);
expect(service.addMember).toHaveBeenCalledWith(workspaceId, mockUser.id, addMemberDto);
});
});
describe("PATCH /api/workspaces/:id/members/:userId", () => {
it("should call service with workspace id, actor id, target user id, and role dto", async () => {
const workspaceId = "ws-1";
const targetUserId = "user-2";
const updateRoleDto = {
role: WorkspaceMemberRole.ADMIN,
};
const mockMember = {
workspaceId,
userId: targetUserId,
role: WorkspaceMemberRole.ADMIN,
joinedAt: new Date("2026-02-01"),
};
mockWorkspacesService.updateMemberRole.mockResolvedValueOnce(mockMember);
const result = await controller.updateMemberRole(
workspaceId,
targetUserId,
updateRoleDto,
mockUser
);
expect(result).toEqual(mockMember);
expect(service.updateMemberRole).toHaveBeenCalledWith(
workspaceId,
mockUser.id,
targetUserId,
updateRoleDto
);
});
});
describe("DELETE /api/workspaces/:id/members/:userId", () => {
it("should call service with workspace id, actor id, and target user id", async () => {
const workspaceId = "ws-1";
const targetUserId = "user-2";
mockWorkspacesService.removeMember.mockResolvedValueOnce(undefined);
await controller.removeMember(workspaceId, targetUserId, mockUser);
expect(service.removeMember).toHaveBeenCalledWith(workspaceId, mockUser.id, targetUserId);
});
});
});