extend(Tests\TestCase::class) ->use(RefreshDatabase::class) ->in('Feature'); pest()->use(InteractsWithFindingsWorkflow::class) ->in('Feature/Findings', 'Feature/Audit'); pest()->extend(Tests\TestCase::class) ->use(RefreshDatabase::class) ->in('Browser'); pest()->extend(Tests\TestCase::class) ->in('Unit'); pest()->extend(Tests\TestCase::class) ->in('Deprecation'); beforeEach(function () { putenv('INTUNE_TENANT_ID'); unset($_ENV['INTUNE_TENANT_ID'], $_SERVER['INTUNE_TENANT_ID']); }); /* |-------------------------------------------------------------------------- | Expectations |-------------------------------------------------------------------------- | | When you're writing tests, you often need to check that values meet certain conditions. The | "expect()" function gives you access to a set of "expectations" methods that you can use | to assert different things. Of course, you may extend the Expectation API at any time. | */ expect()->extend('toBeOne', function () { return $this->toBe(1); }); function fakeIdToken(string $tenantId): string { $header = rtrim(strtr(base64_encode(json_encode(['alg' => 'HS256', 'typ' => 'JWT'])), '+/', '-_'), '='); $payload = rtrim(strtr(base64_encode(json_encode(['tid' => $tenantId])), '+/', '-_'), '='); return $header.'.'.$payload.'.signature'; } /* |-------------------------------------------------------------------------- | Functions |-------------------------------------------------------------------------- | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your | project that you don't want to repeat in every file. Here you can also expose helpers as | global functions to help you to reduce the number of lines of code in your test files. | */ function something() { // .. } /** * @param array $payload * @param array $overrides * @return array */ function mutateTrustedStatePayload(array $payload, array $overrides): array { return array_replace_recursive($payload, $overrides); } /** * @param array $data * @param list $errorKeys */ function assertScopedSelectorRejected(mixed $component, string $action, array $data, array $errorKeys = ['tenant_id']): mixed { return $component ->callAction($action, data: $data) ->assertHasActionErrors($errorKeys); } /** * Spec test naming helper. * * Convention for focused runs: * - Prefix every Spec 081 test title with "Spec081 ". * - Keep filenames suffixed with "Spec081Test.php". * - Command: `vendor/bin/sail artisan test --compact --filter=Spec081` */ function spec081(string $description): string { $normalized = trim($description); if ($normalized === '') { return 'Spec081'; } return str_starts_with($normalized, 'Spec081 ') ? $normalized : 'Spec081 '.$normalized; } /** * Convenience wrapper for Spec 081 tests. */ function itSpec081(string $description, ?\Closure $closure = null): TestCall { $call = it(spec081($description), $closure); $call->group('spec081'); return $call; } function bindFailHardGraphClient(): void { app()->instance(GraphClientInterface::class, new FailHardGraphClient); } function assertNoOutboundHttp(\Closure $callback): mixed { return AssertsNoOutboundHttp::run($callback); } /** * @param array $attributes */ function createInventorySyncOperationRun(Tenant $tenant, array $attributes = []): \App\Models\OperationRun { $context = is_array($attributes['context'] ?? null) ? $attributes['context'] : []; if (array_key_exists('selection_hash', $attributes)) { if (is_string($attributes['selection_hash']) && $attributes['selection_hash'] !== '') { $context['selection_hash'] = $attributes['selection_hash']; } unset($attributes['selection_hash']); } if (array_key_exists('selection_payload', $attributes)) { if (is_array($attributes['selection_payload'])) { $context = array_merge($context, $attributes['selection_payload']); } unset($attributes['selection_payload']); } if (! isset($context['selection_hash']) || ! is_string($context['selection_hash']) || $context['selection_hash'] === '') { $context['selection_hash'] = hash('sha256', 'inventory-sync-selection-default'); } if (! isset($context['policy_types']) || ! is_array($context['policy_types'])) { $context['policy_types'] = ['deviceConfiguration']; } if (! isset($context['categories']) || ! is_array($context['categories'])) { $context['categories'] = []; } if (! array_key_exists('include_foundations', $context)) { $context['include_foundations'] = false; } if (! array_key_exists('include_dependencies', $context)) { $context['include_dependencies'] = false; } $finishedAt = $attributes['finished_at'] ?? null; unset($attributes['finished_at']); $providedStatus = (string) ($attributes['status'] ?? 'success'); $normalizedStatus = match ($providedStatus) { 'pending', 'queued' => 'queued', 'running' => 'running', 'completed' => 'completed', default => 'completed', }; $normalizedOutcome = match ($providedStatus) { 'success' => 'succeeded', 'partial' => 'partially_succeeded', 'skipped' => 'blocked', 'failed' => 'failed', 'pending', 'queued', 'running' => 'pending', default => $normalizedStatus === 'completed' ? 'succeeded' : 'pending', }; $attributes['type'] = (string) ($attributes['type'] ?? 'inventory_sync'); $attributes['workspace_id'] = (int) ($attributes['workspace_id'] ?? $tenant->workspace_id); $attributes['status'] = in_array($providedStatus, ['queued', 'running', 'completed'], true) ? $providedStatus : $normalizedStatus; $attributes['outcome'] = (string) ($attributes['outcome'] ?? $normalizedOutcome); $attributes['context'] = array_merge($context, is_array($attributes['context'] ?? null) ? $attributes['context'] : []); if ($finishedAt !== null && ! array_key_exists('completed_at', $attributes)) { $attributes['completed_at'] = $finishedAt; } return \App\Models\OperationRun::factory() ->for($tenant) ->create($attributes); } /** * Create a completed inventory sync run with coverage proof in context. * * @param array $statusByType Example: ['deviceConfiguration' => 'succeeded'] * @param list $foundationTypes * @param array $attributes */ function createInventorySyncOperationRunWithCoverage( Tenant $tenant, array $statusByType, array $foundationTypes = [], array $attributes = [], ): \App\Models\OperationRun { $context = is_array($attributes['context'] ?? null) ? $attributes['context'] : []; $inventory = is_array($context['inventory'] ?? null) ? $context['inventory'] : []; $inventory['coverage'] = \App\Support\Inventory\InventoryCoverage::buildPayload( statusByType: $statusByType, foundationTypes: $foundationTypes, ); $context['inventory'] = $inventory; $attributes['context'] = $context; if (! array_key_exists('finished_at', $attributes) && ! array_key_exists('completed_at', $attributes)) { $attributes['finished_at'] = now(); } $attributes['type'] ??= \App\Support\OperationRunType::InventorySync->value; $attributes['status'] ??= \App\Support\OperationRunStatus::Completed->value; $attributes['outcome'] ??= \App\Support\OperationRunOutcome::Succeeded->value; return createInventorySyncOperationRun($tenant, $attributes); } /** * @return array{0: User, 1: Tenant} */ function createUserWithTenant( ?Tenant $tenant = null, ?User $user = null, string $role = 'owner', ?string $workspaceRole = null, bool $ensureDefaultMicrosoftProviderConnection = true, string $defaultProviderConnectionType = ProviderConnectionType::Dedicated->value, ): array { $user ??= User::factory()->create(); $tenant ??= Tenant::factory()->create(); $workspaceRole ??= $role; $validWorkspaceRoles = array_map( static fn (\App\Support\Auth\WorkspaceRole $role): string => $role->value, \App\Support\Auth\WorkspaceRole::cases(), ); if (! in_array($workspaceRole, $validWorkspaceRoles, true)) { $workspaceRole = \App\Support\Auth\WorkspaceRole::Owner->value; } $workspace = null; if ($tenant->workspace_id !== null) { $workspace = Workspace::query()->whereKey($tenant->workspace_id)->first(); } if (! $workspace instanceof Workspace) { $workspace = Workspace::factory()->create(); $tenant->forceFill([ 'workspace_id' => (int) $workspace->getKey(), ])->save(); } WorkspaceMembership::query()->updateOrCreate([ 'workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $user->getKey(), ], [ 'role' => $workspaceRole, ]); session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey()); $user->forceFill(['last_workspace_id' => (int) $workspace->getKey()])->save(); $user->tenants()->syncWithoutDetaching([ $tenant->getKey() => ['role' => $role], ]); if ($ensureDefaultMicrosoftProviderConnection) { ensureDefaultProviderConnection($tenant, 'microsoft', $defaultProviderConnectionType); } return [$user, $tenant]; } /** * @return array{tenant: string} */ function filamentTenantRouteParams(Tenant $tenant): array { return ['tenant' => (string) $tenant->external_id]; } function ensureDefaultProviderConnection( Tenant $tenant, string $provider = 'microsoft', string $connectionType = ProviderConnectionType::Dedicated->value, ): ProviderConnection { $resolvedConnectionType = ProviderConnectionType::tryFrom($connectionType) ?? ProviderConnectionType::Dedicated; $connection = ProviderConnection::query() ->where('tenant_id', (int) $tenant->getKey()) ->where('provider', $provider) ->orderByDesc('is_default') ->orderBy('id') ->first(); if (! $connection instanceof ProviderConnection) { $connectionFactory = ProviderConnection::factory(); if ($resolvedConnectionType === ProviderConnectionType::Dedicated) { $connectionFactory = $connectionFactory->dedicated(); } $connection = $connectionFactory->verifiedHealthy()->create([ 'tenant_id' => (int) $tenant->getKey(), 'provider' => $provider, 'entra_tenant_id' => (string) ($tenant->tenant_id ?? fake()->uuid()), 'connection_type' => $resolvedConnectionType->value, 'is_default' => true, ]); } else { $entraTenantId = trim((string) $connection->entra_tenant_id); $currentConnectionType = $connection->connection_type instanceof ProviderConnectionType ? $connection->connection_type->value : (is_string($connection->connection_type) ? $connection->connection_type : null); $currentConsentStatus = $connection->consent_status instanceof ProviderConsentStatus ? $connection->consent_status->value : (is_string($connection->consent_status) ? $connection->consent_status : null); $currentVerificationStatus = $connection->verification_status instanceof ProviderVerificationStatus ? $connection->verification_status->value : (is_string($connection->verification_status) ? $connection->verification_status : null); $updates = []; if ($currentConnectionType !== $resolvedConnectionType->value) { $updates['connection_type'] = $resolvedConnectionType->value; } if (! $connection->is_default) { $updates['is_default'] = true; } if ($connection->status !== 'connected') { $updates['status'] = 'connected'; } if ($currentConsentStatus !== ProviderConsentStatus::Granted->value) { $updates['consent_status'] = ProviderConsentStatus::Granted->value; } if ($currentVerificationStatus !== ProviderVerificationStatus::Healthy->value) { $updates['verification_status'] = ProviderVerificationStatus::Healthy->value; } if ($connection->health_status !== 'ok') { $updates['health_status'] = 'ok'; } if ($entraTenantId === '') { $updates['entra_tenant_id'] = (string) ($tenant->tenant_id ?? fake()->uuid()); } if (! array_key_exists('consent_granted_at', $updates)) { $updates['consent_granted_at'] = now(); } if (! array_key_exists('consent_last_checked_at', $updates)) { $updates['consent_last_checked_at'] = now(); } if (! array_key_exists('last_health_check_at', $updates)) { $updates['last_health_check_at'] = now(); } if (! array_key_exists('migration_review_required', $updates)) { $updates['migration_review_required'] = false; } if ($updates !== []) { $connection->forceFill($updates)->save(); $connection->refresh(); } } $credential = $connection->credential()->first(); if ($resolvedConnectionType === ProviderConnectionType::Platform) { if ($credential instanceof ProviderCredential) { $credential->delete(); $connection->refresh(); } return $connection; } if (! $credential instanceof ProviderCredential) { ProviderCredential::factory()->create([ 'provider_connection_id' => (int) $connection->getKey(), 'type' => ProviderCredentialKind::ClientSecret->value, 'credential_kind' => ProviderCredentialKind::ClientSecret->value, 'source' => ProviderCredentialSource::DedicatedManual->value, 'last_rotated_at' => now(), 'expires_at' => now()->addYear(), 'payload' => [ 'client_id' => fake()->uuid(), 'client_secret' => fake()->sha1(), ], ]); $connection->refresh(); } return $connection; } /** * @param array $attributes */ function createOnboardingDraft(array $attributes = []): TenantOnboardingSession { $workspace = $attributes['workspace'] ?? Workspace::factory()->create(); $tenant = $attributes['tenant'] ?? null; $startedBy = $attributes['started_by'] ?? User::factory()->create(); $updatedBy = $attributes['updated_by'] ?? $startedBy; foreach ([$startedBy, $updatedBy] as $member) { if (! $member instanceof User) { continue; } WorkspaceMembership::query()->firstOrCreate([ 'workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $member->getKey(), ], [ 'role' => 'owner', ]); } $factory = TenantOnboardingSession::factory() ->forWorkspace($workspace) ->startedBy($startedBy) ->updatedBy($updatedBy); if ($tenant instanceof Tenant) { $factory = $factory->forTenant($tenant); } if (($attributes['status'] ?? null) === 'completed') { $factory = $factory->completed(); } if (($attributes['status'] ?? null) === 'cancelled') { $factory = $factory->cancelled(); } unset( $attributes['workspace'], $attributes['tenant'], $attributes['started_by'], $attributes['updated_by'], $attributes['status'], ); return $factory->create($attributes); } /** * @return list */ function tenantActionCatalog(Tenant $tenant, TenantActionSurface $surface, ?User $user = null): array { return app(TenantActionPolicySurface::class)->catalogForTenant($tenant, $surface, $user); } /** * @param list $actions * @return list */ function tenantActionKeys(array $actions): array { return array_values(array_map( static fn (TenantActionDescriptor $action): string => $action->key, $actions, )); } function ensureDefaultPlatformProviderConnection(Tenant $tenant, string $provider = 'microsoft'): ProviderConnection { return ensureDefaultProviderConnection($tenant, $provider, ProviderConnectionType::Platform->value); } function ensureDefaultDedicatedProviderConnection(Tenant $tenant, string $provider = 'microsoft'): ProviderConnection { return ensureDefaultProviderConnection($tenant, $provider, ProviderConnectionType::Dedicated->value); } /** * @param array $snapshot * @param array> $assignments * @param array $scopeTags * @param array> $secretFingerprints */ function expectedPolicyVersionContentHash( array $snapshot, string $policyType, ?string $platform = null, array $assignments = [], array $scopeTags = [], array $secretFingerprints = [], ?int $redactionVersion = 1, ): string { return app(\App\Services\Drift\DriftHasher::class)->hashNormalized([ 'settings' => app(\App\Services\Drift\Normalizers\SettingsNormalizer::class)->normalizeForDiff( $snapshot, $policyType, $platform, ), 'assignments' => app(\App\Services\Drift\Normalizers\AssignmentsNormalizer::class)->normalizeForDiff($assignments), 'scope_tag_ids' => app(\App\Services\Drift\Normalizers\ScopeTagsNormalizer::class)->normalizeIds($scopeTags), 'secret_fingerprints' => [ 'snapshot' => is_array($secretFingerprints['snapshot'] ?? null) ? $secretFingerprints['snapshot'] : [], 'assignments' => is_array($secretFingerprints['assignments'] ?? null) ? $secretFingerprints['assignments'] : [], 'scope_tags' => is_array($secretFingerprints['scope_tags'] ?? null) ? $secretFingerprints['scope_tags'] : [], ], 'redaction_version' => $redactionVersion, ]); }