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
773 lines
21 KiB
TypeScript
773 lines
21 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
import { Test, TestingModule } from "@nestjs/testing";
|
|
import { ActivityLoggingInterceptor } from "./activity-logging.interceptor";
|
|
import { ActivityService } from "../activity.service";
|
|
import { ExecutionContext, CallHandler } from "@nestjs/common";
|
|
import { of } from "rxjs";
|
|
import { ActivityAction, EntityType } from "@prisma/client";
|
|
|
|
describe("ActivityLoggingInterceptor", () => {
|
|
let interceptor: ActivityLoggingInterceptor;
|
|
let activityService: ActivityService;
|
|
|
|
const mockActivityService = {
|
|
logActivity: vi.fn(),
|
|
};
|
|
|
|
beforeEach(async () => {
|
|
const module: TestingModule = await Test.createTestingModule({
|
|
providers: [
|
|
ActivityLoggingInterceptor,
|
|
{
|
|
provide: ActivityService,
|
|
useValue: mockActivityService,
|
|
},
|
|
],
|
|
}).compile();
|
|
|
|
interceptor = module.get<ActivityLoggingInterceptor>(
|
|
ActivityLoggingInterceptor
|
|
);
|
|
activityService = module.get<ActivityService>(ActivityService);
|
|
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
const createMockExecutionContext = (
|
|
method: string,
|
|
params: any = {},
|
|
body: any = {},
|
|
user: any = null,
|
|
ip = "127.0.0.1",
|
|
userAgent = "test-agent"
|
|
): ExecutionContext => {
|
|
return {
|
|
switchToHttp: () => ({
|
|
getRequest: () => ({
|
|
method,
|
|
params,
|
|
body,
|
|
user,
|
|
ip,
|
|
headers: {
|
|
"user-agent": userAgent,
|
|
},
|
|
}),
|
|
}),
|
|
getClass: () => ({ name: "TestController" }),
|
|
getHandler: () => ({ name: "testMethod" }),
|
|
} as any;
|
|
};
|
|
|
|
const createMockCallHandler = (result: any = {}): CallHandler => {
|
|
return {
|
|
handle: () => of(result),
|
|
} as any;
|
|
};
|
|
|
|
describe("intercept", () => {
|
|
it("should not log if user is not authenticated", async () => {
|
|
const context = createMockExecutionContext("POST", {}, {}, null);
|
|
const next = createMockCallHandler();
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
expect(mockActivityService.logActivity).not.toHaveBeenCalled();
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should log POST request as CREATE action", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const body = {
|
|
title: "New Task",
|
|
};
|
|
|
|
const result = {
|
|
id: "task-123",
|
|
workspaceId: "workspace-123",
|
|
title: "New Task",
|
|
};
|
|
|
|
const context = createMockExecutionContext(
|
|
"POST",
|
|
{},
|
|
body,
|
|
user,
|
|
"127.0.0.1",
|
|
"Mozilla/5.0"
|
|
);
|
|
const next = createMockCallHandler(result);
|
|
|
|
mockActivityService.logActivity.mockResolvedValue({
|
|
id: "activity-123",
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
expect(mockActivityService.logActivity).toHaveBeenCalledWith({
|
|
workspaceId: "workspace-123",
|
|
userId: "user-123",
|
|
action: ActivityAction.CREATED,
|
|
entityType: expect.any(String),
|
|
entityId: "task-123",
|
|
details: expect.objectContaining({
|
|
method: "POST",
|
|
controller: "TestController",
|
|
handler: "testMethod",
|
|
}),
|
|
ipAddress: "127.0.0.1",
|
|
userAgent: "Mozilla/5.0",
|
|
});
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should log PATCH request as UPDATE action", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const params = {
|
|
id: "task-456",
|
|
};
|
|
|
|
const body = {
|
|
status: "IN_PROGRESS",
|
|
};
|
|
|
|
const result = {
|
|
id: "task-456",
|
|
workspaceId: "workspace-123",
|
|
status: "IN_PROGRESS",
|
|
};
|
|
|
|
const context = createMockExecutionContext("PATCH", params, body, user);
|
|
const next = createMockCallHandler(result);
|
|
|
|
mockActivityService.logActivity.mockResolvedValue({
|
|
id: "activity-124",
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
expect(mockActivityService.logActivity).toHaveBeenCalledWith({
|
|
workspaceId: "workspace-123",
|
|
userId: "user-123",
|
|
action: ActivityAction.UPDATED,
|
|
entityType: expect.any(String),
|
|
entityId: "task-456",
|
|
details: expect.objectContaining({
|
|
method: "PATCH",
|
|
changes: body,
|
|
}),
|
|
ipAddress: "127.0.0.1",
|
|
userAgent: "test-agent",
|
|
});
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should log PUT request as UPDATE action", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const params = {
|
|
id: "event-789",
|
|
};
|
|
|
|
const body = {
|
|
title: "Updated Event",
|
|
};
|
|
|
|
const result = {
|
|
id: "event-789",
|
|
workspaceId: "workspace-123",
|
|
title: "Updated Event",
|
|
};
|
|
|
|
const context = createMockExecutionContext("PUT", params, body, user);
|
|
const next = createMockCallHandler(result);
|
|
|
|
mockActivityService.logActivity.mockResolvedValue({
|
|
id: "activity-125",
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
expect(mockActivityService.logActivity).toHaveBeenCalledWith({
|
|
workspaceId: "workspace-123",
|
|
userId: "user-123",
|
|
action: ActivityAction.UPDATED,
|
|
entityType: expect.any(String),
|
|
entityId: "event-789",
|
|
details: expect.objectContaining({
|
|
method: "PUT",
|
|
}),
|
|
ipAddress: "127.0.0.1",
|
|
userAgent: "test-agent",
|
|
});
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should log DELETE request as DELETE action", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const params = {
|
|
id: "project-999",
|
|
};
|
|
|
|
const result = {
|
|
id: "project-999",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const context = createMockExecutionContext("DELETE", params, {}, user);
|
|
const next = createMockCallHandler(result);
|
|
|
|
mockActivityService.logActivity.mockResolvedValue({
|
|
id: "activity-126",
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
expect(mockActivityService.logActivity).toHaveBeenCalledWith({
|
|
workspaceId: "workspace-123",
|
|
userId: "user-123",
|
|
action: ActivityAction.DELETED,
|
|
entityType: expect.any(String),
|
|
entityId: "project-999",
|
|
details: expect.objectContaining({
|
|
method: "DELETE",
|
|
}),
|
|
ipAddress: "127.0.0.1",
|
|
userAgent: "test-agent",
|
|
});
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should not log GET requests", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const context = createMockExecutionContext("GET", {}, {}, user);
|
|
const next = createMockCallHandler({ data: [] });
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
expect(mockActivityService.logActivity).not.toHaveBeenCalled();
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should extract entity ID from result if not in params", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const body = {
|
|
title: "New Task",
|
|
};
|
|
|
|
const result = {
|
|
id: "task-new-123",
|
|
workspaceId: "workspace-123",
|
|
title: "New Task",
|
|
};
|
|
|
|
const context = createMockExecutionContext("POST", {}, body, user);
|
|
const next = createMockCallHandler(result);
|
|
|
|
mockActivityService.logActivity.mockResolvedValue({
|
|
id: "activity-127",
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
expect(mockActivityService.logActivity).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
entityId: "task-new-123",
|
|
})
|
|
);
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should handle errors gracefully", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const context = createMockExecutionContext("POST", {}, {}, user);
|
|
const next = createMockCallHandler({ id: "test-123" });
|
|
|
|
mockActivityService.logActivity.mockRejectedValue(
|
|
new Error("Logging failed")
|
|
);
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
// Should not throw error, just log it
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("edge cases", () => {
|
|
it("should handle POST request with no id field in response", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const body = {
|
|
title: "New Task",
|
|
};
|
|
|
|
const result = {
|
|
workspaceId: "workspace-123",
|
|
title: "New Task",
|
|
// No 'id' field in response
|
|
};
|
|
|
|
const context = createMockExecutionContext("POST", {}, body, user);
|
|
const next = createMockCallHandler(result);
|
|
|
|
mockActivityService.logActivity.mockResolvedValue({
|
|
id: "activity-123",
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
// Should not call logActivity when entityId is missing
|
|
expect(mockActivityService.logActivity).not.toHaveBeenCalled();
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should handle user object missing workspaceId", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
// No workspaceId
|
|
};
|
|
|
|
const body = {
|
|
title: "New Task",
|
|
};
|
|
|
|
const result = {
|
|
id: "task-123",
|
|
title: "New Task",
|
|
};
|
|
|
|
const context = createMockExecutionContext("POST", {}, body, user);
|
|
const next = createMockCallHandler(result);
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
// Should not call logActivity when workspaceId is missing
|
|
expect(mockActivityService.logActivity).not.toHaveBeenCalled();
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should handle body missing workspaceId when user also missing workspaceId", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
// No workspaceId
|
|
};
|
|
|
|
const body = {
|
|
title: "New Task",
|
|
// No workspaceId
|
|
};
|
|
|
|
const result = {
|
|
id: "task-123",
|
|
title: "New Task",
|
|
};
|
|
|
|
const context = createMockExecutionContext("POST", {}, body, user);
|
|
const next = createMockCallHandler(result);
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
// Should not call logActivity when workspaceId is missing
|
|
expect(mockActivityService.logActivity).not.toHaveBeenCalled();
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should extract workspaceId from body when not in user object", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
// No workspaceId
|
|
};
|
|
|
|
const body = {
|
|
workspaceId: "workspace-from-body",
|
|
title: "New Task",
|
|
};
|
|
|
|
const result = {
|
|
id: "task-123",
|
|
title: "New Task",
|
|
};
|
|
|
|
const context = createMockExecutionContext("POST", {}, body, user);
|
|
const next = createMockCallHandler(result);
|
|
|
|
mockActivityService.logActivity.mockResolvedValue({
|
|
id: "activity-123",
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
expect(mockActivityService.logActivity).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
workspaceId: "workspace-from-body",
|
|
})
|
|
);
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should handle null result from handler", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const context = createMockExecutionContext("DELETE", { id: "task-123" }, {}, user);
|
|
const next = createMockCallHandler(null);
|
|
|
|
mockActivityService.logActivity.mockResolvedValue({
|
|
id: "activity-123",
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
// Should still log activity with entityId from params
|
|
expect(mockActivityService.logActivity).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
entityId: "task-123",
|
|
workspaceId: "workspace-123",
|
|
})
|
|
);
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should handle undefined result from handler", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const context = createMockExecutionContext("POST", {}, { title: "New Task" }, user);
|
|
const next = createMockCallHandler(undefined);
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
// Should not log when entityId cannot be determined
|
|
expect(mockActivityService.logActivity).not.toHaveBeenCalled();
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should log warning when entityId is missing", async () => {
|
|
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const body = {
|
|
title: "New Task",
|
|
};
|
|
|
|
const result = {
|
|
workspaceId: "workspace-123",
|
|
title: "New Task",
|
|
// No 'id' field
|
|
};
|
|
|
|
const context = createMockExecutionContext("POST", {}, body, user);
|
|
const next = createMockCallHandler(result);
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
consoleSpy.mockRestore();
|
|
});
|
|
|
|
it("should log warning when workspaceId is missing", async () => {
|
|
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
|
|
const user = {
|
|
id: "user-123",
|
|
// No workspaceId
|
|
};
|
|
|
|
const body = {
|
|
title: "New Task",
|
|
};
|
|
|
|
const result = {
|
|
id: "task-123",
|
|
title: "New Task",
|
|
};
|
|
|
|
const context = createMockExecutionContext("POST", {}, body, user);
|
|
const next = createMockCallHandler(result);
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
consoleSpy.mockRestore();
|
|
});
|
|
|
|
it("should handle activity service throwing an error", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const context = createMockExecutionContext("POST", {}, {}, user);
|
|
const next = createMockCallHandler({ id: "test-123" });
|
|
|
|
const activityError = new Error("Activity logging failed");
|
|
mockActivityService.logActivity.mockRejectedValue(activityError);
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
// Should not throw error, just log it
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should handle OPTIONS requests", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const context = createMockExecutionContext("OPTIONS", {}, {}, user);
|
|
const next = createMockCallHandler({});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
// Should not log OPTIONS requests
|
|
expect(mockActivityService.logActivity).not.toHaveBeenCalled();
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should handle HEAD requests", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const context = createMockExecutionContext("HEAD", {}, {}, user);
|
|
const next = createMockCallHandler({});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
// Should not log HEAD requests
|
|
expect(mockActivityService.logActivity).not.toHaveBeenCalled();
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("sensitive data sanitization", () => {
|
|
it("should redact password field", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const body = {
|
|
username: "testuser",
|
|
password: "secret123",
|
|
email: "test@example.com",
|
|
};
|
|
|
|
const result = {
|
|
id: "user-456",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const context = createMockExecutionContext("POST", {}, body, user);
|
|
const next = createMockCallHandler(result);
|
|
|
|
mockActivityService.logActivity.mockResolvedValue({
|
|
id: "activity-123",
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
const logCall = mockActivityService.logActivity.mock.calls[0][0];
|
|
expect(logCall.details.data.password).toBe("[REDACTED]");
|
|
expect(logCall.details.data.username).toBe("testuser");
|
|
expect(logCall.details.data.email).toBe("test@example.com");
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should redact token field", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const body = {
|
|
title: "Integration",
|
|
apiToken: "sk_test_1234567890",
|
|
};
|
|
|
|
const result = {
|
|
id: "integration-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const context = createMockExecutionContext("POST", {}, body, user);
|
|
const next = createMockCallHandler(result);
|
|
|
|
mockActivityService.logActivity.mockResolvedValue({
|
|
id: "activity-124",
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
const logCall = mockActivityService.logActivity.mock.calls[0][0];
|
|
expect(logCall.details.data.apiToken).toBe("[REDACTED]");
|
|
expect(logCall.details.data.title).toBe("Integration");
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should redact sensitive fields in nested objects", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const body = {
|
|
title: "Config",
|
|
settings: {
|
|
apiKey: "secret_key",
|
|
public: "visible_data",
|
|
auth: {
|
|
token: "auth_token_123",
|
|
refreshToken: "refresh_token_456",
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = {
|
|
id: "config-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const context = createMockExecutionContext("POST", {}, body, user);
|
|
const next = createMockCallHandler(result);
|
|
|
|
mockActivityService.logActivity.mockResolvedValue({
|
|
id: "activity-128",
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
const logCall = mockActivityService.logActivity.mock.calls[0][0];
|
|
expect(logCall.details.data.title).toBe("Config");
|
|
expect(logCall.details.data.settings.apiKey).toBe("[REDACTED]");
|
|
expect(logCall.details.data.settings.public).toBe("visible_data");
|
|
expect(logCall.details.data.settings.auth.token).toBe("[REDACTED]");
|
|
expect(logCall.details.data.settings.auth.refreshToken).toBe(
|
|
"[REDACTED]"
|
|
);
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should not modify non-sensitive fields", async () => {
|
|
const user = {
|
|
id: "user-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const body = {
|
|
title: "Safe Data",
|
|
description: "This is public",
|
|
count: 42,
|
|
active: true,
|
|
};
|
|
|
|
const result = {
|
|
id: "item-123",
|
|
workspaceId: "workspace-123",
|
|
};
|
|
|
|
const context = createMockExecutionContext("POST", {}, body, user);
|
|
const next = createMockCallHandler(result);
|
|
|
|
mockActivityService.logActivity.mockResolvedValue({
|
|
id: "activity-130",
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
interceptor.intercept(context, next).subscribe(() => {
|
|
const logCall = mockActivityService.logActivity.mock.calls[0][0];
|
|
expect(logCall.details.data).toEqual(body);
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|