Files
tripz-llc/backend/src/modules/vehicles/vehicles.service.spec.ts
T
Hamza-AyedandClaude Opus 4.8 c2a4b9d1a6 feat: المجموعة C — بيانات السائق والمركبة (كيان مركبة + ملف السائق)
C1: جدول vehicles (مكافئ CarRegistration عند سيرو) — plate/vin مشفَّران،
make/model/year، color+color_hex (لتلوين السيارة في فلاتر)، fuel/owner/
category، is_default، status. سائق قد يملك أكثر من مركبة؛ VehiclesService
يضمن افتراضية واحدة دائماً (أول مركبة تلقائياً، حذف الافتراضية يرقّي غيرها).

C2: حقول الملف على drivers — gender، national_number (مشفَّر + فهرس أعمى
فريد لكل مستأجر، نفس نمط الهاتف)، name_arabic (مشفَّر)، birthdate، address،
الرخصة (type/categories/issue/expiry)، rejected_reason. عبر PATCH
/drivers/profile.

C3: ai_data + user_input (jsonb) على drivers و vehicles — مخرجات Gemini
مقابل مدخلات السائق، تُراكَم للمقارنة حقلاً بحقل.

C4: vehicle_photo min:2 في كتالوج الوثائق.
C5: نوع وثيقة face_liveness (فيديو) — الرفع يقبله بلا قيد mime.

قرار: المركبة كيان مستقل لا حقول مسطّحة (سيرو يفصلها بـisDefault). الحقول
القديمة على drivers تبقى للتوافق؛ المصدر الجديد vehicles.

هجرة: VehiclesAndDriverProfile (جدول vehicles + أعمدة السائق + فهرس فريد
جزئي على الرقم الوطني).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 19:43:47 +03:00

106 lines
3.9 KiB
TypeScript

import { randomUUID } from 'crypto';
import { newDb } from 'pg-mem';
import { DataSource } from 'typeorm';
import { Vehicle } from './entities/vehicle.entity';
import { VehiclesService } from './vehicles.service';
const TENANT = '11111111-1111-1111-1111-111111111111';
const DRIVER = '22222222-2222-2222-2222-222222222222';
describe('VehiclesService (docs/17 — C1)', () => {
let ds: DataSource;
let svc: VehiclesService;
beforeEach(async () => {
const db = newDb({ autoCreateForeignKeyIndices: true });
db.public.registerFunction({ name: 'version', returns: 'text' as any, implementation: () => 'pg-mem' });
db.public.registerFunction({
name: 'current_database',
returns: 'text' as any,
implementation: () => 'tripz',
});
db.registerExtension('uuid-ossp', (schema) =>
schema.registerFunction({
name: 'uuid_generate_v4',
returns: 'uuid' as any,
implementation: () => randomUUID(),
impure: true,
}),
);
await db.public.none(`CREATE EXTENSION "uuid-ossp"`);
ds = (await db.adapters.createTypeormDataSource({
type: 'postgres',
entities: [Vehicle],
entityPrefix: 'tripz_',
})) as DataSource;
await ds.initialize();
await ds.synchronize();
svc = new VehiclesService(ds.getRepository(Vehicle));
});
afterEach(async () => {
if (ds?.isInitialized) await ds.destroy();
});
it('أول مركبة تصير الافتراضية تلقائياً', async () => {
const v = await svc.create(TENANT, DRIVER, { make: 'Toyota', model: 'Corolla' });
expect(v.is_default).toBe(true);
expect(v.status).toBe('pending');
});
it('المركبة الثانية ليست افتراضية إلا بطلب صريح', async () => {
await svc.create(TENANT, DRIVER, { make: 'Toyota' });
const second = await svc.create(TENANT, DRIVER, { make: 'Kia' });
expect(second.is_default).toBe(false);
});
it('تعيين افتراضي جديد يلغي القديم — واحدة افتراضية فقط دائماً', async () => {
const first = await svc.create(TENANT, DRIVER, { make: 'Toyota' });
const second = await svc.create(TENANT, DRIVER, { make: 'Kia' });
await svc.setDefault(TENANT, DRIVER, second.id);
const list = await svc.list(TENANT, DRIVER);
const defaults = list.filter((v) => v.is_default);
expect(defaults).toHaveLength(1);
expect(defaults[0].id).toBe(second.id);
expect((await svc.getDefault(TENANT, DRIVER))!.id).toBe(second.id);
});
it('حذف الافتراضية يرقّي مركبة أخرى — لا يبقى السائق بلا افتراضية', async () => {
const first = await svc.create(TENANT, DRIVER, { make: 'Toyota' });
await svc.create(TENANT, DRIVER, { make: 'Kia' });
await svc.remove(TENANT, DRIVER, first.id);
const def = await svc.getDefault(TENANT, DRIVER);
expect(def).not.toBeNull();
expect(def!.is_default).toBe(true);
});
it('اللوحة والـVIN مشفَّران at-rest، والباقي واضح', async () => {
const v = await svc.create(TENANT, DRIVER, {
plate: 'ABC-123',
vin: '1HGCM82633A004352',
make: 'Honda',
color_hex: '#FF0000',
});
const raw = await ds.query(`SELECT plate, vin, make, color_hex FROM tripz_vehicles WHERE id = $1`, [v.id]);
expect(raw[0].plate).toMatch(/^v1:/); // مشفّر
expect(raw[0].vin).toMatch(/^v1:/);
expect(raw[0].make).toBe('Honda'); // غير حسّاس — واضح
expect(raw[0].color_hex).toBe('#FF0000');
// لكنه يُقرأ مفكوكاً عبر الـORM
const read = await svc.list(TENANT, DRIVER);
expect(read[0].plate).toBe('ABC-123');
expect(read[0].vin).toBe('1HGCM82633A004352');
});
it('المستأجرون معزولون', async () => {
await svc.create(TENANT, DRIVER, { make: 'Toyota' });
expect(await svc.list('33333333-3333-3333-3333-333333333333', DRIVER)).toHaveLength(0);
});
});