🔥 **Cleanup & Tests:** Removed the obsolete auth.register component and its related route, feature tests, and browser tests. Disabled public registration and added tests to ensure /register returns a 404. Added new tests for service, lecturer, city, venue, and meetup CRUD flows.

This commit is contained in:
BT
2026-05-03 20:09:07 +02:00
parent a4cbb10604
commit a363c99453
13 changed files with 344 additions and 120 deletions
+1
View File
@@ -0,0 +1 @@
{"sessionId":"95f2d618-b0a9-4b52-b725-3a049ae47e93","pid":146659,"procStart":"1263491","acquiredAt":1777828038888}
@@ -1,104 +0,0 @@
<?php
use App\Models\User;
use App\Traits\SeoTrait;
use Illuminate\Auth\Events\Registered;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules;
use Livewire\Attributes\Layout;
use Livewire\Component;
new #[Layout('components.layouts.auth')]
class extends Component {
use SeoTrait;
public string $name = '';
public string $email = '';
public string $password = '';
public string $password_confirmation = '';
/**
* Handle an incoming registration request.
*/
public function register(): void
{
$validated = $this->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
'password' => ['required', 'string', 'confirmed', Rules\Password::defaults()],
]);
$validated['password'] = Hash::make($validated['password']);
event(new Registered(($user = User::create($validated))));
Auth::login($user);
$this->redirectIntended(route('dashboard', ['country' => str(session('lang_country', config('app.domain_country')))->after('-')->lower()],absolute: false), navigate: true);
}
}; ?>
<div class="flex flex-col gap-6">
<x-auth-header :title="__('Create an account')"
:description="__('Enter your details below to create your account')"/>
<!-- Session Status -->
<x-auth-session-status class="text-center" :status="session('status')"/>
<form wire:submit="register" class="flex flex-col gap-6">
<!-- Name -->
<flux:input
wire:model="name"
:label="__('Name')"
type="text"
required
autofocus
autocomplete="name"
:placeholder="__('Full name')"
/>
<!-- Email Address -->
<flux:input
wire:model="email"
:label="__('Email address')"
type="email"
required
autocomplete="email"
placeholder="email@example.com"
/>
<!-- Password -->
<flux:input
wire:model="password"
:label="__('Password')"
type="password"
required
autocomplete="new-password"
:placeholder="__('Password')"
viewable
/>
<!-- Confirm Password -->
<flux:input
wire:model="password_confirmation"
:label="__('Confirm password')"
type="password"
required
autocomplete="new-password"
:placeholder="__('Confirm password')"
viewable
/>
<div class="flex items-center justify-end">
<flux:button type="submit" variant="primary" class="w-full">
{{ __('Create account') }}
</flux:button>
</div>
</form>
<div class="space-x-1 rtl:space-x-reverse text-center text-sm text-zinc-600 dark:text-zinc-400">
<span>{{ __('Already have an account?') }}</span>
<flux:link :href="route('login')" wire:navigate>{{ __('Log in') }}</flux:link>
</div>
</div>
+2 -4
View File
@@ -1,6 +1,7 @@
<?php
use App\Http\Controllers\Auth\VerifyEmailController;
use App\Livewire\Actions\Logout;
use Illuminate\Support\Facades\Route;
Route::middleware('guest')
@@ -8,9 +9,6 @@ Route::middleware('guest')
Route::livewire('/login', 'auth.login')
->name('login');
Route::livewire('/register', 'auth.register')
->name('register');
Route::livewire('/forgot-password', 'auth.forgot-password')
->name('password.request');
@@ -31,5 +29,5 @@ Route::middleware('auth')
->name('password.confirm');
});
Route::post('logout', App\Livewire\Actions\Logout::class)
Route::post('logout', Logout::class)
->name('logout');
-6
View File
@@ -7,9 +7,3 @@ it('renders the login page with QR code and language selector', function () {
->assertSee('Bitcoin, not blockchain')
->assertNoJavaScriptErrors();
});
it('renders the registration page', function () {
$page = visit('/register');
$page->assertNoJavaScriptErrors();
});
@@ -0,0 +1,67 @@
<?php
use App\Models\City;
use App\Models\Country;
use App\Models\Venue;
beforeEach(function () {
$this->country = Country::factory()->create(['code' => 'de', 'name' => 'Deutschland']);
});
it('creates a new city end-to-end with valid coordinates', function () {
actingAsUser();
$page = visit('/de/city-create');
$page->fill('[wire\\:model="name"]', 'BrowserTestCity')
->fill('[wire\\:model="latitude"]', '52.520008')
->fill('[wire\\:model="longitude"]', '13.404954')
->click('[data-flux-button][type="submit"]')
->wait(2)
->assertNoJavaScriptErrors();
expect(City::query()->where('name', 'BrowserTestCity')->exists())->toBeTrue();
});
it('shows validation errors when city form is submitted empty', function () {
actingAsUser();
$page = visit('/de/city-create');
$page->click('[data-flux-button][type="submit"]')
->wait(1)
->assertNoJavaScriptErrors();
expect(City::query()->count())->toBe(0);
});
it('creates a new venue connected to an existing city', function () {
actingAsUser();
$city = City::factory()->create(['country_id' => $this->country->id, 'name' => 'VenueTestCity']);
$page = visit('/de/venue-create');
$page->fill('[wire\\:model="name"]', 'BrowserTestVenue')
->fill('[wire\\:model="street"]', 'Teststraße 1');
$page->script("Livewire.getByName('venues.create')[0].set('city_id', {$city->id})");
$page->wait(0.5)
->click('[data-flux-button][type="submit"]')
->wait(2)
->assertNoJavaScriptErrors();
expect(Venue::query()->where('name', 'BrowserTestVenue')->exists())->toBeTrue();
});
it('shows a validation error when creating a venue without a name', function () {
actingAsUser();
City::factory()->create(['country_id' => $this->country->id]);
$page = visit('/de/venue-create');
$page->fill('[wire\\:model="street"]', 'No-Name Street')
->click('[data-flux-button][type="submit"]')
->wait(1)
->assertNoJavaScriptErrors();
expect(Venue::query()->count())->toBe(0);
});
@@ -0,0 +1,58 @@
<?php
use App\Models\Lecturer;
it('creates a new lecturer end-to-end with valid data', function () {
actingAsUser();
$page = visit('/de/lecturer-create');
$page->fill('[wire\\:model="name"]', 'BrowserTester Saylor')
->fill('[wire\\:model="subtitle"]', 'Browser Test Subject')
->fill('[wire\\:model="intro"]', 'A short intro line.')
->click('[data-flux-button][type="submit"]')
->wait(2)
->assertNoJavaScriptErrors();
expect(Lecturer::query()->where('name', 'BrowserTester Saylor')->exists())->toBeTrue();
});
it('shows a required error when submitting without a lecturer name', function () {
actingAsUser();
$page = visit('/de/lecturer-create');
$page->click('[data-flux-button][type="submit"]')
->wait(1)
->assertNoJavaScriptErrors();
expect(Lecturer::query()->count())->toBe(0);
});
it('rejects creation when the lecturer name already exists', function () {
actingAsUser();
Lecturer::factory()->create(['name' => 'Existing Saylor']);
$page = visit('/de/lecturer-create');
$page->fill('[wire\\:model="name"]', 'Existing Saylor')
->click('[data-flux-button][type="submit"]')
->wait(1)
->assertNoJavaScriptErrors();
expect(Lecturer::query()->where('name', 'Existing Saylor')->count())->toBe(1);
});
it('rejects creation with an invalid website URL', function () {
actingAsUser();
$page = visit('/de/lecturer-create');
$page->fill('[wire\\:model="name"]', 'Bad URL Lecturer')
->fill('[wire\\:model="website"]', 'not-a-url')
->click('[data-flux-button][type="submit"]')
->wait(1)
->assertNoJavaScriptErrors();
expect(Lecturer::query()->where('name', 'Bad URL Lecturer')->exists())->toBeFalse();
});
@@ -0,0 +1,60 @@
<?php
use App\Models\City;
use App\Models\Country;
use App\Models\Meetup;
beforeEach(function () {
$this->country = Country::factory()->create(['code' => 'de', 'name' => 'Deutschland']);
$this->city = City::factory()->create([
'country_id' => $this->country->id,
'name' => 'BrowserMeetupTestCity',
]);
});
it('creates a new meetup end-to-end via the create form', function () {
actingAsUser();
$cityId = $this->city->id;
$page = visit('/de/meetup-create');
$page->fill('[wire\\:model="name"]', 'BrowserSeeded Meetup');
$page->script("Livewire.getByName('meetups.create')[0].set('city_id', {$cityId})");
$page->wait(0.5)
->select('[wire\\:model="community"]', 'einundzwanzig');
$page->script("Livewire.getByName('meetups.create')[0].call('createMeetup')");
$page->wait(2)
->assertNoJavaScriptErrors();
expect(Meetup::query()->where('name', 'BrowserSeeded Meetup')->exists())->toBeTrue();
});
it('blocks meetup creation when the name is missing', function () {
actingAsUser();
$cityId = $this->city->id;
$page = visit('/de/meetup-create');
$page->script("Livewire.getByName('meetups.create')[0].set('city_id', {$cityId})");
$page->wait(0.5)
->select('[wire\\:model="community"]', 'einundzwanzig');
$page->script("Livewire.getByName('meetups.create')[0].call('createMeetup')");
$page->wait(1)
->assertNoJavaScriptErrors();
expect(Meetup::query()->count())->toBe(0);
});
it('blocks meetup creation when no city is selected', function () {
actingAsUser();
$page = visit('/de/meetup-create');
$page->fill('[wire\\:model="name"]', 'NoCityMeetup')
->select('[wire\\:model="community"]', 'einundzwanzig');
$page->script("Livewire.getByName('meetups.create')[0].call('createMeetup')");
$page->wait(1)
->assertNoJavaScriptErrors();
expect(Meetup::query()->where('name', 'NoCityMeetup')->exists())->toBeFalse();
});
@@ -0,0 +1,57 @@
<?php
use App\Enums\SelfHostedServiceType;
use App\Models\City;
use App\Models\Country;
use App\Models\Meetup;
use App\Models\SelfHostedService;
/**
* NOTE: The Search-Inputs of the index pages use Postgres `ilike`, which the
* SQLite test database does not support. We therefore exercise the type-badge
* filter (uses plain `=`) instead and assert the index list itself reacts
* to it.
*/
beforeEach(function () {
$this->country = Country::factory()->create(['code' => 'de']);
$this->city = City::factory()->create(['country_id' => $this->country->id]);
});
it('renders all seeded services on the public services index', function () {
SelfHostedService::factory()->create(['name' => 'NodeAlpha', 'type' => SelfHostedServiceType::Mempool]);
SelfHostedService::factory()->create(['name' => 'BetaService', 'type' => SelfHostedServiceType::Other]);
$page = visit('/de/services');
$page->assertSee('NodeAlpha')
->assertSee('BetaService')
->assertNoJavaScriptErrors();
});
it('filters services by clicking a type-badge in the type-cloud', function () {
SelfHostedService::factory()->create(['name' => 'OnlyMempoolNode', 'type' => SelfHostedServiceType::Mempool]);
SelfHostedService::factory()->create(['name' => 'OnlyOtherThing', 'type' => SelfHostedServiceType::Other]);
$page = visit('/de/services');
$page->assertSee('OnlyMempoolNode')
->assertSee('OnlyOtherThing')
->click('[wire\\:click="filterByType(\'mempool\')"]')
->wait(1)
->assertSee('OnlyMempoolNode')
->assertDontSee('OnlyOtherThing')
->assertNoJavaScriptErrors();
});
it('shows seeded meetups on the public meetups index', function () {
Meetup::factory()->create([
'city_id' => $this->city->id,
'name' => 'BrowserSeeded Meetup XYZ',
'visible_on_map' => true,
]);
$page = visit('/de/meetups');
$page->assertSee('BrowserSeeded Meetup XYZ')
->assertNoJavaScriptErrors();
});
@@ -0,0 +1,49 @@
<?php
use App\Enums\SelfHostedServiceType;
use App\Models\SelfHostedService;
it('creates a new SelfHostedService end-to-end and shows it on the index', function () {
actingAsUser();
$page = visit('/de/service-create');
$page->fill('[wire\\:model="form.name"]', 'BrowserTestNode')
->select('[wire\\:model="form.type"]', SelfHostedServiceType::Mempool->value)
->fill('[wire\\:model="form.url_clearnet"]', 'https://browsertest.example.com')
->fill('[wire\\:model="form.intro"]', 'A node spun up by a browser test.')
->click('[data-flux-button][type="submit"]')
->wait(2)
->assertNoJavaScriptErrors()
->assertSee('BrowserTestNode');
expect(SelfHostedService::query()->where('name', 'BrowserTestNode')->exists())->toBeTrue();
});
it('blocks submission without a name and shows a required error', function () {
actingAsUser();
$page = visit('/de/service-create');
$page->select('[wire\\:model="form.type"]', SelfHostedServiceType::Other->value)
->fill('[wire\\:model="form.url_clearnet"]', 'https://no-name.example.com')
->click('[data-flux-button][type="submit"]')
->wait(1)
->assertNoJavaScriptErrors();
expect(SelfHostedService::query()->count())->toBe(0);
});
it('rejects submission when no URL or IP is provided', function () {
actingAsUser();
$page = visit('/de/service-create');
$page->fill('[wire\\:model="form.name"]', 'NoUrlService')
->select('[wire\\:model="form.type"]', SelfHostedServiceType::Other->value)
->click('[data-flux-button][type="submit"]')
->wait(1)
->assertNoJavaScriptErrors();
expect(SelfHostedService::query()->where('name', 'NoUrlService')->exists())->toBeFalse();
});
@@ -0,0 +1,46 @@
<?php
use App\Models\User;
it('lets an authenticated user update their profile name and persists it', function () {
$user = actingAsUser(['name' => 'Old Name']);
$page = visit('/de/settings/profile');
$page->assertSee('Old Name')
->fill('name', 'New Browser Name')
->click('Save')
->wait(1)
->assertSee('Saved.')
->assertNoJavaScriptErrors();
expect($user->refresh()->name)->toBe('New Browser Name');
});
it('shows a validation error when the profile name is cleared', function () {
actingAsUser(['name' => 'Original']);
$page = visit('/de/settings/profile');
$page->fill('name', '')
->click('Save')
->wait(1)
->assertNoJavaScriptErrors();
expect(User::query()->where('name', '')->exists())->toBeFalse();
});
it('still shows the updated name after a full page reload', function () {
$user = actingAsUser(['name' => 'Before Reload']);
$page = visit('/de/settings/profile');
$page->fill('name', 'After Reload')
->click('Save')
->wait(1);
$reloaded = visit('/de/settings/profile');
$reloaded->assertSee('After Reload')
->assertNoJavaScriptErrors();
expect($user->refresh()->name)->toBe('After Reload');
});
-1
View File
@@ -20,7 +20,6 @@ it('loads all listed public pages without console errors or JS errors', function
$pages = visit([
'/welcome',
'/login',
'/register',
'/forgot-password',
'/de/meetups',
'/de/courses',
-4
View File
@@ -6,10 +6,6 @@ it('mounts the auth.login component', function () {
Livewire::test('auth.login')->assertStatus(200);
});
it('mounts the auth.register component', function () {
Livewire::test('auth.register')->assertStatus(200);
});
it('mounts the auth.forgot-password component', function () {
Livewire::test('auth.forgot-password')->assertStatus(200);
});
+4 -1
View File
@@ -25,7 +25,6 @@ it('returns a successful response for the listed public route', function (string
})->with([
'welcome' => '/welcome',
'login' => '/login',
'register' => '/register',
'forgot password' => '/forgot-password',
'meetups index' => '/de/meetups',
'meetups all' => '/de/all-meetups',
@@ -42,6 +41,10 @@ it('redirects / to /welcome', function () {
$this->get('/')->assertRedirect('/welcome');
});
it('returns 404 for /register because public registration is disabled', function () {
$this->get('/register')->assertNotFound();
});
it('redirects /de/dashboard to login when guest', function () {
$this->get('/de/dashboard')->assertRedirect(route('login'));
});