minor fixes
This commit is contained in:
65
tests/Feature/DiscoverRisingFeedTest.php
Normal file
65
tests/Feature/DiscoverRisingFeedTest.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Artwork;
|
||||
use App\Models\ArtworkMetricSnapshotHourly;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
beforeEach(function (): void {
|
||||
Cache::flush();
|
||||
});
|
||||
|
||||
it('GET /rss/discover/rising returns 200', function (): void {
|
||||
$this->get('/rss/discover/rising')
|
||||
->assertOk();
|
||||
});
|
||||
|
||||
it('uses the low-signal fallback ordering in the RSS rising feed', function (): void {
|
||||
$olderActiveArtwork = Artwork::withoutEvents(fn () => Artwork::factory()->create([
|
||||
'title' => 'RSS Older Active Artwork',
|
||||
'is_public' => true,
|
||||
'is_approved' => true,
|
||||
'published_at' => now()->subDays(3),
|
||||
'created_at' => now()->subDays(3),
|
||||
]));
|
||||
|
||||
$newerQuietArtwork = Artwork::withoutEvents(fn () => Artwork::factory()->create([
|
||||
'title' => 'RSS Newer Quiet Artwork',
|
||||
'is_public' => true,
|
||||
'is_approved' => true,
|
||||
'published_at' => now()->subHour(),
|
||||
'created_at' => now()->subHour(),
|
||||
]));
|
||||
|
||||
$previousHour = now()->startOfHour()->subHour();
|
||||
$currentHour = now()->startOfHour();
|
||||
|
||||
ArtworkMetricSnapshotHourly::create([
|
||||
'artwork_id' => $olderActiveArtwork->id,
|
||||
'bucket_hour' => $previousHour,
|
||||
'views_count' => 10,
|
||||
'downloads_count' => 0,
|
||||
'favourites_count' => 0,
|
||||
'comments_count' => 0,
|
||||
'shares_count' => 0,
|
||||
]);
|
||||
|
||||
ArtworkMetricSnapshotHourly::create([
|
||||
'artwork_id' => $olderActiveArtwork->id,
|
||||
'bucket_hour' => $currentHour,
|
||||
'views_count' => 45,
|
||||
'downloads_count' => 2,
|
||||
'favourites_count' => 1,
|
||||
'comments_count' => 0,
|
||||
'shares_count' => 0,
|
||||
]);
|
||||
|
||||
$response = $this->get('/rss/discover/rising');
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertSeeInOrder([
|
||||
'RSS Older Active Artwork',
|
||||
'RSS Newer Quiet Artwork',
|
||||
], false);
|
||||
|
||||
expect($newerQuietArtwork->id)->not->toBe($olderActiveArtwork->id);
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Artwork;
|
||||
use App\Services\ArtworkService;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
@@ -40,3 +41,25 @@ it('home page still renders with rising section data', function () {
|
||||
$this->get('/')
|
||||
->assertStatus(200);
|
||||
});
|
||||
|
||||
it('uses the low-signal fallback ordering when rising search results have no momentum', function () {
|
||||
$olderArtwork = Artwork::withoutEvents(fn () => Artwork::factory()->create([
|
||||
'title' => 'Older Fallback Artwork',
|
||||
'is_public' => true,
|
||||
'is_approved' => true,
|
||||
'published_at' => now()->subDays(4),
|
||||
'created_at' => now()->subDays(4),
|
||||
]));
|
||||
|
||||
$newerArtwork = Artwork::withoutEvents(fn () => Artwork::factory()->create([
|
||||
'title' => 'Newer Fallback Artwork',
|
||||
'is_public' => true,
|
||||
'is_approved' => true,
|
||||
'published_at' => now()->subHour(),
|
||||
'created_at' => now()->subHour(),
|
||||
]));
|
||||
|
||||
$this->get('/discover/rising')
|
||||
->assertStatus(200)
|
||||
->assertSeeInOrder(['Newer Fallback Artwork', 'Older Fallback Artwork'], false);
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
use App\Models\Artwork;
|
||||
use App\Models\ArtworkStats;
|
||||
use App\Models\ArtworkMetricSnapshotHourly;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
/**
|
||||
* Helper: create an artwork row without triggering observers (avoids GREATEST() SQLite issue).
|
||||
@@ -137,6 +136,92 @@ it('computes heat_score from snapshot deltas', function () {
|
||||
expect((int) $stat->shares_1h)->toBe(1); // 1 - 0
|
||||
});
|
||||
|
||||
it('smooths heat_score over a wider lookback window while keeping 1h counters exact', function () {
|
||||
$artwork = createArtworkWithoutObserver([
|
||||
'is_approved' => true,
|
||||
'is_public' => true,
|
||||
'published_at' => now()->subHours(10),
|
||||
]);
|
||||
|
||||
ArtworkStats::upsert([
|
||||
['artwork_id' => $artwork->id, 'views' => 0, 'downloads' => 0, 'favorites' => 0],
|
||||
], ['artwork_id']);
|
||||
|
||||
$sixHoursAgo = now()->startOfHour()->subHours(6);
|
||||
$prevHour = now()->startOfHour()->subHour();
|
||||
$currentHour = now()->startOfHour();
|
||||
|
||||
ArtworkMetricSnapshotHourly::create([
|
||||
'artwork_id' => $artwork->id,
|
||||
'bucket_hour' => $sixHoursAgo,
|
||||
'views_count' => 10,
|
||||
'downloads_count' => 0,
|
||||
'favourites_count' => 0,
|
||||
'comments_count' => 0,
|
||||
'shares_count' => 0,
|
||||
]);
|
||||
|
||||
ArtworkMetricSnapshotHourly::create([
|
||||
'artwork_id' => $artwork->id,
|
||||
'bucket_hour' => $prevHour,
|
||||
'views_count' => 30,
|
||||
'downloads_count' => 1,
|
||||
'favourites_count' => 0,
|
||||
'comments_count' => 0,
|
||||
'shares_count' => 0,
|
||||
]);
|
||||
|
||||
ArtworkMetricSnapshotHourly::create([
|
||||
'artwork_id' => $artwork->id,
|
||||
'bucket_hour' => $currentHour,
|
||||
'views_count' => 30,
|
||||
'downloads_count' => 1,
|
||||
'favourites_count' => 0,
|
||||
'comments_count' => 0,
|
||||
'shares_count' => 0,
|
||||
]);
|
||||
|
||||
$this->artisan('nova:recalculate-heat --lookback-hours=6')
|
||||
->assertSuccessful();
|
||||
|
||||
$stat = ArtworkStats::where('artwork_id', $artwork->id)->first();
|
||||
|
||||
expect((float) $stat->heat_score)->toBeGreaterThan(0);
|
||||
expect((int) $stat->views_1h)->toBe(0);
|
||||
expect((int) $stat->downloads_1h)->toBe(0);
|
||||
});
|
||||
|
||||
it('does not assign heat from a single snapshot without a baseline', function () {
|
||||
$artwork = createArtworkWithoutObserver([
|
||||
'is_approved' => true,
|
||||
'is_public' => true,
|
||||
'published_at' => now()->subHours(5),
|
||||
]);
|
||||
|
||||
ArtworkStats::upsert([
|
||||
['artwork_id' => $artwork->id, 'views' => 0, 'downloads' => 0, 'favorites' => 0],
|
||||
], ['artwork_id']);
|
||||
|
||||
ArtworkMetricSnapshotHourly::create([
|
||||
'artwork_id' => $artwork->id,
|
||||
'bucket_hour' => now()->startOfHour(),
|
||||
'views_count' => 300,
|
||||
'downloads_count' => 25,
|
||||
'favourites_count' => 4,
|
||||
'comments_count' => 1,
|
||||
'shares_count' => 0,
|
||||
]);
|
||||
|
||||
$this->artisan('nova:recalculate-heat --lookback-hours=24')
|
||||
->assertSuccessful();
|
||||
|
||||
$stat = ArtworkStats::where('artwork_id', $artwork->id)->first();
|
||||
|
||||
expect((float) $stat->heat_score)->toBe(0.0);
|
||||
expect((int) $stat->views_1h)->toBe(300);
|
||||
expect((int) $stat->downloads_1h)->toBe(25);
|
||||
});
|
||||
|
||||
it('handles negative deltas gracefully by clamping to zero', function () {
|
||||
$artwork = createArtworkWithoutObserver([
|
||||
'is_approved' => true,
|
||||
|
||||
Reference in New Issue
Block a user