'null']); }); // ── ArtworkViewController (POST /api/art/{id}/view) ────────────────────────── it('returns 404 for a non-existent artwork on view', function () { $this->postJson('/api/art/99999/view')->assertStatus(404); }); it('returns 404 for a private artwork on view', function () { $artwork = Artwork::factory()->create(['is_public' => false]); $this->postJson("/api/art/{$artwork->id}/view")->assertStatus(404); }); it('returns 404 for an unapproved artwork on view', function () { $artwork = Artwork::factory()->create(['is_approved' => false]); $this->postJson("/api/art/{$artwork->id}/view")->assertStatus(404); }); it('records a view and returns ok=true on first call', function () { $artwork = Artwork::factory()->create([ 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subDay(), ]); // Ensure a stats row exists with 0 views DB::table('artwork_stats')->insertOrIgnore([ 'artwork_id' => $artwork->id, 'views' => 0, 'downloads' => 0, 'favorites' => 0, 'rating_avg' => 0, 'rating_count' => 0, ]); $mock = $this->mock(ArtworkStatsService::class); $mock->shouldReceive('logViewEvent') ->once() ->with($artwork->id, null); // null = guest (unauthenticated request) $mock->shouldReceive('incrementViews') ->once() ->with($artwork->id, 1, true); $response = $this->postJson("/api/art/{$artwork->id}/view"); $response->assertStatus(200) ->assertJsonPath('ok', true) ->assertJsonPath('counted', true); }); it('skips DB increment and returns counted=false if artwork was already viewed this session', function () { $artwork = Artwork::factory()->create([ 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subDay(), ]); // Mark as already viewed in the session session()->put("art_viewed.{$artwork->id}", true); $mock = $this->mock(ArtworkStatsService::class); $mock->shouldReceive('incrementViews')->never(); $response = $this->postJson("/api/art/{$artwork->id}/view"); $response->assertStatus(200) ->assertJsonPath('ok', true) ->assertJsonPath('counted', false); }); // ── ArtworkDownloadController (POST /api/art/{id}/download) ────────────────── it('returns 404 for a non-existent artwork on download', function () { $this->postJson('/api/art/99999/download')->assertStatus(404); }); it('returns 404 for a private artwork on download', function () { $artwork = Artwork::factory()->create(['is_public' => false]); $this->postJson("/api/art/{$artwork->id}/download")->assertStatus(404); }); it('records a download and returns ok=true with a url', function () { $artwork = Artwork::factory()->create([ 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subDay(), ]); $mock = $this->mock(ArtworkStatsService::class); $mock->shouldReceive('incrementDownloads') ->once() ->with($artwork->id, 1, true); $response = $this->postJson("/api/art/{$artwork->id}/download"); $response->assertStatus(200) ->assertJsonPath('ok', true) ->assertJsonStructure(['ok', 'url']); }); it('inserts a row in artwork_downloads on valid download', function () { $user = User::factory()->create(); $artwork = Artwork::factory()->create([ 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subDay(), ]); // Stub the stats service so we don't need Redis $mock = $this->mock(ArtworkStatsService::class); $mock->shouldReceive('incrementDownloads')->once(); $this->actingAs($user)->postJson("/api/art/{$artwork->id}/download"); $this->assertDatabaseHas('artwork_downloads', [ 'artwork_id' => $artwork->id, 'user_id' => $user->id, ]); }); it('records download as guest (no user_id) when unauthenticated', function () { $artwork = Artwork::factory()->create([ 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subDay(), ]); $mock = $this->mock(ArtworkStatsService::class); $mock->shouldReceive('incrementDownloads')->once(); $this->postJson("/api/art/{$artwork->id}/download"); $this->assertDatabaseHas('artwork_downloads', [ 'artwork_id' => $artwork->id, 'user_id' => null, ]); }); // ── Route names ─────────────────────────────────────────────────────────────── it('view endpoint route is named api.art.view', function () { $artwork = Artwork::factory()->create([ 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subDay(), ]); expect(route('api.art.view', ['id' => $artwork->id]))->toContain("/api/art/{$artwork->id}/view"); }); it('download endpoint route is named api.art.download', function () { $artwork = Artwork::factory()->create([ 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subDay(), ]); expect(route('api.art.download', ['id' => $artwork->id]))->toContain("/api/art/{$artwork->id}/download"); });