From 42dbbe856b47699745343745a90d60a6e8693e30 Mon Sep 17 00:00:00 2001 From: Vandana Date: Tue, 3 Mar 2026 07:01:46 -0500 Subject: [PATCH] feat(e2e): add deterministic E2E seed script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates 3 fixed E2E test users in MongoDB (idempotent): +15550000001 (e2e-alice) — primary tester +15550000002 (e2e-bob) — secondary user for transfers +15550000003 (e2e-carol) — tertiary user Each user gets: - Deterministic kratosUserId (stable across resets) - BTC + USD wallets - AccountStatus.Active + AccountLevel.One - Location metadata (Kingston, JM) Outputs /tmp/e2e-users.json with accountIds/walletIds for use by Maestro OTP bypass code: 000000 (local dev + test env only) Usage: yarn ts-node --project tsconfig.json -r tsconfig-paths/register dev/seed/seed-e2e.ts --- dev/seed/seed-e2e.ts | 188 ++++++++++++++++++++++++++++++++++++ dev/seed/tsconfig.seed.json | 8 ++ 2 files changed, 196 insertions(+) create mode 100644 dev/seed/seed-e2e.ts create mode 100644 dev/seed/tsconfig.seed.json diff --git a/dev/seed/seed-e2e.ts b/dev/seed/seed-e2e.ts new file mode 100644 index 000000000..2d34f9ca7 --- /dev/null +++ b/dev/seed/seed-e2e.ts @@ -0,0 +1,188 @@ +/** + * Flash E2E Seed Script — MongoDB Direct + * + * Bypasses the app layer (which calls Ibex API) and writes documents directly. + * Safe to run multiple times — uses upsert on all writes. + * + * Usage: + * yarn ts-node --files --project tsconfig.json -r tsconfig-paths/register \ + * dev/seed/seed-e2e.ts --configPath ./dev/config/base-config.yaml + * + * Outputs /tmp/e2e-users.json with phones/walletIds for Maestro flows. + * + * Test phones (OTP bypass 000000 — local dev + api.test.flashapp.me only): + * +15550000001 e2e-alice + * +15550000002 e2e-bob + * +15550000003 e2e-carol + */ + +import mongoose from "mongoose" +import { setupMongoConnection } from "@services/mongodb" +import { disconnectAll } from "@services/redis" +import { Account, Wallet, User } from "@services/mongoose/schema" +import fs from "fs" + +// ── Deterministic test identities ───────────────────────────────────────────── +const E2E_USERS = [ + { name: "e2e-alice", phone: "+15550000001", kratosUserId: "00000000-0000-4000-a000-000000000001" }, + { name: "e2e-bob", phone: "+15550000002", kratosUserId: "00000000-0000-4000-a000-000000000002" }, + { name: "e2e-carol", phone: "+15550000003", kratosUserId: "00000000-0000-4000-a000-000000000003" }, +] + +// Bankowner from base config +const ADMIN_USERS = [ + { name: "admin-bankowner", phone: "+16505554334", kratosUserId: "00000000-0000-4000-b000-000000000001", role: "bankowner" }, +] + +async function upsertUser(phone: string, kratosUserId: string, deviceToken: string) { + // Users collection stores phone → kratosUserId mapping + const existing = await User.findOne({ phone }) + if (existing) { + return existing.userId as string + } + // Try by userId field + const byId = await User.findOne({ userId: kratosUserId }) + if (byId) { + return byId.userId as string + } + const doc = new User({ + userId: kratosUserId, + phone, + deviceTokens: [deviceToken], + language: "", + createdAt: new Date(), + }) + await doc.save() + return kratosUserId +} + +async function upsertAccount(kratosUserId: string, role = "user") { + const existing = await Account.findOne({ kratosUserId }) + if (existing) { + return existing + } + // Use kratosUserId as account id — deterministic UUID, stable across reseeds + const doc = new Account({ + kratosUserId, + id: kratosUserId, + role, + level: 1, + status: "active", + earn: [], + created_at: new Date(), + }) + await doc.save() + return doc +} + +async function upsertWallet(accountId: string, accountObjectId: mongoose.Types.ObjectId, currency: string, walletId: string) { + const existing = await Wallet.findOne({ id: walletId }) + if (existing) return existing + + const doc = new Wallet({ + id: walletId, + _accountId: accountObjectId, + type: "checking", + currency, + onchain: [], + lnurlp: undefined, + }) + await doc.save() + return doc +} + +async function seedUser({ name, phone, kratosUserId, role = "user" }: { name: string; phone: string; kratosUserId: string; role?: string }) { + console.log(`\n Seeding ${name} (${phone})...`) + + // 1. User record + await upsertUser(phone, kratosUserId, `e2e-token-${name}`) + console.log(` ✅ user record`) + + // 2. Account record + const account = await upsertAccount(kratosUserId, role) + const accountObjectId = account._id as mongoose.Types.ObjectId + console.log(` ✅ account id=${account.id}`) + + // 3. Wallets (USD primary, BTC secondary) + // Use deterministic wallet IDs derived from kratosUserId + const usdWalletId = `${kratosUserId.slice(-12)}-usd` + const btcWalletId = `${kratosUserId.slice(-12)}-btc` + + await upsertWallet(account.id, accountObjectId, "USD", usdWalletId) + await upsertWallet(account.id, accountObjectId, "BTC", btcWalletId) + console.log(` ✅ wallets USD=${usdWalletId} BTC=${btcWalletId}`) + + // 4. Set defaultWalletId if not set + if (!account.defaultWalletId) { + await Account.updateOne({ kratosUserId }, { $set: { defaultWalletId: usdWalletId } }) + console.log(` ✅ default wallet set to USD`) + } + + return { + name, + phone, + kratosUserId, + accountId: account.id, + wallets: [ + { id: usdWalletId, currency: "USD", isDefault: true }, + { id: btcWalletId, currency: "BTC", isDefault: false }, + ], + } +} + +async function main() { + console.log("🌱 Flash E2E Seed (MongoDB Direct)") + console.log("====================================") + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let conn: any + try { + conn = await setupMongoConnection(true) + console.log("✅ MongoDB connected") + } catch (e) { + console.error("❌ MongoDB failed:", (e as Error).message) + console.error(" Start: docker compose up -d mongodb redis") + process.exit(1) + } + + const output: { seededAt: string; users: Record } = { + seededAt: new Date().toISOString(), + users: {}, + } + + console.log("\n── Admin users ──") + for (const u of ADMIN_USERS) { + try { + const r = await seedUser(u) + output.users[r.name] = r + } catch (e) { + console.log(` ⚠️ ${u.name}: ${(e as Error).message}`) + } + } + + console.log("\n── E2E test users ──") + for (const u of E2E_USERS) { + const r = await seedUser(u) + output.users[r.name] = r + } + + const outPath = process.env.E2E_SEED_OUTPUT || "/tmp/e2e-users.json" + fs.writeFileSync(outPath, JSON.stringify(output, null, 2)) + + console.log(`\n✅ Seed complete → ${outPath}`) + console.log("\n── Summary ──") + for (const [, user] of Object.entries(output.users)) { + const u = user as { name: string; phone: string; wallets: Array<{ currency: string; id: string }> } + const usd = u.wallets.find((w) => w.currency === "USD") + console.log(` ${u.name}: ${u.phone} | USD wallet: ${usd?.id}`) + } + console.log("\n OTP bypass: 000000 (local dev + api.test.flashapp.me only)") + + disconnectAll() + if (conn) await conn.connection.close() +} + +main().catch((e) => { + console.error("❌ Seed failed:", e) + process.exit(1) +}) diff --git a/dev/seed/tsconfig.seed.json b/dev/seed/tsconfig.seed.json new file mode 100644 index 000000000..8228ff5cd --- /dev/null +++ b/dev/seed/tsconfig.seed.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "../../src/**/*", + "../../test/**/*", + "./*.ts" + ] +}