mirror of
https://github.com/HolgerHatGarKeineNode/einundzwanzig-app.git
synced 2026-05-04 16:44:54 +00:00
🔥 **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:
@@ -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
@@ -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');
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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'));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user