TenantAtlas/tests/Unit/FoundationMappingServiceTest.php
ahmido 93dbd3b13d fix(tests): remove per-file TestCase uses (#45)
What Changed

Removed per-file uses(TestCase::class ...) bindings in Unit tests to avoid Pest v4 “folder already uses the test case” discovery failure (kept RefreshDatabase where needed).
Updated the backup scheduling job test to pass the newly required BulkOperationService when manually calling RunBackupScheduleJob::handle().
Where

Unit (bulk cleanup across 56 files)
RunBackupScheduleJobTest.php
Verification

./vendor/bin/sail test → 443 passed, 5 skipped

Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local>
Reviewed-on: #45
2026-01-08 00:41:46 +00:00

280 lines
9.2 KiB
PHP

<?php
use App\Models\BackupItem;
use App\Models\BackupSet;
use App\Models\Tenant;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphResponse;
use App\Services\Intune\FoundationMappingService;
use App\Services\Intune\FoundationSnapshotService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery\MockInterface;
uses(RefreshDatabase::class);
class FoundationMappingGraphClient implements GraphClientInterface
{
public array $requests = [];
/**
* @param array<int, GraphResponse> $responses
*/
public function __construct(private array $responses = []) {}
public function listPolicies(string $policyType, array $options = []): GraphResponse
{
return new GraphResponse(success: true, data: []);
}
public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse
{
return new GraphResponse(success: true, data: []);
}
public function getOrganization(array $options = []): GraphResponse
{
return new GraphResponse(success: true, data: []);
}
public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse
{
return new GraphResponse(success: true, data: []);
}
public function getServicePrincipalPermissions(array $options = []): GraphResponse
{
return new GraphResponse(success: true, data: []);
}
public function request(string $method, string $path, array $options = []): GraphResponse
{
$this->requests[] = [
'method' => $method,
'path' => $path,
'options' => $options,
];
return array_shift($this->responses) ?? new GraphResponse(success: true, data: []);
}
}
it('maps existing foundations by display name', function () {
$tenant = Tenant::factory()->create();
$backupSet = BackupSet::factory()->for($tenant)->create();
$item = BackupItem::factory()
->for($tenant)
->for($backupSet)
->state([
'policy_id' => null,
'policy_identifier' => 'filter-1',
'policy_type' => 'assignmentFilter',
'platform' => 'all',
'payload' => [
'id' => 'filter-1',
'displayName' => 'Filter One',
],
'metadata' => [
'displayName' => 'Filter One',
],
])
->create();
$this->mock(FoundationSnapshotService::class, function (MockInterface $mock) {
$mock->shouldReceive('fetchAll')
->once()
->andReturn([
'items' => [
[
'source_id' => 'filter-2',
'display_name' => 'Filter One',
'payload' => [],
'metadata' => [],
],
],
'failures' => [],
]);
});
$client = new FoundationMappingGraphClient;
app()->instance(GraphClientInterface::class, $client);
$service = app(FoundationMappingService::class);
$result = $service->map($tenant, collect([$item]), false);
expect($result['failed'])->toBe(0);
expect($result['skipped'])->toBe(0);
expect($result['mapping'])->toBe(['filter-1' => 'filter-2']);
expect($result['entries'])->toHaveCount(1);
expect($result['entries'][0]['decision'])->toBe('mapped_existing');
expect($result['entries'][0]['targetId'])->toBe('filter-2');
expect($result['entries'][0]['sourceName'])->toBe('Filter One');
});
it('creates missing foundations when executing', function () {
config()->set('graph_contracts.types.assignmentFilter', [
'resource' => 'deviceManagement/assignmentFilters',
'create_method' => 'POST',
'update_strip_keys' => ['isBuiltIn'],
]);
$tenant = Tenant::factory()->create([
'tenant_id' => 'tenant-1',
'app_client_id' => 'client-1',
'app_client_secret' => 'secret-1',
]);
$backupSet = BackupSet::factory()->for($tenant)->create();
$item = BackupItem::factory()
->for($tenant)
->for($backupSet)
->state([
'policy_id' => null,
'policy_identifier' => 'filter-1',
'policy_type' => 'assignmentFilter',
'platform' => 'all',
'payload' => [
'id' => 'filter-1',
'@odata.type' => '#microsoft.graph.deviceAndAppManagementAssignmentFilter',
'displayName' => 'Filter One',
'isBuiltIn' => false,
],
'metadata' => [
'displayName' => 'Filter One',
],
])
->create();
$this->mock(FoundationSnapshotService::class, function (MockInterface $mock) {
$mock->shouldReceive('fetchAll')
->once()
->andReturn([
'items' => [
[
'source_id' => 'filter-2',
'display_name' => 'Filter One',
'payload' => [],
'metadata' => [],
],
[
'source_id' => 'filter-3',
'display_name' => 'Filter One',
'payload' => [],
'metadata' => [],
],
],
'failures' => [],
]);
});
$client = new FoundationMappingGraphClient([
new GraphResponse(true, [
'id' => 'filter-99',
'displayName' => 'Filter One (Copy)',
]),
]);
app()->instance(GraphClientInterface::class, $client);
$service = app(FoundationMappingService::class);
$result = $service->map($tenant, collect([$item]), true);
expect($result['mapping'])->toBe(['filter-1' => 'filter-99']);
expect($result['entries'][0]['decision'])->toBe('created_copy');
expect($result['entries'][0]['targetName'])->toBe('Filter One (Copy)');
expect($client->requests)->toHaveCount(1);
expect($client->requests[0]['method'])->toBe('POST');
expect($client->requests[0]['path'])->toBe('deviceManagement/assignmentFilters');
$payload = $client->requests[0]['options']['json'];
expect($payload['displayName'])->toBe('Filter One (Copy)');
expect($payload)->not->toHaveKey('id');
expect($payload)->not->toHaveKey('@odata.type');
expect($payload)->not->toHaveKey('isBuiltIn');
});
it('skips built-in scope tags', function () {
$tenant = Tenant::factory()->create();
$backupSet = BackupSet::factory()->for($tenant)->create();
$item = BackupItem::factory()
->for($tenant)
->for($backupSet)
->state([
'policy_id' => null,
'policy_identifier' => '0',
'policy_type' => 'roleScopeTag',
'platform' => 'all',
'payload' => [
'id' => '0',
'displayName' => 'Default',
'isBuiltIn' => true,
],
'metadata' => [
'displayName' => 'Default',
],
])
->create();
$this->mock(FoundationSnapshotService::class, function (MockInterface $mock) {
$mock->shouldReceive('fetchAll')
->once()
->andReturn([
'items' => [],
'failures' => [],
]);
});
$client = new FoundationMappingGraphClient;
app()->instance(GraphClientInterface::class, $client);
$service = app(FoundationMappingService::class);
$result = $service->map($tenant, collect([$item]), false);
expect($result['skipped'])->toBe(1);
expect($result['entries'][0]['decision'])->toBe('skipped');
expect($result['entries'][0]['reason'])->toBe('Built-in scope tag cannot be created.');
});
it('marks failures when foundation listing fails', function () {
$tenant = Tenant::factory()->create();
$backupSet = BackupSet::factory()->for($tenant)->create();
$item = BackupItem::factory()
->for($tenant)
->for($backupSet)
->state([
'policy_id' => null,
'policy_identifier' => 'filter-1',
'policy_type' => 'assignmentFilter',
'platform' => 'all',
'payload' => [
'id' => 'filter-1',
'displayName' => 'Filter One',
],
'metadata' => [
'displayName' => 'Filter One',
],
])
->create();
$this->mock(FoundationSnapshotService::class, function (MockInterface $mock) {
$mock->shouldReceive('fetchAll')
->once()
->andReturn([
'items' => [],
'failures' => [
[
'foundation_type' => 'assignmentFilter',
'reason' => 'Graph failure',
'status' => 500,
],
],
]);
});
$client = new FoundationMappingGraphClient;
app()->instance(GraphClientInterface::class, $client);
$service = app(FoundationMappingService::class);
$result = $service->map($tenant, collect([$item]), false);
expect($result['failed'])->toBe(1);
expect($result['entries'][0]['decision'])->toBe('failed');
expect($result['entries'][0]['reason'])->toBe('Graph failure');
});