Snapshot of main at 9f39a6e1e (2026-11-02)
This commit is contained in:
commit
6ccf1792d7
2580 files changed
+1040202
No files matched your search
@@ -0,0 +1,119 @@
|
||||
<?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]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user