Files
portal/app/tests/Feature/Console/ResetMonthlyCountersCommandTest.php
T

76 lines
3.1 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Models\Project;
use App\Models\Tenant;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Tests\Concerns\SharesSupplierPdo;
uses(DatabaseTransactions::class);
uses(SharesSupplierPdo::class);
beforeEach(function () {
DB::statement("SELECT set_config('app.current_tenant_id', '0', true)");
});
it('resets tenants.delivered_in_month and projects.delivered_in_month to 0', function () {
$tenantA = Tenant::factory()->create(['delivered_in_month' => 100]);
$tenantB = Tenant::factory()->create(['delivered_in_month' => 5]);
$tenantC = Tenant::factory()->create(['delivered_in_month' => 0]);
Project::factory()->create(['tenant_id' => $tenantA->id, 'delivered_in_month' => 50]);
Project::factory()->create(['tenant_id' => $tenantA->id, 'delivered_in_month' => 50]);
Project::factory()->create(['tenant_id' => $tenantB->id, 'delivered_in_month' => 5]);
Artisan::call('projects:reset-monthly');
expect($tenantA->fresh()->delivered_in_month)->toBe(0);
expect($tenantB->fresh()->delivered_in_month)->toBe(0);
expect($tenantC->fresh()->delivered_in_month)->toBe(0);
expect(Project::sum('delivered_in_month'))->toBe(0);
});
it('is idempotent — second run reports 0 affected', function () {
$tenant = Tenant::factory()->create(['delivered_in_month' => 10]);
Project::factory()->create(['tenant_id' => $tenant->id, 'delivered_in_month' => 10]);
// Первый прогон сбрасывает оба счётчика.
Artisan::call('projects:reset-monthly');
// Второй прогон должен сообщить о 0 затронутых строках (WHERE delivered_in_month <> 0).
// info() пишет ОДНУ строку «Monthly reset: 0 tenants, 0 projects.» —
// expectsOutputToContain в Laravel 13 line-by-line, поэтому проверяем
// обе подстроки одним substring'ом.
$this->artisan('projects:reset-monthly')
->expectsOutputToContain('0 tenants, 0 projects')
->assertExitCode(0);
});
it('Schedule entry registered for monthly on 1st 00:00 Europe/Moscow', function () {
/** @var Schedule $schedule */
$schedule = app(Schedule::class);
$found = collect($schedule->events())->first(
fn ($event) => str_contains($event->command ?? '', 'projects:reset-monthly')
);
expect($found)->not->toBeNull();
expect($found->expression)->toBe('0 0 1 * *');
expect($found->timezone)->toBe('Europe/Moscow');
});
it('uses pgsql_supplier BYPASSRLS connection (touches all tenants without SET LOCAL)', function () {
Tenant::factory()->count(3)->create(['delivered_in_month' => 7]);
// Без SET LOCAL app.current_tenant_id reset должен затронуть всех 3 tenant'ов.
// Это тест на использование pgsql_supplier (BYPASSRLS), не default pgsql.
Artisan::call('projects:reset-monthly');
expect(Tenant::where('delivered_in_month', '>', 0)->count())->toBe(0);
});