169 lines
8.0 KiB
TypeScript
169 lines
8.0 KiB
TypeScript
import { afterAll, beforeAll, expect, it, vi } from 'vitest';
|
|
import { getFirestore, Timestamp } from 'firebase-admin/firestore';
|
|
|
|
import {
|
|
cleanupCampaignComment, cleanupPost, fnCampaignCommentCount, fnCampaignCommentLikeCount, fnPostCommentCount, fnPostLikeCount, recountPost,
|
|
} from '../src/inside/lounge.js';
|
|
import { bootAdmin, shutdownAdmin } from './support.js';
|
|
|
|
/** 라운지 좋아요 · 댓글 수는 하위 문서를 다시 세어 적는다 — 재전달에도 부풀지 않는다. */
|
|
|
|
const POST = 'test-lounge-post';
|
|
|
|
beforeAll(() => { bootAdmin(); });
|
|
afterAll(async () => {
|
|
const db = getFirestore();
|
|
for (const id of [POST, 'test-lounge-gone', 'test-lounge-stale', 'test-lounge-del']) {
|
|
await db.recursiveDelete(db.doc(`posts/${id}`));
|
|
}
|
|
await db.recursiveDelete(db.doc('campaigns/test-cmp-comments'));
|
|
await shutdownAdmin();
|
|
});
|
|
|
|
/** 트리거 이벤트 모양. `before`/`after` 는 문서 스냅샷 흉내만 낸다. */
|
|
function written(document: string, params: Record<string, string>, data?: unknown) {
|
|
return {
|
|
specversion: '1.0', id: `e-${Math.random()}`, source: 'firestore',
|
|
type: 'google.cloud.firestore.document.v1.written', time: new Date().toISOString(),
|
|
data, params, location: 'asia-northeast3', project: 'demo-fanuzu',
|
|
database: '(default)', namespace: '', document,
|
|
} as never;
|
|
}
|
|
|
|
const snap = (fields: Record<string, unknown> | null) => ({
|
|
exists: fields !== null,
|
|
get: (k: string) => fields?.[k],
|
|
});
|
|
|
|
it('좋아요 수는 몇 번을 돌아도 하위 문서 수와 같다', async () => {
|
|
const db = getFirestore();
|
|
await db.doc(`posts/${POST}`).set({ likeCount: 99, commentCount: 99 });
|
|
for (const uid of ['a', 'b', 'c']) {
|
|
await db.doc(`posts/${POST}/likes/${uid}`).set({ createdAt: new Date() });
|
|
}
|
|
await recountPost(POST, 'likes');
|
|
await recountPost(POST, 'likes');
|
|
expect((await db.doc(`posts/${POST}`).get()).get('likeCount')).toBe(3);
|
|
|
|
await db.doc(`posts/${POST}/likes/a`).delete();
|
|
// 트리거 배선 — 경로 파라미터로 글을 찾는다.
|
|
await fnPostLikeCount.run(written(`posts/${POST}/likes/a`, { postId: POST, uid: 'a' }));
|
|
expect((await db.doc(`posts/${POST}`).get()).get('likeCount')).toBe(2);
|
|
});
|
|
|
|
it('먼저 센 값이 나중에 센 값을 덮지 않는다', async () => {
|
|
const db = getFirestore();
|
|
const ref = db.doc('posts/test-lounge-stale');
|
|
// 이미 「미래」 시점에 센 값이 적혀 있다 — 지금 센 값은 그보다 오래됐다.
|
|
await ref.set({ likeCount: 7, likeCountAt: Timestamp.fromMillis(Date.now() + 60_000) });
|
|
await db.doc('posts/test-lounge-stale/likes/a').set({ createdAt: new Date() });
|
|
await recountPost('test-lounge-stale', 'likes');
|
|
expect((await ref.get()).get('likeCount')).toBe(7);
|
|
});
|
|
|
|
it('댓글 수는 보이는 댓글만 세고, 본문만 고친 수정은 다시 세지 않는다', async () => {
|
|
const db = getFirestore();
|
|
await db.doc(`posts/${POST}/comments/c1`).set({ visibility: 'visible' });
|
|
await db.doc(`posts/${POST}/comments/c2`).set({ visibility: 'visible' });
|
|
await db.doc(`posts/${POST}/comments/c3`).set({ visibility: 'hidden' });
|
|
await recountPost(POST, 'comments');
|
|
expect((await db.doc(`posts/${POST}`).get()).get('commentCount')).toBe(2);
|
|
|
|
await db.doc(`posts/${POST}`).update({ commentCount: 42 });
|
|
const params = { postId: POST, commentId: 'c1' };
|
|
const doc = `posts/${POST}/comments/c1`;
|
|
await fnPostCommentCount.run(written(doc, params, {
|
|
before: snap({ visibility: 'visible', body: 'a' }),
|
|
after: snap({ visibility: 'visible', body: 'b' }),
|
|
}));
|
|
expect((await db.doc(`posts/${POST}`).get()).get('commentCount')).toBe(42);
|
|
|
|
// 숨김 전환은 다시 센다.
|
|
await db.doc(`posts/${POST}/comments/c2`).update({ visibility: 'hidden' });
|
|
await fnPostCommentCount.run(written(doc, { postId: POST, commentId: 'c2' }, {
|
|
before: snap({ visibility: 'visible' }),
|
|
after: snap({ visibility: 'hidden' }),
|
|
}));
|
|
expect((await db.doc(`posts/${POST}`).get()).get('commentCount')).toBe(1);
|
|
});
|
|
|
|
it('지워진 글은 다시 만들지 않는다', async () => {
|
|
const db = getFirestore();
|
|
await db.doc('posts/test-lounge-gone/likes/a').set({ createdAt: new Date() });
|
|
await recountPost('test-lounge-gone', 'likes');
|
|
expect((await db.doc('posts/test-lounge-gone').get()).exists).toBe(false);
|
|
});
|
|
|
|
it('글이 지워지면 좋아요 · 댓글과 작성자의 그 글 사진을 지운다', async () => {
|
|
const db = getFirestore();
|
|
await db.doc('posts/test-lounge-del/likes/a').set({ createdAt: new Date() });
|
|
await db.doc('posts/test-lounge-del/comments/c').set({ visibility: 'visible' });
|
|
const deleteFiles = vi.fn(async () => {});
|
|
await cleanupPost('test-lounge-del', 'alice', deleteFiles);
|
|
expect((await db.collection('posts/test-lounge-del/likes').get()).size).toBe(0);
|
|
expect((await db.collection('posts/test-lounge-del/comments').get()).size).toBe(0);
|
|
expect(deleteFiles).toHaveBeenCalledWith('users/alice/lounge/test-lounge-del-');
|
|
});
|
|
|
|
it('캠페인 응원 게시판 댓글 수도 보이는 댓글만 다시 세어 캠페인 문서에 적는다', async () => {
|
|
const db = getFirestore();
|
|
const cmp = 'campaigns/test-cmp-comments';
|
|
await db.doc(cmp).set({ status: 'funding', commentCount: 99 });
|
|
await db.doc(`${cmp}/comments/a`).set({ visibility: 'visible' });
|
|
await db.doc(`${cmp}/comments/b`).set({ visibility: 'hidden' });
|
|
const params = { campaignId: 'test-cmp-comments', commentId: 'a' };
|
|
await fnCampaignCommentCount.run(written(`${cmp}/comments/a`, params, {
|
|
before: snap(null),
|
|
after: snap({ visibility: 'visible' }),
|
|
}));
|
|
expect((await db.doc(cmp).get()).get('commentCount')).toBe(1);
|
|
|
|
// 본문만 고친 수정은 다시 세지 않는다.
|
|
await db.doc(cmp).update({ commentCount: 42 });
|
|
await fnCampaignCommentCount.run(written(`${cmp}/comments/a`, params, {
|
|
before: snap({ visibility: 'visible', body: 'x' }),
|
|
after: snap({ visibility: 'visible', body: 'y' }),
|
|
}));
|
|
expect((await db.doc(cmp).get()).get('commentCount')).toBe(42);
|
|
|
|
// 삭제는 다시 센다.
|
|
await db.doc(`${cmp}/comments/a`).delete();
|
|
await fnCampaignCommentCount.run(written(`${cmp}/comments/a`, params, {
|
|
before: snap({ visibility: 'visible' }),
|
|
after: snap(null),
|
|
}));
|
|
expect((await db.doc(cmp).get()).get('commentCount')).toBe(0);
|
|
});
|
|
|
|
it('응원 게시판 댓글 좋아요 수는 댓글 문서에 다시 세어 적고 댓글 수는 건드리지 않는다', async () => {
|
|
const db = getFirestore();
|
|
const cmp = 'campaigns/test-cmp-comments';
|
|
const comment = `${cmp}/comments/liked`;
|
|
await db.doc(cmp).set({ status: 'funding', commentCount: 5 });
|
|
await db.doc(comment).set({ visibility: 'visible', likeCount: 99 });
|
|
for (const uid of ['a', 'b']) {
|
|
await db.doc(`${comment}/likes/${uid}`).set({ createdAt: new Date() });
|
|
}
|
|
const params = { campaignId: 'test-cmp-comments', commentId: 'liked', uid: 'a' };
|
|
await fnCampaignCommentLikeCount.run(written(`${comment}/likes/a`, params));
|
|
await fnCampaignCommentLikeCount.run(written(`${comment}/likes/a`, params));
|
|
expect((await db.doc(comment).get()).get('likeCount')).toBe(2);
|
|
expect((await db.doc(cmp).get()).get('commentCount')).toBe(5);
|
|
});
|
|
|
|
it('응원 게시판 댓글이 지워지면 하위 좋아요를 지우고 댓글 문서를 되살리지 않는다', async () => {
|
|
const db = getFirestore();
|
|
const comment = 'campaigns/test-cmp-comments/comments/gone';
|
|
await db.doc(comment).set({ visibility: 'visible', likeCount: 2 });
|
|
for (const uid of ['a', 'b']) await db.doc(`${comment}/likes/${uid}`).set({ createdAt: new Date() });
|
|
await db.doc(comment).delete();
|
|
await cleanupCampaignComment('test-cmp-comments', 'gone');
|
|
await cleanupCampaignComment('test-cmp-comments', 'gone');
|
|
expect((await db.collection(`${comment}/likes`).get()).size).toBe(0);
|
|
// 좋아요 삭제로 뒤늦게 도는 수 트리거도 없는 댓글을 만들지 않는다.
|
|
await fnCampaignCommentLikeCount.run(written(`${comment}/likes/a`, {
|
|
campaignId: 'test-cmp-comments', commentId: 'gone', uid: 'a',
|
|
}));
|
|
expect((await db.doc(comment).get()).exists).toBe(false);
|
|
});
|