137 lines
6.7 KiB
PHP
137 lines
6.7 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Services\OneYearArticleLikes;
|
|
use Carbon\CarbonImmutable;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Tests\TestCase;
|
|
|
|
#[\PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses]
|
|
#[\PHPUnit\Framework\Attributes\PreserveGlobalState(false)]
|
|
class OneYearArticleLikesTest extends TestCase
|
|
{
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
config(['database.default' => 'sqlite', 'database.connections.sqlite.database' => ':memory:',
|
|
'scout.driver' => null, 'cache.default' => 'array']);
|
|
DB::purge('sqlite');
|
|
Schema::create('articles', function (Blueprint $t) {
|
|
$t->id();
|
|
$t->integer('iStatus')->default(1);
|
|
foreach (['iLikeCount', 'i7like', 'i365like'] as $field) {
|
|
$t->integer($field)->default(0);
|
|
}
|
|
$t->timestamp('sLastLikedAt')->nullable();
|
|
$t->timestamps();
|
|
});
|
|
(require database_path('migrations/2026_10_29_200000_add_one_year_article_likes.php'))->up();
|
|
$this->travelTo(CarbonImmutable::parse('2026-01-01 12:00:00'));
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
$this->travelBack();
|
|
parent::tearDown();
|
|
}
|
|
|
|
private function article(array $values = []): int
|
|
{
|
|
return DB::table('articles')->insertGetId($values + [
|
|
'iYearLikeTarget' => 200, 'sYearLikeStartedAt' => now(),
|
|
'created_at' => now(), 'updated_at' => now(),
|
|
]);
|
|
}
|
|
|
|
public function test_quota_is_paid_gradually_exactly_once_and_stops(): void
|
|
{
|
|
$id = $this->article(['iLikeCount' => 17]);
|
|
$service = app(OneYearArticleLikes::class);
|
|
$this->assertSame(0, $service->sync());
|
|
$this->travel(183)->days();
|
|
$this->assertSame(100, $service->sync());
|
|
$this->assertSame(0, $service->sync());
|
|
DB::table('articles')->where('id', $id)->increment('iLikeCount', 3);
|
|
$this->travelTo(CarbonImmutable::parse('2027-01-01 12:00:00'));
|
|
$this->assertSame(100, $service->sync());
|
|
$this->assertSame(200, (int) DB::table('articles')->value('iYearLikeAdded'));
|
|
$this->assertSame(220, (int) DB::table('articles')->value('iLikeCount'));
|
|
$this->assertSame('2026-01-01 12:00:00', DB::table('articles')->value('updated_at'));
|
|
$this->travel(400)->days();
|
|
$this->assertSame(0, $service->sync());
|
|
$this->assertSame(220, (int) DB::table('articles')->value('iLikeCount'));
|
|
}
|
|
|
|
public function test_deadline_catches_up_after_downtime_but_ignores_disabled_and_future_plans(): void
|
|
{
|
|
$this->article(['iYearLikeTarget' => 500]);
|
|
$this->article(['iYearLikeTarget' => 0]);
|
|
$this->article(['iStatus' => 0]);
|
|
$this->article(['sYearLikeStartedAt' => '2030-01-01 00:00:00']);
|
|
$this->travel(370)->days();
|
|
$this->assertSame(500, app(OneYearArticleLikes::class)->sync());
|
|
$this->assertSame(500, (int) DB::table('articles')->sum('iYearLikeAdded'));
|
|
$this->assertSame(0, app(OneYearArticleLikes::class)->sync());
|
|
}
|
|
|
|
public function test_leap_day_uses_calendar_year_and_null_start_is_initialized_without_backfill(): void
|
|
{
|
|
$this->travelTo(CarbonImmutable::parse('2024-02-29 12:00:00'));
|
|
$id = $this->article(['iYearLikeTarget' => 365]);
|
|
$this->travelTo(CarbonImmutable::parse('2025-02-28 12:00:00'));
|
|
$this->assertSame(365, app(OneYearArticleLikes::class)->syncArticle($id));
|
|
$new = $this->article(['sYearLikeStartedAt' => null, 'created_at' => '2020-01-01 00:00:00']);
|
|
$this->assertSame(0, app(OneYearArticleLikes::class)->syncArticle($new));
|
|
$this->assertSame('2025-02-28 12:00:00', DB::table('articles')->where('id', $new)->value('sYearLikeStartedAt'));
|
|
}
|
|
|
|
public function test_backfill_is_limited_to_editorial_missing_settings_and_is_idempotent(): void
|
|
{
|
|
Schema::table('articles', fn (Blueprint $t) => $t->string('sSlug')->nullable());
|
|
Schema::create('editorial_documents', function (Blueprint $t) {
|
|
$t->integer('article_id');
|
|
$t->integer('published_revision')->nullable();
|
|
});
|
|
$blank = $this->article(['iYearLikeTarget' => 0, 'sYearLikeStartedAt' => null, 'iLikeCount' => 12]);
|
|
$legacy = $this->article(['iYearLikeTarget' => 0, 'sYearLikeStartedAt' => null, 'i365like' => 30]);
|
|
$resource = $this->article(['iYearLikeTarget' => 0, 'sYearLikeStartedAt' => null]);
|
|
DB::table('editorial_documents')->insert([
|
|
['article_id' => $blank, 'published_revision' => 1],
|
|
['article_id' => $legacy, 'published_revision' => 1],
|
|
]);
|
|
$this->artisan('article:backfill-editorial-likes')->assertSuccessful();
|
|
$this->assertSame(0, (int) DB::table('articles')->sum('iYearLikeTarget'));
|
|
$this->artisan('article:backfill-editorial-likes --apply')->assertSuccessful();
|
|
$a = DB::table('articles')->where('id', $blank)->first();
|
|
$this->assertGreaterThanOrEqual(5, (int) $a->i7like);
|
|
$this->assertLessThanOrEqual(35, (int) $a->i7like);
|
|
$this->assertSame(50, (int) $a->i365like);
|
|
$this->assertSame(12, (int) $a->iLikeCount);
|
|
$this->assertGreaterThanOrEqual(100, (int) $a->iYearLikeTarget);
|
|
$this->assertLessThanOrEqual(500, (int) $a->iYearLikeTarget);
|
|
$this->assertSame(30, (int) DB::table('articles')->where('id', $legacy)->value('i365like'));
|
|
$this->assertSame(0, (int) DB::table('articles')->where('id', $legacy)->value('i7like'));
|
|
$this->assertSame(0, (int) DB::table('articles')->where('id', $resource)->value('iYearLikeTarget'));
|
|
$before = DB::table('articles')->orderBy('id')->get()->toJson();
|
|
$this->artisan('article:backfill-editorial-likes --apply')->assertSuccessful();
|
|
$this->assertSame($before, DB::table('articles')->orderBy('id')->get()->toJson());
|
|
}
|
|
|
|
public function test_scheduled_command_runs_the_quota_without_resetting_other_settings(): void
|
|
{
|
|
$id = $this->article(['i365like' => 365]);
|
|
$this->travel(365)->days();
|
|
$this->artisan('article:sync-likes')->assertSuccessful();
|
|
$this->artisan('article:sync-likes')->assertSuccessful();
|
|
$this->assertSame(201, (int) DB::table('articles')->where('id', $id)->value('iLikeCount'));
|
|
$this->travel(1)->days();
|
|
$this->artisan('article:sync-likes')->assertSuccessful();
|
|
$this->assertSame(202, (int) DB::table('articles')->where('id', $id)->value('iLikeCount'));
|
|
$this->assertSame(365, (int) DB::table('articles')->where('id', $id)->value('i365like'));
|
|
$this->assertSame(200, (int) DB::table('articles')->where('id', $id)->value('iYearLikeAdded'));
|
|
}
|
|
}
|