feat(#37-41): Add domains, ideas, relationships, agents, widgets schema
Schema additions for issues #37-41: New models: - Domain (#37): Life domains (work, marriage, homelab, etc.) - Idea (#38): Brain dumps with pgvector embeddings - Relationship (#39): Generic entity linking (blocks, depends_on) - Agent (#40): ClawdBot agent tracking with metrics - AgentSession (#40): Conversation session tracking - WidgetDefinition (#41): HUD widget registry - UserLayout (#41): Per-user dashboard configuration Updated models: - Task, Event, Project: Added domainId foreign key - User, Workspace: Added new relations New enums: - IdeaStatus: CAPTURED, PROCESSING, ACTIONABLE, ARCHIVED, DISCARDED - RelationshipType: BLOCKS, BLOCKED_BY, DEPENDS_ON, etc. - AgentStatus: IDLE, WORKING, WAITING, ERROR, TERMINATED - EntityType: Added IDEA, DOMAIN Migration: 20260129182803_add_domains_ideas_agents_widgets
This commit is contained in:
383
apps/api/src/activity/activity.controller.spec.ts
Normal file
383
apps/api/src/activity/activity.controller.spec.ts
Normal file
@@ -0,0 +1,383 @@
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { ActivityController } from "./activity.controller";
|
||||
import { ActivityService } from "./activity.service";
|
||||
import { ActivityAction, EntityType } from "@prisma/client";
|
||||
import type { QueryActivityLogDto } from "./dto";
|
||||
import { AuthGuard } from "../auth/guards/auth.guard";
|
||||
import { ExecutionContext } from "@nestjs/common";
|
||||
|
||||
describe("ActivityController", () => {
|
||||
let controller: ActivityController;
|
||||
let service: ActivityService;
|
||||
|
||||
const mockActivityService = {
|
||||
findAll: vi.fn(),
|
||||
findOne: vi.fn(),
|
||||
getAuditTrail: vi.fn(),
|
||||
};
|
||||
|
||||
const mockAuthGuard = {
|
||||
canActivate: vi.fn((context: ExecutionContext) => {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
request.user = {
|
||||
id: "user-123",
|
||||
workspaceId: "workspace-123",
|
||||
email: "test@example.com",
|
||||
};
|
||||
return true;
|
||||
}),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [ActivityController],
|
||||
providers: [
|
||||
{
|
||||
provide: ActivityService,
|
||||
useValue: mockActivityService,
|
||||
},
|
||||
],
|
||||
})
|
||||
.overrideGuard(AuthGuard)
|
||||
.useValue(mockAuthGuard)
|
||||
.compile();
|
||||
|
||||
controller = module.get<ActivityController>(ActivityController);
|
||||
service = module.get<ActivityService>(ActivityService);
|
||||
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("findAll", () => {
|
||||
const mockPaginatedResult = {
|
||||
data: [
|
||||
{
|
||||
id: "activity-1",
|
||||
workspaceId: "workspace-123",
|
||||
userId: "user-123",
|
||||
action: ActivityAction.CREATED,
|
||||
entityType: EntityType.TASK,
|
||||
entityId: "task-123",
|
||||
details: {},
|
||||
createdAt: new Date("2024-01-01"),
|
||||
user: {
|
||||
id: "user-123",
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
},
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
total: 1,
|
||||
page: 1,
|
||||
limit: 50,
|
||||
totalPages: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const mockRequest = {
|
||||
user: {
|
||||
id: "user-123",
|
||||
workspaceId: "workspace-123",
|
||||
email: "test@example.com",
|
||||
},
|
||||
};
|
||||
|
||||
it("should return paginated activity logs using authenticated user's workspaceId", async () => {
|
||||
const query: QueryActivityLogDto = {
|
||||
workspaceId: "workspace-123",
|
||||
page: 1,
|
||||
limit: 50,
|
||||
};
|
||||
|
||||
mockActivityService.findAll.mockResolvedValue(mockPaginatedResult);
|
||||
|
||||
const result = await controller.findAll(query, mockRequest);
|
||||
|
||||
expect(result).toEqual(mockPaginatedResult);
|
||||
expect(mockActivityService.findAll).toHaveBeenCalledWith({
|
||||
...query,
|
||||
workspaceId: "workspace-123",
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle query with filters", async () => {
|
||||
const query: QueryActivityLogDto = {
|
||||
workspaceId: "workspace-123",
|
||||
userId: "user-123",
|
||||
action: ActivityAction.CREATED,
|
||||
entityType: EntityType.TASK,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
};
|
||||
|
||||
mockActivityService.findAll.mockResolvedValue(mockPaginatedResult);
|
||||
|
||||
await controller.findAll(query, mockRequest);
|
||||
|
||||
expect(mockActivityService.findAll).toHaveBeenCalledWith({
|
||||
...query,
|
||||
workspaceId: "workspace-123",
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle query with date range", async () => {
|
||||
const startDate = new Date("2024-01-01");
|
||||
const endDate = new Date("2024-01-31");
|
||||
|
||||
const query: QueryActivityLogDto = {
|
||||
workspaceId: "workspace-123",
|
||||
startDate,
|
||||
endDate,
|
||||
page: 1,
|
||||
limit: 50,
|
||||
};
|
||||
|
||||
mockActivityService.findAll.mockResolvedValue(mockPaginatedResult);
|
||||
|
||||
await controller.findAll(query, mockRequest);
|
||||
|
||||
expect(mockActivityService.findAll).toHaveBeenCalledWith({
|
||||
...query,
|
||||
workspaceId: "workspace-123",
|
||||
});
|
||||
});
|
||||
|
||||
it("should use user's workspaceId even if query provides different one", async () => {
|
||||
const query: QueryActivityLogDto = {
|
||||
workspaceId: "different-workspace",
|
||||
page: 1,
|
||||
limit: 50,
|
||||
};
|
||||
|
||||
mockActivityService.findAll.mockResolvedValue(mockPaginatedResult);
|
||||
|
||||
await controller.findAll(query, mockRequest);
|
||||
|
||||
// Should use authenticated user's workspaceId, not query's
|
||||
expect(mockActivityService.findAll).toHaveBeenCalledWith({
|
||||
...query,
|
||||
workspaceId: "workspace-123",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("findOne", () => {
|
||||
const mockActivity = {
|
||||
id: "activity-123",
|
||||
workspaceId: "workspace-123",
|
||||
userId: "user-123",
|
||||
action: ActivityAction.CREATED,
|
||||
entityType: EntityType.TASK,
|
||||
entityId: "task-123",
|
||||
details: {},
|
||||
createdAt: new Date(),
|
||||
user: {
|
||||
id: "user-123",
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
},
|
||||
};
|
||||
|
||||
const mockRequest = {
|
||||
user: {
|
||||
id: "user-123",
|
||||
workspaceId: "workspace-123",
|
||||
email: "test@example.com",
|
||||
},
|
||||
};
|
||||
|
||||
it("should return a single activity log using authenticated user's workspaceId", async () => {
|
||||
mockActivityService.findOne.mockResolvedValue(mockActivity);
|
||||
|
||||
const result = await controller.findOne("activity-123", mockRequest);
|
||||
|
||||
expect(result).toEqual(mockActivity);
|
||||
expect(mockActivityService.findOne).toHaveBeenCalledWith(
|
||||
"activity-123",
|
||||
"workspace-123"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return null if activity not found", async () => {
|
||||
mockActivityService.findOne.mockResolvedValue(null);
|
||||
|
||||
const result = await controller.findOne("nonexistent", mockRequest);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should throw error if user workspaceId is missing", async () => {
|
||||
const requestWithoutWorkspace = {
|
||||
user: {
|
||||
id: "user-123",
|
||||
email: "test@example.com",
|
||||
},
|
||||
};
|
||||
|
||||
await expect(
|
||||
controller.findOne("activity-123", requestWithoutWorkspace)
|
||||
).rejects.toThrow("User workspaceId not found");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAuditTrail", () => {
|
||||
const mockAuditTrail = [
|
||||
{
|
||||
id: "activity-1",
|
||||
workspaceId: "workspace-123",
|
||||
userId: "user-123",
|
||||
action: ActivityAction.CREATED,
|
||||
entityType: EntityType.TASK,
|
||||
entityId: "task-123",
|
||||
details: { title: "New Task" },
|
||||
createdAt: new Date("2024-01-01"),
|
||||
user: {
|
||||
id: "user-123",
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "activity-2",
|
||||
workspaceId: "workspace-123",
|
||||
userId: "user-456",
|
||||
action: ActivityAction.UPDATED,
|
||||
entityType: EntityType.TASK,
|
||||
entityId: "task-123",
|
||||
details: { title: "Updated Task" },
|
||||
createdAt: new Date("2024-01-02"),
|
||||
user: {
|
||||
id: "user-456",
|
||||
name: "Another User",
|
||||
email: "another@example.com",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const mockRequest = {
|
||||
user: {
|
||||
id: "user-123",
|
||||
workspaceId: "workspace-123",
|
||||
email: "test@example.com",
|
||||
},
|
||||
};
|
||||
|
||||
it("should return audit trail for a task using authenticated user's workspaceId", async () => {
|
||||
mockActivityService.getAuditTrail.mockResolvedValue(mockAuditTrail);
|
||||
|
||||
const result = await controller.getAuditTrail(
|
||||
mockRequest,
|
||||
EntityType.TASK,
|
||||
"task-123"
|
||||
);
|
||||
|
||||
expect(result).toEqual(mockAuditTrail);
|
||||
expect(mockActivityService.getAuditTrail).toHaveBeenCalledWith(
|
||||
"workspace-123",
|
||||
EntityType.TASK,
|
||||
"task-123"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return audit trail for an event", async () => {
|
||||
const eventAuditTrail = [
|
||||
{
|
||||
id: "activity-3",
|
||||
workspaceId: "workspace-123",
|
||||
userId: "user-123",
|
||||
action: ActivityAction.CREATED,
|
||||
entityType: EntityType.EVENT,
|
||||
entityId: "event-123",
|
||||
details: {},
|
||||
createdAt: new Date(),
|
||||
user: {
|
||||
id: "user-123",
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
mockActivityService.getAuditTrail.mockResolvedValue(eventAuditTrail);
|
||||
|
||||
const result = await controller.getAuditTrail(
|
||||
mockRequest,
|
||||
EntityType.EVENT,
|
||||
"event-123"
|
||||
);
|
||||
|
||||
expect(result).toEqual(eventAuditTrail);
|
||||
expect(mockActivityService.getAuditTrail).toHaveBeenCalledWith(
|
||||
"workspace-123",
|
||||
EntityType.EVENT,
|
||||
"event-123"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return audit trail for a project", async () => {
|
||||
const projectAuditTrail = [
|
||||
{
|
||||
id: "activity-4",
|
||||
workspaceId: "workspace-123",
|
||||
userId: "user-123",
|
||||
action: ActivityAction.CREATED,
|
||||
entityType: EntityType.PROJECT,
|
||||
entityId: "project-123",
|
||||
details: {},
|
||||
createdAt: new Date(),
|
||||
user: {
|
||||
id: "user-123",
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
mockActivityService.getAuditTrail.mockResolvedValue(projectAuditTrail);
|
||||
|
||||
const result = await controller.getAuditTrail(
|
||||
mockRequest,
|
||||
EntityType.PROJECT,
|
||||
"project-123"
|
||||
);
|
||||
|
||||
expect(result).toEqual(projectAuditTrail);
|
||||
expect(mockActivityService.getAuditTrail).toHaveBeenCalledWith(
|
||||
"workspace-123",
|
||||
EntityType.PROJECT,
|
||||
"project-123"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return empty array if no audit trail found", async () => {
|
||||
mockActivityService.getAuditTrail.mockResolvedValue([]);
|
||||
|
||||
const result = await controller.getAuditTrail(
|
||||
mockRequest,
|
||||
EntityType.WORKSPACE,
|
||||
"workspace-999"
|
||||
);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("should throw error if user workspaceId is missing", async () => {
|
||||
const requestWithoutWorkspace = {
|
||||
user: {
|
||||
id: "user-123",
|
||||
email: "test@example.com",
|
||||
},
|
||||
};
|
||||
|
||||
await expect(
|
||||
controller.getAuditTrail(
|
||||
requestWithoutWorkspace,
|
||||
EntityType.TASK,
|
||||
"task-123"
|
||||
)
|
||||
).rejects.toThrow("User workspaceId not found");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user