tenant = Tenant::factory()->create(['balance_leads' => 100]); $this->user = User::factory()->for($this->tenant)->create(); $this->actingAs($this->user); DB::statement('SET app.current_tenant_id = '.(int) $this->tenant->id); $this->project = Project::factory()->for($this->tenant)->create(['name' => 'PD Flow Test']); }); it('records pd events through the whole deal lifecycle (create → view → export → delete)', function () { // ── 1. CREATE (manual) → pd action='created', purpose='lead_create_manual' ── $created = $this->postJson('/api/deals', [ 'project_name' => $this->project->name, 'phone' => '+7 (999) 123-45-67', ]); $created->assertStatus(201); $dealId = (int) $created->json('deal.id'); expect($dealId)->toBeGreaterThan(0); // ── 2. VIEW → pd action='viewed', purpose='lead_card_view' ── $this->getJson("/api/deals/{$dealId}")->assertOk(); // ── 3. EXPORT → pd action='exported', purpose='deals_export_csv' ── // Mirror: DealExportPdLogTest — POST /api/deals/export with format=csv // We need at least one deal in the tenant for a non-empty export; the // deal we just created qualifies. $exported = $this->post('/api/deals/export', ['format' => 'csv']); $exported->assertStatus(200); // ── 4. DELETE report file → pd action='deleted', purpose='report_file_{id}' ── // Mirror: ReportFileDeletePdLogTest — create a DONE ReportJob with file_path, // then DELETE /api/reports/jobs/{id}. $job = ReportJob::create([ 'tenant_id' => $this->tenant->id, 'user_id' => $this->user->id, 'type' => 'deals_export', 'parameters' => ['format' => 'csv', 'date_from' => '2026-01-01', 'date_to' => '2026-12-31'], 'status' => ReportJob::STATUS_DONE, 'file_path' => 'reports/'.(int) $this->tenant->id.'/pd_flow_test.csv', ]); $this->deleteJson("/api/reports/jobs/{$job->id}")->assertOk(); // ── ASSERT — scoped to THIS tenant ──────────────────────────────────────── $rows = DB::table('pd_processing_log') ->where('tenant_id', $this->tenant->id) ->get(); $byAction = $rows->groupBy('action'); // All four lifecycle actions must be present. expect($byAction->has('created'))->toBeTrue() ->and($byAction->has('viewed'))->toBeTrue() ->and($byAction->has('exported'))->toBeTrue() ->and($byAction->has('deleted'))->toBeTrue(); // Correct purpose for each action. expect($rows->firstWhere('action', 'created')->purpose)->toBe('lead_create_manual'); expect($rows->firstWhere('action', 'viewed')->purpose)->toBe('lead_card_view'); expect($rows->contains(fn ($r) => $r->action === 'exported' && $r->purpose === 'deals_export_csv'))->toBeTrue(); expect($rows->firstWhere('action', 'deleted')->purpose)->toBe('report_file_'.$job->id); // 'created' and 'viewed' rows are tied to the deal we created. expect((int) $rows->firstWhere('action', 'created')->subject_id)->toBe($dealId); expect((int) $rows->firstWhere('action', 'viewed')->subject_id)->toBe($dealId); // All rows carry the correct actor. foreach (['created', 'viewed', 'exported', 'deleted'] as $action) { $row = $rows->firstWhere('action', $action); expect((int) $row->actor_tenant_user_id)->toBe($this->user->id); expect($row->actor_admin_user_id)->toBeNull(); } });