416 lines
12 KiB
PHP
416 lines
12 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
use App\Models\Category;
|
||
|
|
use App\Models\Product;
|
||
|
|
use App\Models\Role;
|
||
|
|
use App\Models\User;
|
||
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||
|
|
use Illuminate\Http\UploadedFile;
|
||
|
|
use Illuminate\Support\Facades\Storage;
|
||
|
|
|
||
|
|
uses(RefreshDatabase::class);
|
||
|
|
|
||
|
|
beforeEach(function () {
|
||
|
|
Storage::fake('public');
|
||
|
|
|
||
|
|
// Create roles
|
||
|
|
$this->userRole = Role::create([
|
||
|
|
'name' => 'User',
|
||
|
|
'slug' => 'user',
|
||
|
|
'description' => 'Regular user',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$this->moderatorRole = Role::create([
|
||
|
|
'name' => 'Moderator',
|
||
|
|
'slug' => 'moderator',
|
||
|
|
'description' => 'Moderator',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$this->adminRole = Role::create([
|
||
|
|
'name' => 'Admin',
|
||
|
|
'slug' => 'admin',
|
||
|
|
'description' => 'Admin',
|
||
|
|
]);
|
||
|
|
|
||
|
|
// Create category
|
||
|
|
$this->category = Category::create([
|
||
|
|
'name' => 'Test Category',
|
||
|
|
'slug' => 'test-category',
|
||
|
|
'description' => 'Test description',
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('guests can view approved products', function () {
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$user->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$product = Product::create([
|
||
|
|
'title' => 'Test Product',
|
||
|
|
'slug' => 'test-product',
|
||
|
|
'description' => 'Test description',
|
||
|
|
'website_url' => 'https://example.com',
|
||
|
|
'user_id' => $user->id,
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
'status' => 'approved',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response = $this->get(route('products.index'));
|
||
|
|
|
||
|
|
$response->assertStatus(200);
|
||
|
|
$response->assertSee('Test Product');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('guests cannot view pending products', function () {
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$user->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$product = Product::create([
|
||
|
|
'title' => 'Pending Product',
|
||
|
|
'slug' => 'pending-product',
|
||
|
|
'description' => 'Test description',
|
||
|
|
'website_url' => 'https://example.com',
|
||
|
|
'user_id' => $user->id,
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
'status' => 'pending',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response = $this->get(route('products.show', $product));
|
||
|
|
|
||
|
|
$response->assertStatus(404);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('authenticated users can submit products', function () {
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$user->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$response = $this->actingAs($user)->post(route('products.store'), [
|
||
|
|
'title' => 'New Product',
|
||
|
|
'description' => 'This is a new product',
|
||
|
|
'website_url' => 'https://newproduct.com',
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response->assertRedirect();
|
||
|
|
$this->assertDatabaseHas('products', [
|
||
|
|
'title' => 'New Product',
|
||
|
|
'user_id' => $user->id,
|
||
|
|
'status' => 'pending',
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('guests cannot submit products', function () {
|
||
|
|
$response = $this->get(route('products.create'));
|
||
|
|
|
||
|
|
$response->assertRedirect(route('login'));
|
||
|
|
});
|
||
|
|
|
||
|
|
test('product submission requires title', function () {
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$user->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$response = $this->actingAs($user)->post(route('products.store'), [
|
||
|
|
'description' => 'Test description',
|
||
|
|
'website_url' => 'https://example.com',
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response->assertSessionHasErrors('title');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('product submission requires description', function () {
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$user->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$response = $this->actingAs($user)->post(route('products.store'), [
|
||
|
|
'title' => 'Test Product',
|
||
|
|
'website_url' => 'https://example.com',
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response->assertSessionHasErrors('description');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('product submission requires valid url', function () {
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$user->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$response = $this->actingAs($user)->post(route('products.store'), [
|
||
|
|
'title' => 'Test Product',
|
||
|
|
'description' => 'Test description',
|
||
|
|
'website_url' => 'not-a-valid-url',
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response->assertSessionHasErrors('website_url');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('product submission requires valid category', function () {
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$user->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$response = $this->actingAs($user)->post(route('products.store'), [
|
||
|
|
'title' => 'Test Product',
|
||
|
|
'description' => 'Test description',
|
||
|
|
'website_url' => 'https://example.com',
|
||
|
|
'category_id' => 999,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response->assertSessionHasErrors('category_id');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('users can upload thumbnail when submitting product', function () {
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$user->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$file = UploadedFile::fake()->image('thumbnail.jpg');
|
||
|
|
|
||
|
|
$response = $this->actingAs($user)->post(route('products.store'), [
|
||
|
|
'title' => 'Test Product',
|
||
|
|
'description' => 'Test description',
|
||
|
|
'website_url' => 'https://example.com',
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
'thumbnail' => $file,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$product = Product::where('title', 'Test Product')->first();
|
||
|
|
|
||
|
|
expect($product->thumbnail)->not->toBeNull();
|
||
|
|
Storage::disk('public')->assertExists($product->thumbnail);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('thumbnail must be an image', function () {
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$user->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$file = UploadedFile::fake()->create('document.pdf');
|
||
|
|
|
||
|
|
$response = $this->actingAs($user)->post(route('products.store'), [
|
||
|
|
'title' => 'Test Product',
|
||
|
|
'description' => 'Test description',
|
||
|
|
'website_url' => 'https://example.com',
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
'thumbnail' => $file,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response->assertSessionHasErrors('thumbnail');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('product owner can edit their product', function () {
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$user->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$product = Product::create([
|
||
|
|
'title' => 'Test Product',
|
||
|
|
'slug' => 'test-product',
|
||
|
|
'description' => 'Test description',
|
||
|
|
'website_url' => 'https://example.com',
|
||
|
|
'user_id' => $user->id,
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
'status' => 'pending',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response = $this->actingAs($user)->put(route('products.update', $product), [
|
||
|
|
'title' => 'Updated Product',
|
||
|
|
'description' => 'Updated description',
|
||
|
|
'website_url' => 'https://updated.com',
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response->assertRedirect();
|
||
|
|
$this->assertDatabaseHas('products', [
|
||
|
|
'id' => $product->id,
|
||
|
|
'title' => 'Updated Product',
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('non-owner cannot edit product', function () {
|
||
|
|
$owner = User::factory()->create();
|
||
|
|
$owner->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$other = User::factory()->create();
|
||
|
|
$other->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$product = Product::create([
|
||
|
|
'title' => 'Test Product',
|
||
|
|
'slug' => 'test-product',
|
||
|
|
'description' => 'Test description',
|
||
|
|
'website_url' => 'https://example.com',
|
||
|
|
'user_id' => $owner->id,
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
'status' => 'pending',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response = $this->actingAs($other)->put(route('products.update', $product), [
|
||
|
|
'title' => 'Updated Product',
|
||
|
|
'description' => 'Updated description',
|
||
|
|
'website_url' => 'https://updated.com',
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response->assertStatus(403);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('product owner can delete their product', function () {
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$user->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$product = Product::create([
|
||
|
|
'title' => 'Test Product',
|
||
|
|
'slug' => 'test-product',
|
||
|
|
'description' => 'Test description',
|
||
|
|
'website_url' => 'https://example.com',
|
||
|
|
'user_id' => $user->id,
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
'status' => 'pending',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response = $this->actingAs($user)->delete(route('products.destroy', $product));
|
||
|
|
|
||
|
|
$response->assertRedirect();
|
||
|
|
$this->assertDatabaseMissing('products', ['id' => $product->id]);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('moderators can approve products', function () {
|
||
|
|
$moderator = User::factory()->create();
|
||
|
|
$moderator->roles()->attach($this->moderatorRole);
|
||
|
|
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$user->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$product = Product::create([
|
||
|
|
'title' => 'Test Product',
|
||
|
|
'slug' => 'test-product',
|
||
|
|
'description' => 'Test description',
|
||
|
|
'website_url' => 'https://example.com',
|
||
|
|
'user_id' => $user->id,
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
'status' => 'pending',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response = $this->actingAs($moderator)->post(route('products.approve', $product));
|
||
|
|
|
||
|
|
$response->assertRedirect();
|
||
|
|
$this->assertDatabaseHas('products', [
|
||
|
|
'id' => $product->id,
|
||
|
|
'status' => 'approved',
|
||
|
|
]);
|
||
|
|
|
||
|
|
expect($product->fresh()->approved_at)->not->toBeNull();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('moderators can reject products', function () {
|
||
|
|
$moderator = User::factory()->create();
|
||
|
|
$moderator->roles()->attach($this->moderatorRole);
|
||
|
|
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$user->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$product = Product::create([
|
||
|
|
'title' => 'Test Product',
|
||
|
|
'slug' => 'test-product',
|
||
|
|
'description' => 'Test description',
|
||
|
|
'website_url' => 'https://example.com',
|
||
|
|
'user_id' => $user->id,
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
'status' => 'pending',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response = $this->actingAs($moderator)->post(route('products.reject', $product));
|
||
|
|
|
||
|
|
$response->assertRedirect();
|
||
|
|
$this->assertDatabaseHas('products', [
|
||
|
|
'id' => $product->id,
|
||
|
|
'status' => 'rejected',
|
||
|
|
]);
|
||
|
|
|
||
|
|
expect($product->fresh()->rejected_at)->not->toBeNull();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('regular users cannot approve products', function () {
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$user->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$product = Product::create([
|
||
|
|
'title' => 'Test Product',
|
||
|
|
'slug' => 'test-product',
|
||
|
|
'description' => 'Test description',
|
||
|
|
'website_url' => 'https://example.com',
|
||
|
|
'user_id' => $user->id,
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
'status' => 'pending',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response = $this->actingAs($user)->post(route('products.approve', $product));
|
||
|
|
|
||
|
|
$response->assertStatus(403);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('regular users cannot access product management page', function () {
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$user->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$response = $this->actingAs($user)->get(route('products.manage'));
|
||
|
|
|
||
|
|
$response->assertStatus(403);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('moderators can access product management page', function () {
|
||
|
|
$moderator = User::factory()->create();
|
||
|
|
$moderator->roles()->attach($this->moderatorRole);
|
||
|
|
|
||
|
|
$response = $this->actingAs($moderator)->get(route('products.manage'));
|
||
|
|
|
||
|
|
$response->assertStatus(200);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('admins can access product management page', function () {
|
||
|
|
$admin = User::factory()->create();
|
||
|
|
$admin->roles()->attach($this->adminRole);
|
||
|
|
|
||
|
|
$response = $this->actingAs($admin)->get(route('products.manage'));
|
||
|
|
|
||
|
|
$response->assertStatus(200);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('slug is auto-generated from title', function () {
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$user->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$product = Product::create([
|
||
|
|
'title' => 'My Awesome Product',
|
||
|
|
'description' => 'Test description',
|
||
|
|
'website_url' => 'https://example.com',
|
||
|
|
'user_id' => $user->id,
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
'status' => 'pending',
|
||
|
|
]);
|
||
|
|
|
||
|
|
expect($product->slug)->toBe('my-awesome-product');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('duplicate slugs are handled correctly', function () {
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$user->roles()->attach($this->userRole);
|
||
|
|
|
||
|
|
$product1 = Product::create([
|
||
|
|
'title' => 'Test Product',
|
||
|
|
'description' => 'Test description',
|
||
|
|
'website_url' => 'https://example.com',
|
||
|
|
'user_id' => $user->id,
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
'status' => 'pending',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$product2 = Product::create([
|
||
|
|
'title' => 'Test Product',
|
||
|
|
'description' => 'Test description',
|
||
|
|
'website_url' => 'https://example2.com',
|
||
|
|
'user_id' => $user->id,
|
||
|
|
'category_id' => $this->category->id,
|
||
|
|
'status' => 'pending',
|
||
|
|
]);
|
||
|
|
|
||
|
|
expect($product1->slug)->toBe('test-product');
|
||
|
|
expect($product2->slug)->toBe('test-product-1');
|
||
|
|
});
|