*/ 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()); } }