522 lines
24 KiB
PHP
522 lines
24 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Polls;
|
|
|
|
use App\Http\Livewire\MainPage\PollCardFull;
|
|
use App\Http\Livewire\Polls\Manage;
|
|
use App\Http\Livewire\Polls\PollForm;
|
|
use App\Models\Admin;
|
|
use App\Models\Category;
|
|
use App\Models\Organization;
|
|
use App\Models\Poll;
|
|
use App\Models\PollQuestion;
|
|
use App\Models\PollResponse;
|
|
use App\Models\PostTranslation;
|
|
use App\Models\User;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Livewire\Livewire;
|
|
use Spatie\Permission\Models\Permission;
|
|
use Spatie\Permission\Models\Role;
|
|
use Spatie\Permission\PermissionRegistrar;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* Where polls appear: publishing on open, the late-publication warning,
|
|
* the main-page card, the post pages and the closed state.
|
|
*/
|
|
class PollPagesTest extends TestCase
|
|
{
|
|
use DatabaseTransactions;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
// phpunit.xml configures Redis, which is the dev site's cache too
|
|
config(['cache.default' => 'array', 'scout.driver' => null]);
|
|
app(PermissionRegistrar::class)->forgetCachedPermissions();
|
|
app()->setLocale('en');
|
|
}
|
|
|
|
/** A poll with one yes/no question; $from is the post's publication date (null = unpublished). */
|
|
private function makePoll(string $title, array $attributes = [], ?string $from = '2026-01-01 00:00:00'): Poll
|
|
{
|
|
$category = Category::firstOrCreate(['type' => Poll::class, 'categoryable_id' => null, 'categoryable_type' => null]);
|
|
\App\Models\CategoryTranslation::firstOrCreate(
|
|
['category_id' => $category->id, 'locale' => 'en'],
|
|
['name' => 'Poll', 'slug' => 'poll-pages-test-' . $category->id]
|
|
);
|
|
$postId = DB::table('posts')->insertGetId([
|
|
'postable_id' => 1, 'postable_type' => Admin::class, 'category_id' => $category->id,
|
|
'created_at' => now(), 'updated_at' => now(),
|
|
]);
|
|
DB::table('post_translations')->insert([
|
|
'post_id' => $postId, 'locale' => 'en', 'slug' => 'poll-pages-' . $postId, 'title' => $title,
|
|
'excerpt' => "Intro of $title", 'from' => $from, 'created_at' => now(), 'updated_at' => now(),
|
|
]);
|
|
$poll = Poll::create(array_merge(['post_id' => $postId, 'status' => Poll::STATUS_OPEN], $attributes));
|
|
PollQuestion::create([
|
|
'poll_id' => $poll->id, 'position' => 1, 'type' => 'yes_no', 'required' => true,
|
|
'label' => ['en' => "Question of $title"], 'settings' => [],
|
|
]);
|
|
|
|
return $poll;
|
|
}
|
|
|
|
private function actAs(Model $profile, User $user): void
|
|
{
|
|
$this->actingAs($user, 'web');
|
|
session(['activeProfileType' => get_class($profile), 'activeProfileId' => $profile->getKey()]);
|
|
}
|
|
|
|
private function actAsAdmin(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$admin = Admin::factory()->create();
|
|
$admin->users()->attach($user);
|
|
$role = Role::firstOrCreate(['name' => "Admin\\{$admin->id}\\poll-pages-role", 'guard_name' => 'web']);
|
|
$role->givePermissionTo(Permission::firstOrCreate(['name' => 'manage polls', 'guard_name' => 'web']));
|
|
$user->assignRole($role);
|
|
$this->actingAs($user, 'web');
|
|
$this->actingAs($admin, 'admin');
|
|
session(['activeProfileType' => Admin::class, 'activeProfileId' => $admin->id]);
|
|
}
|
|
|
|
private function vote(Poll $poll, User $user): void
|
|
{
|
|
PollResponse::create([
|
|
'poll_id' => $poll->id, 'voter_id' => $user->id, 'voter_type' => User::class, 'user_id' => $user->id,
|
|
'dedupe_key' => $poll->dedupeKey($user, $user->id), 'submitted_at' => now(),
|
|
]);
|
|
}
|
|
|
|
// -----------------------------------------------------------------
|
|
// Publishing
|
|
// -----------------------------------------------------------------
|
|
|
|
public function test_opening_a_poll_publishes_its_unpublished_post(): void
|
|
{
|
|
$this->actAsAdmin();
|
|
$poll = $this->makePoll('Unpublished poll', ['status' => Poll::STATUS_DRAFT], null);
|
|
$translation = PostTranslation::where('post_id', $poll->post_id)->sole();
|
|
|
|
// Saving as draft leaves the post unpublished
|
|
Livewire::test(Manage::class)->call('openEditModal', $poll->id)->call('savePoll');
|
|
$this->assertNull($translation->fresh()->from);
|
|
|
|
Livewire::test(Manage::class)
|
|
->call('openEditModal', $poll->id)
|
|
->set('form.status', Poll::STATUS_OPEN)
|
|
->set('form.opens_at', '2026-11-01 09:00')
|
|
->call('savePoll')
|
|
->assertHasNoErrors();
|
|
|
|
$this->assertSame('2026-11-01 09:00', \Illuminate\Support\Carbon::parse($translation->fresh()->from)->format('Y-m-d H:i'));
|
|
}
|
|
|
|
public function test_an_existing_publication_date_is_kept_and_a_later_one_warned_about(): void
|
|
{
|
|
$this->actAsAdmin();
|
|
$poll = $this->makePoll('Late post', ['status' => Poll::STATUS_DRAFT], '2026-12-24 08:00:00');
|
|
|
|
$component = Livewire::test(Manage::class)
|
|
->call('openEditModal', $poll->id)
|
|
->set('tab', 'settings')
|
|
->assertDontSee('is published later than the poll opens')
|
|
->set('form.status', Poll::STATUS_OPEN)
|
|
->set('form.opens_at', '2026-11-01 09:00')
|
|
->assertSee('is published later than the poll opens')
|
|
->assertSee('EN:');
|
|
|
|
$this->assertArrayHasKey('en', $component->instance()->latePublications());
|
|
|
|
$component->call('savePoll')->assertHasNoErrors();
|
|
$this->assertSame('2026-12-24 08:00', \Illuminate\Support\Carbon::parse(PostTranslation::where('post_id', $poll->post_id)->sole()->from)->format('Y-m-d H:i'));
|
|
|
|
// Opening on or after the publication date: no warning
|
|
Livewire::test(Manage::class)
|
|
->call('openEditModal', $poll->id)
|
|
->set('form.opens_at', '2026-12-24 08:00')
|
|
->set('tab', 'settings')
|
|
->assertDontSee('is published later than the poll opens');
|
|
}
|
|
|
|
// -----------------------------------------------------------------
|
|
// Main-page card
|
|
// -----------------------------------------------------------------
|
|
|
|
public function test_card_shows_the_soonest_closing_unanswered_poll(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$this->actAs($user, $user);
|
|
// Other open polls in the test database would compete for the card; close them for this test
|
|
Poll::query()->update(['status' => Poll::STATUS_CLOSED]);
|
|
|
|
$later = $this->makePoll('Closes later', ['closes_at' => now()->addDays(10)]);
|
|
$sooner = $this->makePoll('Closes sooner', ['closes_at' => now()->addDays(2)]);
|
|
$this->makePoll('Not for users', ['target_profile_types' => [Organization::class]]);
|
|
$this->makePoll('Unpublished', [], null);
|
|
|
|
Livewire::test(PollCardFull::class)
|
|
->assertSet('pollId', $sooner->id)
|
|
->assertSee('Closes sooner')
|
|
->assertSee('Intro of Closes sooner')
|
|
->assertSee('Question of Closes sooner');
|
|
|
|
// Answered: the next one takes its place
|
|
$this->vote($sooner, $user);
|
|
Livewire::test(PollCardFull::class)->assertSet('pollId', $later->id);
|
|
|
|
$this->vote($later, $user);
|
|
Livewire::test(PollCardFull::class)->assertSet('pollId', null)->assertDontSee('Question of');
|
|
}
|
|
|
|
public function test_card_links_to_the_poll_page_for_several_questions(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$this->actAs($user, $user);
|
|
Poll::query()->update(['status' => Poll::STATUS_CLOSED]);
|
|
$poll = $this->makePoll('Long poll');
|
|
PollQuestion::create([
|
|
'poll_id' => $poll->id, 'position' => 2, 'type' => 'yes_no', 'required' => true,
|
|
'label' => ['en' => 'Second question of Long poll'], 'settings' => [],
|
|
]);
|
|
|
|
Livewire::test(PollCardFull::class)
|
|
->assertSet('pollId', $poll->id)
|
|
->assertSee('Long poll')
|
|
->assertSee('Intro of Long poll')
|
|
->assertSee('Take part')
|
|
->assertSee(route('poll.show', ['slug' => $this->slugOf($poll)]), false)
|
|
->assertDontSee('Question of Long poll')
|
|
->assertDontSee('Read more');
|
|
}
|
|
|
|
public function test_card_shows_a_single_question_with_read_more_when_there_is_content(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$this->actAs($user, $user);
|
|
Poll::query()->update(['status' => Poll::STATUS_CLOSED]);
|
|
$poll = $this->makePoll('Short poll');
|
|
|
|
// No content: just the question
|
|
Livewire::test(PollCardFull::class)
|
|
->assertSee('Question of Short poll')
|
|
->assertDontSee('Read more')
|
|
->assertDontSee('Take part');
|
|
|
|
// With content: "Read more..." to the poll page, above the question
|
|
PostTranslation::where('post_id', $poll->post_id)->update(['content' => '<p>The background of this poll.</p>']);
|
|
Livewire::test(PollCardFull::class)
|
|
->assertSeeInOrder(['Read more...', 'Question of Short poll'])
|
|
->assertSee(route('poll.show', ['slug' => $this->slugOf($poll)]), false)
|
|
->assertDontSee('The background of this poll.');
|
|
|
|
// Empty markup from the editor counts as no content
|
|
PostTranslation::where('post_id', $poll->post_id)->update(['content' => '<p><br></p>']);
|
|
Livewire::test(PollCardFull::class)->assertDontSee('Read more');
|
|
}
|
|
|
|
public function test_card_is_empty_for_guests(): void
|
|
{
|
|
Livewire::test(PollCardFull::class)->assertSet('pollId', null);
|
|
}
|
|
|
|
// -----------------------------------------------------------------
|
|
// Closed polls on the post page
|
|
// -----------------------------------------------------------------
|
|
|
|
public function test_closed_poll_shows_results_on_the_post_page_only(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$this->actAs($user, $user);
|
|
$poll = $this->makePoll('Finished poll', [
|
|
'status' => Poll::STATUS_CLOSED,
|
|
'results_visibility' => Poll::RESULTS_AFTER_CLOSE,
|
|
]);
|
|
|
|
Livewire::test(PollForm::class, ['pollId' => $poll->id])
|
|
->assertSet('state', PollForm::STATE_CLOSED)
|
|
->assertSee('This poll is closed.')
|
|
->assertSee('Results')
|
|
->assertDontSee('Submit vote');
|
|
|
|
Livewire::test(PollForm::class, ['pollId' => $poll->id, 'context' => 'card'])
|
|
->assertSet('state', PollForm::STATE_HIDDEN);
|
|
|
|
// Results hidden when the poll never shows them
|
|
$poll->update(['results_visibility' => Poll::RESULTS_NEVER]);
|
|
Livewire::test(PollForm::class, ['pollId' => $poll->id])
|
|
->assertSee('This poll is closed.')
|
|
->assertDontSee('Question of Finished poll');
|
|
|
|
// Profiles the poll was not for see nothing
|
|
$poll->update(['target_profile_types' => [Organization::class]]);
|
|
Livewire::test(PollForm::class, ['pollId' => $poll->id])->assertSet('state', PollForm::STATE_HIDDEN);
|
|
}
|
|
|
|
// -----------------------------------------------------------------
|
|
// Post pages
|
|
// -----------------------------------------------------------------
|
|
|
|
/**
|
|
* Render the poll page through PollController::show. Called directly: localized
|
|
* routes are registered without a locale prefix in tests, so an HTTP request would 404.
|
|
*/
|
|
private function pollPage(string $slug): string
|
|
{
|
|
$view = app(\App\Http\Controllers\PollController::class)->show($slug);
|
|
|
|
return $view->render();
|
|
}
|
|
|
|
private function slugOf(Poll $poll): string
|
|
{
|
|
return PostTranslation::where('post_id', $poll->post_id)->value('slug');
|
|
}
|
|
|
|
public function test_poll_page_is_only_for_logged_in_profiles(): void
|
|
{
|
|
// Same protection as the other member pages: login, verified email, completed
|
|
// registration and accepted principles
|
|
$middleware = app('router')->getRoutes()->getByName('poll.show')->gatherMiddleware();
|
|
foreach (['auth:web', 'verified', 'registration-complete', 'principles-accepted'] as $required) {
|
|
$this->assertContains($required, $middleware);
|
|
}
|
|
|
|
// Safety net in the controller: a guest is sent to the login and the poll is remembered
|
|
$poll = $this->makePoll('Guest poll');
|
|
$slug = $this->slugOf($poll);
|
|
$response = $this->pollResponse($slug);
|
|
$this->assertTrue($response->isRedirect());
|
|
$this->assertStringContainsString('/login', $response->getTargetUrl());
|
|
$this->assertStringEndsWith('/' . $slug, session('url.intended'));
|
|
}
|
|
|
|
public function test_explicit_indexing_never_indexes_a_poll(): void
|
|
{
|
|
// A spy engine records what Scout sends; synchronous so nothing is queued
|
|
$engine = new class extends \Laravel\Scout\Engines\NullEngine {
|
|
public array $updated = [];
|
|
public array $deleted = [];
|
|
public function update($models) { $this->updated = array_merge($this->updated, $models->modelKeys()); }
|
|
public function delete($models) { $this->deleted = array_merge($this->deleted, $models->modelKeys()); }
|
|
};
|
|
app(\Laravel\Scout\EngineManager::class)->extend('spy', fn () => $engine);
|
|
config(['scout.driver' => 'spy', 'scout.queue' => false]);
|
|
|
|
$poll = $this->makePoll('Never indexed');
|
|
$pollPost = \App\Models\Post::find($poll->post_id);
|
|
$plain = \App\Models\Post::withoutSyncingToSearch(fn () => \App\Models\Post::create(['postable_id' => 1, 'postable_type' => Admin::class]));
|
|
|
|
// $post->searchable(), as Polls/Manage, Posts/Manage and the observers call it
|
|
$pollPost->searchable();
|
|
$this->assertNotContains($pollPost->id, $engine->updated);
|
|
$this->assertContains($pollPost->id, $engine->deleted);
|
|
|
|
// A collection with a poll and a normal post: only the normal post is indexed
|
|
$engine->updated = $engine->deleted = [];
|
|
\App\Models\Post::whereKey([$pollPost->id, $plain->id])->get()->searchable();
|
|
$this->assertSame([$plain->id], $engine->updated);
|
|
$this->assertSame([$pollPost->id], $engine->deleted);
|
|
}
|
|
|
|
public function test_poll_posts_stay_out_of_search_and_guest_pdfs(): void
|
|
{
|
|
$poll = $this->makePoll('Hidden poll');
|
|
$post = \App\Models\Post::find($poll->post_id);
|
|
$this->assertFalse($post->shouldBeSearchable());
|
|
|
|
$plain = \App\Models\Post::withoutSyncingToSearch(fn () => \App\Models\Post::create(['postable_id' => 1, 'postable_type' => Admin::class]));
|
|
$this->assertTrue($plain->shouldBeSearchable());
|
|
|
|
// A poll attached to a post of another category counts too
|
|
$other = \App\Models\Post::withoutSyncingToSearch(fn () => \App\Models\Post::create(['postable_id' => 1, 'postable_type' => Admin::class]));
|
|
Poll::create(['post_id' => $other->id, 'status' => Poll::STATUS_OPEN]);
|
|
$this->assertFalse($other->fresh()->shouldBeSearchable());
|
|
|
|
try {
|
|
app(\App\Http\Controllers\PostController::class)->postPdf($poll->post_id);
|
|
$this->fail('A guest got the PDF of a poll post');
|
|
} catch (\Symfony\Component\HttpKernel\Exception\HttpException $e) {
|
|
$this->assertSame(404, $e->getStatusCode());
|
|
}
|
|
}
|
|
|
|
/** Render or redirect the poll page as the current session, with optional query. */
|
|
private function pollResponse(string $slug, string $query = '')
|
|
{
|
|
$request = \Illuminate\Http\Request::create(url('/en/poll/' . $slug . $query));
|
|
$request->setLaravelSession($this->app['session.store']);
|
|
$this->app->instance('request', $request);
|
|
$this->app['url']->setRequest($request);
|
|
|
|
return app(\App\Http\Controllers\PollController::class)->show($slug);
|
|
}
|
|
|
|
public function test_after_login_the_only_eligible_profile_is_switched_to(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$organization = Organization::factory()->create(['name' => 'Switch Target Org']);
|
|
$user->organizations()->attach($organization->id);
|
|
$this->actAs($user, $user);
|
|
$poll = $this->makePoll('Organizations only', ['target_profile_types' => [Organization::class]]);
|
|
$slug = $this->slugOf($poll);
|
|
$direct = route('organization.direct-login', ['organizationId' => $organization->id])
|
|
. '?intended=' . urlencode(url('/en/poll/' . $slug));
|
|
|
|
// A link with ?switch=1 alone does not switch (security audit R2)
|
|
$this->assertFalse($this->pollResponse($slug, '?switch=1') instanceof \Illuminate\Http\RedirectResponse);
|
|
|
|
// Back from the login (one-time marker): straight to the organization's own login link, which returns here
|
|
session(['poll_switch_after_login' => '/en/poll/' . $slug]);
|
|
$response = $this->pollResponse($slug);
|
|
$this->assertTrue($response->isRedirect());
|
|
$this->assertSame($direct, $response->getTargetUrl());
|
|
$this->assertNull(session('poll_switch_after_login')); // used once
|
|
|
|
// A normal visit offers it instead of redirecting
|
|
$html = $this->pollResponse($slug)->render();
|
|
$this->assertStringContainsString('Take part as Switch Target Org', $html);
|
|
$this->assertStringContainsString(e($direct), $html);
|
|
|
|
// As the organization: no switch offered, the form is there
|
|
$this->actAs($organization, $user);
|
|
session(['poll_switch_after_login' => '/en/poll/' . $slug]);
|
|
$html = $this->pollResponse($slug)->render();
|
|
$this->assertStringNotContainsString('Take part as', $html);
|
|
}
|
|
|
|
public function test_several_eligible_profiles_are_offered_not_switched_to(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$organization = Organization::factory()->create(['name' => 'Choice Org']);
|
|
$bank = \App\Models\Bank::factory()->create(['name' => 'Choice Bank']);
|
|
$user->organizations()->attach($organization->id);
|
|
$user->banksManaged()->attach($bank->id);
|
|
$this->actAs($user, $user);
|
|
$poll = $this->makePoll('Orgs and banks', ['target_profile_types' => [Organization::class, \App\Models\Bank::class]]);
|
|
|
|
session(['poll_switch_after_login' => '/en/poll/' . $this->slugOf($poll)]);
|
|
$response = $this->pollResponse($this->slugOf($poll));
|
|
|
|
$html = $response->render();
|
|
$this->assertStringContainsString('Take part as Choice Org', $html);
|
|
$this->assertStringContainsString('Take part as Choice Bank', $html);
|
|
$this->assertStringContainsString(e(route('bank.direct-login', ['bankId' => $bank->id])), $html);
|
|
}
|
|
|
|
public function test_no_switch_without_an_eligible_profile(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$this->actAs($user, $user);
|
|
$poll = $this->makePoll('Banks only', ['target_profile_types' => [\App\Models\Bank::class]]);
|
|
|
|
session(['poll_switch_after_login' => '/en/poll/' . $this->slugOf($poll)]);
|
|
$html = $this->pollResponse($this->slugOf($poll))->render();
|
|
|
|
$this->assertStringNotContainsString('Take part as', $html);
|
|
}
|
|
|
|
public function test_login_response_returns_to_a_poll_page(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$this->actingAs($user, 'web');
|
|
|
|
foreach (['/en/poll/some-poll', '/nl/peiling/een-peiling', '/de/umfrage/x', '/es/encuesta/x', '/fr/sondage/x'] as $path) {
|
|
session(['url.intended' => url($path)]);
|
|
$response = app(\App\Http\Responses\LoginResponse::class)->toResponse(request());
|
|
$this->assertSame(url($path), $response->getTargetUrl(), $path);
|
|
$this->assertSame($path, session()->pull('poll_switch_after_login'), $path);
|
|
}
|
|
|
|
// Other pages still go to the main page
|
|
session(['url.intended' => url('/en/polls/1/results.csv')]);
|
|
$response = app(\App\Http\Responses\LoginResponse::class)->toResponse(request());
|
|
$this->assertNotSame(url('/en/polls/1/results.csv'), $response->getTargetUrl());
|
|
}
|
|
|
|
public function test_poll_page_shows_post_and_form(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$this->actAs($user, $user);
|
|
$poll = $this->makePoll('Dedicated page poll');
|
|
PostTranslation::where('post_id', $poll->post_id)->update(['content' => '<p>Rich <strong>text</strong></p><script>alert(1)</script>']);
|
|
|
|
$html = $this->pollPage($this->slugOf($poll));
|
|
|
|
$this->assertStringContainsString('Dedicated page poll', $html);
|
|
$this->assertStringContainsString('Intro of Dedicated page poll', $html);
|
|
$this->assertStringContainsString('Rich <strong>text</strong>', $html);
|
|
$this->assertStringNotContainsString('<script>alert(1)</script>', $html);
|
|
$this->assertStringContainsString('Question of Dedicated page poll', $html);
|
|
$this->assertStringContainsString('Submit vote', $html);
|
|
}
|
|
|
|
public function test_unpublished_poll_page_is_only_for_post_admins(): void
|
|
{
|
|
$poll = $this->makePoll('Hidden poll', [], null);
|
|
$slug = $this->slugOf($poll);
|
|
|
|
$user = User::factory()->create();
|
|
$this->actAs($user, $user);
|
|
$this->assertStringNotContainsString('Hidden poll', $this->pollPage($slug));
|
|
|
|
$this->actAsAdmin();
|
|
$this->assertStringContainsString('Hidden poll', $this->pollPage($slug));
|
|
}
|
|
|
|
public function test_draft_poll_is_not_found_even_with_a_published_post(): void
|
|
{
|
|
// Published post, poll still a draft (security audit R3)
|
|
$poll = $this->makePoll('Draft with published post', ['status' => Poll::STATUS_DRAFT]);
|
|
$slug = $this->slugOf($poll);
|
|
|
|
$user = User::factory()->create();
|
|
$this->actAs($user, $user);
|
|
$this->assertStringNotContainsString('Draft with published post', $this->pollPage($slug));
|
|
|
|
$this->actAsAdmin();
|
|
$this->assertStringContainsString('Draft with published post', $this->pollPage($slug));
|
|
}
|
|
|
|
public function test_only_admin_profiles_preview_unpublished_polls(): void
|
|
{
|
|
// An organization whose role somehow has poll rights still gets no preview (security audit R4)
|
|
$poll = $this->makePoll('Unpublished for orgs', [], null);
|
|
$user = User::factory()->create();
|
|
$organization = Organization::factory()->create();
|
|
$user->organizations()->attach($organization->id);
|
|
$role = Role::firstOrCreate(['name' => "Organization\\{$organization->id}\\preview-role", 'guard_name' => 'web']);
|
|
$role->givePermissionTo(Permission::firstOrCreate(['name' => 'manage polls', 'guard_name' => 'web']));
|
|
$user->assignRole($role);
|
|
app(PermissionRegistrar::class)->forgetCachedPermissions();
|
|
$this->actAs($organization, $user);
|
|
|
|
$this->assertTrue(\Illuminate\Support\Facades\Gate::allows('access polls')); // the permission is there...
|
|
$this->assertStringNotContainsString('Unpublished for orgs', $this->pollPage($this->slugOf($poll))); // ...but no preview
|
|
}
|
|
|
|
public function test_post_pages_of_a_poll_redirect_to_the_poll_page(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$this->actAs($user, $user);
|
|
$poll = $this->makePoll('Redirected poll');
|
|
$slug = $this->slugOf($poll);
|
|
$posts = app(\App\Http\Controllers\PostController::class);
|
|
|
|
$bySlug = $posts->showBySlug($slug);
|
|
$this->assertInstanceOf(\Illuminate\Http\RedirectResponse::class, $bySlug);
|
|
$this->assertSame(route('poll.show', ['slug' => $slug]), $bySlug->getTargetUrl());
|
|
|
|
$byId = $posts->showById($poll->post_id);
|
|
$this->assertInstanceOf(\Illuminate\Http\RedirectResponse::class, $byId);
|
|
$this->assertSame(route('poll.show', ['slug' => $slug]), $byId->getTargetUrl());
|
|
|
|
// Not a poll: not found on the poll route
|
|
$this->assertStringNotContainsString('Redirected poll', $this->pollPage('no-such-slug'));
|
|
}
|
|
}
|