where('email', $email)->value('id'); if ($existing !== null) { return (int) $existing; } return (int) DB::table('saas_admin_users')->insertGetId([ 'email' => $email, 'full_name' => 'Audit Test Stub', 'password_hash' => '$2y$04$system-stub-not-loginable', 'role' => 'super_admin', 'is_active' => false, 'sso_provider' => 'local', 'is_break_glass' => false, ]); } it('setExportMode writes audit log row', function (): void { $adminId = stubAdminUser(); $this->actingAs(User::factory()->create()); DB::table('system_settings')->updateOrInsert( ['key' => 'supplier_export_mode'], ['value' => 'batch', 'type' => 'string', 'updated_at' => now()], ); $countBefore = DB::table('saas_admin_audit_log')->count(); $this->postJson('/api/admin/supplier-integration/export-mode', [ 'mode' => 'online', 'admin_user_id' => $adminId, ])->assertOk(); expect(DB::table('saas_admin_audit_log')->count())->toBe($countBefore + 1); $row = DB::table('saas_admin_audit_log') ->orderByDesc('id') ->first(); expect($row->action)->toBe('supplier_integration.export_mode_set') ->and($row->target_type)->toBe('system_setting') ->and($row->target_id)->toBeNull() ->and($row->admin_user_id)->toBe($adminId); $after = json_decode($row->payload_after, true); expect($after['mode'])->toBe('online'); }); it('manualQueueResolve writes audit log row', function (): void { $adminId = stubAdminUser(); $admin = User::factory()->create(); $this->actingAs($admin); $tenant = Tenant::factory()->create(); $project = Project::factory()->for($tenant)->create(); $queueRow = SupplierManualSyncQueue::create([ 'project_id' => $project->id, 'platform' => 'B1', 'operation' => 'create', 'payload_snapshot' => ['signal_type' => 'site', 'unique_key' => 'audit-test.ru'], 'failure_reason' => 'contract_break', 'status' => 'pending', ]); $channelMock = new class implements SupplierProjectChannel { public function createProject(SupplierProjectDto $dto): int { return 0; } public function updateProject(int $externalId, SupplierProjectDto $dto): void {} public function listProjects(): array { return [['id' => 77777, 'platform' => 'B1', 'signal_type' => 'site', 'unique_key' => 'audit-test.ru']]; } }; app()->instance(SupplierProjectChannel::class, $channelMock); $countBefore = DB::table('saas_admin_audit_log')->count(); $this->postJson("/api/admin/supplier-integration/manual-queue/{$queueRow->id}/resolve", [ 'admin_user_id' => $adminId, ])->assertOk(); expect(DB::table('saas_admin_audit_log')->count())->toBe($countBefore + 1); $row = DB::table('saas_admin_audit_log') ->orderByDesc('id') ->first(); expect($row->action)->toBe('supplier_integration.manual_queue_resolved') ->and($row->target_type)->toBe('manual_queue_item') ->and((int) $row->target_id)->toBe($queueRow->id) ->and($row->admin_user_id)->toBe($adminId); }); it('projectsDestroy writes audit log row', function (): void { $adminId = stubAdminUser(); $this->actingAs(User::factory()->create()); $clientMock = new class extends SupplierPortalClient { public function __construct() {} public function deleteProject(int $externalId): void {} }; app()->instance(SupplierPortalClient::class, $clientMock); $sp = SupplierProject::query()->create([ 'platform' => 'B1', 'signal_type' => 'site', 'unique_key' => 'audit-destroy.ru', 'subject_code' => 77, 'current_limit' => 0, 'current_workdays' => [1, 2, 3, 4, 5, 6, 7], 'current_regions' => null, 'sync_status' => 'ok', 'supplier_external_id' => '12345', ]); $countBefore = DB::table('saas_admin_audit_log')->count(); $this->postJson('/api/admin/supplier-integration/projects/delete', [ 'ids' => [$sp->id], 'admin_user_id' => $adminId, ])->assertOk()->assertJson(['deleted' => 1, 'failures' => []]); expect(DB::table('saas_admin_audit_log')->count())->toBe($countBefore + 1); $row = DB::table('saas_admin_audit_log') ->orderByDesc('id') ->first(); expect($row->action)->toBe('supplier_integration.projects_destroyed') ->and($row->target_type)->toBe('supplier_projects_bulk') ->and($row->target_id)->toBeNull() ->and($row->admin_user_id)->toBe($adminId); $after = json_decode($row->payload_after, true); expect($after['deleted'])->toBe(1); });