seed(RolesAndPermissionsSeeder::class); }); test('accessibleBy includes a users own authored press release', function () { $user = User::factory()->create(); $pr = PressRelease::factory()->create(['user_id' => $user->id, 'company_id' => null]); $ids = PressRelease::accessibleBy($user)->pluck('id'); expect($ids)->toContain($pr->id); }); test('ownedByApiUser matches only the authoring user, not company access', function () { $author = User::factory()->create(); $companyMate = User::factory()->create(); $company = Company::factory()->create(); $company->users()->attach($companyMate->id, ['role' => 'member']); $pr = PressRelease::factory()->create(['user_id' => $author->id, 'company_id' => $company->id]); expect(PressRelease::withoutGlobalScopes()->ownedByApiUser($author)->pluck('id'))->toContain($pr->id) ->and(PressRelease::withoutGlobalScopes()->ownedByApiUser($companyMate)->pluck('id'))->not->toContain($pr->id); }); test('accessibleBy includes press releases of an assigned company', function () { $user = User::factory()->create(); $company = Company::factory()->presseecho()->create(); $user->companies()->attach($company->id, ['role' => 'owner']); $other = User::factory()->create(); $pr = PressRelease::factory()->create([ 'user_id' => $other->id, 'company_id' => $company->id, 'portal' => $company->portal->value, ]); $ids = PressRelease::accessibleBy($user)->pluck('id'); expect($ids)->toContain($pr->id); }); test('accessibleBy excludes press releases of strangers and unrelated companies', function () { $user = User::factory()->create(); $stranger = User::factory()->create(); $foreignCompany = Company::factory()->presseecho()->create(); $foreignPr = PressRelease::factory()->create([ 'user_id' => $stranger->id, 'company_id' => $foreignCompany->id, 'portal' => $foreignCompany->portal->value, ]); $ids = PressRelease::accessibleBy($user)->pluck('id'); expect($ids)->not->toContain($foreignPr->id); }); test('accessibleStatusCountsFor counts authored and company PMs once each', function () { $user = User::factory()->create(); $company = Company::factory()->presseecho()->create(); $user->companies()->attach($company->id, ['role' => 'owner']); $stranger = User::factory()->create(); PressRelease::factory()->create(['user_id' => $user->id, 'company_id' => null, 'status' => 'draft']); PressRelease::factory()->create(['user_id' => $stranger->id, 'company_id' => $company->id, 'status' => 'published', 'portal' => $company->portal->value]); // Autor UND zugeordnete Firma: darf nur einmal zählen (UNION-Dedup). PressRelease::factory()->create(['user_id' => $user->id, 'company_id' => $company->id, 'status' => 'published', 'portal' => $company->portal->value]); // Fremde PM ohne Bezug: ausgeschlossen. PressRelease::factory()->create(['user_id' => $stranger->id, 'company_id' => null, 'status' => 'draft']); $counts = PressRelease::accessibleStatusCountsFor($user); expect($counts['draft'] ?? 0)->toBe(1) ->and($counts['published'] ?? 0)->toBe(2) ->and(array_sum($counts))->toBe(3); }); test('accessibleStatusCountsFor applies the additional constraint to both branches', function () { $user = User::factory()->create(); $company = Company::factory()->presseecho()->create(); $user->companies()->attach($company->id, ['role' => 'owner']); PressRelease::factory()->create(['user_id' => $user->id, 'company_id' => null, 'status' => 'draft']); PressRelease::factory()->create(['user_id' => $user->id, 'company_id' => $company->id, 'status' => 'draft', 'portal' => $company->portal->value]); $unassigned = PressRelease::accessibleStatusCountsFor($user, fn ($q) => $q->whereNull('company_id')); expect(array_sum($unassigned))->toBe(1) ->and($unassigned['draft'] ?? 0)->toBe(1); }); test('accessibleCompanyIds returns owned and member company ids without duplicates', function () { $user = User::factory()->create(); $owned = Company::factory()->create(['owner_user_id' => $user->id]); $member = Company::factory()->create(); $member->users()->attach($user->id, ['role' => 'member']); Company::factory()->create(); // fremde Firma $ids = $user->accessibleCompanyIds(); expect($ids)->toEqualCanonicalizing([$owned->id, $member->id]); }); test('accessibleCompanyIds does not hydrate Company models', function () { $user = User::factory()->create(); Company::factory()->count(3)->create(['owner_user_id' => $user->id]); $hydrated = 0; Company::retrieved(function () use (&$hydrated): void { $hydrated++; }); $user->accessibleCompanyIds(); expect($hydrated)->toBe(0); }); test('accessibleBy bypasses the portal global scope so own PMs are visible cross-portal', function () { $user = User::factory()->create(); $pr = PressRelease::factory()->create([ 'user_id' => $user->id, 'company_id' => null, 'portal' => Portal::Businessportal24->value, ]); // Simuliert eingeschränkten Portal-Kontext (Presseecho); der Scope muss // die eigene PM dennoch finden, weil er die GlobalScopes entfernt. CurrentPortalContext::set(Portal::Presseecho); $found = PressRelease::accessibleBy($user)->find($pr->id); expect($found)->not->toBeNull(); });