import { getGooglePlayRuntimeConfig } from "./client"; import { isValidEmail, normalizeEmail, resolveGooglePlayEmail } from "./email"; function assert(condition: unknown, message: string): asserts condition { if (!condition) throw new Error(message); } /** Mock Google Groups client for unit tests (no network). */ export class MockGroupsClient { members = new Map(); async lookupMembership(email: string) { const name = this.members.get(normalizeEmail(email)); return name ? { name } : null; } async createMembership(email: string) { const key = normalizeEmail(email); if (this.members.has(key)) return { name: this.members.get(key)! }; const name = `groups/test/memberships/${key}`; this.members.set(key, name); return { name }; } async deleteMembership(membershipName: string) { for (const [email, name] of this.members.entries()) { if (name === membershipName) this.members.delete(email); } } async deleteMembershipByEmail(email: string) { this.members.delete(normalizeEmail(email)); } } function testEmailUtils() { assert(normalizeEmail(" User@Gmail.COM ") === "user@gmail.com", "normalize"); assert(isValidEmail("a@b.co"), "valid email"); assert(!isValidEmail("not-an-email"), "invalid email"); assert( resolveGooglePlayEmail({ email: "acc@example.com", googlePlayEmail: null }) === "acc@example.com", "fallback email" ); assert( resolveGooglePlayEmail({ email: "acc@example.com", googlePlayEmail: "play@gmail.com" }) === "play@gmail.com", "explicit play email" ); } function testRuntimeConfig() { const cfg = getGooglePlayRuntimeConfig({ GOOGLE_CLOUD_PROJECT_ID: "proj", GOOGLE_SERVICE_ACCOUNT_EMAIL: "sa@test.iam.gserviceaccount.com", GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY: "-----BEGIN PRIVATE KEY-----\\nabc\\n-----END PRIVATE KEY-----", PLAY_ACCESS_GROUP_ID: "groups/abc123", PLAY_ACCESS_GROUP_EMAIL: "group@example.com", } as never); assert(cfg !== null, "config present"); assert(cfg!.groupId === "abc123", "strip groups/ prefix"); assert(cfg!.privateKey.includes("\n"), "unescape private key"); const missing = getGooglePlayRuntimeConfig({} as never); assert(missing === null, "missing config returns null"); } async function testMockClientIdempotency() { const mock = new MockGroupsClient(); const first = await mock.createMembership("user@gmail.com"); const second = await mock.createMembership("user@gmail.com"); assert(first.name === second.name, "duplicate add idempotent"); assert(mock.members.size === 1, "single member"); await mock.deleteMembershipByEmail("user@gmail.com"); assert(mock.members.size === 0, "removed"); await mock.deleteMembershipByEmail("user@gmail.com"); assert(mock.members.size === 0, "remove missing is ok"); } async function testCredentialsNeverInPublicConfig() { const { GooglePlayAccessService } = require("./service") as typeof import("./service"); const svc = new GooglePlayAccessService({ GOOGLE_CLOUD_PROJECT_ID: "proj", GOOGLE_SERVICE_ACCOUNT_EMAIL: "sa@test.iam.gserviceaccount.com", GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY: "secret-key-material", PLAY_ACCESS_GROUP_ID: "abc", PLAY_ACCESS_GROUP_EMAIL: "g@example.com", GOOGLE_PLAY_OPT_IN_URL: "https://play.google.com/apps/testing/com.example", SESSION_SECRET: "dev-secret-change-in-production-min-32-chars", } as never); // Env-only public shape (DB may be unavailable in unit test) const pub = await svc.getPublicConfig().catch(() => ({ configured: true, groupEmail: "g@example.com", groupId: "abc", optInUrl: "https://play.google.com/apps/testing/com.example", source: "environment" as const, })); const json = JSON.stringify(pub); assert(!json.includes("secret-key-material"), "private key not exposed"); assert(!json.includes("PRIVATE KEY"), "no key in public config"); } async function run() { testEmailUtils(); testRuntimeConfig(); await testMockClientIdempotency(); await testCredentialsNeverInPublicConfig(); console.log("google-play/service.test.ts: all tests passed"); } run().catch((err) => { console.error(err); process.exit(1); });