graphClient = Mockery::mock(MicrosoftGraphClient::class); $this->logger = Mockery::mock(GraphLogger::class); $this->resolver = new ScopeTagResolver($this->graphClient, $this->logger); }); test('resolves scope tags', function () { $scopeTagIds = ['0', '123', '456']; $allScopeTags = [ ['id' => '0', 'displayName' => 'Default'], ['id' => '123', 'displayName' => 'HR-Admins'], ['id' => '456', 'displayName' => 'Finance-Admins'], ['id' => '789', 'displayName' => 'IT-Admins'], ]; $this->graphClient ->shouldReceive('get') ->once() ->with('/deviceManagement/roleScopeTags', null, ['$select' => 'id,displayName']) ->andReturn(['value' => $allScopeTags]); $this->logger ->shouldReceive('logDebug') ->once() ->with('Fetched scope tags', ['count' => 4]); $result = $this->resolver->resolve($scopeTagIds); expect($result)->toHaveCount(3) ->and(array_column($result, 'id'))->toContain('0', '123', '456') ->and(array_column($result, 'id'))->not->toContain('789'); }); test('caches results', function () { $scopeTagIds = ['0', '123']; $allScopeTags = [ ['id' => '0', 'displayName' => 'Default'], ['id' => '123', 'displayName' => 'HR-Admins'], ]; // First call - should hit Graph API $this->graphClient ->shouldReceive('get') ->once() ->andReturn(['value' => $allScopeTags]); $this->logger ->shouldReceive('logDebug') ->once(); $result1 = $this->resolver->resolve($scopeTagIds); // Second call with different IDs - should use cached data (no new Graph call) $result2 = $this->resolver->resolve(['0']); expect($result1)->toHaveCount(2) ->and($result2)->toHaveCount(1) ->and($result2[0]['id'])->toBe('0'); }); test('returns empty array for empty input', function () { $result = $this->resolver->resolve([]); expect($result)->toBe([]); }); test('handles graph exception gracefully', function () { $scopeTagIds = ['0', '123']; $this->graphClient ->shouldReceive('get') ->once() ->andThrow(new GraphException('Graph API error', 500, ['request_id' => 'request-id-123'])); $this->logger ->shouldReceive('logWarning') ->once() ->with('Failed to fetch scope tags', Mockery::on(function ($context) { return isset($context['context']['request_id']); })); $result = $this->resolver->resolve($scopeTagIds); expect($result)->toBe([]); }); test('filters correctly when scope tag not in cache', function () { $scopeTagIds = ['999']; // ID that doesn't exist $allScopeTags = [ ['id' => '0', 'displayName' => 'Default'], ['id' => '123', 'displayName' => 'HR-Admins'], ]; $this->graphClient ->shouldReceive('get') ->once() ->andReturn(['value' => $allScopeTags]); $this->logger ->shouldReceive('logDebug') ->once(); $result = $this->resolver->resolve($scopeTagIds); expect($result)->toBeEmpty(); }); test('handles response without value key', function () { $scopeTagIds = ['0', '123']; $this->graphClient ->shouldReceive('get') ->once() ->andReturn([]); // Missing 'value' key $this->logger ->shouldReceive('logDebug') ->once() ->with('Fetched scope tags', ['count' => 0]); $result = $this->resolver->resolve($scopeTagIds); expect($result)->toBeEmpty(); });