feat: complete user authentication, admin guard, customer account dashboard and branded HTML emails

This commit is contained in:
2026-11-01 13:55:45 +01:00
parent b6ee6938cb
commit ee89da5e61
37 changed files with 1895 additions and 22 deletions
+45 -8
View File
@@ -6,6 +6,7 @@ use App\Models\Category;
use App\Models\Order;
use App\Models\Product;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
@@ -13,10 +14,46 @@ class AdminContentTest extends TestCase
{
use RefreshDatabase;
public function test_admin_content_editor_loads_successfully(): void
protected User $admin;
protected function setUp(): void
{
parent::setUp();
$this->admin = User::create([
'name' => 'Admin Jouwdriedee',
'email' => 'info@jouwdriedee.nl',
'password' => bcrypt('Jw3D!x9#QmK7$vR2'),
'is_admin' => true,
]);
}
public function test_guest_is_redirected_to_login_when_accessing_admin(): void
{
$response = $this->get(route('admin.content.index'));
$response->assertRedirect(route('login'));
}
public function test_regular_customer_cannot_access_admin(): void
{
$customer = User::create([
'name' => 'Klant Piet',
'email' => 'piet@example.com',
'password' => bcrypt('secret1234'),
'is_admin' => false,
]);
$response = $this->actingAs($customer)->get(route('admin.content.index'));
$response->assertRedirect(route('home'));
$response->assertSessionHas('error');
}
public function test_admin_content_editor_loads_successfully(): void
{
$response = $this->actingAs($this->admin)->get(route('admin.content.index'));
$response->assertStatus(200);
$response->assertSee('Homepage Hero');
$response->assertSee('Shop Content & Stijl CMS', false);
@@ -35,7 +72,7 @@ class AdminContentTest extends TestCase
'announcement_text' => 'Tijdelijke actie: gratis verzending!',
];
$response = $this->post(route('admin.content.update'), $payload);
$response = $this->actingAs($this->admin)->post(route('admin.content.update'), $payload);
$response->assertRedirect(route('admin.content.index'));
$response->assertSessionHas('success');
@@ -60,7 +97,7 @@ class AdminContentTest extends TestCase
Setting::set('hero_title', 'Tijdelijke Titel');
$this->assertSame('Tijdelijke Titel', Setting::get('hero_title'));
$response = $this->post(route('admin.content.reset'));
$response = $this->actingAs($this->admin)->post(route('admin.content.reset'));
$response->assertRedirect(route('admin.content.index'));
$this->assertNull(Setting::get('hero_title'));
@@ -84,17 +121,17 @@ class AdminContentTest extends TestCase
]);
// Products index
$indexResponse = $this->get(route('admin.products.index'));
$indexResponse = $this->actingAs($this->admin)->get(route('admin.products.index'));
$indexResponse->assertStatus(200);
$indexResponse->assertSee('Test Fidget Draak');
// Edit page
$editResponse = $this->get(route('admin.products.edit', $product));
$editResponse = $this->actingAs($this->admin)->get(route('admin.products.edit', $product));
$editResponse->assertStatus(200);
$editResponse->assertSee('Test Fidget Draak');
// Update product
$updateResponse = $this->put(route('admin.products.update', $product), [
$updateResponse = $this->actingAs($this->admin)->put(route('admin.products.update', $product), [
'name' => 'Aangepaste Fidget Draak',
'category_id' => $category->id,
'base_price' => 18.00,
@@ -128,12 +165,12 @@ class AdminContentTest extends TestCase
'status' => 'pending',
]);
$response = $this->get(route('admin.orders.index'));
$response = $this->actingAs($this->admin)->get(route('admin.orders.index'));
$response->assertStatus(200);
$response->assertSee('ORD-TEST-999');
$response->assertSee('Jan Jansen');
$showResponse = $this->get(route('admin.orders.show', $order));
$showResponse = $this->actingAs($this->admin)->get(route('admin.orders.show', $order));
$showResponse->assertStatus(200);
$showResponse->assertSee('ORD-TEST-999');
}
+218
View File
@@ -0,0 +1,218 @@
<?php
namespace Tests\Feature;
use App\Mail\OrderConfirmationMail;
use App\Mail\PasswordResetMail;
use App\Mail\WelcomeMail;
use App\Models\Category;
use App\Models\Material;
use App\Models\Product;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;
class AuthAndEmailTest extends TestCase
{
use RefreshDatabase;
public function test_customer_can_register_and_receives_welcome_email(): void
{
Mail::fake();
$response = $this->post(route('register.submit'), [
'name' => 'Sophie de Vries',
'email' => 'sophie@example.com',
'password' => 'VeiligWachtwoord123!',
'password_confirmation' => 'VeiligWachtwoord123!',
]);
$response->assertRedirect(route('account.index'));
$this->assertAuthenticated();
$this->assertDatabaseHas('users', [
'email' => 'sophie@example.com',
'name' => 'Sophie de Vries',
'is_admin' => false,
]);
Mail::assertSent(WelcomeMail::class, function ($mail) {
return $mail->hasTo('sophie@example.com')
&& str_contains($mail->envelope()->subject, 'Welkom bij Jouwdriedee.nl');
});
}
public function test_customer_can_login_with_valid_credentials(): void
{
$user = User::create([
'name' => 'Lars Bakker',
'email' => 'lars@example.com',
'password' => Hash::make('MijnGeheimeWachtwoord'),
'is_admin' => false,
]);
$response = $this->post(route('login.submit'), [
'email' => 'lars@example.com',
'password' => 'MijnGeheimeWachtwoord',
]);
$response->assertRedirect(route('account.index'));
$this->assertAuthenticatedAs($user);
}
public function test_admin_login_redirects_to_admin_content_dashboard(): void
{
$admin = User::create([
'name' => 'Jouwdriedee Beheer',
'email' => 'info@jouwdriedee.nl',
'password' => Hash::make('Jw3D!x9#QmK7$vR2'),
'is_admin' => true,
]);
$response = $this->post(route('login.submit'), [
'email' => 'info@jouwdriedee.nl',
'password' => 'Jw3D!x9#QmK7$vR2',
]);
$response->assertRedirect(route('admin.content.index'));
$this->assertAuthenticatedAs($admin);
}
public function test_login_fails_with_invalid_credentials(): void
{
User::create([
'name' => 'Lars Bakker',
'email' => 'lars@example.com',
'password' => Hash::make('MijnGeheimeWachtwoord'),
]);
$response = $this->post(route('login.submit'), [
'email' => 'lars@example.com',
'password' => 'FoutWachtwoord',
]);
$response->assertSessionHasErrors('email');
$this->assertGuest();
}
public function test_customer_can_request_password_reset_and_receives_email(): void
{
Mail::fake();
$user = User::create([
'name' => 'Emma Visser',
'email' => 'emma@example.com',
'password' => Hash::make('Wachtwoord123'),
]);
$response = $this->post(route('password.email'), [
'email' => 'emma@example.com',
]);
$response->assertSessionHas('success');
Mail::assertSent(PasswordResetMail::class, function ($mail) {
return $mail->hasTo('emma@example.com')
&& str_contains($mail->envelope()->subject, 'Wachtwoord opnieuw instellen');
});
$this->assertDatabaseHas('password_reset_tokens', [
'email' => 'emma@example.com',
]);
}
public function test_customer_can_reset_password_with_valid_token(): void
{
$user = User::create([
'name' => 'Emma Visser',
'email' => 'emma@example.com',
'password' => Hash::make('OudWachtwoord123'),
]);
$rawToken = 'test-secret-token-1234567890';
DB::table('password_reset_tokens')->insert([
'email' => 'emma@example.com',
'token' => Hash::make($rawToken),
'created_at' => now(),
]);
$response = $this->post(route('password.update'), [
'token' => $rawToken,
'email' => 'emma@example.com',
'password' => 'GloednieuwWachtwoord123!',
'password_confirmation' => 'GloednieuwWachtwoord123!',
]);
$response->assertRedirect(route('account.index'));
$this->assertAuthenticatedAs($user);
$user->refresh();
$this->assertTrue(Hash::check('GloednieuwWachtwoord123!', $user->password));
$this->assertDatabaseMissing('password_reset_tokens', [
'email' => 'emma@example.com',
]);
}
public function test_checkout_sends_order_confirmation_email(): void
{
Mail::fake();
Material::updateOrCreate(['code' => 'pla'], [
'name' => 'Bio-PLA+',
'code' => 'pla',
'price_multiplier' => 1.0,
'is_active' => true,
]);
$category = Category::create([
'name' => 'Fidgets',
'slug' => 'fidgets',
]);
$product = Product::create([
'name' => 'Kristaldraak Mini',
'slug' => 'kristaldraak-mini',
'category_id' => $category->id,
'base_price' => 15.00,
'description' => 'Test draakje',
'is_active' => true,
]);
// Put item in cart
session()->put('cart', [
'kristaldraak-mini-pla-rood' => [
'id' => $product->id,
'name' => 'Kristaldraak Mini',
'slug' => 'kristaldraak-mini',
'price' => 15.00,
'quantity' => 2,
'material' => 'pla',
'color' => 'Vurig Rood',
'scale' => '100%',
'infill' => '20%',
],
]);
$response = $this->post(route('checkout.store'), [
'customer_name' => 'Klant Tester',
'customer_email' => 'klant@test.nl',
'customer_phone' => '0612345678',
'shipping_street' => 'Hoofdstraat',
'shipping_number' => '42',
'shipping_postal_code' => '1000AA',
'shipping_city' => 'Utrecht',
'payment_method' => 'ideal',
]);
$response->assertRedirect();
Mail::assertSent(OrderConfirmationMail::class, function ($mail) {
return $mail->hasTo('klant@test.nl')
&& str_contains($mail->envelope()->subject, 'Bestelbevestiging');
});
}
}
+15
View File
@@ -5,6 +5,7 @@ namespace Tests\Feature;
use App\Models\Category;
use App\Models\Product;
use App\Models\Setting;
use App\Models\User;
use App\Services\MakerWorldService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
@@ -14,6 +15,20 @@ class MakerWorldImporterTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$admin = User::create([
'name' => 'Admin Jouwdriedee',
'email' => 'info@jouwdriedee.nl',
'password' => bcrypt('Jw3D!x9#QmK7$vR2'),
'is_admin' => true,
]);
$this->actingAs($admin);
}
protected function fakeMakerWorldApi(): void
{
Http::fake([