mock(GraphClientInterface::class, function (MockInterface $mock) { $mock->shouldReceive('listPolicies') ->atLeast() ->once() ->andReturn(new GraphResponse(true, [], 200)); }); $sync = app(InventorySyncService::class); $selectionPayload = $sync->defaultSelectionPayload(); $computed = $sync->normalizeAndHashSelection($selectionPayload); $policyTypes = $computed['selection']['policy_types']; $run = $sync->createPendingRunForUser($tenant, $user, $selectionPayload); $bulkRun = app(BulkOperationService::class)->createRun( tenant: $tenant, user: $user, resource: 'inventory', action: 'sync', itemIds: $policyTypes, totalItems: count($policyTypes), ); $job = new RunInventorySyncJob( tenantId: (int) $tenant->getKey(), userId: (int) $user->getKey(), bulkRunId: (int) $bulkRun->getKey(), inventorySyncRunId: (int) $run->getKey(), ); $job->handle(app(BulkOperationService::class), $sync, app(AuditLogger::class)); $run->refresh(); $bulkRun->refresh(); expect($run->user_id)->toBe($user->id); expect($run->status)->toBe(InventorySyncRun::STATUS_SUCCESS); expect($run->started_at)->not->toBeNull(); expect($run->finished_at)->not->toBeNull(); expect($bulkRun->status)->toBe('completed'); expect($bulkRun->processed_items)->toBe(count($policyTypes)); expect($bulkRun->succeeded)->toBe(count($policyTypes)); }); it('maps skipped inventory sync runs to bulk progress as skipped with reason', function () { [$user, $tenant] = createUserWithTenant(role: 'owner'); $sync = app(InventorySyncService::class); $selectionPayload = $sync->defaultSelectionPayload(); $run = $sync->createPendingRunForUser($tenant, $user, $selectionPayload); $computed = $sync->normalizeAndHashSelection($selectionPayload); $policyTypes = $computed['selection']['policy_types']; $bulkRun = app(BulkOperationService::class)->createRun( tenant: $tenant, user: $user, resource: 'inventory', action: 'sync', itemIds: $policyTypes, totalItems: count($policyTypes), ); $mockSync = \Mockery::mock(InventorySyncService::class); $mockSync ->shouldReceive('executePendingRun') ->once() ->andReturnUsing(function (InventorySyncRun $inventorySyncRun) { $inventorySyncRun->forceFill([ 'status' => InventorySyncRun::STATUS_SKIPPED, 'error_codes' => ['locked'], 'selection_payload' => $inventorySyncRun->selection_payload ?? [], 'started_at' => now(), 'finished_at' => now(), ])->save(); return $inventorySyncRun; }); $job = new RunInventorySyncJob( tenantId: (int) $tenant->getKey(), userId: (int) $user->getKey(), bulkRunId: (int) $bulkRun->getKey(), inventorySyncRunId: (int) $run->getKey(), ); $job->handle(app(BulkOperationService::class), $mockSync, app(AuditLogger::class)); $run->refresh(); $bulkRun->refresh(); expect($run->status)->toBe(InventorySyncRun::STATUS_SKIPPED); expect($bulkRun->status)->toBe('completed') ->and($bulkRun->processed_items)->toBe(count($policyTypes)) ->and($bulkRun->skipped)->toBe(count($policyTypes)) ->and($bulkRun->succeeded)->toBe(0) ->and($bulkRun->failed)->toBe(0); expect($bulkRun->failures)->toBeArray(); expect($bulkRun->failures[0]['type'] ?? null)->toBe('skipped'); expect($bulkRun->failures[0]['reason'] ?? null)->toBe('locked'); });