117 lines
4.6 KiB
JavaScript
117 lines
4.6 KiB
JavaScript
import { assertFails, assertSucceeds } from '@firebase/rules-unit-testing';
|
|
import { deleteObject, ref, uploadBytes } from 'firebase/storage';
|
|
import { afterAll, beforeAll, describe, test } from 'vitest';
|
|
|
|
import { createStorageTestEnvironment } from '../support/environment.mjs';
|
|
|
|
/// 프로필 사진 업로드 — 정책 원장 B-44 의 *Storage 규칙 서버 검증* 이 실제로
|
|
/// 거르는지 본다. 클라이언트 1차 검증(`checkProfilePhoto`)은 앱 쪽 테스트가 잡고,
|
|
/// 여기서는 **앱을 고쳐 우회해도 막히는가**를 확인한다.
|
|
|
|
let testEnvironment;
|
|
|
|
const storageFor = (uid) =>
|
|
testEnvironment.authenticatedContext(uid).storage();
|
|
const anonymousStorage = () =>
|
|
testEnvironment.unauthenticatedContext().storage();
|
|
|
|
/// 규칙은 내용을 보지 않고 크기와 선언된 종류만 본다.
|
|
const bytesOf = (size) => new Uint8Array(size);
|
|
|
|
const image = { contentType: 'image/png' };
|
|
|
|
beforeAll(async () => {
|
|
testEnvironment = await createStorageTestEnvironment();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await testEnvironment.cleanup();
|
|
});
|
|
|
|
describe('프로필 사진 — users/{uid}/profile', () => {
|
|
test('본인 경로에 이미지를 올린다', async () => {
|
|
await assertSucceeds(
|
|
uploadBytes(ref(storageFor('alice'), 'users/alice/profile/photo'), bytesOf(1024), image),
|
|
);
|
|
});
|
|
|
|
test('10MB 를 넘으면 거부한다', async () => {
|
|
await assertFails(
|
|
uploadBytes(
|
|
ref(storageFor('alice'), 'users/alice/profile/photo'),
|
|
bytesOf(10 * 1024 * 1024 + 1),
|
|
image,
|
|
),
|
|
);
|
|
});
|
|
|
|
test('이미지가 아닌 종류는 거부한다', async () => {
|
|
await assertFails(
|
|
uploadBytes(
|
|
ref(storageFor('alice'), 'users/alice/profile/photo'),
|
|
bytesOf(1024),
|
|
{ contentType: 'application/pdf' },
|
|
),
|
|
);
|
|
});
|
|
|
|
test('남의 경로에는 올리지 못한다', async () => {
|
|
await assertFails(
|
|
uploadBytes(ref(storageFor('bob'), 'users/alice/profile/photo'), bytesOf(1024), image),
|
|
);
|
|
});
|
|
|
|
test('로그인하지 않으면 올리지 못한다', async () => {
|
|
await assertFails(
|
|
uploadBytes(ref(anonymousStorage(), 'users/alice/profile/photo'), bytesOf(1024), image),
|
|
);
|
|
});
|
|
|
|
test('라운지 사진은 본인 경로에 10MB 이하 이미지만, 지우기도 본인만', async () => {
|
|
const id = 'AbCdEfGhIjKlMnOpQrSt'; // Firestore 자동 ID 모양(20자)
|
|
const path = `users/alice/lounge/${id}-0`;
|
|
await assertSucceeds(uploadBytes(ref(storageFor('alice'), path), bytesOf(1024), image));
|
|
// 이미 올린 파일 덮어쓰기는 닫는다 — 글의 URL 이 가리키는 내용이 바뀐다.
|
|
await assertFails(uploadBytes(ref(storageFor('alice'), path), bytesOf(1024), image));
|
|
await assertFails(
|
|
uploadBytes(
|
|
ref(storageFor('alice'), `users/alice/lounge/${id}-1`),
|
|
bytesOf(10 * 1024 * 1024 + 1),
|
|
image,
|
|
),
|
|
);
|
|
await assertFails(
|
|
uploadBytes(ref(storageFor('alice'), `users/alice/lounge/${id}-2`), bytesOf(1024), {
|
|
contentType: 'application/pdf',
|
|
}),
|
|
);
|
|
await assertFails(uploadBytes(ref(storageFor('bob'), `users/alice/lounge/${id}-3`), bytesOf(1024), image));
|
|
// 파일 이름 규칙 밖 — 칸 번호 5 · 임의 이름.
|
|
await assertFails(uploadBytes(ref(storageFor('alice'), `users/alice/lounge/${id}-5`), bytesOf(1024), image));
|
|
await assertFails(uploadBytes(ref(storageFor('alice'), 'users/alice/lounge/anything'), bytesOf(1024), image));
|
|
await assertFails(deleteObject(ref(storageFor('bob'), path)));
|
|
await assertSucceeds(deleteObject(ref(storageFor('alice'), path)));
|
|
});
|
|
|
|
test('열지 않은 경로는 기본 거부다', async () => {
|
|
await assertFails(
|
|
uploadBytes(ref(storageFor('alice'), 'posts/post-1/media/1.png'), bytesOf(1024), image),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('캠페인 결과보고 사진·영수증 — campaignReports/{campaignId} (B-35 운영 전용)', () => {
|
|
const path = 'campaignReports/campaign-1/r1.png';
|
|
const ops = () => testEnvironment.authenticatedContext('ops-user', { role: 'ops' }).storage();
|
|
|
|
test('운영이 이미지를 올린다', async () => {
|
|
await assertSucceeds(uploadBytes(ref(ops(), path), bytesOf(1024), image));
|
|
});
|
|
|
|
test('운영이 아니면(개설자 포함) · 10MB 초과 · 이미지 아닌 종류는 거부한다', async () => {
|
|
await assertFails(uploadBytes(ref(storageFor('alice'), path), bytesOf(1024), image));
|
|
await assertFails(uploadBytes(ref(ops(), path), bytesOf(10 * 1024 * 1024 + 1), image));
|
|
await assertFails(uploadBytes(ref(ops(), path), bytesOf(1024), { contentType: 'text/html' }));
|
|
});
|
|
});
|