Files
BarevClub/packages/services/test/helpers.ts
T
2026-10-17 19:45:51 +04:00

88 lines
3.9 KiB
TypeScript

import path from 'node:path';
import { config } from 'dotenv';
import { CATALOG, DEFAULT_CATEGORY_DISCOUNTS, fromYerevan, toYerevan, YEREVAN_DISTRICTS } from '@barev/core';
import { createDb, type Db } from '@barev/db';
import type { Ctx, DomainEvent } from '../src/context.ts';
config({ path: path.resolve(import.meta.dirname, '../../../.env'), quiet: true });
function testUrl() {
const u = new URL(process.env.DATABASE_URL!);
u.pathname = '/barev_test';
return u.toString();
}
export const db: Db = createDb(testUrl());
export function makeCtx(now: Date) {
const events: DomainEvent[] = [];
const clock = { now };
const ctx: Ctx = { db, notify: { emit: (e) => void events.push(e) }, now: () => clock.now };
return { ctx, events, clock };
}
export async function wipe() {
const tables = await db.$queryRaw<{ tablename: string }[]>`
SELECT tablename FROM pg_tables WHERE schemaname = 'public' AND tablename <> '_prisma_migrations'`;
await db.$executeRawUnsafe(`TRUNCATE ${tables.map((t) => `"public"."${t.tablename}"`).join(', ')} RESTART IDENTITY CASCADE`);
}
/** Понедельник 2026-10-19, 10:00 по Еревану — «сейчас» в тестах */
export const NOW = fromYerevan(2026, 10, 19, 10 * 60);
/** Слот послезавтра (ср) в hh:00 — вне 24-часового порога */
export const slotAt = (dayOffset: number, hour: number) => {
const p = toYerevan(new Date(NOW.getTime() + dayOffset * 24 * 3600_000));
return fromYerevan(p.year, p.month, p.day, hour * 60);
};
let tgSeq = 1000;
export async function fixtures() {
await wipe();
for (const [i, code] of YEREVAN_DISTRICTS.entries()) await db.district.create({ data: { code, sortOrder: i } });
const kentron = await db.district.findUniqueOrThrow({ where: { code: 'kentron' } });
const home = await db.category.create({ data: { code: 'HOME', barevPlusDiscountPct: DEFAULT_CATEGORY_DISCOUNTS.HOME } });
const c = CATALOG.find((s) => s.code === 'cleaning')!;
const cleaning = await db.service.create({
data: {
code: c.code,
categoryId: home.id,
flow: c.flow,
providerRole: c.providerRole,
pricing: c.pricing,
paramsSchema: c.paramsSchema,
slotConfig: c.slotConfig,
lateCancelFeePct: 30,
checklist: { create: c.checklist.map((x, i) => ({ code: x.code, requiresPhoto: !!x.requiresPhoto, sortOrder: i })) },
},
});
const agency = await db.agency.create({ data: { code: 'DOMIK', name: 'Domik', commissionPct: 10 } });
await db.agency.create({ data: { code: 'CLOSED', name: 'Closed', active: false } });
const schedule = [0, 1, 2, 3, 4, 5, 6].map((weekday) => ({ weekday, startMinute: 9 * 60, endMinute: 20 * 60 }));
const mkProvider = async (name: string, languages: ('ru' | 'en' | 'hy')[], companyId?: string) => {
const user = await db.user.create({ data: { telegramId: BigInt(++tgSeq), firstName: name } });
return db.provider.create({
data: {
name,
userId: user.id,
role: 'CLEANER',
languages,
companyId,
inviteCode: `INV-${name}`,
onShift: true,
districts: { create: [{ districtId: kentron.id }] },
schedule: { create: schedule },
},
});
};
const maria = await mkProvider('Maria', ['ru', 'en']);
const ani = await mkProvider('Ani', ['hy']);
const company = await db.providerCompany.create({ data: { name: 'Chisto', roles: ['CLEANER'], inviteCode: 'CO-1', rateValue: 65 } });
const lusine = await mkProvider('Lusine', ['ru'], company.id);
const dispatcher = await db.user.create({ data: { telegramId: BigInt(++tgSeq), firstName: 'Karen' } });
await db.companyDispatcher.create({ data: { companyId: company.id, userId: dispatcher.id } });
return { kentron, cleaning, agency, maria, ani, company, lusine, dispatcher };
}
export const tgUser = (id: number, lang = 'ru') => ({ id, first_name: `User${id}`, language_code: lang });