create(); $role = Role::firstOrCreate(['name' => 'ROLE_ADMIN'], ['color' => '#111111']); $admin->roles()->attach($role); return $admin; } it('lists ranks for authenticated users', function (): void { $user = User::factory()->create(); Rank::create(['name' => 'Bronze']); Sanctum::actingAs($user); $response = $this->getJson('/api/ranks'); $response->assertOk(); $response->assertJsonFragment(['name' => 'Bronze']); }); it('forbids non-admin rank changes', function (): void { $user = User::factory()->create(); $rank = Rank::create(['name' => 'Nope']); Sanctum::actingAs($user); $response = $this->postJson('/api/ranks', [ 'name' => 'Silver', ]); $response->assertStatus(403); $response = $this->patchJson("/api/ranks/{$rank->id}", [ 'name' => 'Nope', ]); $response->assertStatus(403); $response = $this->deleteJson("/api/ranks/{$rank->id}"); $response->assertStatus(403); $response = $this->postJson("/api/ranks/{$rank->id}/badge-image", [ 'file' => UploadedFile::fake()->image('badge.png', 50, 50), ]); $response->assertStatus(403); }); it('creates ranks as admin', function (): void { $admin = makeAdminForRanks(); Sanctum::actingAs($admin); $response = $this->postJson('/api/ranks', [ 'name' => 'Silver', 'badge_type' => 'text', 'badge_text' => 'S', 'color' => '#abcdef', ]); $response->assertStatus(201); $response->assertJsonFragment([ 'name' => 'Silver', 'badge_text' => 'S', ]); }); it('creates ranks with none badge type', function (): void { $admin = makeAdminForRanks(); Sanctum::actingAs($admin); $response = $this->postJson('/api/ranks', [ 'name' => 'NoBadge', 'badge_type' => 'none', ]); $response->assertStatus(201); $response->assertJsonFragment([ 'name' => 'NoBadge', 'badge_text' => null, ]); }); it('updates ranks and clears badge images when switching to text', function (): void { Storage::fake('public'); $admin = makeAdminForRanks(); $rank = Rank::create([ 'name' => 'Gold', 'badge_type' => 'image', 'badge_text' => null, 'badge_image_path' => 'rank-badges/old.png', 'color' => '#ffaa00', ]); Storage::disk('public')->put('rank-badges/old.png', 'old'); Sanctum::actingAs($admin); $response = $this->patchJson("/api/ranks/{$rank->id}", [ 'name' => 'Gold', 'badge_type' => 'text', 'badge_text' => 'G', 'color' => '#ffaa00', ]); $response->assertOk(); Storage::disk('public')->assertMissing('rank-badges/old.png'); }); it('updates ranks with badge_type none', function (): void { $admin = makeAdminForRanks(); $rank = Rank::create([ 'name' => 'Plain', 'badge_type' => 'text', 'badge_text' => 'P', ]); Sanctum::actingAs($admin); $response = $this->patchJson("/api/ranks/{$rank->id}", [ 'name' => 'Plain', 'badge_type' => 'none', ]); $response->assertOk(); $response->assertJsonFragment(['badge_text' => null]); }); it('updates ranks to image badge and keeps existing image', function (): void { Storage::fake('public'); $admin = makeAdminForRanks(); $rank = Rank::create([ 'name' => 'ImageRank', 'badge_type' => 'image', 'badge_text' => null, 'badge_image_path' => 'rank-badges/existing.png', ]); Storage::disk('public')->put('rank-badges/existing.png', 'existing'); Sanctum::actingAs($admin); $response = $this->patchJson("/api/ranks/{$rank->id}", [ 'name' => 'ImageRank', 'badge_type' => 'image', ]); $response->assertOk(); Storage::disk('public')->assertExists('rank-badges/existing.png'); }); it('uploads a rank badge image', function (): void { Storage::fake('public'); $admin = makeAdminForRanks(); $rank = Rank::create(['name' => 'Platinum']); Sanctum::actingAs($admin); $response = $this->postJson("/api/ranks/{$rank->id}/badge-image", [ 'file' => UploadedFile::fake()->image('badge.png', 50, 50), ]); $response->assertOk(); $response->assertJsonFragment(['badge_type' => 'image']); }); it('includes badge image url in rank list when present', function (): void { Storage::fake('public'); Storage::disk('public')->put('rank-badges/show.png', 'img'); $user = User::factory()->create(); Rank::create([ 'name' => 'WithImage', 'badge_type' => 'image', 'badge_image_path' => 'rank-badges/show.png', ]); Sanctum::actingAs($user); $response = $this->getJson('/api/ranks'); $response->assertOk(); $response->assertJsonFragment([ 'name' => 'WithImage', ]); expect($response->getData(true)[0]['badge_image_url'])->not->toBeNull(); }); it('uploads badge image replaces existing one', function (): void { Storage::fake('public'); $admin = makeAdminForRanks(); $rank = Rank::create([ 'name' => 'Replace', 'badge_type' => 'image', 'badge_image_path' => 'rank-badges/old.png', ]); Storage::disk('public')->put('rank-badges/old.png', 'old'); Sanctum::actingAs($admin); $response = $this->postJson("/api/ranks/{$rank->id}/badge-image", [ 'file' => UploadedFile::fake()->image('badge.png', 50, 50), ]); $response->assertOk(); Storage::disk('public')->assertMissing('rank-badges/old.png'); }); it('deletes ranks as admin', function (): void { Storage::fake('public'); $admin = makeAdminForRanks(); $rank = Rank::create([ 'name' => 'ToDelete', 'badge_type' => 'image', 'badge_image_path' => 'rank-badges/delete.png', ]); Storage::disk('public')->put('rank-badges/delete.png', 'old'); Sanctum::actingAs($admin); $response = $this->deleteJson("/api/ranks/{$rank->id}"); $response->assertStatus(204); Storage::disk('public')->assertMissing('rank-badges/delete.png'); $this->assertDatabaseMissing('ranks', ['id' => $rank->id]); });