fix: show post and comment actions only to the verified author
Deploy EG-BIM QA Gateway / deploy (push) Successful in 40s

This commit is contained in:
root committed 2026-10-30 15:58:41 +09:00
1 parent 688d16555e
commit dbf5dc3611
8 files changed
+89 -30

No files matched your search

+34 -4
View File
@@ -21,13 +21,13 @@ function element() {
};
}
async function detailPage({ comments = [], cachedComments = [], createResponse = {}, patchResponse = {}, authRefresh = false } = {}) {
async function detailPage({ comments = [], cachedComments = [], createResponse = {}, patchResponse = {}, authRefresh = false, user = USER, postOwner = USER.requesterId } = {}) {
const elements = new Map([
'#detail', '[data-detail-category]', '[data-detail-title]', '[data-detail-status]',
'[data-detail-date]', '[data-detail-author]', '[data-detail-company]',
'[data-detail-department]', '[data-detail-content]', '[data-detail-secret]',
'#comments', '#commentCount', '#commentForm', '#commentContent',
'#commentError', '#commentSubmit', '[data-comment-edit-input]'
'#commentError', '#commentSubmit', '[data-comment-edit-input]', '#editButton', '#deleteButton'
].map((selector) => [selector, element()]));
const storage = new Map([[COMMENTS_KEY, JSON.stringify({ [POST]: cachedComments })]]);
const requests = [];
@@ -45,7 +45,7 @@ async function detailPage({ comments = [], cachedComments = [], createResponse =
else if (method === 'PATCH') body = patchResponse;
else if (method === 'DELETE') body = { ok: true };
else if (path.endsWith('/comments')) body = { comments };
else body = { id: POST, title: '문의 제목', contents: '문의 내용', requester_id: USER.requesterId };
else body = { id: POST, title: '문의 제목', contents: '문의 내용', requester_id: postOwner, requester_name: USER.name };
return new Response(JSON.stringify(body));
}
});
@@ -54,7 +54,7 @@ async function detailPage({ comments = [], cachedComments = [], createResponse =
resolvedAuthUser = user;
if (refresh) authReady = Promise.resolve(user);
} }; })();`), context);
context.testPage.setUser(USER, authRefresh);
context.testPage.setUser(user, authRefresh);
await context.testPage.initDetail();
await new Promise((resolve) => setImmediate(resolve));
return {
@@ -141,3 +141,33 @@ test('a cached comment without an ID remains read-only when loading the page', a
assert.doesNotMatch(page.html(), /data-comment-edit|data-comment-delete|data-comment-save/);
assert.equal(page.requests.some((request) => ['PATCH', 'DELETE'].includes(request.method)), false);
});
test('another author post hides both post buttons while the viewer own comment keeps its actions', async () => {
const page = await detailPage({ postOwner: 'another-user', comments: [comment(), comment({ id: SECOND, author_id: 'another-user' })] });
assert.equal(page.elements.get('#editButton').hidden, true);
assert.equal(page.elements.get('#deleteButton').hidden, true);
assert.match(page.html(), new RegExp('data-comment-edit="' + FIRST + '"'));
assert.doesNotMatch(page.html(), new RegExp('data-comment-(edit|delete)="' + SECOND + '"'));
});
test('a matching display name never shows post or comment buttons when author IDs are missing', async () => {
const page = await detailPage({ postOwner: '', comments: [comment({ author_id: '' })] });
assert.equal(page.elements.get('#editButton').hidden, true);
assert.equal(page.elements.get('#deleteButton').hidden, true);
assert.doesNotMatch(page.html(), /data-comment-edit|data-comment-delete/);
});
test('post and comment actions follow only the current primary identity and disappear without a session', async () => {
const page = await detailPage({
user: { ...USER, ssoSubject: 'stale-owner', userUuid: 'stale-owner' },
comments: [comment(), comment({ id: SECOND, author_id: 'stale-owner' })]
});
assert.equal(page.elements.get('#editButton').hidden, false);
assert.equal(page.elements.get('#deleteButton').hidden, false);
assert.match(page.html(), new RegExp('data-comment-edit="' + FIRST + '"'));
assert.doesNotMatch(page.html(), new RegExp('data-comment-(edit|delete)="' + SECOND + '"'));
const anonymous = await detailPage({ user: null, authRefresh: true, comments: [comment()] });
assert.equal(anonymous.elements.get('#editButton').hidden, true);
assert.equal(anonymous.elements.get('#deleteButton').hidden, true);
assert.doesNotMatch(anonymous.html(), /data-comment-edit|data-comment-delete/);
});
+21
View File
@@ -42,3 +42,24 @@ test('own-post filtering follows canonical identity even after company and depar
assert.equal(page.isPostOwner({ requesterId: 'fixture-owner', company: '이전 회사' }, user), true);
assert.equal(page.isPostOwner({ requesterId: 'another-owner', author: '동명이인', company: user.company }, user), false);
});
test('a matching display name, login or email never grants post ownership without an author ID', () => {
const user = { requesterId: 'fixture-owner', name: '동명이인', loginId: 'same@example.test', email: 'same@example.test' };
for (const author of [user.name, user.loginId, user.email]) {
assert.equal(page.isPostOwner({ author }, user), false);
}
assert.equal(page.isPostOwner({ requesterId: 'fixture-owner' }, null), false);
});
test('the signed primary identity takes precedence over stale alternate browser IDs', () => {
const user = { requesterId: 'current-owner', ssoSubject: 'old-owner', userUuid: 'old-owner' };
assert.equal(page.isPostOwner({ requesterId: 'old-owner' }, user), false);
assert.equal(page.isPostOwner({ requesterId: 'current-owner' }, user), true);
});
test('server requester and user UUID fields remain the author identity when normalizing a session', () => {
const user = page.normalizeAuthUser({ requesterId: 'current-owner', userUuid: 'current-owner', email: 'fixture@example.test' }, {});
assert.equal(user.requesterId, 'current-owner');
assert.equal(user.ssoSubject, 'current-owner');
assert.equal(user.userUuid, 'current-owner');
});