Files
SkinbaseNova/tests/Feature/PruneMetricSnapshotsCommandTest.php
klevze 8a80aae21e Ship production optimization M1-M12.5A: queues, metrics, HTTP observability, and vector search reliability.
Keep similar-ai from tripping the global circuit on a lone URL 502, clamp Qdrant search to 100, and add Server-Timing plus slow-request logging. Studio shared props, Academy S3 exists caching, heat chunking, and Redis/scheduler hygiene stay in this rollout.
2026-08-25 07:58:47 +02:00

165 lines
5.1 KiB
PHP

<?php
use App\Models\ArtworkMetricSnapshotHourly;
it('prunes snapshots older than keep-days and keeps recent ones', function () {
$artwork = createArtworkWithoutObserver([
'is_approved' => true,
'is_public' => true,
'published_at' => now()->subDays(40),
]);
ArtworkMetricSnapshotHourly::create([
'artwork_id' => $artwork->id,
'bucket_hour' => now()->subDays(40)->startOfHour(),
'views_count' => 10,
'downloads_count' => 1,
'favourites_count' => 0,
'comments_count' => 0,
'shares_count' => 0,
]);
ArtworkMetricSnapshotHourly::create([
'artwork_id' => $artwork->id,
'bucket_hour' => now()->subDays(10)->startOfHour(),
'views_count' => 20,
'downloads_count' => 2,
'favourites_count' => 0,
'comments_count' => 0,
'shares_count' => 0,
]);
ArtworkMetricSnapshotHourly::create([
'artwork_id' => $artwork->id,
'bucket_hour' => now()->subHour()->startOfHour(),
'views_count' => 30,
'downloads_count' => 3,
'favourites_count' => 0,
'comments_count' => 0,
'shares_count' => 0,
]);
$this->artisan('nova:prune-metric-snapshots --keep-days=30 --sleep-ms=0')
->assertSuccessful();
$hours = ArtworkMetricSnapshotHourly::where('artwork_id', $artwork->id)
->orderBy('bucket_hour')
->pluck('views_count')
->all();
expect($hours)->toBe([20, 30]);
});
it('deletes in bounded batches and is idempotent', function () {
$artwork = createArtworkWithoutObserver([
'is_approved' => true,
'is_public' => true,
'published_at' => now()->subDays(20),
]);
foreach (range(1, 5) as $daysAgo) {
ArtworkMetricSnapshotHourly::create([
'artwork_id' => $artwork->id,
'bucket_hour' => now()->subDays($daysAgo + 10)->startOfHour(),
'views_count' => $daysAgo,
'downloads_count' => 0,
'favourites_count' => 0,
'comments_count' => 0,
'shares_count' => 0,
]);
}
$this->artisan('nova:prune-metric-snapshots --keep-days=7 --chunk=2 --sleep-ms=0')
->assertSuccessful();
expect(ArtworkMetricSnapshotHourly::where('artwork_id', $artwork->id)->count())->toBe(0);
$this->artisan('nova:prune-metric-snapshots --keep-days=7 --chunk=2 --sleep-ms=0')
->assertSuccessful();
expect(ArtworkMetricSnapshotHourly::where('artwork_id', $artwork->id)->count())->toBe(0);
});
it('dry-run does not delete rows', function () {
$artwork = createArtworkWithoutObserver([
'is_approved' => true,
'is_public' => true,
'published_at' => now()->subDays(20),
]);
ArtworkMetricSnapshotHourly::create([
'artwork_id' => $artwork->id,
'bucket_hour' => now()->subDays(14)->startOfHour(),
'views_count' => 1,
'downloads_count' => 0,
'favourites_count' => 0,
'comments_count' => 0,
'shares_count' => 0,
]);
$this->artisan('nova:prune-metric-snapshots --keep-days=7 --dry-run')
->assertSuccessful();
expect(ArtworkMetricSnapshotHourly::where('artwork_id', $artwork->id)->count())->toBe(1);
});
it('uses config retention when keep-days is omitted', function () {
config(['metrics.hourly_snapshot_retention_days' => 7]);
$artwork = createArtworkWithoutObserver([
'is_approved' => true,
'is_public' => true,
'published_at' => now()->subDays(20),
]);
ArtworkMetricSnapshotHourly::create([
'artwork_id' => $artwork->id,
'bucket_hour' => now()->subDays(10)->startOfHour(),
'views_count' => 1,
'downloads_count' => 0,
'favourites_count' => 0,
'comments_count' => 0,
'shares_count' => 0,
]);
ArtworkMetricSnapshotHourly::create([
'artwork_id' => $artwork->id,
'bucket_hour' => now()->subDays(2)->startOfHour(),
'views_count' => 2,
'downloads_count' => 0,
'favourites_count' => 0,
'comments_count' => 0,
'shares_count' => 0,
]);
$this->artisan('nova:prune-metric-snapshots --sleep-ms=0')
->assertSuccessful();
expect(ArtworkMetricSnapshotHourly::where('artwork_id', $artwork->id)->count())->toBe(1);
});
it('honours max-batches so a run cannot delete the whole table at once', function () {
$artwork = createArtworkWithoutObserver([
'is_approved' => true,
'is_public' => true,
'published_at' => now()->subDays(20),
]);
foreach (range(1, 4) as $n) {
ArtworkMetricSnapshotHourly::create([
'artwork_id' => $artwork->id,
'bucket_hour' => now()->subDays(10 + $n)->startOfHour(),
'views_count' => $n,
'downloads_count' => 0,
'favourites_count' => 0,
'comments_count' => 0,
'shares_count' => 0,
]);
}
$this->artisan('nova:prune-metric-snapshots --keep-days=7 --chunk=1 --max-batches=2 --sleep-ms=0')
->assertSuccessful();
expect(ArtworkMetricSnapshotHourly::where('artwork_id', $artwork->id)->count())->toBe(2);
});