Add attachments to comments
- CommentAttachment link table (comment <-> file) + migration - fileIds in create/update comment; update replaces the whole set, synced in one transaction with the comment - CommentDto.attachments; empty for deleted comments - Soft delete drops attachments so GC can collect the files - Max 10 files per comment, ordered by upload time (uuid v7) - Registers comments as a file owner - Rename CommentAuthorExpanderService -> CommentExpanderService (expands authors and files) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
423afa658d
commit
60fb39f98f
28 files changed
+522
-69
No files matched your search
@@ -73,4 +73,50 @@ describe('Comment', () => {
|
||||
expect(() => buildComment().delete('user-2')).toThrow(CommentForbiddenException);
|
||||
});
|
||||
});
|
||||
|
||||
describe('attachments', () => {
|
||||
it('creates a comment with deduplicated files', () => {
|
||||
const comment = Comment.create({ issueId: 1, authorId: 'user-1', content: 'Hi', fileIds: ['a', 'b', 'a'] });
|
||||
|
||||
expect(comment.fileIds).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('limits the number of files', () => {
|
||||
const fileIds = Array.from({ length: Comment.MAX_ATTACHMENTS + 1 }, (_, i) => `f${i}`);
|
||||
|
||||
expect(() => Comment.create({ issueId: 1, authorId: 'user-1', content: 'Hi', fileIds })).toThrow();
|
||||
});
|
||||
|
||||
it('treats a changed file set as an edit, even with the same text', () => {
|
||||
const comment = buildComment();
|
||||
|
||||
expect(comment.edit('Original', 'user-1', ['a'])).toBe(true);
|
||||
expect(comment.fileIds).toEqual(['a']);
|
||||
expect(comment.editedAt).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
it('keeps files when fileIds is not passed', () => {
|
||||
const comment = buildComment();
|
||||
comment.edit('Original', 'user-1', ['a']);
|
||||
|
||||
expect(comment.edit('Changed', 'user-1')).toBe(true);
|
||||
expect(comment.fileIds).toEqual(['a']);
|
||||
});
|
||||
|
||||
it('reports no change for the same text and files', () => {
|
||||
const comment = buildComment();
|
||||
comment.edit('Original', 'user-1', ['a', 'b']);
|
||||
|
||||
expect(comment.edit('Original', 'user-1', ['a', 'b'])).toBe(false);
|
||||
});
|
||||
|
||||
it('drops files on delete so GC can collect them', () => {
|
||||
const comment = buildComment();
|
||||
comment.edit('Original', 'user-1', ['a']);
|
||||
|
||||
comment.delete('user-1');
|
||||
|
||||
expect(comment.fileIds).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user