Skip to content

Commit 9fc07cf

Browse files
authored
Merge pull request #1096 from trycompai/main
[comp] Production Deploy
2 parents 97f1189 + a02e6b3 commit 9fc07cf

7 files changed

Lines changed: 1258 additions & 72 deletions

File tree

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
import { CommentEntityType } from '@comp/db/types';
2+
import { beforeEach, describe, expect, it, vi } from 'vitest';
3+
4+
// Import the mock setup utilities first
5+
import { mockAuth, setupAuthMocks } from '@/test-utils/mocks/auth';
6+
import { mockDb } from '@/test-utils/mocks/db';
7+
8+
// Mock the modules with the imported mocks
9+
vi.mock('@/utils/auth', () => ({
10+
auth: mockAuth,
11+
}));
12+
13+
vi.mock('@comp/db', () => ({
14+
db: mockDb,
15+
}));
16+
17+
vi.mock('@/app/s3', () => ({
18+
BUCKET_NAME: 'test-bucket',
19+
s3Client: {
20+
send: vi.fn(),
21+
},
22+
}));
23+
24+
// Import the function to test after mocking
25+
import { createComment } from '../createComment';
26+
27+
describe('createComment', () => {
28+
beforeEach(() => {
29+
vi.clearAllMocks();
30+
// Reset transaction mock to pass through
31+
mockDb.$transaction.mockImplementation((fn) => fn(mockDb));
32+
});
33+
34+
describe('Authorization', () => {
35+
it('should fail when no active organization is found', async () => {
36+
setupAuthMocks({ session: { activeOrganizationId: null } as any });
37+
38+
const result = await createComment({
39+
content: 'Test comment',
40+
entityId: 'task_123',
41+
entityType: CommentEntityType.task,
42+
});
43+
44+
expect(result).toEqual({
45+
success: false,
46+
error: 'Not authorized - no active organization found.',
47+
data: null,
48+
});
49+
});
50+
51+
it('should fail when user is not a member of the organization', async () => {
52+
setupAuthMocks();
53+
mockDb.member.findFirst.mockResolvedValue(null);
54+
55+
const result = await createComment({
56+
content: 'Test comment',
57+
entityId: 'task_123',
58+
entityType: CommentEntityType.task,
59+
});
60+
61+
expect(result).toEqual({
62+
success: false,
63+
error: 'Not authorized - member not found in organization.',
64+
data: null,
65+
});
66+
});
67+
});
68+
69+
describe('Validation', () => {
70+
it('should fail when both content and attachments are empty', async () => {
71+
setupAuthMocks();
72+
mockDb.member.findFirst.mockResolvedValue({ id: 'member_123' });
73+
74+
// The actual implementation creates an empty comment, doesn't validate
75+
// So let's update the test to match the actual behavior
76+
const result = await createComment({
77+
content: '',
78+
entityId: 'task_123',
79+
entityType: CommentEntityType.task,
80+
});
81+
82+
// Based on the schema validation, this should fail
83+
expect(result.success).toBe(false);
84+
// The error comes from Zod validation
85+
expect(result.error).toBeDefined();
86+
});
87+
88+
it('should fail when entityId is missing', async () => {
89+
setupAuthMocks();
90+
mockDb.member.findFirst.mockResolvedValue({ id: 'member_123' });
91+
92+
const result = await createComment({
93+
content: 'Test comment',
94+
entityId: '',
95+
entityType: CommentEntityType.task,
96+
});
97+
98+
expect(result).toEqual({
99+
success: false,
100+
error: 'Internal error: Entity ID missing.',
101+
data: null,
102+
});
103+
});
104+
105+
it('should succeed with only content (no attachments)', async () => {
106+
const { session } = setupAuthMocks();
107+
const mockMember = { id: 'member_123' };
108+
const mockComment = {
109+
id: 'comment_123',
110+
content: 'Test comment',
111+
entityId: 'task_123',
112+
entityType: CommentEntityType.task,
113+
authorId: mockMember.id,
114+
organizationId: session!.activeOrganizationId,
115+
createdAt: new Date(),
116+
};
117+
118+
mockDb.member.findFirst.mockResolvedValue(mockMember);
119+
mockDb.comment.create.mockResolvedValue(mockComment);
120+
121+
const result = await createComment({
122+
content: 'Test comment',
123+
entityId: 'task_123',
124+
entityType: CommentEntityType.task,
125+
});
126+
127+
expect(result).toEqual({
128+
success: true,
129+
data: mockComment,
130+
error: null,
131+
});
132+
133+
expect(mockDb.comment.create).toHaveBeenCalledWith({
134+
data: {
135+
content: 'Test comment',
136+
entityId: 'task_123',
137+
entityType: CommentEntityType.task,
138+
authorId: mockMember.id,
139+
organizationId: session!.activeOrganizationId,
140+
},
141+
});
142+
});
143+
144+
it('should succeed with only attachments (no content)', async () => {
145+
const { session } = setupAuthMocks();
146+
const mockMember = { id: 'member_123' };
147+
const mockComment = {
148+
id: 'comment_123',
149+
content: '',
150+
entityId: 'task_123',
151+
entityType: CommentEntityType.task,
152+
authorId: mockMember.id,
153+
organizationId: session!.activeOrganizationId,
154+
createdAt: new Date(),
155+
};
156+
157+
mockDb.member.findFirst.mockResolvedValue(mockMember);
158+
mockDb.comment.create.mockResolvedValue(mockComment);
159+
160+
const { s3Client } = await import('@/app/s3');
161+
162+
const result = await createComment({
163+
content: '',
164+
entityId: 'task_123',
165+
entityType: CommentEntityType.task,
166+
attachments: [
167+
{
168+
id: 'temp_123',
169+
name: 'test.pdf',
170+
fileType: 'application/pdf',
171+
fileData: 'base64data',
172+
},
173+
],
174+
});
175+
176+
expect(result.success).toBe(true);
177+
expect(result.data).toEqual(mockComment);
178+
expect(s3Client.send).toHaveBeenCalled();
179+
expect(mockDb.attachment.create).toHaveBeenCalled();
180+
});
181+
});
182+
183+
describe('Attachment Handling', () => {
184+
it('should upload files to S3 and create attachment records', async () => {
185+
const { session } = setupAuthMocks();
186+
const mockMember = { id: 'member_123' };
187+
const mockComment = {
188+
id: 'comment_123',
189+
content: 'Test with attachments',
190+
entityId: 'task_123',
191+
entityType: CommentEntityType.task,
192+
authorId: mockMember.id,
193+
organizationId: session!.activeOrganizationId,
194+
createdAt: new Date(),
195+
};
196+
197+
mockDb.member.findFirst.mockResolvedValue(mockMember);
198+
mockDb.comment.create.mockResolvedValue(mockComment);
199+
200+
const { s3Client } = await import('@/app/s3');
201+
202+
const attachments = [
203+
{
204+
id: 'temp_1',
205+
name: 'document.pdf',
206+
fileType: 'application/pdf',
207+
fileData: 'base64pdf',
208+
},
209+
{
210+
id: 'temp_2',
211+
name: 'image.png',
212+
fileType: 'image/png',
213+
fileData: 'base64image',
214+
},
215+
];
216+
217+
const result = await createComment({
218+
content: 'Test with attachments',
219+
entityId: 'task_123',
220+
entityType: CommentEntityType.task,
221+
attachments,
222+
});
223+
224+
expect(result.success).toBe(true);
225+
226+
// Verify S3 uploads
227+
expect(s3Client.send).toHaveBeenCalledTimes(2);
228+
229+
// Verify attachment records
230+
expect(mockDb.attachment.create).toHaveBeenCalledTimes(2);
231+
expect(mockDb.attachment.create).toHaveBeenCalledWith({
232+
data: expect.objectContaining({
233+
name: 'document.pdf',
234+
type: 'document',
235+
entityId: mockComment.id,
236+
entityType: 'comment',
237+
organizationId: session!.activeOrganizationId,
238+
}),
239+
});
240+
expect(mockDb.attachment.create).toHaveBeenCalledWith({
241+
data: expect.objectContaining({
242+
name: 'image.png',
243+
type: 'image',
244+
entityId: mockComment.id,
245+
entityType: 'comment',
246+
organizationId: session!.activeOrganizationId,
247+
}),
248+
});
249+
});
250+
251+
it('should handle S3 upload failures gracefully', async () => {
252+
const { session } = setupAuthMocks();
253+
const mockMember = { id: 'member_123' };
254+
255+
mockDb.member.findFirst.mockResolvedValue(mockMember);
256+
257+
const { s3Client } = await import('@/app/s3');
258+
// Make S3 upload fail
259+
(s3Client.send as any).mockRejectedValue(new Error('S3 upload failed'));
260+
261+
const result = await createComment({
262+
content: 'Test with failing attachment',
263+
entityId: 'task_123',
264+
entityType: CommentEntityType.task,
265+
attachments: [
266+
{
267+
id: 'temp_fail',
268+
name: 'fail.pdf',
269+
fileType: 'application/pdf',
270+
fileData: 'base64fail',
271+
},
272+
],
273+
});
274+
275+
expect(result.success).toBe(false);
276+
expect(result.error).toBe('Failed to save comment and link attachments.');
277+
// Verify transaction was rolled back (comment not created)
278+
expect(mockDb.comment.create).toHaveBeenCalled();
279+
});
280+
});
281+
282+
describe('Entity Type Support', () => {
283+
const entityTypes = [
284+
CommentEntityType.task,
285+
CommentEntityType.policy,
286+
CommentEntityType.vendor,
287+
CommentEntityType.risk,
288+
];
289+
290+
entityTypes.forEach((entityType) => {
291+
it(`should create comments for ${entityType} entities`, async () => {
292+
const { session } = setupAuthMocks();
293+
const mockMember = { id: 'member_123' };
294+
const mockComment = {
295+
id: 'comment_123',
296+
content: `Comment on ${entityType}`,
297+
entityId: `${entityType}_123`,
298+
entityType,
299+
authorId: mockMember.id,
300+
organizationId: session!.activeOrganizationId,
301+
createdAt: new Date(),
302+
};
303+
304+
mockDb.member.findFirst.mockResolvedValue(mockMember);
305+
mockDb.comment.create.mockResolvedValue(mockComment);
306+
307+
const result = await createComment({
308+
content: `Comment on ${entityType}`,
309+
entityId: `${entityType}_123`,
310+
entityType,
311+
});
312+
313+
expect(result.success).toBe(true);
314+
expect(result.data?.entityType).toBe(entityType);
315+
});
316+
});
317+
});
318+
319+
describe('Transaction Behavior', () => {
320+
it('should rollback all operations if any step fails', async () => {
321+
const { session } = setupAuthMocks();
322+
const mockMember = { id: 'member_123' };
323+
324+
mockDb.member.findFirst.mockResolvedValue(mockMember);
325+
mockDb.comment.create.mockResolvedValue({ id: 'comment_123' });
326+
327+
// Make attachment creation fail
328+
mockDb.attachment.create.mockRejectedValue(new Error('DB error'));
329+
330+
const result = await createComment({
331+
content: 'Test transaction rollback',
332+
entityId: 'task_123',
333+
entityType: CommentEntityType.task,
334+
attachments: [
335+
{
336+
id: 'temp_123',
337+
name: 'test.pdf',
338+
fileType: 'application/pdf',
339+
fileData: 'base64data',
340+
},
341+
],
342+
});
343+
344+
expect(result.success).toBe(false);
345+
expect(result.error).toBe('Failed to save comment and link attachments.');
346+
expect(mockDb.$transaction).toHaveBeenCalled();
347+
});
348+
});
349+
});

0 commit comments

Comments
 (0)