previousHttpHost = $_SERVER['HTTP_HOST'] ?? null; $_SERVER['HTTP_HOST'] = config('domains.domains.crm.host'); $this->refreshApplication(); $this->artisan('migrate'); }); afterEach(function () { if ($this->previousHttpHost === null) { unset($_SERVER['HTTP_HOST']); } else { $_SERVER['HTTP_HOST'] = $this->previousHttpHost; } }); function vipTestUser(int $admin = 0): User { $account = UserAccount::create(['first_name' => 'Vera', 'last_name' => 'Berater']); return User::forceCreate([ 'email' => 'vip-'.uniqid('', true).'@example.com', 'password' => bcrypt('secret'), 'lang' => 'de', 'admin' => $admin, 'account_id' => $account->id, 'payment_account' => now()->addYear(), 'wizard' => 100, 'active' => 1, 'active_date' => now()->subMonths(6), ]); } function vipOnlyChallenge(array $overrides = []): Incentive { return Incentive::factory()->create(array_merge([ 'name' => 'Recruiting Challenge', 'slug' => 'recruiting-challenge-vip-'.uniqid(), 'qualification_start' => '2026-10-01', 'qualification_end' => '2027-01-31', 'calculation_end' => '2027-01-31', 'points_partner_onetime' => 1, 'points_abo_onetime' => 0, 'min_direct_partners' => 0, 'min_customer_abos' => 0, 'max_winners' => 3, 'amount_per_partner' => 50, 'bonus_rank_1' => 500, 'include_sales_points' => false, 'vip_only' => true, ], $overrides)); } it('zeigt ein VIP-Incentive in der Navigation nur VIP-Konten', function () { $public = Incentive::factory()->create(['qualification_start' => '2026-04-01']); $vipOnly = vipOnlyChallenge(); expect(Incentive::activeForNavigation(vipTestUser())->pluck('id')->all())->toBe([$public->id]) ->and(Incentive::activeForNavigation(vipTestUser(admin: 1))->pluck('id')->all())->toBe([$public->id, $vipOnly->id]) ->and(Incentive::activeForNavigation(null)->pluck('id')->all())->toBe([$public->id]) ->and($vipOnly->isVisibleFor(vipTestUser()))->toBeFalse() ->and($vipOnly->isVisibleFor(vipTestUser(admin: 1)))->toBeTrue() ->and($public->isVisibleFor(vipTestUser()))->toBeTrue(); }); it('liefert normalen Beratern 404 fuer Teaser, Rangliste, Details und Teilnahme', function () { $incentive = vipOnlyChallenge(); $this->actingAs(vipTestUser()); $this->get(route('user_incentive_teaser', [$incentive->slug]))->assertNotFound(); $this->get(route('user_incentive_show', [$incentive->slug]))->assertRedirect(route('user_incentive_teaser', [$incentive->slug]).'#ranking'); $this->get(route('user_incentive_details', [$incentive->slug]))->assertNotFound(); $this->post(route('user_incentive_participate', [$incentive->slug]), ['accept_terms' => 1])->assertNotFound(); }); it('zeigt VIP-Konten Teaser und Rangliste eines VIP-Incentives', function () { $incentive = vipOnlyChallenge(); $this->actingAs(vipTestUser(admin: 1)); $this->get(route('user_incentive_teaser', [$incentive->slug]))->assertOk(); }); it('zeigt in der Navigation ein VIP-Badge am VIP-Incentive', function () { $incentive = vipOnlyChallenge(); $public = vipOnlyChallenge(['vip_only' => false, 'name' => 'Offenes Incentive']); $this->actingAs(vipTestUser(admin: 1)); $html = $this->get(route('user_incentive_teaser', [$public->slug]))->assertOk()->getContent(); $vipEntry = substr($html, strpos($html, route('user_incentive_teaser', [$incentive->slug])), 600); $publicEntry = substr($html, strpos($html, route('user_incentive_teaser', [$public->slug]).'"'), 600); expect($vipEntry)->toContain('badge badge-secondary') ->and($publicEntry)->not->toContain('badge badge-secondary'); }); it('zeigt die Dashboard-Karte eines VIP-Incentives normalen Beratern nicht', function () { vipOnlyChallenge(['name' => 'VIP Dashboard Challenge']); $this->actingAs(vipTestUser()); $this->get(route('home'))->assertOk()->assertDontSee('VIP Dashboard Challenge'); }); it('zeigt die Dashboard-Karte eines VIP-Incentives VIP-Konten mit nur einem Button zur Seite', function () { vipOnlyChallenge(['name' => 'VIP Dashboard Challenge']); $this->actingAs(vipTestUser(admin: 1)); $this->get(route('home'))->assertOk() ->assertSee('VIP Dashboard Challenge') ->assertSee(__('incentive.dashboard_btn_teaser')) ->assertDontSee(__('incentive.dashboard_btn_ranking')) ->assertDontSee('#ranking'); }); it('zeigt allen Beratern ein Incentive ohne VIP-Sperre', function () { $incentive = vipOnlyChallenge(['vip_only' => false]); $this->actingAs(vipTestUser()); $this->get(route('user_incentive_teaser', [$incentive->slug]))->assertOk(); }); it('speichert den VIP-Haken im Admin und zeigt ihn auf der Detailseite', function () { $this->actingAs(vipTestUser(admin: 2)); $this->post(route('admin_incentive_store'), [ 'name' => 'VIP Test Challenge', 'qualification_start' => '01.10.2026', 'qualification_end' => '31.01.2027', 'calculation_end' => '31.01.2027', 'points_partner_onetime' => 1, 'points_abo_onetime' => 0, 'min_direct_partners' => 0, 'min_customer_abos' => 0, 'max_winners' => 3, 'include_sales_points' => '0', 'vip_only' => '1', 'status' => 1, ])->assertRedirect(route('admin_incentives')); $incentive = Incentive::where('name', 'VIP Test Challenge')->firstOrFail(); expect($incentive->vip_only)->toBeTrue(); $this->get(route('admin_incentive_show', [$incentive->id])) ->assertOk() ->assertSee('id="incentive-vip-only-badge"', false); }); describe('Incentive-Seite: Teilnahme-Block oben, Rangliste unten', function () { it('zeigt ohne Bestaetigung den Teilnahme-Block mit Praemie zuerst, dann Trennlinie und Inhalt', function () { $incentive = vipOnlyChallenge(['vip_only' => false, 'image' => 'challenge.jpg', 'terms' => '
Bedingungen Text
']); $this->actingAs(vipTestUser()); $response = $this->get(route('user_incentive_teaser', [$incentive->slug]))->assertOk(); $html = $response->getContent(); $join = strpos($html, 'id="incentive-join"'); $divider = strpos($html, 'class="inc-divider"'); $hero = strpos($html, 'class="incentive-hero mb-5"'); $howItWorks = strpos($html, 'id="incentive-how-it-works"'); $ranking = strpos($html, 'id="ranking"'); expect($join)->not->toBeFalse() ->and($divider)->toBeGreaterThan($join) ->and($hero)->toBeGreaterThan($divider) ->and($howItWorks)->toBeGreaterThan($hero) ->and($ranking)->toBeGreaterThan($howItWorks) ->and(substr_count($html, 'id="terms"'))->toBe(1); $response->assertSee(__('incentive.join_title')) ->assertSee('50,00 €') ->assertSee(__('incentive.join_highlight_rank', ['amount' => '500,00 €', 'n' => 1])) ->assertSee(__('incentive.join_terms_toggle')) ->assertSee('Bedingungen Text', false) ->assertSee('id="incentive-payout-tile"', false) ->assertSee('id="incentive-how-it-works"', false) ->assertDontSee(__('incentive.section_points')) ->assertDontSee(__('incentive.points_short')); }); it('nennt den Block "Teilnahme bestaetigen", wenn Punkte schon laufen', function () { $incentive = vipOnlyChallenge(['vip_only' => false]); $user = vipTestUser(); IncentiveParticipant::factory()->unconfirmed()->create([ 'incentive_id' => $incentive->id, 'user_id' => $user->id, 'qualified_partners' => 2, 'total_points' => 2, 'is_qualified' => true, 'rank' => 1, ]); $this->actingAs($user); $this->get(route('user_incentive_teaser', [$incentive->slug])) ->assertOk() ->assertSee(__('incentive.join_confirm_title')) ->assertSee(__('incentive.payout_after_confirmation')) ->assertSee('600,00 €'); }); it('zeigt bestaetigten Teilnehmern den Kopf mit Stand statt des Teilnahme-Blocks', function () { $incentive = vipOnlyChallenge(['vip_only' => false]); $user = vipTestUser(); IncentiveParticipant::factory()->create([ 'incentive_id' => $incentive->id, 'user_id' => $user->id, 'qualified_partners' => 1, 'total_points' => 1, 'is_qualified' => true, 'rank' => 1, ]); $this->actingAs($user); $this->get(route('user_incentive_teaser', [$incentive->slug])) ->assertOk() ->assertDontSee('id="incentive-join"', false) ->assertSee('id="incentive-participant-header"', false) ->assertSee(__('incentive.my_details')) ->assertSee(__('incentive.join_terms_toggle')) ->assertSee('id="ranking"', false) ->assertSee('id="incentive-how-it-works"', false) ->assertDontSee(__('incentive.section_points')); }); }); describe('Teaser-Seite der Challenge', function () { it('zeigt den Call to Action ganz oben, die Praemie je Teampartner und keine Galerie', function () { $incentive = vipOnlyChallenge(['vip_only' => false, 'image' => 'challenge.jpg', 'bonus_rank_2' => 300, 'bonus_rank_3' => 200]); $this->actingAs(vipTestUser()); $response = $this->get(route('user_incentive_teaser', [$incentive->slug]))->assertOk(); $html = $response->getContent(); $join = strpos($html, 'id="incentive-join"'); $hero = strpos($html, 'class="incentive-hero mb-5"'); expect($join)->not->toBeFalse() ->and($hero)->toBeGreaterThan($join); $response->assertSee(__('incentive.join_title')) ->assertSee(__('incentive.payout_per_partner_label')) ->assertSee('id="incentive-bonus-tile"', false) ->assertSee('id="incentive-how-it-works"', false) ->assertSee(__('incentive.section_how_it_works')) ->assertDontSee(__('incentive.section_points')) ->assertDontSee('class="gallery-section', false) ->assertDontSee(__('incentive.gallery_title')); }); it('nimmt Galerie-Bilder nur aus dem eigenen Ordner des Incentives', function () { $incentive = vipOnlyChallenge(['vip_only' => false, 'image' => 'hero.jpg']); $other = vipOnlyChallenge(['vip_only' => false, 'image' => 'alt.jpg']); $dir = public_path($incentive->imageDirectory()); @mkdir($dir, 0777, true); file_put_contents($dir.'/hero.jpg', 'x'); file_put_contents($dir.'/b.jpg', 'x'); file_put_contents($dir.'/a.jpg', 'x'); try { expect($incentive->galleryImages())->toBe([$incentive->imageDirectory().'/a.jpg', $incentive->imageDirectory().'/b.jpg']) ->and($incentive->heroImagePath())->toBe($incentive->imageDirectory().'/hero.jpg') ->and($other->galleryImages())->toBe([]) ->and($other->heroImagePath())->toBe('img/incentive/'.$other->image); } finally { array_map('unlink', glob($dir.'/*') ?: []); @rmdir($dir); } }); }); /** * Kundenentscheid 25.09.2026 (Datenschutz): Unbestaetigte zaehlen im Hintergrund mit, * andere Berater sehen sie aber nicht, auch nicht anonym. */ function rankingPrivacyChallenge(): Incentive { $incentive = vipOnlyChallenge(['vip_only' => false]); $confirmedUser = vipTestUser(); $confirmedUser->account->update(['first_name' => 'Anna', 'last_name' => 'Bestaetigt']); $unconfirmedUser = vipTestUser(); $unconfirmedUser->account->update(['first_name' => 'Nina', 'last_name' => 'Offen']); IncentiveParticipant::factory()->create([ 'incentive_id' => $incentive->id, 'user_id' => $confirmedUser->id, 'qualified_partners' => 1, 'total_points' => 1, 'rank' => 1, ]); IncentiveParticipant::factory()->unconfirmed()->create([ 'incentive_id' => $incentive->id, 'user_id' => $unconfirmedUser->id, 'qualified_partners' => 3, 'total_points' => 3, 'rank' => null, ]); return $incentive; } it('blendet unbestaetigte Teilnehmer in der Rangliste fuer normale Berater aus', function () { $incentive = rankingPrivacyChallenge(); $this->actingAs(vipTestUser()); $this->get(route('user_incentive_teaser', [$incentive->slug])) ->assertOk() ->assertSee('Anna Bestaetigt') ->assertDontSee('Nina Offen') ->assertDontSee(__('incentive.anonymous_consultant')); }); it('zeigt VIP-Konten auch die unbestaetigten Teilnehmer', function () { $incentive = rankingPrivacyChallenge(); $this->actingAs(vipTestUser(admin: 1)); $this->get(route('user_incentive_teaser', [$incentive->slug])) ->assertOk() ->assertSee('Anna Bestaetigt') ->assertSee('Nina Offen'); });