Flutter 앱 골격 · 5탭 네비게이션 · 로그인 화면 · Firestore 보안 규칙·인덱스

This commit is contained in:
Sn-Kinos
2026-08-10 01:02:03 +09:00
parent fbba4405c3
commit 233ef99837
113 changed files with 8549 additions and 2 deletions
@@ -0,0 +1,74 @@
import { readFileSync } from 'node:fs';
import {
collection,
getDocs,
orderBy,
query,
where,
} from 'firebase/firestore';
import {
createTestEnvironment,
seedDocuments,
} from '../support/environment.mjs';
const manifest = JSON.parse(
readFileSync(new URL('../../firestore.indexes.json', import.meta.url), 'utf8'),
);
const deliberatelyMissingFields = [
['fandomId', 'ASCENDING'],
['visibility', 'ASCENDING'],
['createdAt', 'DESCENDING'],
['likeCount', 'DESCENDING'],
];
const hasDeliberatelyMissingIndex = manifest.indexes.some((index) => {
if (index.collectionGroup !== 'posts' || index.fields.length !== deliberatelyMissingFields.length) {
return false;
}
return deliberatelyMissingFields.every(
([fieldPath, order], position) =>
index.fields[position].fieldPath === fieldPath && index.fields[position].order === order,
);
});
if (hasDeliberatelyMissingIndex) {
throw new Error('Probe precondition failed: deliberately missing composite index exists');
}
console.log('INDEX MANIFEST CONTROL PASS: required 4-field posts composite index -> absent');
const testEnvironment = await createTestEnvironment();
try {
await testEnvironment.clearFirestore();
await seedDocuments(testEnvironment, {
'posts/post-1': {
fandomId: 'fandom-a',
visibility: 'visible',
createdAt: '2026-08-08T00:00:00Z',
likeCount: 1,
},
'posts/post-2': {
fandomId: 'fandom-a',
visibility: 'visible',
createdAt: '2026-08-08T01:00:00Z',
likeCount: 2,
},
});
const firestore = testEnvironment.authenticatedContext('alice').firestore();
const missingIndexQuery = query(
collection(firestore, 'posts'),
where('fandomId', '==', 'fandom-a'),
where('visibility', '==', 'visible'),
orderBy('createdAt', 'desc'),
orderBy('likeCount', 'desc'),
);
const result = await getDocs(missingIndexQuery);
console.log(`EMULATOR MISSING-INDEX QUERY RESULT: allowed (${result.size} documents)`);
console.log('INDEX VALIDATION VERDICT: unsupported — emulator does not enforce composite indexes');
} finally {
await testEnvironment.cleanup();
}
@@ -0,0 +1,41 @@
import { doc, getDoc, setDoc } from 'firebase/firestore';
import { assertSucceeds } from '@firebase/rules-unit-testing';
import {
createTestEnvironment,
seedDocuments,
userDocument,
} from '../support/environment.mjs';
const testEnvironment = await createTestEnvironment();
try {
await testEnvironment.clearFirestore();
const uid = 'negative-control-user';
const userDb = testEnvironment.authenticatedContext(uid).firestore();
let denial;
try {
await setDoc(doc(userDb, `users/${uid}/wallet/main`), { balance: 999_999 });
} catch (error) {
denial = error;
}
if (denial?.code !== 'permission-denied') {
throw new Error(
`Negative control failed: expected permission-denied, received ${denial?.code ?? 'success'}`,
);
}
console.log('NEGATIVE CONTROL PASS: authenticated wallet write -> permission-denied');
await seedDocuments(testEnvironment, {
[`users/${uid}`]: userDocument(uid),
[`users/${uid}/wallet/main`]: { balance: 100 },
});
await assertSucceeds(getDoc(doc(userDb, `users/${uid}/wallet/main`)));
console.log('REACHABILITY CONTROL PASS: owner wallet read -> allowed');
} finally {
await testEnvironment.cleanup();
}