1차 보완(0.1.1+14) 내용 합치기

This commit is contained in:
Sn-Kinos
2026-10-16 17:58:55 +09:00
62 changed files with 2480 additions and 447 deletions
@@ -4,7 +4,9 @@ import { getFirestore } from 'firebase-admin/firestore';
import { getStorage } from 'firebase-admin/storage';
import { bootAdmin, shutdownAdmin, wipeUser } from './support.js';
import { fnAccountWithdraw, fnAuthOnCreate, fnSignupConsent } from '../src/account/account.js';
import {
fnAccountWithdraw, fnAuthOnCreate, fnConsentRecord, fnSignupConsent, hasLocationConsent,
} from '../src/account/account.js';
import { fnAuthKakao, KAKAO_PROVIDER_ID, kakaoUidOf } from '../src/account/kakao.js';
/** 가입 약관 기록(E-04 · E-05) · 계정 생성 트리거의 덮어쓰기 금지 · 국가 미고정(E-13) · 서비스 탈퇴(E-42) · 카카오 이메일(E-02). */
@@ -66,20 +68,57 @@ describe('계정 생성 트리거', () => {
});
});
describe('fnSignupConsent', () => {
it('필수 하나라도 빠지면 거부', async () => {
const r = fnSignupConsent.run(as(U, { version: 'v1', agreed: ['service', 'location', 'age'] }));
describe('fnSignupConsent — Q-E1 · R02', () => {
it('필수(서비스 · 개인정보 수집·이용 · 만 14세) 하나라도 빠지면 거부', async () => {
const r = fnSignupConsent.run(as(U, { version: 'v1', agreed: ['service', 'age'] }));
expect(await codeOf(r)).toBe('invalid-argument');
expect((await db().doc(`users/${U}`).get()).get('termsAgreedAt')).toBeUndefined();
});
it('항목별 행 · termsAgreedAt · 마케팅 = Push Marketing', async () => {
await fnSignupConsent.run(as(U, { version: 'v1', agreed: ['service', 'privacy', 'location', 'age'] }));
it('위치기반 약관 없이 가입된다 · 항목별 행 · 마케팅 기본 OFF = Push Marketing', async () => {
await fnSignupConsent.run(as(U, { version: 'v1', agreed: ['service', 'privacy', 'age'] }));
const user = (await db().doc(`users/${U}`).get()).data()!;
expect(user.termsAgreedAt).toBeDefined();
expect(user.notificationPrefs.marketing).toBe(false);
const rows = (await db().collection(`users/${U}/consents`).get()).docs.map((d) => d.id).sort();
expect(rows).toEqual(['age_v1_grant', 'location_v1_grant', 'marketing_v1_revoke', 'privacy_v1_grant', 'service_v1_grant']);
expect(rows).toEqual(['age_v1_grant', 'marketing_v1_revoke', 'privacy_v1_grant', 'service_v1_grant']);
expect(await hasLocationConsent(U)).toBe(false);
});
it('예전 앱(0.2.0+10)이 가입 화면에서 받은 location 동의는 기록해 위치 Quest 를 막지 않는다', async () => {
await fnSignupConsent.run(as(U, { version: 'v2', agreed: ['service', 'privacy', 'location', 'age'] }));
const rows = (await db().collection(`users/${U}/consents`).get()).docs.map((d) => d.id);
expect(rows).toContain('location_v2_grant');
expect(await hasLocationConsent(U)).toBe(true);
});
});
describe('fnConsentRecord — 위치기능 최초 사용 전 동의 · 철회', () => {
it('동의 → 철회 → 재동의가 각각 행으로 남고(덮어쓰지 않음) 현재 상태를 따른다', async () => {
await fnConsentRecord.run(as(U, { docType: 'location', docVersion: 'loc-1', granted: true }));
expect(await hasLocationConsent(U)).toBe(true);
await fnConsentRecord.run(as(U, { docType: 'location', docVersion: 'loc-1', granted: false }));
expect(await hasLocationConsent(U)).toBe(false);
await fnConsentRecord.run(as(U, { docType: 'location', docVersion: 'loc-1', granted: true }));
expect(await hasLocationConsent(U)).toBe(true);
const rows = (await db().collection(`users/${U}/consents`).get()).docs.map((d) => d.data());
expect(rows.map((r) => r.granted).sort()).toEqual([false, true, true]);
expect(rows.every((r) => r.docType === 'location' && r.docVersion === 'loc-1' && r.createdAt)).toBe(true);
const state = (await db().doc(`users/${U}`).get()).get('consentState.location');
expect(state.granted).toBe(true);
expect(state.version).toBe('loc-1');
});
it('가입 필수 항목은 이 경로로 철회할 수 없다', async () => {
expect(await codeOf(fnConsentRecord.run(as(U, { docType: 'service', docVersion: 'v1', granted: false }))))
.toBe('invalid-argument');
});
it('마케팅 철회는 Push Marketing 구분과 같은 값이 된다', async () => {
await fnConsentRecord.run(as(U, { docType: 'marketing', docVersion: 'v1', granted: true }));
expect((await db().doc(`users/${U}`).get()).get('notificationPrefs.marketing')).toBe(true);
await fnConsentRecord.run(as(U, { docType: 'marketing', docVersion: 'v1', granted: false }));
expect((await db().doc(`users/${U}`).get()).get('notificationPrefs.marketing')).toBe(false);
});
});