Improve collaboration request details response

This commit is contained in:
boutmoun123
2026-06-17 22:38:22 +03:00
parent 3d2a41b22a
commit 23c1a2d12b
3 changed files with 209 additions and 4 deletions
+29 -2
View File
@@ -3046,7 +3046,10 @@
"pm.test('Status is 200 or 201', function () { pm.expect([200, 201]).to.include(pm.response.code); });", "pm.test('Status is 200 or 201', function () { pm.expect([200, 201]).to.include(pm.response.code); });",
"const json = pm.response.json();", "const json = pm.response.json();",
"pm.expect(json).to.have.property('request');", "pm.expect(json).to.have.property('request');",
"pm.expect(json.request).to.have.property('requester');",
"pm.expect(json.request).to.have.property('targetUser');",
"pm.environment.set('collaborationRequestId', json.request._id || json.request.id);", "pm.environment.set('collaborationRequestId', json.request._id || json.request.id);",
"pm.environment.set('requestId', json.request._id || json.request.id);",
"pm.test('Rich collaboration fields are accepted', function () {", "pm.test('Rich collaboration fields are accepted', function () {",
" pm.expect(json.request).to.have.property('collaborationType');", " pm.expect(json.request).to.have.property('collaborationType');",
" pm.expect(json.request).to.have.property('message');", " pm.expect(json.request).to.have.property('message');",
@@ -3104,7 +3107,10 @@
"pm.test('Status is 200 or 201', function () { pm.expect([200, 201]).to.include(pm.response.code); });", "pm.test('Status is 200 or 201', function () { pm.expect([200, 201]).to.include(pm.response.code); });",
"const json = pm.response.json();", "const json = pm.response.json();",
"pm.expect(json).to.have.property('request');", "pm.expect(json).to.have.property('request');",
"pm.expect(json.request).to.have.property('requester');",
"pm.expect(json.request).to.have.property('targetUser');",
"pm.environment.set('collaborationRequestId', json.request._id || json.request.id);", "pm.environment.set('collaborationRequestId', json.request._id || json.request.id);",
"pm.environment.set('requestId', json.request._id || json.request.id);",
"pm.test('General collaboration request has no post', function () {", "pm.test('General collaboration request has no post', function () {",
" pm.expect(json.request.postId === null || typeof json.request.postId === 'undefined').to.equal(true);", " pm.expect(json.request.postId === null || typeof json.request.postId === 'undefined').to.equal(true);",
" pm.expect(json.request).to.have.property('status', 'pending');", " pm.expect(json.request).to.have.property('status', 'pending');",
@@ -3138,6 +3144,7 @@
"pm.expect(json).to.have.property('pagination');", "pm.expect(json).to.have.property('pagination');",
"if (json.items.length) {", "if (json.items.length) {",
" pm.environment.set('collaborationRequestId', json.items[0]._id || json.items[0].id);", " pm.environment.set('collaborationRequestId', json.items[0]._id || json.items[0].id);",
" pm.environment.set('requestId', json.items[0]._id || json.items[0].id);",
" pm.expect(json.items[0]).to.have.property('status');", " pm.expect(json.items[0]).to.have.property('status');",
" pm.expect(json.items[0]).to.have.property('collaborationType');", " pm.expect(json.items[0]).to.have.property('collaborationType');",
"}" "}"
@@ -3170,10 +3177,15 @@
"pm.expect(json).to.have.property('pagination');", "pm.expect(json).to.have.property('pagination');",
"if (json.items.length) {", "if (json.items.length) {",
" pm.environment.set('collaborationRequestId', json.items[0]._id || json.items[0].id);", " pm.environment.set('collaborationRequestId', json.items[0]._id || json.items[0].id);",
" pm.environment.set('requestId', json.items[0]._id || json.items[0].id);",
" pm.expect(json.items[0]).to.have.property('status');", " pm.expect(json.items[0]).to.have.property('status');",
" pm.expect(json.items[0]).to.have.property('requesterId');", " pm.expect(json.items[0]).to.have.property('requesterId');",
" pm.expect(json.items[0]).to.have.property('targetUserId');", " pm.expect(json.items[0]).to.have.property('targetUserId');",
" pm.expect(json.items[0]).to.have.property('postId');", " pm.expect(json.items[0]).to.have.property('postId');",
" pm.expect(json.items[0]).to.have.property('requester');",
" pm.expect(json.items[0]).to.have.property('targetUser');",
" pm.expect(json.items[0].requester).to.have.property('username');",
" pm.expect(json.items[0].targetUser).to.have.property('username');",
"}" "}"
] ]
} }
@@ -3204,10 +3216,15 @@
"pm.expect(json).to.have.property('pagination');", "pm.expect(json).to.have.property('pagination');",
"if (json.items.length) {", "if (json.items.length) {",
" pm.environment.set('collaborationRequestId', json.items[0]._id || json.items[0].id);", " pm.environment.set('collaborationRequestId', json.items[0]._id || json.items[0].id);",
" pm.environment.set('requestId', json.items[0]._id || json.items[0].id);",
" pm.expect(json.items[0]).to.have.property('status');", " pm.expect(json.items[0]).to.have.property('status');",
" pm.expect(json.items[0]).to.have.property('requesterId');", " pm.expect(json.items[0]).to.have.property('requesterId');",
" pm.expect(json.items[0]).to.have.property('targetUserId');", " pm.expect(json.items[0]).to.have.property('targetUserId');",
" pm.expect(json.items[0]).to.have.property('postId');", " pm.expect(json.items[0]).to.have.property('postId');",
" pm.expect(json.items[0]).to.have.property('requester');",
" pm.expect(json.items[0]).to.have.property('targetUser');",
" pm.expect(json.items[0].requester).to.have.property('username');",
" pm.expect(json.items[0].targetUser).to.have.property('username');",
"}" "}"
] ]
} }
@@ -3235,6 +3252,8 @@
"pm.test('Status is 200', function () { pm.response.to.have.status(200); });", "pm.test('Status is 200', function () { pm.response.to.have.status(200); });",
"const json = pm.response.json();", "const json = pm.response.json();",
"pm.expect(json).to.have.property('request');", "pm.expect(json).to.have.property('request');",
"pm.expect(json.request).to.have.property('requester');",
"pm.expect(json.request).to.have.property('targetUser');",
"pm.expect(json.request).to.have.property('status');", "pm.expect(json.request).to.have.property('status');",
"pm.expect(json.request).to.have.property('requesterId');", "pm.expect(json.request).to.have.property('requesterId');",
"pm.expect(json.request).to.have.property('targetUserId');", "pm.expect(json.request).to.have.property('targetUserId');",
@@ -3265,7 +3284,10 @@
"pm.test('Status is 200', function () { pm.response.to.have.status(200); });", "pm.test('Status is 200', function () { pm.response.to.have.status(200); });",
"const json = pm.response.json();", "const json = pm.response.json();",
"pm.expect(json).to.have.property('approved', true);", "pm.expect(json).to.have.property('approved', true);",
"pm.expect(json).to.have.property('request');" "pm.expect(json.request).to.have.property('status', 'approved');",
"pm.expect(json).to.have.property('request');",
"pm.expect(json.request).to.have.property('requester');",
"pm.expect(json.request).to.have.property('targetUser');"
] ]
} }
} }
@@ -3292,7 +3314,10 @@
"pm.test('Status is 200', function () { pm.response.to.have.status(200); });", "pm.test('Status is 200', function () { pm.response.to.have.status(200); });",
"const json = pm.response.json();", "const json = pm.response.json();",
"pm.expect(json).to.have.property('rejected', true);", "pm.expect(json).to.have.property('rejected', true);",
"pm.expect(json).to.have.property('request');" "pm.expect(json.request).to.have.property('status', 'rejected');",
"pm.expect(json).to.have.property('request');",
"pm.expect(json.request).to.have.property('requester');",
"pm.expect(json.request).to.have.property('targetUser');"
] ]
} }
} }
@@ -3320,6 +3345,8 @@
"const json = pm.response.json();", "const json = pm.response.json();",
"pm.expect(json).to.have.property('cancelled', true);", "pm.expect(json).to.have.property('cancelled', true);",
"pm.expect(json).to.have.property('request');", "pm.expect(json).to.have.property('request');",
"pm.expect(json.request).to.have.property('requester');",
"pm.expect(json.request).to.have.property('targetUser');",
"pm.expect(json.request).to.have.property('status', 'cancelled');" "pm.expect(json.request).to.have.property('status', 'cancelled');"
] ]
} }
@@ -10,6 +10,33 @@ const chain = <T>(value: T) => ({
exec: jest.fn().mockResolvedValue(value), exec: jest.fn().mockResolvedValue(value),
}); });
const createUser = (overrides: Record<string, any> = {}) => {
const userId = overrides._id ?? new Types.ObjectId();
return {
_id: userId,
id: userId.toString(),
username: overrides.username ?? 'artist',
name: overrides.name ?? 'Artist Name',
stageName: overrides.stageName ?? 'Stage Artist',
avatar: overrides.avatar ?? '/avatar.png',
isVerified: overrides.isVerified ?? false,
...overrides,
};
};
const createPost = (overrides: Record<string, any> = {}) => {
const postId = overrides._id ?? new Types.ObjectId();
return {
_id: postId,
id: postId.toString(),
content: overrides.content ?? 'Post content',
thumbnailUrl: overrides.thumbnailUrl ?? '/thumb.png',
media: overrides.media ?? {},
authorId: overrides.authorId ?? new Types.ObjectId(),
...overrides,
};
};
const createRequestDoc = (overrides: Record<string, any> = {}) => { const createRequestDoc = (overrides: Record<string, any> = {}) => {
const requestId = overrides._id ?? new Types.ObjectId(); const requestId = overrides._id ?? new Types.ObjectId();
return { return {
@@ -116,6 +143,13 @@ describe('CollaborationRequestsService', () => {
type: 'collaboration_request', type: 'collaboration_request',
referenceId: postId, referenceId: postId,
deepLink: `/posts/${postId}`, deepLink: `/posts/${postId}`,
metadata: expect.objectContaining({
collaborationRequestId: request.id,
requestId: request.id,
collaborationType: 'duet',
status: 'pending',
postId,
}),
}), }),
); );
}); });
@@ -321,6 +355,58 @@ describe('CollaborationRequestsService', () => {
}); });
}); });
it('returns request details with requester, targetUser, and post aliases', async () => {
const requesterId = new Types.ObjectId().toString();
const targetUserId = new Types.ObjectId().toString();
const postId = new Types.ObjectId().toString();
const requester = createUser({ _id: new Types.ObjectId(requesterId), username: 'rami' });
const targetUser = createUser({ _id: new Types.ObjectId(targetUserId), username: 'sara' });
const post = createPost({ _id: new Types.ObjectId(postId), authorId: targetUser._id });
const request = createRequestDoc({
postId: post,
requesterId: requester,
targetUserId: targetUser,
attachmentUrl: '/uploads/collaboration-requests/audio/file.mp3',
attachmentType: 'audio',
});
const { service, model } = createService();
model.findOne.mockReturnValue(chain(request));
const result = await service.getById(requesterId, request.id);
expect(result.request).toMatchObject({
id: request.id,
postId,
attachmentUrl: '/uploads/collaboration-requests/audio/file.mp3',
attachmentType: 'audio',
requester: expect.objectContaining({ id: requesterId, username: 'rami' }),
targetUser: expect.objectContaining({ id: targetUserId, username: 'sara' }),
post: expect.objectContaining({ id: postId, content: 'Post content' }),
});
});
it('lists received requests with populated requester and targetUser aliases', async () => {
const requesterId = new Types.ObjectId().toString();
const targetUserId = new Types.ObjectId().toString();
const items = [
createRequestDoc({
requesterId: createUser({ _id: new Types.ObjectId(requesterId), username: 'rami' }),
targetUserId: createUser({ _id: new Types.ObjectId(targetUserId), username: 'sara' }),
}),
];
const { service, model } = createService();
model.find.mockReturnValue(chain(items));
model.countDocuments.mockReturnValue(chain(1));
const result = await service.listReceived(targetUserId, { page: 1, limit: 20 });
expect(result.items[0]).toMatchObject({
requester: expect.objectContaining({ id: requesterId, username: 'rami' }),
targetUser: expect.objectContaining({ id: targetUserId, username: 'sara' }),
});
});
it('approves a pending request, updates post collaborators, and notifies requester', async () => { it('approves a pending request, updates post collaborators, and notifies requester', async () => {
const targetUserId = new Types.ObjectId().toString(); const targetUserId = new Types.ObjectId().toString();
const requesterId = new Types.ObjectId().toString(); const requesterId = new Types.ObjectId().toString();
@@ -294,7 +294,10 @@ export class CollaborationRequestsService {
this.collaborationRequestModel.countDocuments(filter).exec(), this.collaborationRequestModel.countDocuments(filter).exec(),
]); ]);
return buildPaginatedResponse(items, { page, limit, total, offset: skip }); return buildPaginatedResponse(
items.map((item: CollaborationRequestDocument) => this.serializeRequest(item)),
{ page, limit, total, offset: skip },
);
} }
async approve(targetUserId: string, requestId: string) { async approve(targetUserId: string, requestId: string) {
@@ -510,7 +513,7 @@ export class CollaborationRequestsService {
throw new NotFoundException('Collaboration request not found'); throw new NotFoundException('Collaboration request not found');
} }
return request; return this.serializeRequest(request);
} }
private async createCollaborationNotification(options: { private async createCollaborationNotification(options: {
@@ -538,6 +541,9 @@ export class CollaborationRequestsService {
metadata: { metadata: {
...(options.metadata ?? {}), ...(options.metadata ?? {}),
collaborationRequestId: options.requestId, collaborationRequestId: options.requestId,
requestId: options.requestId,
status: options.metadata?.status ?? 'pending',
postId: options.resourceType === 'post' ? options.referenceId : options.metadata?.postId,
}, },
}); });
} catch (error) { } catch (error) {
@@ -594,4 +600,90 @@ export class CollaborationRequestsService {
} }
return ''; return '';
} }
private serializeRequest(request: unknown) {
const plain = this.toPlainRecord(request);
const requester = this.serializeUser(plain.requesterId);
const targetUser = this.serializeUser(plain.targetUserId);
const post = this.serializePost(plain.postId);
const postId = this.extractEntityId(plain.postId) || plain.postId;
return {
...plain,
id: this.extractEntityId(plain) || plain.id,
requesterId: plain.requesterId,
targetUserId: plain.targetUserId,
postId,
requester,
targetUser,
post,
attachmentUrl: typeof plain.attachmentUrl === 'string' ? plain.attachmentUrl : '',
attachmentType: plain.attachmentType ?? null,
};
}
private serializeUser(value: unknown) {
if (!value) {
return null;
}
const plain = this.toPlainRecord(value);
const id = this.extractEntityId(plain) || this.extractEntityId(value);
return {
...plain,
_id: plain._id ?? id,
id,
username: plain.username ?? '',
name: plain.name ?? '',
stageName: plain.stageName ?? '',
avatar: plain.avatar ?? '',
isVerified: Boolean(plain.isVerified),
};
}
private serializePost(value: unknown) {
if (!value) {
return null;
}
const plain = this.toPlainRecord(value);
const id = this.extractEntityId(plain) || this.extractEntityId(value);
return {
...plain,
_id: plain._id ?? id,
id,
};
}
private toPlainRecord(value: unknown): Record<string, any> {
if (!value) {
return {};
}
if (typeof value === 'string' || value instanceof Types.ObjectId) {
const id = this.extractEntityId(value);
return { _id: id, id };
}
if (typeof value === 'object') {
const maybeDocument = value as { toJSON?: () => unknown; toObject?: () => unknown };
if (typeof maybeDocument.toJSON === 'function') {
const json = maybeDocument.toJSON();
if (json && typeof json === 'object') {
return json as Record<string, any>;
}
}
if (typeof maybeDocument.toObject === 'function') {
const object = maybeDocument.toObject();
if (object && typeof object === 'object') {
return object as Record<string, any>;
}
}
return { ...(value as Record<string, any>) };
}
return {};
}
} }