getJson('/api/members/2024')->assertSuccessful(); } $this->getJson('/api/members/2024')->assertStatus(429); }); test('api routes include rate limit headers', function () { $response = $this->getJson('/api/members/2024'); $response->assertSuccessful(); $response->assertHeader('X-RateLimit-Limit', 60); $response->assertHeader('X-RateLimit-Remaining'); }); test('nostr profile api route is rate limited', function () { for ($i = 0; $i < 60; $i++) { $this->getJson('/api/nostr/profile/testkey'.$i); } $this->getJson('/api/nostr/profile/testkey')->assertStatus(429); }); test('voting actions are rate limited after 10 attempts', function () { $pleb = EinundzwanzigPleb::factory()->create(); $project = ProjectProposal::factory()->create(); NostrAuth::login($pleb->pubkey); for ($i = 0; $i < 10; $i++) { RateLimiter::attempt('voting:127.0.0.1', 10, function () {}); } Livewire::test('association.project-support.show', ['projectProposal' => $project->slug]) ->call('handleApprove') ->assertStatus(429); }); test('nostr login is rate limited after 10 attempts', function () { $pleb = EinundzwanzigPleb::factory()->create(); for ($i = 0; $i < 10; $i++) { RateLimiter::attempt('nostr-login:127.0.0.1', 10, function () {}); } Livewire::test('association.project-support.index') ->call('handleNostrLogin', $pleb->pubkey) ->assertStatus(429); }); test('project proposal creation is rate limited after 5 attempts', function () { $pleb = EinundzwanzigPleb::factory()->active()->withPaidCurrentYear()->create(); NostrAuth::login($pleb->pubkey); for ($i = 0; $i < 5; $i++) { RateLimiter::attempt('project-proposal-create:127.0.0.1', 5, function () {}); } Livewire::test('association.project-support.form.create') ->set('form.name', 'Test Project') ->set('form.description', 'Test Description') ->set('form.support_in_sats', 21000) ->set('form.website', 'https://example.com') ->call('save') ->assertStatus(429); }); test('project proposal update is rate limited after 5 attempts', function () { $pleb = EinundzwanzigPleb::factory()->create(); $project = ProjectProposal::factory()->create([ 'einundzwanzig_pleb_id' => $pleb->id, ]); NostrAuth::login($pleb->pubkey); for ($i = 0; $i < 5; $i++) { RateLimiter::attempt('project-proposal-update:127.0.0.1', 5, function () {}); } Livewire::test('association.project-support.form.edit', ['projectProposal' => $project->slug]) ->set('form.name', 'Updated Name') ->call('update') ->assertStatus(429); }); test('voting works within rate limit', function () { $pleb = EinundzwanzigPleb::factory()->create(); $project = ProjectProposal::factory()->create(); NostrAuth::login($pleb->pubkey); Livewire::test('association.project-support.show', ['projectProposal' => $project->slug]) ->call('handleApprove') ->assertHasNoErrors(); $vote = \App\Models\Vote::query() ->where('project_proposal_id', $project->id) ->where('einundzwanzig_pleb_id', $pleb->id) ->first(); expect($vote)->not->toBeNull() ->and($vote->value)->toBeTrue(); });