120 lines
4.7 KiB
PHP
120 lines
4.7 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Http\Livewire\MainPage\CallCardCarousel;
|
|
use App\Http\Livewire\ReactionButton;
|
|
use App\Models\User;
|
|
use Cog\Laravel\Love\ReactionType\Models\ReactionType;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Livewire\Features\SupportLockedProperties\CannotUpdateLockedPropertyException;
|
|
use Livewire\Livewire;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* Livewire lets the browser change any public property that is not #[Locked]. On production a bot sent arrays into
|
|
* ReactionButton and the call carousel (2026-10-19), and ReactionButton's own permission flags could be switched:
|
|
* requiresInteraction off gave a star without a transaction, interactionMethod named any method of the visitor's
|
|
* profile. Those properties are locked now, and the actions re-check everything on the server.
|
|
*/
|
|
class LivewireLockedPropertiesTest extends TestCase
|
|
{
|
|
use DatabaseTransactions;
|
|
|
|
private User $me;
|
|
private User $other;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
// Laravel Love caches reaction types in a static array that survives the rollback of an earlier test
|
|
(new \ReflectionProperty(ReactionType::class, 'nameCache'))->setValue(null, []);
|
|
foreach (['Like' => 1, 'Star' => 1] as $name => $mass) {
|
|
ReactionType::query()->firstOrCreate(['name' => $name], ['mass' => $mass]);
|
|
}
|
|
config([config('app.timebank_config') . '.reactions.star' => ['enabled' => true, 'only_with_interaction' => true, 'interaction' => 'hasTransactionsWith']]);
|
|
config([config('app.timebank_config') . '.reactions.like' => ['enabled' => true, 'only_with_interaction' => false, 'interaction' => 'hasTransactionsWith']]);
|
|
|
|
[$this->me, $this->other] = User::factory()->count(2)->create();
|
|
$this->me->refresh();
|
|
$this->other->refresh();
|
|
$this->actAs($this->me);
|
|
}
|
|
|
|
private function actAs(User $user): void
|
|
{
|
|
$this->actingAs($user, 'web');
|
|
session(['activeProfileType' => User::class, 'activeProfileId' => $user->id, 'active_guard' => 'web']);
|
|
}
|
|
|
|
private function button(string $type)
|
|
{
|
|
return Livewire::test(ReactionButton::class, ['typeName' => $type, 'modelInstance' => $this->other]);
|
|
}
|
|
|
|
private function hasReacted(string $type): bool
|
|
{
|
|
return $this->me->fresh()->viaLoveReacter()->hasReactedTo($this->other->fresh(), $type);
|
|
}
|
|
|
|
public function test_the_permission_flags_cannot_be_changed_from_the_browser(): void
|
|
{
|
|
foreach (['requiresInteraction' => false, 'isEnabled' => true, 'canReact' => true, 'interactionMethod' => 'delete', 'typeName' => ['x'], 'reactedByReactor' => false] as $property => $value) {
|
|
try {
|
|
$this->button('star')->set($property, $value);
|
|
$this->fail("{$property} could be set from the browser");
|
|
} catch (CannotUpdateLockedPropertyException) {
|
|
$this->addToAssertionCount(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function test_the_confirmation_dialog_stays_settable(): void
|
|
{
|
|
$this->button('like')->set('showConfirmModal', true)->assertSet('showConfirmModal', true);
|
|
}
|
|
|
|
public function test_a_star_needs_a_transaction_also_when_the_action_is_called_directly(): void
|
|
{
|
|
$this->button('star')->assertSet('canReact', false)->call('confirmReaction');
|
|
|
|
$this->assertFalse($this->hasReacted('star'));
|
|
}
|
|
|
|
public function test_a_like_works_once(): void
|
|
{
|
|
$component = $this->button('like')->call('confirmReaction')->call('confirmReaction');
|
|
|
|
$this->assertTrue($this->hasReacted('like'));
|
|
$component->assertSet('count', 1);
|
|
$this->assertSame(1, DB::table('love_reactions')->where('reactant_id', $this->other->fresh()->love_reactant_id)->count());
|
|
}
|
|
|
|
public function test_an_unknown_reaction_type_becomes_a_like(): void
|
|
{
|
|
Livewire::test(ReactionButton::class, ['typeName' => 'no-such-type', 'modelInstance' => $this->other])
|
|
->assertSet('typeName', 'like')
|
|
->assertStatus(200);
|
|
}
|
|
|
|
public function test_a_profile_switch_in_another_tab_is_refused(): void
|
|
{
|
|
$component = $this->button('like');
|
|
$third = User::factory()->create();
|
|
session(['activeProfileId' => $third->id]);
|
|
|
|
$component->call('confirmReaction');
|
|
|
|
$this->assertFalse($this->hasReacted('like'));
|
|
}
|
|
|
|
public function test_the_call_carousel_cannot_be_changed_from_the_browser(): void
|
|
{
|
|
$this->expectException(CannotUpdateLockedPropertyException::class);
|
|
|
|
Livewire::test(CallCardCarousel::class, ['related' => false])->assertStatus(200)->set('calls', [1, 2]);
|
|
}
|
|
}
|