144 lines
4.8 KiB
PHP
144 lines
4.8 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace Tests\Unit\Dashboard\Stats;
|
||
|
|
|
||
|
|
use App\Dashboard\Stats\PublishSuccessRate;
|
||
|
|
use App\Enums\PublishStatusEnum;
|
||
|
|
use App\Models\RouteArticle;
|
||
|
|
use App\Support\DateRange;
|
||
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||
|
|
use Illuminate\Support\Carbon;
|
||
|
|
use Tests\TestCase;
|
||
|
|
|
||
|
|
class PublishSuccessRateTest extends TestCase
|
||
|
|
{
|
||
|
|
use RefreshDatabase;
|
||
|
|
|
||
|
|
private function range(): DateRange
|
||
|
|
{
|
||
|
|
return new DateRange(
|
||
|
|
Carbon::parse('2026-07-01 00:00:00'),
|
||
|
|
Carbon::parse('2026-07-31 23:59:59'),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return array<string, float|null>
|
||
|
|
*/
|
||
|
|
private function values(): array
|
||
|
|
{
|
||
|
|
$result = (new PublishSuccessRate)->for($this->range());
|
||
|
|
|
||
|
|
return array_combine($result->labels, $result->series[0]->values);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function attempt(PublishStatusEnum $status, string $updatedAt): RouteArticle
|
||
|
|
{
|
||
|
|
/** @var RouteArticle $article */
|
||
|
|
$article = RouteArticle::factory()->create(['publish_status' => $status]);
|
||
|
|
|
||
|
|
// Model::update() would re-stamp updated_at to now(); bypass via the query builder.
|
||
|
|
RouteArticle::query()
|
||
|
|
->whereKey($article->getKey())
|
||
|
|
->update(['updated_at' => Carbon::parse($updatedAt)]);
|
||
|
|
|
||
|
|
return $article;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_it_computes_the_published_share_of_daily_attempts(): void
|
||
|
|
{
|
||
|
|
$this->attempt(PublishStatusEnum::PUBLISHED, '2026-07-10 09:00:00');
|
||
|
|
$this->attempt(PublishStatusEnum::PUBLISHED, '2026-07-10 10:00:00');
|
||
|
|
$this->attempt(PublishStatusEnum::PUBLISHED, '2026-07-10 11:00:00');
|
||
|
|
$this->attempt(PublishStatusEnum::ERROR, '2026-07-10 12:00:00');
|
||
|
|
|
||
|
|
$this->assertSame(75.0, $this->values()['2026-07-10']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_it_excludes_skipped_articles_from_the_denominator(): void
|
||
|
|
{
|
||
|
|
$this->attempt(PublishStatusEnum::PUBLISHED, '2026-07-10 09:00:00');
|
||
|
|
$this->attempt(PublishStatusEnum::SKIPPED, '2026-07-10 10:00:00');
|
||
|
|
|
||
|
|
$this->assertSame(100.0, $this->values()['2026-07-10']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_it_excludes_articles_still_in_flight(): void
|
||
|
|
{
|
||
|
|
$this->attempt(PublishStatusEnum::PUBLISHED, '2026-07-10 09:00:00');
|
||
|
|
$this->attempt(PublishStatusEnum::PUBLISHING, '2026-07-10 10:00:00');
|
||
|
|
$this->attempt(PublishStatusEnum::UNPUBLISHED, '2026-07-10 11:00:00');
|
||
|
|
|
||
|
|
$this->assertSame(100.0, $this->values()['2026-07-10']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_it_reports_no_rate_for_a_day_with_no_attempts(): void
|
||
|
|
{
|
||
|
|
$this->attempt(PublishStatusEnum::PUBLISHED, '2026-07-10 09:00:00');
|
||
|
|
|
||
|
|
$this->assertNull($this->values()['2026-07-11']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_it_reports_a_zero_rate_when_every_attempt_failed(): void
|
||
|
|
{
|
||
|
|
$this->attempt(PublishStatusEnum::ERROR, '2026-07-10 09:00:00');
|
||
|
|
|
||
|
|
$this->assertSame(0.0, $this->values()['2026-07-10']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_it_counts_an_article_on_the_day_it_was_last_touched(): void
|
||
|
|
{
|
||
|
|
$this->attempt(PublishStatusEnum::ERROR, '2026-07-10 09:00:00');
|
||
|
|
$this->attempt(PublishStatusEnum::PUBLISHED, '2026-07-12 09:00:00');
|
||
|
|
|
||
|
|
$values = $this->values();
|
||
|
|
|
||
|
|
$this->assertSame(0.0, $values['2026-07-10']);
|
||
|
|
$this->assertSame(100.0, $values['2026-07-12']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_a_retry_moves_the_article_to_the_retry_day(): void
|
||
|
|
{
|
||
|
|
$article = $this->attempt(PublishStatusEnum::ERROR, '2026-07-10 09:00:00');
|
||
|
|
|
||
|
|
RouteArticle::query()
|
||
|
|
->whereKey($article->getKey())
|
||
|
|
->update([
|
||
|
|
'publish_status' => PublishStatusEnum::PUBLISHED,
|
||
|
|
'updated_at' => Carbon::parse('2026-07-12 09:00:00'),
|
||
|
|
]);
|
||
|
|
|
||
|
|
$values = $this->values();
|
||
|
|
|
||
|
|
$this->assertNull($values['2026-07-10']);
|
||
|
|
$this->assertSame(100.0, $values['2026-07-12']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_it_ignores_attempts_outside_the_range(): void
|
||
|
|
{
|
||
|
|
$this->attempt(PublishStatusEnum::PUBLISHED, '2026-06-30 23:00:00');
|
||
|
|
$this->attempt(PublishStatusEnum::ERROR, '2026-08-01 01:00:00');
|
||
|
|
|
||
|
|
$this->assertSame([], array_filter($this->values(), fn (?float $value): bool => $value !== null));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_it_rounds_the_rate_to_one_decimal(): void
|
||
|
|
{
|
||
|
|
$this->attempt(PublishStatusEnum::PUBLISHED, '2026-07-10 09:00:00');
|
||
|
|
$this->attempt(PublishStatusEnum::PUBLISHED, '2026-07-10 10:00:00');
|
||
|
|
$this->attempt(PublishStatusEnum::ERROR, '2026-07-10 11:00:00');
|
||
|
|
|
||
|
|
$this->assertSame(66.7, $this->values()['2026-07-10']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_it_refuses_to_bucket_a_range_wider_than_the_day_limit(): void
|
||
|
|
{
|
||
|
|
$result = (new PublishSuccessRate)->for(new DateRange(
|
||
|
|
Carbon::parse('2020-01-01 00:00:00'),
|
||
|
|
Carbon::parse('2026-12-31 23:59:59'),
|
||
|
|
));
|
||
|
|
|
||
|
|
$this->assertTrue($result->isTooWide());
|
||
|
|
}
|
||
|
|
}
|