mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Fixed comment likes being incorrect when logged in as an Admin (#21833)
ref https://linear.app/ghost/issue/PLG-296/ When logged in as an Admin, comments-ui switches comment reads from the Members API over to the Admin API so that hidden comments can be displayed to allow moderation activities. However, the Admin API not using member authentication and CORS preventing the front-end members auth cookie being passed over to the Admin API domain meant that the logged-in member's likes were missing when fetching via the Admin API as there is no available reference to the logged in member. This change works around the problem by introducing an `impersonate_member_uuid` param to the comments read/browse endpoints of the Admin API. When passed, the provided uuid is used to simulate that member being logged in so that likes are correctly shown. - Introduced `impersonation_member_id` parameter to resolve issues with admin API not returning correct "liked" status for comments when an admin is logged in. - Updated API endpoints in `comment-replies.js` and `comments.js` to handle `impersonation_member_id`. - Adjusted `CommentsController` to validate and process the `impersonation_member_id` parameter before passing it to database queries. - Enhanced test coverage to ensure proper handling of the new parameter and accurate "liked" status behavior.
This commit is contained in:
parent
ece7c93759
commit
04f0b9fc3f
14 changed files with 359 additions and 24 deletions
|
@ -116,7 +116,7 @@ const App: React.FC<AppProps> = ({scriptTag}) => {
|
|||
admin = await adminApi.getUser();
|
||||
if (admin && state.labs.commentImprovements) {
|
||||
// this is a bit of a hack, but we need to fetch the comments fully populated if the user is an admin
|
||||
const adminComments = await adminApi.browse({page: 1, postId: options.postId, order: state.order});
|
||||
const adminComments = await adminApi.browse({page: 1, postId: options.postId, order: state.order, memberUuid: state.member?.uuid});
|
||||
setState({
|
||||
...state,
|
||||
adminApi: adminApi,
|
||||
|
|
|
@ -10,7 +10,7 @@ async function loadMoreComments({state, api, options, order}: {state: EditableAp
|
|||
}
|
||||
let data;
|
||||
if (state.admin && state.adminApi && state.labs.commentImprovements) {
|
||||
data = await state.adminApi.browse({page, postId: options.postId, order: order || state.order});
|
||||
data = await state.adminApi.browse({page, postId: options.postId, order: order || state.order, memberUuid: state.member?.uuid});
|
||||
} else {
|
||||
data = await api.comments.browse({page, postId: options.postId, order: order || state.order});
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ async function setOrder({state, data: {order}, options, api, dispatchAction}: {s
|
|||
try {
|
||||
let data;
|
||||
if (state.admin && state.adminApi && state.labs.commentImprovements) {
|
||||
data = await state.adminApi.browse({page: 1, postId: options.postId, order});
|
||||
data = await state.adminApi.browse({page: 1, postId: options.postId, order, memberUuid: state.member?.uuid});
|
||||
} else {
|
||||
data = await api.comments.browse({page: 1, postId: options.postId, order});
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ async function setOrder({state, data: {order}, options, api, dispatchAction}: {s
|
|||
async function loadMoreReplies({state, api, data: {comment, limit}, isReply}: {state: EditableAppContext, api: GhostApi, data: {comment: any, limit?: number | 'all'}, isReply: boolean}): Promise<Partial<EditableAppContext>> {
|
||||
let data;
|
||||
if (state.admin && state.adminApi && state.labs.commentImprovements && !isReply) { // we don't want the admin api to load reply data for replying to a reply, so we pass isReply: true
|
||||
data = await state.adminApi.replies({commentId: comment.id, afterReplyId: comment.replies[comment.replies.length - 1]?.id, limit});
|
||||
data = await state.adminApi.replies({commentId: comment.id, afterReplyId: comment.replies[comment.replies.length - 1]?.id, limit, memberUuid: state.member?.uuid});
|
||||
} else {
|
||||
data = await api.comments.replies({commentId: comment.id, afterReplyId: comment.replies[comment.replies.length - 1]?.id, limit});
|
||||
}
|
||||
|
@ -150,13 +150,13 @@ async function hideComment({state, data: comment}: {state: EditableAppContext, a
|
|||
|
||||
async function showComment({state, api, data: comment}: {state: EditableAppContext, api: GhostApi, adminApi: any, data: {id: string}}) {
|
||||
if (state.adminApi) {
|
||||
await state.adminApi.showComment(comment.id);
|
||||
await state.adminApi.showComment({id: comment.id});
|
||||
}
|
||||
// We need to refetch the comment, to make sure we have an up to date HTML content
|
||||
// + all relations are loaded as the current member (not the admin)
|
||||
let data;
|
||||
if (state.admin && state.adminApi && state.labs.commentImprovements) {
|
||||
data = await state.adminApi.read({commentId: comment.id});
|
||||
data = await state.adminApi.read({commentId: comment.id, memberUuid: state.member?.uuid});
|
||||
} else {
|
||||
data = await api.comments.read(comment.id);
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ describe('setupAdminAPI', () => {
|
|||
const adminUrl = 'https://example.com';
|
||||
const api = setupAdminAPI({adminUrl});
|
||||
|
||||
const apiPromise = api.showComment('123');
|
||||
const apiPromise = api.showComment({id: '123'});
|
||||
|
||||
const eventHandler = addEventListenerSpy.mock.calls.find(
|
||||
([eventType]) => eventType === 'message'
|
||||
|
|
|
@ -59,11 +59,11 @@ export function setupAdminAPI({adminUrl}: {adminUrl: string}) {
|
|||
async hideComment(id: string) {
|
||||
return await callApi('hideComment', {id});
|
||||
},
|
||||
async showComment(id: string) {
|
||||
async showComment({id} : {id: string}) {
|
||||
return await callApi('showComment', {id});
|
||||
},
|
||||
|
||||
async browse({page, postId, order}: {page: number, postId: string, order?: string}) {
|
||||
async browse({page, postId, order, memberUuid}: {page: number, postId: string, order?: string, memberUuid?: string}) {
|
||||
let filter = null;
|
||||
if (firstCommentCreatedAt && !order) {
|
||||
filter = `created_at:<=${firstCommentCreatedAt}`;
|
||||
|
@ -80,6 +80,10 @@ export function setupAdminAPI({adminUrl}: {adminUrl: string}) {
|
|||
params.set('order', order);
|
||||
}
|
||||
|
||||
if (memberUuid) {
|
||||
params.set('impersonate_member_uuid', memberUuid);
|
||||
}
|
||||
|
||||
const response = await callApi('browseComments', {postId, params: params.toString()});
|
||||
if (!firstCommentCreatedAt) {
|
||||
const firstComment = response.comments[0];
|
||||
|
@ -90,7 +94,7 @@ export function setupAdminAPI({adminUrl}: {adminUrl: string}) {
|
|||
|
||||
return response;
|
||||
},
|
||||
async replies({commentId, afterReplyId, limit}: {commentId: string; afterReplyId: string; limit?: number | 'all'}) {
|
||||
async replies({commentId, afterReplyId, limit, memberUuid}: {commentId: string; afterReplyId: string; limit?: number | 'all', memberUuid?: string}) {
|
||||
const filter = `id:>'${afterReplyId}'`;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
@ -103,14 +107,26 @@ export function setupAdminAPI({adminUrl}: {adminUrl: string}) {
|
|||
params.set('filter', filter);
|
||||
}
|
||||
|
||||
if (memberUuid) {
|
||||
params.set('impersonate_member_uuid', memberUuid);
|
||||
}
|
||||
|
||||
const response = await callApi('getReplies', {commentId, params: params.toString()});
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
async read({commentId}: {commentId: string}) {
|
||||
const response = await callApi('readComment', {commentId});
|
||||
return response;
|
||||
async read({commentId, memberUuid}: {commentId: string, memberUuid?: string}) {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (memberUuid) {
|
||||
params.set('impersonate_member_uuid', memberUuid);
|
||||
}
|
||||
|
||||
return await callApi('readComment', {
|
||||
commentId,
|
||||
...(params.toString() && {params: params.toString()})
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -18,8 +18,7 @@ test.describe('Admin moderation', async () => {
|
|||
member?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
};
|
||||
async function initializeTest(page, options: InitializeTestOptions = {}) {
|
||||
options = {isAdmin: true, labs: false, member: {id: '1'}, ...options};
|
||||
|
||||
options = {isAdmin: true, labs: false, member: {id: '1', uuid: '12345'}, ...options};
|
||||
if (options.isAdmin) {
|
||||
await mockAdminAuthFrame({page, admin});
|
||||
} else {
|
||||
|
@ -124,6 +123,106 @@ test.describe('Admin moderation', async () => {
|
|||
});
|
||||
|
||||
test.describe('commentImprovements', function () {
|
||||
test('memeber uuid are passed to admin browse api params', async ({page}) => {
|
||||
mockedApi.addComment({html: '<p>This is comment 1</p>'});
|
||||
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
|
||||
const {frame} = await initializeTest(page, {labs: true});
|
||||
const comments = await frame.getByTestId('comment-component');
|
||||
await expect(comments).toHaveCount(1);
|
||||
expect(adminBrowseSpy.called).toBe(true);
|
||||
const lastCall = adminBrowseSpy.lastCall.args[0];
|
||||
const url = new URL(lastCall.request().url());
|
||||
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
|
||||
});
|
||||
|
||||
test('member uuid gets set when loading more comments', async ({page}) => {
|
||||
// create 25 comments
|
||||
for (let i = 0; i < 25; i++) {
|
||||
mockedApi.addComment({html: `<p>This is comment ${i}</p>`});
|
||||
}
|
||||
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
|
||||
const {frame} = await initializeTest(page, {labs: true});
|
||||
await frame.getByTestId('pagination-component').click();
|
||||
const lastCall = adminBrowseSpy.lastCall.args[0];
|
||||
const url = new URL(lastCall.request().url());
|
||||
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
|
||||
});
|
||||
|
||||
test('member uuid gets set when changing order', async ({page}) => {
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is the oldest</p>',
|
||||
created_at: new Date('2024-02-01T00:00:00Z')
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 2</p>',
|
||||
created_at: new Date('2024-03-02T00:00:00Z')
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is the newest comment</p>',
|
||||
created_at: new Date('2024-04-03T00:00:00Z')
|
||||
});
|
||||
|
||||
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
|
||||
const {frame} = await initializeTest(page, {labs: true});
|
||||
|
||||
const sortingForm = await frame.getByTestId('comments-sorting-form');
|
||||
|
||||
await sortingForm.click();
|
||||
|
||||
const sortingDropdown = await frame.getByTestId(
|
||||
'comments-sorting-form-dropdown'
|
||||
);
|
||||
|
||||
const optionSelect = await sortingDropdown.getByText('Newest');
|
||||
mockedApi.setDelay(100);
|
||||
await optionSelect.click();
|
||||
const lastCall = adminBrowseSpy.lastCall.args[0];
|
||||
const url = new URL(lastCall.request().url());
|
||||
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
|
||||
});
|
||||
|
||||
test('member uuid gets set when loading more replies', async ({page}) => {
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 1</p>',
|
||||
replies: [
|
||||
buildReply({html: '<p>This is reply 1</p>'}),
|
||||
buildReply({html: '<p>This is reply 2</p>'}),
|
||||
buildReply({html: '<p>This is reply 3</p>'}),
|
||||
buildReply({html: '<p>This is reply 4</p>'}),
|
||||
buildReply({html: '<p>This is reply 5</p>'}),
|
||||
buildReply({html: '<p>This is reply 6</p>'})
|
||||
]
|
||||
});
|
||||
|
||||
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'getReplies');
|
||||
const {frame} = await initializeTest(page, {labs: true});
|
||||
const comments = await frame.getByTestId('comment-component');
|
||||
const comment = comments.nth(0);
|
||||
await comment.getByTestId('reply-pagination-button').click();
|
||||
const lastCall = adminBrowseSpy.lastCall.args[0];
|
||||
const url = new URL(lastCall.request().url());
|
||||
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
|
||||
});
|
||||
|
||||
test('member uuid gets set when reading a comment (after unhiding)', async ({page}) => {
|
||||
mockedApi.addComment({html: '<p>This is comment 1</p>'});
|
||||
mockedApi.addComment({html: '<p>This is comment 2</p>', status: 'hidden'});
|
||||
const adminReadSpy = sinon.spy(mockedApi.adminRequestHandlers, 'getOrUpdateComment');
|
||||
const {frame} = await initializeTest(page, {labs: true});
|
||||
const comments = await frame.getByTestId('comment-component');
|
||||
await expect(comments).toHaveCount(2);
|
||||
await expect(comments.nth(1)).toContainText('Hidden for members');
|
||||
const moreButtons = comments.nth(1).getByTestId('more-button');
|
||||
await moreButtons.click();
|
||||
await moreButtons.getByTestId('show-button').click();
|
||||
await expect(comments.nth(1)).not.toContainText('Hidden for members');
|
||||
|
||||
const lastCall = adminReadSpy.lastCall.args[0];
|
||||
const url = new URL(lastCall.request().url());
|
||||
|
||||
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
|
||||
});
|
||||
|
||||
test('hidden comments are not displayed for non-admins', async ({page}) => {
|
||||
mockedApi.addComment({html: '<p>This is comment 1</p>'});
|
||||
mockedApi.addComment({html: '<p>This is comment 2</p>', status: 'hidden'});
|
||||
|
|
|
@ -435,6 +435,7 @@ export class MockedApi {
|
|||
const limit = parseInt(url.searchParams.get('limit') ?? '5');
|
||||
const filter = url.searchParams.get('filter') ?? '';
|
||||
const order = url.searchParams.get('order') ?? '';
|
||||
const memberUuid = url.searchParams.get('impersonate_member_uuid') ?? '';
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
|
@ -443,7 +444,28 @@ export class MockedApi {
|
|||
limit,
|
||||
filter,
|
||||
order,
|
||||
admin: true
|
||||
admin: true,
|
||||
memberUuid
|
||||
}))
|
||||
});
|
||||
},
|
||||
|
||||
async getReplies(route) {
|
||||
await this.#delayResponse();
|
||||
const url = new URL(route.request().url());
|
||||
|
||||
const limit = parseInt(url.searchParams.get('limit') ?? '5');
|
||||
const commentId = url.pathname.split('/').reverse()[2];
|
||||
const filter = url.searchParams.get('filter') ?? '';
|
||||
const memberUuid = url.searchParams.get('impersonate_member_uuid') ?? '';
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
body: JSON.stringify(this.browseReplies({
|
||||
limit,
|
||||
filter,
|
||||
commentId,
|
||||
memberUuid
|
||||
}))
|
||||
});
|
||||
},
|
||||
|
@ -454,6 +476,7 @@ export class MockedApi {
|
|||
|
||||
if (route.request().method() === 'GET') {
|
||||
const commentId = url.pathname.split('/').reverse()[1];
|
||||
const memberUuid = url.searchParams.get('impersonate_member_uuid') ?? '';
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
body: JSON.stringify(this.browseComments({
|
||||
|
@ -461,7 +484,8 @@ export class MockedApi {
|
|||
filter: `id:'${commentId}'`,
|
||||
page: 1,
|
||||
order: '',
|
||||
admin: true
|
||||
admin: true,
|
||||
memberUuid
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
@ -470,7 +494,6 @@ export class MockedApi {
|
|||
const commentId = url.pathname.split('/').reverse()[1];
|
||||
const payload = JSON.parse(route.request().postData());
|
||||
const comment = findCommentById(this.comments, commentId);
|
||||
|
||||
if (!comment) {
|
||||
await route.fulfill({status: 404});
|
||||
return;
|
||||
|
@ -506,6 +529,7 @@ export class MockedApi {
|
|||
// Admin API -----------------------------------------------------------
|
||||
await page.route(`${path}/ghost/api/admin/users/me/`, this.adminRequestHandlers.getUser.bind(this));
|
||||
await page.route(`${path}/ghost/api/admin/comments/post/*/*`, this.adminRequestHandlers.browseComments.bind(this));
|
||||
await page.route(`${path}/ghost/api/admin/comments/*/`, this.adminRequestHandlers.getOrUpdateComment.bind(this));
|
||||
await page.route(`${path}/ghost/api/admin/comments/*/*`, this.adminRequestHandlers.getOrUpdateComment.bind(this));
|
||||
await page.route(`${path}/ghost/api/admin/comments/*/replies/*`, this.adminRequestHandlers.getReplies.bind(this));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,6 +70,7 @@ function authFrameMain() {
|
|||
respond(null, json);
|
||||
} catch (err) {
|
||||
console.log('e2e Admin endpoint error:', err); // eslint-disable-line no-console
|
||||
console.log('error with', data); // eslint-disable-line no-console
|
||||
respond(err, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,8 +51,8 @@ window.addEventListener('message', async function (event) {
|
|||
|
||||
if (data.action === 'readComment') {
|
||||
try {
|
||||
const {commentId} = data;
|
||||
const res = await fetch(adminUrl + '/comments/' + commentId + '/');
|
||||
const {commentId, params} = data;
|
||||
const res = await fetch(adminUrl + '/comments/' + commentId + '/' + '?' + new URLSearchParams(params).toString());
|
||||
const json = await res.json();
|
||||
respond(null, json);
|
||||
} catch (err) {
|
||||
|
|
|
@ -18,7 +18,8 @@ const controller = {
|
|||
'filter',
|
||||
'order',
|
||||
'debug',
|
||||
'id'
|
||||
'id',
|
||||
'impersonate_member_uuid'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
|
@ -37,7 +38,8 @@ const controller = {
|
|||
cacheInvalidate: false
|
||||
},
|
||||
options: [
|
||||
'include'
|
||||
'include',
|
||||
'impersonate_member_uuid'
|
||||
],
|
||||
data: [
|
||||
'id',
|
||||
|
|
|
@ -54,7 +54,8 @@ const controller = {
|
|||
'fields',
|
||||
'filter',
|
||||
'order',
|
||||
'debug'
|
||||
'debug',
|
||||
'impersonate_member_uuid'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
|
|
|
@ -23,6 +23,14 @@ module.exports = class CommentsController {
|
|||
this.stats = stats;
|
||||
}
|
||||
|
||||
async #setImpersonationContext(options) {
|
||||
if (options.impersonate_member_uuid) {
|
||||
options.context = options.context || {};
|
||||
options.context.member = options.context.member || {};
|
||||
options.context.member.id = await this.service.getMemberIdByUUID(options.impersonate_member_uuid);
|
||||
}
|
||||
}
|
||||
|
||||
#checkMember(frame) {
|
||||
if (!frame.options?.context?.member?.id) {
|
||||
throw new errors.UnauthorizedError({
|
||||
|
@ -51,6 +59,7 @@ module.exports = class CommentsController {
|
|||
frame.options.filter = `post_id:${frame.options.post_id}`;
|
||||
}
|
||||
}
|
||||
|
||||
return await this.service.getComments(frame.options);
|
||||
}
|
||||
|
||||
|
@ -71,7 +80,15 @@ module.exports = class CommentsController {
|
|||
frame.options.filter = `post_id:${frame.options.post_id}`;
|
||||
}
|
||||
}
|
||||
|
||||
frame.options.isAdmin = true;
|
||||
// Admin routes in Comments-UI lack member context due to cross-domain constraints (CORS), which prevents
|
||||
// credentials from being passed. This causes issues like the inability to determine if a
|
||||
// logged-in admin (acting on behalf of a member) has already liked a comment.
|
||||
// To resolve this, we retrieve the `impersonate_member_uuid` from the request params and
|
||||
// explicitly set it in the context options as the acting member's ID.
|
||||
// Note: This approach is applied to several admin routes where member context is required.
|
||||
await this.#setImpersonationContext(frame.options);
|
||||
return await this.service.getAdminComments(frame.options);
|
||||
}
|
||||
|
||||
|
@ -88,6 +105,8 @@ module.exports = class CommentsController {
|
|||
async adminReplies(frame) {
|
||||
frame.options.isAdmin = true;
|
||||
frame.options.order = 'created_at asc'; // we always want to load replies from oldest to newest
|
||||
await this.#setImpersonationContext(frame.options);
|
||||
|
||||
return this.service.getReplies(frame.options.id, _.omit(frame.options, 'id'));
|
||||
}
|
||||
|
||||
|
@ -95,6 +114,7 @@ module.exports = class CommentsController {
|
|||
* @param {Frame} frame
|
||||
*/
|
||||
async read(frame) {
|
||||
await this.#setImpersonationContext(frame.options);
|
||||
return await this.service.getCommentByID(frame.data.id, frame.options);
|
||||
}
|
||||
|
||||
|
|
|
@ -400,6 +400,18 @@ class CommentsService {
|
|||
|
||||
return model;
|
||||
}
|
||||
|
||||
async getMemberIdByUUID(uuid, options) {
|
||||
const member = await this.models.Member.findOne({uuid}, options);
|
||||
|
||||
if (!member) {
|
||||
throw new errors.NotFoundError({
|
||||
message: tpl(messages.memberNotFound)
|
||||
});
|
||||
}
|
||||
|
||||
return member.id;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CommentsService;
|
||||
|
|
|
@ -156,6 +156,16 @@ Object {
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`Admin Comments API - commentImprovements off likes Can like a comment 1: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "*",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Admin Comments API - commentImprovements on Hide Can hide comments 1: [body] 1`] = `
|
||||
Object {
|
||||
"comments": Array [
|
||||
|
@ -319,3 +329,103 @@ Object {
|
|||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Admin Comments API - commentImprovements on Logged in member gets own likes via admin api can get comment liked status by impersonating member 1: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "*",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Admin Comments API - commentImprovements on Logged in member gets own likes via admin api can get comment liked status by impersonating member via admin browse route 1: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "*",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Admin Comments API - commentImprovements on Logged in member gets own likes via admin api can get comment liked status by impersonating member via admin get by comment id read route 1: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "*",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Admin Comments API - commentImprovements on Logged in member gets own likes via admin api can get comment liked status by impersonating member via admin get by comment read route 1: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "*",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Admin Comments API - commentImprovements on Logged in member gets own likes via admin api can get comment liked status by impersonating member via admin get by comment replies route 1: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "*",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Admin Comments API - commentImprovements on Logged in member gets own likes via admin api can get comment liked status by impersonating member via admin get by comment replies route 2: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "*",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Admin Comments API - commentImprovements on Logged in member gets own likes via admin api can get comment liked status by impersonating member via admin get by comment replies route 3: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "*",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/, /api/members/comments/67568ca99db975825f9772a5/replies/",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Admin Comments API - commentImprovements on Logged in member likes via admin api can get comment liked status by impersonating member 1: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "*",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Admin Comments API - commentImprovements on can get logged in member likes via admin api Can like a comment 1: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "*",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Admin Comments API - commentImprovements on likes Can like a comment 1: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "*",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"x-cache-invalidate": "/api/members/comments/post/618ba1ffbe2896088840a6e1/",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -718,5 +718,55 @@ async function getMemberComments(url, commentsMatcher = [membersCommentMatcher])
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (enableCommentImprovements) {
|
||||
describe('Logged in member gets own likes via admin api', function () {
|
||||
let comment;
|
||||
let post;
|
||||
this.beforeEach(async function () {
|
||||
post = fixtureManager.get('posts', 1);
|
||||
comment = await dbFns.addComment({
|
||||
post_id: post.id,
|
||||
member_id: fixtureManager.get('members', 1).id
|
||||
});
|
||||
await membersApi.loginAs(fixtureManager.get('members', 1).email);
|
||||
|
||||
await membersApi
|
||||
.post(`/api/comments/${comment.get('id')}/like/`)
|
||||
.expectStatus(204)
|
||||
.matchHeaderSnapshot({
|
||||
etag: anyEtag
|
||||
})
|
||||
.expectEmptyBody();
|
||||
});
|
||||
it('can get comment liked status by impersonating member via admin browse route', async function () {
|
||||
// Like the comment
|
||||
const res = await adminApi.get(`/comments/post/${post.id}/?impersonate_member_uuid=${fixtureManager.get('members', 1).uuid}`);
|
||||
res.body.comments[0].liked.should.eql(true);
|
||||
});
|
||||
|
||||
it('can get comment liked status by impersonating member via admin get by comment id read route', async function () {
|
||||
const res = await adminApi.get(`/comments/${comment.get('id')}/?impersonate_member_uuid=${fixtureManager.get('members', 1).uuid}`);
|
||||
res.body.comments[0].liked.should.eql(true);
|
||||
});
|
||||
|
||||
it('can get comment liked status by impersonating member via admin get by comment replies route', async function () {
|
||||
const {parent, replies} = await dbFns.addCommentWithReplies({
|
||||
member_id: fixtureManager.get('members', 1).id,
|
||||
replies: [{
|
||||
member_id: fixtureManager.get('members', 1).id
|
||||
}]
|
||||
});
|
||||
|
||||
await membersApi
|
||||
.post(`/api/comments/${replies[0].id}/like/`)
|
||||
.expectStatus(204)
|
||||
.expectEmptyBody();
|
||||
|
||||
const res = await adminApi.get(`/comments/${parent.get('id')}/replies/?impersonate_member_uuid=${fixtureManager.get('members', 1).uuid}`);
|
||||
res.body.comments[0].liked.should.eql(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue