Files
photo-live/services/api/src/scripts/seed-it-users.ts
T
浅念 fb3c278e50 test(mobile): integration suite against real backend on mumu
P3 Task 11 (WRITE & COMPILE phase; device run pending MuMu availability).

- integration_test/p3_flow_test.dart: login -> album (zh+en) -> category
  -> invite -> enqueue 3 real JPEGs -> queue completed -> server history
  (sourceDeviceId) -> ov public API sees 3 -> hide via API + UI sync ->
  DIO fault-gate offline (waitingNetwork -> retry -> 4 completed) ->
  password change (session kept) -> relogin -> in-app locale smoke.
- integration_test/p3_background_test.dart: IT_PHASE=prepare/verify spec
  for HOME-background continuation and force-stop kill-restart recovery
  (2 x ~14MB noise JPEGs give a deterministic mid-flight window).
- helpers: dart-define config, fault-injecting DIO adapter, app harness
  (picker/notification/locale provider overrides), UI step library with
  live-binding polling (no pumpAndSettle).
- scripts/run-mobile-it.sh: device detect (exit 2), backend ensure
  (compose + api/ov/worker/sync, exit 3), idempotent per-run user seed
  (no DB reset), APK build/install + adb push of testdata, phase 1 /
  --background phase 2 orchestration with server-side confirmed-count
  truth via curl.
- services/api seed:it script (create-if-absent staff accounts).
- debug-only network security config: cleartext HTTP permitted to
  10.0.2.2/127.0.0.1/localhost only (release unaffected).
- testdata: 3 x ~120KB + 2 x ~14MB generated JPEGs.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-19 05:35:16 +08:00

103 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// services/api/src/scripts/seed-it-users.ts —— P3 Task 11 集成测试专用
// 用户种子脚本(MuMu + 真实后端套件的预置步骤,由 scripts/run-mobile-it.sh
// 调用)。运行方式:`IT_USERS="a:pw1,b:pw2" pnpm --filter @photo/api-cn seed:it`
// (= tsc 构建后 node dist/scripts/seed-it-users.js)。
//
// 与共享 dev 库的隔离约定(集成测试【不做 DB reset】,见脚本头注释):
// - 每次运行由编排脚本生成【全新唯一账号】(时间戳后缀)传入本脚本;
// - 本脚本只做「不存在则建」,存在即跳过且【绝不改写密码/角色】——
// 与 seed-admin 同幂等语义,重复调用安全;
// - 角色固定 staff:相册创建/邀请查找在 cn 侧仅要求已认证(SessionGuard),
// 测试账号无需 admin 角色。
import { existsSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { config } from 'dotenv';
import { PrismaClient } from '@photo/db-cn';
import { hashPassword } from '../auth/password';
/** 解析 `account:password,...` 列表(冒号冲突风险可接受:账号不含冒号)。 */
export function parseItUsers(raw: string): Array<{ account: string; password: string }> {
return raw
.split(',')
.map((pair) => pair.trim())
.filter((pair) => pair !== '')
.map((pair) => {
const sep = pair.indexOf(':');
if (sep <= 0) {
throw new Error(`seed-it-users: 非法条目 "${pair}"(应为 account:password)`);
}
const account = pair.slice(0, sep).trim();
const password = pair.slice(sep + 1);
if (account === '' || password === '') {
throw new Error(`seed-it-users: 空账号/密码(${pair})`);
}
return { account, password };
});
}
/** 种子核心(可测):逐个建号,已存在跳过。返回账号 → 是否新建。 */
export async function seedItUsers(
prisma: PrismaClient,
users: Array<{ account: string; password: string }>,
): Promise<Map<string, boolean>> {
const result = new Map<string, boolean>();
for (const { account, password } of users) {
const existing = await prisma.users.findUnique({ where: { account } });
if (existing !== null) {
result.set(account, false);
continue;
}
await prisma.users.create({
data: {
account,
passwordHash: await hashPassword(password),
displayName: account,
role: 'staff',
credentialStatus: 'active',
},
});
result.set(account, true);
}
return result;
}
/** 定位仓库根 .env(与 seed-admin 相同约定:不覆盖已有进程环境变量)。 */
function loadRootEnv(): void {
let dir = __dirname;
while (!existsSync(join(dir, 'pnpm-workspace.yaml'))) {
const parent = dirname(dir);
if (parent === dir) {
throw new Error('seed-it-users: pnpm-workspace.yaml not found');
}
dir = parent;
}
config({ path: join(dir, '.env') });
}
async function main(): Promise<void> {
loadRootEnv();
const raw = process.env.IT_USERS;
if (!raw) {
throw new Error('seed-it-users: IT_USERS 环境变量缺失(格式 account:password,...)');
}
const users = parseItUsers(raw);
const prisma = new PrismaClient();
try {
const created = await seedItUsers(prisma, users);
for (const [account, wasCreated] of created) {
console.log(
`seed-it-users: ${account} ${wasCreated ? 'created (staff)' : 'exists, skipped'}`,
);
}
} finally {
await prisma.$disconnect();
}
}
if (require.main === module) {
void main().catch((err: unknown) => {
console.error(err instanceof Error ? err.message : err);
process.exitCode = 1;
});
}