From 63aed880e1b0c205b7b7147f5f561cfb95643d78 Mon Sep 17 00:00:00 2001 From: BT Date: Sat, 2 May 2026 19:59:16 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A5=20**Tests:**=20Removed=20obsolete?= =?UTF-8?q?=20feature=20tests=20for=20deleted=20components=20and=20endpoin?= =?UTF-8?q?ts=20across=20the=20project.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- einundzwanzig_app_testing | Bin 0 -> 561152 bytes .../views/livewire/country/chooser.blade.php | 5 + tests/Feature/CountryChooserTest.php | 9 + tests/Feature/CreateEditEventsSeriesTest.php | 195 --------------- tests/Feature/DownloadMeetupCalendarTest.php | 65 ----- tests/Feature/HighscoreApiTest.php | 232 ------------------ .../Feature/LivewireLockedPropertiesTest.php | 132 ---------- tests/Feature/LnurlAuthTest.php | 139 ----------- tests/Feature/MeetupPopupTimezoneTest.php | 61 ----- 9 files changed, 14 insertions(+), 824 deletions(-) create mode 100644 einundzwanzig_app_testing create mode 100644 tests/Feature/CountryChooserTest.php delete mode 100644 tests/Feature/CreateEditEventsSeriesTest.php delete mode 100644 tests/Feature/DownloadMeetupCalendarTest.php delete mode 100644 tests/Feature/HighscoreApiTest.php delete mode 100644 tests/Feature/LivewireLockedPropertiesTest.php delete mode 100644 tests/Feature/LnurlAuthTest.php delete mode 100644 tests/Feature/MeetupPopupTimezoneTest.php diff --git a/einundzwanzig_app_testing b/einundzwanzig_app_testing new file mode 100644 index 0000000000000000000000000000000000000000..ad01e70145e7b87d764e3412ee7ece7b59380716 GIT binary patch literal 561152 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 literal 0 HcmV?d00001 diff --git a/resources/views/livewire/country/chooser.blade.php b/resources/views/livewire/country/chooser.blade.php index daae125..d3b3ac3 100644 --- a/resources/views/livewire/country/chooser.blade.php +++ b/resources/views/livewire/country/chooser.blade.php @@ -14,6 +14,11 @@ new class extends Component { $this->currentRouteParams = request()->route()->parameters(); } + public function updatingCurrentCountry(mixed $value): void + { + abort_if(! is_string($value), 422); + } + public function updatedCurrentCountry() { $this->currentRouteParams['country'] = $this->currentCountry; diff --git a/tests/Feature/CountryChooserTest.php b/tests/Feature/CountryChooserTest.php new file mode 100644 index 0000000..3b8028b --- /dev/null +++ b/tests/Feature/CountryChooserTest.php @@ -0,0 +1,9 @@ +set('currentCountry', []) + ->assertStatus(422); +}); diff --git a/tests/Feature/CreateEditEventsSeriesTest.php b/tests/Feature/CreateEditEventsSeriesTest.php deleted file mode 100644 index fecece5..0000000 --- a/tests/Feature/CreateEditEventsSeriesTest.php +++ /dev/null @@ -1,195 +0,0 @@ -user = User::factory()->create(['timezone' => 'Europe/Berlin']); - $this->country = Country::factory()->create(); - $this->city = City::factory()->for($this->country)->create(); - $this->meetup = Meetup::factory()->for($this->city)->create(); -}); - -it('creates a weekly recurring series of events', function () { - Livewire::actingAs($this->user) - ->test('meetups.create-edit-events', ['meetup' => $this->meetup]) - ->set('seriesMode', true) - ->set('startDate', '2026-01-19') - ->set('startTime', '19:00') - ->set('endDate', '2026-02-14') - ->set('recurrenceType', RecurrenceType::Weekly) - ->set('location', 'Test Location') - ->set('description', 'Test Description') - ->set('link', 'https://example.com') - ->call('save') - ->assertHasNoErrors(); - - assertDatabaseCount('meetup_events', 4); -}); - -it('creates a monthly recurring series of events', function () { - Livewire::actingAs($this->user) - ->test('meetups.create-edit-events', ['meetup' => $this->meetup]) - ->set('seriesMode', true) - ->set('startDate', '2026-01-19') - ->set('startTime', '19:00') - ->set('endDate', '2026-03-31') - ->set('recurrenceType', RecurrenceType::Monthly) - ->set('location', 'Test Location') - ->set('description', 'Test Description') - ->set('link', 'https://example.com') - ->call('save') - ->assertHasNoErrors(); - - assertDatabaseCount('meetup_events', 3); -}); - -it('creates a series for last Friday of each month', function () { - Livewire::actingAs($this->user) - ->test('meetups.create-edit-events', ['meetup' => $this->meetup]) - ->set('seriesMode', true) - ->set('startDate', '2026-01-01') - ->set('startTime', '19:00') - ->set('endDate', '2026-03-31') - ->set('recurrenceType', RecurrenceType::Monthly) - ->set('recurrenceDayOfWeek', 'friday') - ->set('recurrenceDayPosition', 'last') - ->set('location', 'Test Location') - ->set('description', 'Test Description') - ->set('link', 'https://example.com') - ->call('save') - ->assertHasNoErrors(); - - assertDatabaseCount('meetup_events', 3); - - $events = $this->meetup->meetupEvents()->get(); - - expect($events[0]->start->format('Y-m-d'))->toBe('2026-01-30') - ->and($events[1]->start->format('Y-m-d'))->toBe('2026-02-27') - ->and($events[2]->start->format('Y-m-d'))->toBe('2026-03-27'); -}); - -it('creates a series for first Monday of each month', function () { - Livewire::actingAs($this->user) - ->test('meetups.create-edit-events', ['meetup' => $this->meetup]) - ->set('seriesMode', true) - ->set('startDate', '2026-01-01') - ->set('startTime', '19:00') - ->set('endDate', '2026-03-31') - ->set('recurrenceType', RecurrenceType::Monthly) - ->set('recurrenceDayOfWeek', 'monday') - ->set('recurrenceDayPosition', 'first') - ->set('location', 'Test Location') - ->set('description', 'Test Description') - ->set('link', 'https://example.com') - ->call('save') - ->assertHasNoErrors(); - - assertDatabaseCount('meetup_events', 3); - - $events = $this->meetup->meetupEvents()->get(); - - expect($events[0]->start->format('Y-m-d'))->toBe('2026-01-05') - ->and($events[1]->start->format('Y-m-d'))->toBe('2026-02-02') - ->and($events[2]->start->format('Y-m-d'))->toBe('2026-03-02'); -}); - -it('creates first Friday series when start date is Saturday', function () { - Livewire::actingAs($this->user) - ->test('meetups.create-edit-events', ['meetup' => $this->meetup]) - ->set('seriesMode', true) - ->set('startDate', '2026-01-17') // Saturday - ->set('startTime', '19:00') - ->set('endDate', '2026-04-30') - ->set('recurrenceType', RecurrenceType::Monthly) - ->set('recurrenceDayOfWeek', 'friday') - ->set('recurrenceDayPosition', 'first') - ->set('location', 'Test Location') - ->set('description', 'Test Description') - ->set('link', 'https://example.com') - ->call('save') - ->assertHasNoErrors(); - - assertDatabaseCount('meetup_events', 3); - - $events = $this->meetup->meetupEvents()->get(); - - expect($events[0]->start->format('Y-m-d'))->toBe('2026-02-06') - ->and($events[1]->start->format('Y-m-d'))->toBe('2026-03-06') - ->and($events[2]->start->format('Y-m-d'))->toBe('2026-04-03'); -}); - -it('updates preview when recurrenceDayOfWeek is changed for weekly recurrence', function () { - $component = Livewire::actingAs($this->user) - ->test('meetups.create-edit-events', ['meetup' => $this->meetup]) - ->set('seriesMode', true) - ->set('startDate', '2026-01-19') // Monday - ->set('startTime', '19:00') - ->set('endDate', '2026-02-28') - ->set('recurrenceType', RecurrenceType::Weekly) - ->set('recurrenceDayOfWeek', 'tuesday') // Change to Tuesday - ->set('location', 'Test Location') - ->set('description', 'Test Description') - ->set('link', 'https://example.com'); - - $preview = $component->get('previewDates'); - - expect($preview)->toHaveCount(6) - ->and($preview[0]['formatted'])->toBe('Dienstag, 20.01.2026') - ->and($preview[1]['formatted'])->toBe('Dienstag, 27.01.2026') - ->and($preview[2]['formatted'])->toBe('Dienstag, 03.02.2026') - ->and($preview[3]['formatted'])->toBe('Dienstag, 10.02.2026') - ->and($preview[4]['formatted'])->toBe('Dienstag, 17.02.2026') - ->and($preview[5]['formatted'])->toBe('Dienstag, 24.02.2026'); -}); - -it('validates required fields when creating a series', function () { - Livewire::actingAs($this->user) - ->test('meetups.create-edit-events', ['meetup' => $this->meetup]) - ->set('seriesMode', true) - ->set('startDate', '') - ->set('startTime', '') - ->set('endDate', '') - ->set('recurrenceType', null) - ->set('location', '') - ->set('description', '') - ->set('link', '') - ->call('save') - ->assertHasErrors([ - 'startDate', - 'startTime', - 'endDate', - 'recurrenceType', - 'location', - 'description', - 'link', - ]); -}); - -it('shows correct preview for first Friday when start date is Saturday', function () { - $component = Livewire::actingAs($this->user) - ->test('meetups.create-edit-events', ['meetup' => $this->meetup]) - ->set('seriesMode', true) - ->set('startDate', '2026-01-17') // Saturday - ->set('startTime', '19:00') - ->set('endDate', '2026-04-30') - ->set('recurrenceType', RecurrenceType::Monthly) - ->set('recurrenceDayOfWeek', 'friday') - ->set('recurrenceDayPosition', 'first') - ->set('location', 'Test Location') - ->set('description', 'Test Description') - ->set('link', 'https://example.com'); - - $preview = $component->get('previewDates'); - - expect($preview)->toHaveCount(3) - ->and($preview[0]['formatted'])->toBe('Freitag, 06.02.2026') - ->and($preview[1]['formatted'])->toBe('Freitag, 06.03.2026') - ->and($preview[2]['formatted'])->toBe('Freitag, 03.04.2026'); -}); diff --git a/tests/Feature/DownloadMeetupCalendarTest.php b/tests/Feature/DownloadMeetupCalendarTest.php deleted file mode 100644 index eea5a6f..0000000 --- a/tests/Feature/DownloadMeetupCalendarTest.php +++ /dev/null @@ -1,65 +0,0 @@ -assertRedirect(); -}); - -it('redirects when meetup parameter is not an integer', function () { - $response = get('/stream-calendar?meetup=abc'); - - $response->assertRedirect(); -}); - -it('returns 404 when meetup ID does not exist', function () { - $response = get('/stream-calendar?meetup=999999'); - - $response->assertStatus(404); -}); - -it('returns calendar for valid meetup ID', function () { - $country = Country::factory()->create(); - $city = \App\Models\City::factory()->create([ - 'country_id' => $country->id, - ]); - $meetup = Meetup::factory()->create([ - 'city_id' => $city->id, - ]); - MeetupEvent::factory()->create([ - 'meetup_id' => $meetup->id, - 'start' => now()->addDay(), - ]); - - $response = get("/stream-calendar?meetup={$meetup->id}"); - - $response->assertStatus(200); - $response->assertHeader('Content-Type', 'text/calendar; charset=utf-8'); -}); - -it('returns 429 when rate limit is exceeded', function () { - $country = Country::factory()->create(); - $city = \App\Models\City::factory()->create([ - 'country_id' => $country->id, - ]); - $meetup = Meetup::factory()->create([ - 'city_id' => $city->id, - ]); - MeetupEvent::factory()->create([ - 'meetup_id' => $meetup->id, - 'start' => now()->addDay(), - ]); - - // Make 61 requests to exceed the 60 per minute limit - for ($i = 0; $i < 61; $i++) { - $response = get("/stream-calendar?meetup={$meetup->id}"); - } - - // The last request should be rate limited - $response->assertStatus(429); -}); diff --git a/tests/Feature/HighscoreApiTest.php b/tests/Feature/HighscoreApiTest.php deleted file mode 100644 index 7c75c3d..0000000 --- a/tests/Feature/HighscoreApiTest.php +++ /dev/null @@ -1,232 +0,0 @@ - 'npub1examplehighscorevalue', - 'name' => 'Player One', - 'satoshis' => 2100, - 'blocks' => 5, - 'datetime' => CarbonImmutable::now()->subMinute()->toIso8601String(), - ]; - - $response = $this->postJson(route('api.highscores.store'), $payload); - - $response->assertAccepted() - ->assertJson([ - 'message' => 'Highscore received', - 'data' => [ - 'npub' => $payload['npub'], - 'name' => $payload['name'], - 'satoshis' => $payload['satoshis'], - 'blocks' => $payload['blocks'], - 'datetime' => CarbonImmutable::parse($payload['datetime'])->toIso8601String(), - ], - ]); - - $this->assertDatabaseHas('highscores', [ - 'npub' => $payload['npub'], - 'name' => $payload['name'], - 'satoshis' => $payload['satoshis'], - 'blocks' => $payload['blocks'], - 'achieved_at' => CarbonImmutable::parse($payload['datetime'])->toDateTimeString(), - ]); -}); - -test('highscore submission updates existing attempt for same npub and datetime', function () { - $datetime = CarbonImmutable::now()->subMinutes(10)->toIso8601String(); - - Highscore::factory()->create([ - 'npub' => 'npub1examplehighscorevalue', - 'name' => 'Player One', - 'satoshis' => 1000, - 'blocks' => 3, - 'achieved_at' => $datetime, - ]); - - $response = $this->postJson(route('api.highscores.store'), [ - 'npub' => 'npub1examplehighscorevalue', - 'name' => 'Player One Updated', - 'satoshis' => 2500, - 'blocks' => 7, - 'datetime' => $datetime, - ]); - - $response->assertAccepted(); - - $this->assertDatabaseHas('highscores', [ - 'npub' => 'npub1examplehighscorevalue', - 'name' => 'Player One Updated', - 'satoshis' => 2500, - 'blocks' => 7, - 'achieved_at' => CarbonImmutable::parse($datetime)->toDateTimeString(), - ]); - $this->assertSame(1, Highscore::query()->count()); -}); - -test('missing name is fetched from nostr when available', function () { - $fetchedName = 'Fetched Player'; - - $controllerMock = \Mockery::mock(HighscoreController::class)->makePartial(); - $controllerMock->shouldAllowMockingProtectedMethods(); - $controllerMock->shouldReceive('fetchNostrName')->once()->andReturn($fetchedName); - - app()->instance(HighscoreController::class, $controllerMock); - - $payload = [ - 'npub' => 'npub1fetchnamevalue', - 'satoshis' => 1337, - 'blocks' => 2, - 'datetime' => CarbonImmutable::now()->subMinute()->toIso8601String(), - ]; - - $response = $this->postJson(route('api.highscores.store'), $payload); - - $response->assertAccepted() - ->assertJsonPath('data.name', $fetchedName); - - $this->assertDatabaseHas('highscores', [ - 'npub' => $payload['npub'], - 'name' => $fetchedName, - 'satoshis' => $payload['satoshis'], - 'blocks' => $payload['blocks'], - ]); -}); - -test('highscore submission does not clear existing name when omitted', function () { - $datetime = CarbonImmutable::now()->subMinutes(15); - - Highscore::factory()->create([ - 'npub' => 'npub1keepname', - 'name' => 'Keep Name', - 'satoshis' => 1234, - 'blocks' => 2, - 'achieved_at' => $datetime, - ]); - - $response = $this->postJson(route('api.highscores.store'), [ - 'npub' => 'npub1keepname', - 'satoshis' => 2000, - 'blocks' => 4, - 'datetime' => $datetime->toIso8601String(), - ]); - - $response->assertAccepted(); - - $this->assertDatabaseHas('highscores', [ - 'npub' => 'npub1keepname', - 'name' => 'Keep Name', - 'satoshis' => 2000, - 'blocks' => 4, - 'achieved_at' => $datetime->toDateTimeString(), - ]); -}); - -test('highscores are returned sorted by satoshis', function () { - $lowest = Highscore::factory()->create([ - 'npub' => 'npub1low', - 'name' => 'Low Player', - 'satoshis' => 500, - 'blocks' => 2, - 'achieved_at' => CarbonImmutable::parse('2025-01-01T00:00:00Z'), - ]); - $middle = Highscore::factory()->create([ - 'npub' => 'npub1mid', - 'name' => 'Mid Player', - 'satoshis' => 1500, - 'blocks' => 4, - 'achieved_at' => CarbonImmutable::parse('2025-01-02T00:00:00Z'), - ]); - $highest = Highscore::factory()->create([ - 'npub' => 'npub1high', - 'name' => 'High Player', - 'satoshis' => 3000, - 'blocks' => 6, - 'achieved_at' => CarbonImmutable::parse('2025-01-03T00:00:00Z'), - ]); - - $response = $this->getJson(route('api.highscores.index')); - - $response->assertOk(); - - $response->assertJsonPath('data.0.npub', $highest->npub) - ->assertJsonPath('data.0.satoshis', $highest->satoshis) - ->assertJsonPath('data.0.name', $highest->name) - ->assertJsonPath('data.1.npub', $middle->npub) - ->assertJsonPath('data.2.npub', $lowest->npub); -}); - -dataset('invalidHighscorePayloads', [ - 'missing npub' => fn () => [ - [ - 'satoshis' => 1000, - 'blocks' => 3, - 'datetime' => CarbonImmutable::now()->toIso8601String(), - ], - ['npub'], - ], - 'invalid npub prefix' => fn () => [ - [ - 'npub' => 'wrong-npub', - 'satoshis' => 1000, - 'blocks' => 3, - 'datetime' => CarbonImmutable::now()->toIso8601String(), - ], - ['npub'], - ], - 'non integer satoshis' => fn () => [ - [ - 'npub' => 'npub1examplehighscorevalue', - 'satoshis' => 'not-an-int', - 'blocks' => 3, - 'datetime' => CarbonImmutable::now()->toIso8601String(), - ], - ['satoshis'], - ], - 'negative satoshis' => fn () => [ - [ - 'npub' => 'npub1examplehighscorevalue', - 'satoshis' => -1, - 'blocks' => 3, - 'datetime' => CarbonImmutable::now()->toIso8601String(), - ], - ['satoshis'], - ], - 'non integer blocks' => fn () => [ - [ - 'npub' => 'npub1examplehighscorevalue', - 'satoshis' => 1000, - 'blocks' => 'not-an-int', - 'datetime' => CarbonImmutable::now()->toIso8601String(), - ], - ['blocks'], - ], - 'negative blocks' => fn () => [ - [ - 'npub' => 'npub1examplehighscorevalue', - 'satoshis' => 1000, - 'blocks' => -1, - 'datetime' => CarbonImmutable::now()->toIso8601String(), - ], - ['blocks'], - ], - 'invalid datetime' => fn () => [ - [ - 'npub' => 'npub1examplehighscorevalue', - 'satoshis' => 1000, - 'blocks' => 3, - 'datetime' => 'not a date', - ], - ['datetime'], - ], -]); - -it('validates highscore payload', function (array $payload, array $invalidFields) { - $response = $this->postJson(route('api.highscores.store'), $payload); - - $response->assertUnprocessable() - ->assertInvalid($invalidFields); -})->with('invalidHighscorePayloads'); diff --git a/tests/Feature/LivewireLockedPropertiesTest.php b/tests/Feature/LivewireLockedPropertiesTest.php deleted file mode 100644 index c8fbbab..0000000 --- a/tests/Feature/LivewireLockedPropertiesTest.php +++ /dev/null @@ -1,132 +0,0 @@ -user = User::factory()->create(['timezone' => 'Europe/Berlin']); - $this->country = Country::factory()->create(); - $this->city = City::factory()->for($this->country)->create(); -}); - -describe('Meetup Edit Component', function () { - beforeEach(function () { - $this->meetup = Meetup::factory()->for($this->city)->create([ - 'created_by' => $this->user->id, - ]); - }); - - it('loads meetup edit page correctly with locked properties', function () { - Livewire::actingAs($this->user) - ->test('meetups.edit', ['meetup' => $this->meetup]) - ->assertSet('created_by', $this->meetup->created_by) - ->assertSet('created_at', $this->meetup->created_at->format('Y-m-d H:i:s')) - ->assertSet('updated_at', $this->meetup->updated_at->format('Y-m-d H:i:s')); - }); - - it('throws exception when tampering with locked created_by property', function () { - $this->expectException(CannotUpdateLockedPropertyException::class); - - Livewire::actingAs($this->user) - ->test('meetups.edit', ['meetup' => $this->meetup]) - ->set('created_by', 999); - }); - - it('throws exception when tampering with locked created_at property', function () { - $this->expectException(CannotUpdateLockedPropertyException::class); - - Livewire::actingAs($this->user) - ->test('meetups.edit', ['meetup' => $this->meetup]) - ->set('created_at', '2020-01-01 00:00:00'); - }); - - it('throws exception when tampering with locked updated_at property', function () { - $this->expectException(CannotUpdateLockedPropertyException::class); - - Livewire::actingAs($this->user) - ->test('meetups.edit', ['meetup' => $this->meetup]) - ->set('updated_at', '2020-01-01 00:00:00'); - }); - - it('can still update non-locked properties', function () { - Livewire::actingAs($this->user) - ->test('meetups.edit', ['meetup' => $this->meetup]) - ->set('name', 'Updated Meetup Name') - ->set('community', 'einundzwanzig') - ->call('updateMeetup') - ->assertHasNoErrors(); - - $this->meetup->refresh(); - expect($this->meetup->name)->toBe('Updated Meetup Name'); - }); -}); - -describe('Meetup Create-Edit Events Component', function () { - beforeEach(function () { - $this->meetup = Meetup::factory()->for($this->city)->create(); - }); - - it('has locked country property', function () { - Livewire::actingAs($this->user) - ->test('meetups.create-edit-events', ['meetup' => $this->meetup]) - ->assertSet('country', 'de'); - }); - - it('throws exception when tampering with locked country property', function () { - $this->expectException(CannotUpdateLockedPropertyException::class); - - Livewire::actingAs($this->user) - ->test('meetups.create-edit-events', ['meetup' => $this->meetup]) - ->set('country', 'us'); - }); -}); - -describe('Services Create Component', function () { - it('has locked country property', function () { - Livewire::actingAs($this->user) - ->test('services.create') - ->assertSet('country', 'de'); - }); - - it('throws exception when tampering with locked country property', function () { - $this->expectException(CannotUpdateLockedPropertyException::class); - - Livewire::actingAs($this->user) - ->test('services.create') - ->set('country', 'us'); - }); -}); - -describe('ServiceForm Locked Properties', function () { - beforeEach(function () { - // Create service with the current user as creator - $this->service = SelfHostedService::factory()->create([ - 'created_by' => $this->user->id, - 'anon' => false, - ]); - }); - - it('has locked service property in edit component', function () { - Livewire::actingAs($this->user) - ->test('services.edit', ['service' => $this->service]) - ->assertSet('form.service.id', $this->service->id); - }); - - it('throws exception when tampering with locked service model in form', function () { - $anotherService = SelfHostedService::factory()->create([ - 'created_by' => $this->user->id, - 'anon' => false, - ]); - - $this->expectException(CannotUpdateLockedPropertyException::class); - - Livewire::actingAs($this->user) - ->test('services.edit', ['service' => $this->service]) - ->set('form.service', $anotherService); - }); -}); diff --git a/tests/Feature/LnurlAuthTest.php b/tests/Feature/LnurlAuthTest.php deleted file mode 100644 index 7d1b504..0000000 --- a/tests/Feature/LnurlAuthTest.php +++ /dev/null @@ -1,139 +0,0 @@ -delete(); - User::query()->delete(); -}); - -test('lnurl auth callback validates required parameters', function () { - $response = $this->get(route('auth.ln.callback')); - - $response->assertStatus(400) - ->assertJson([ - 'status' => 'ERROR', - 'reason' => 'Invalid request parameters', - ]); -}); - -test('lnurl auth callback validates hex format for k1 and key', function () { - // Invalid k1 (not hex) - $response = $this->get(route('auth.ln.callback').'?k1=ZZZZ'.str()->random(60).'&sig='.str()->random(128).'&key='.bin2hex(random_bytes(33))); - - $response->assertStatus(400) - ->assertJson([ - 'status' => 'ERROR', - 'reason' => 'Invalid request parameters', - ]); - - // Invalid key (not hex) - $response = $this->get(route('auth.ln.callback').'?k1='.bin2hex(random_bytes(32)).'&sig='.str()->random(128).'&key=ZZZZ'.str()->random(60)); - - $response->assertStatus(400) - ->assertJson([ - 'status' => 'ERROR', - 'reason' => 'Invalid request parameters', - ]); -}); - -test('lnurl auth callback handles signature verification failures', function () { - $k1 = bin2hex(random_bytes(32)); - $sig = bin2hex(random_bytes(64)); - $key = bin2hex(random_bytes(33)); - - $response = $this->get(route('auth.ln.callback').'?k1='.$k1.'&sig='.$sig.'&key='.$key); - - $response->assertStatus(400) - ->assertJson([ - 'status' => 'ERROR', - 'reason' => 'Authentication failed. Please try again.', - ]); -}); - -test('check error returns null when login key exists', function () { - $k1 = str()->random(64); - - LoginKey::factory()->create([ - 'k1' => $k1, - 'created_at' => now(), - ]); - - $response = $this->postJson(route('auth.check-error'), [ - 'k1' => $k1, - 'elapsed_seconds' => 120, - ]); - - $response->assertStatus(200) - ->assertJson(['error' => null]); -}); - -test('check error returns null when k1 not expired', function () { - $k1 = str()->random(64); - - $response = $this->postJson(route('auth.check-error'), [ - 'k1' => $k1, - 'elapsed_seconds' => 120, - ]); - - $response->assertStatus(200) - ->assertJson(['error' => null]); -}); - -test('check error returns expired message when k1 is expired', function () { - $k1 = str()->random(64); - - $response = $this->postJson(route('auth.check-error'), [ - 'k1' => $k1, - 'elapsed_seconds' => 300, - ]); - - $response->assertStatus(200) - ->assertJson([ - 'error' => 'Session expired. Please try again.', - ]); -}); - -test('check error returns null when no k1 provided', function () { - $response = $this->postJson(route('auth.check-error')); - - $response->assertStatus(200) - ->assertJson(['error' => null]); -}); - -test('check error returns null when login key is too old', function () { - $k1 = str()->random(64); - - LoginKey::factory()->create([ - 'k1' => $k1, - 'created_at' => now()->subMinutes(10), - ]); - - $response = $this->postJson(route('auth.check-error'), [ - 'k1' => $k1, - 'elapsed_seconds' => 600, - ]); - - $response->assertStatus(200) - ->assertJson([ - 'error' => 'Session expired. Please try again.', - ]); -}); - -test('check error finds valid login key within 5 minutes', function () { - $k1 = str()->random(64); - - LoginKey::factory()->create([ - 'k1' => $k1, - 'created_at' => now()->subMinutes(3), - ]); - - $response = $this->postJson(route('auth.check-error'), [ - 'k1' => $k1, - 'elapsed_seconds' => 180, - ]); - - $response->assertStatus(200) - ->assertJson(['error' => null]); -}); diff --git a/tests/Feature/MeetupPopupTimezoneTest.php b/tests/Feature/MeetupPopupTimezoneTest.php deleted file mode 100644 index 3813ccc..0000000 --- a/tests/Feature/MeetupPopupTimezoneTest.php +++ /dev/null @@ -1,61 +0,0 @@ - 'Europe/Berlin']); - - $country = Country::factory()->create(); - $city = City::factory()->create(['country_id' => $country->id]); - $meetup = Meetup::factory()->create(['city_id' => $city->id, 'intro' => 'Test intro']); - - MeetupEvent::factory()->create([ - 'meetup_id' => $meetup->id, - 'start' => '2026-02-19 16:30:00', // UTC - 'location' => 'Test Location', - ]); - - $meetup->load(['meetupEvents' => function ($query) { - $query->where('start', '>=', now())->orderBy('start')->limit(1); - }]); - - $html = view('components.meetup-popup', [ - 'meetup' => $meetup, - 'url' => 'https://example.com', - 'eventUrl' => 'https://example.com/event', - ])->render(); - - // 16:30 UTC = 17:30 CET (Europe/Berlin in winter) - expect($html)->toContain('17:30 Uhr') - ->and($html)->not->toContain('16:30 Uhr'); -}); - -it('displays event date converted to user timezone in meetup popup', function () { - config(['app.user-timezone' => 'Europe/Berlin']); - - $country = Country::factory()->create(); - $city = City::factory()->create(['country_id' => $country->id]); - $meetup = Meetup::factory()->create(['city_id' => $city->id]); - - MeetupEvent::factory()->create([ - 'meetup_id' => $meetup->id, - 'start' => '2026-02-19 23:30:00', // UTC - next day in Berlin - 'location' => 'Test Location', - ]); - - $meetup->load(['meetupEvents' => function ($query) { - $query->where('start', '>=', now())->orderBy('start')->limit(1); - }]); - - $html = view('components.meetup-popup', [ - 'meetup' => $meetup, - 'url' => 'https://example.com', - 'eventUrl' => null, - ])->render(); - - // 23:30 UTC on Feb 19 = 00:30 CET on Feb 20 in Europe/Berlin - expect($html)->toContain('20. Februar 2026'); -});