where('key', $key)->delete(); return; } DB::table('system_settings')->upsert( [ 'key' => $key, 'value' => (string) $months, 'type' => 'int', 'description' => "Test retention for {$table}", 'updated_at' => now(), ], ['key'], ['value', 'updated_at'], ); } // --------------------------------------------------------------------------- // Helper: create a test partition for a given table and month. // Returns partition name (e.g. auth_log_y2026_m02). // --------------------------------------------------------------------------- function createTestPartition(string $table, Carbon $monthStart): string { /** @var MonthlyPartitionManager $mgr */ $mgr = app(MonthlyPartitionManager::class); $mgr->ensureMonth($table, $monthStart); return $mgr->partitionName($table, $monthStart); } // --------------------------------------------------------------------------- // Helper: check whether a partition physically exists in pg_class. // Named with "Drop" prefix to avoid collision with MonthlyPartitionManagerTest. // --------------------------------------------------------------------------- function dropExpiredPartitionExists(string $partitionName): bool { return DB::selectOne( "SELECT 1 AS ok FROM pg_class WHERE relname = ? AND relkind = 'r'", [$partitionName], ) !== null; } // --------------------------------------------------------------------------- // Shared teardown: reset Carbon fake time after each test. // --------------------------------------------------------------------------- afterEach(function () { Carbon::setTestNow(null); }); // =========================================================================== // Tests // =========================================================================== test('dry-run: partition is not dropped', function () { Carbon::setTestNow('2026-05-15'); $oldMonth = Carbon::create(2026, 2, 1)->startOfMonth(); // 3 months ago $partition = createTestPartition('auth_log', $oldMonth); setRetention('auth_log', 1); // cutoff = 2026-04, so 2026-02 would normally drop expect(dropExpiredPartitionExists($partition))->toBeTrue('Partition must exist before command'); $this->artisan('partitions:drop-expired --dry-run')->assertSuccessful(); // Dry-run never physically drops expect(dropExpiredPartitionExists($partition))->toBeTrue('Dry-run must NOT drop the partition'); })->skip(fn () => ! authLogIsPartitioned(), 'auth_log is not partitioned (migration not applied)'); test('drops partition older than retention boundary', function () { Carbon::setTestNow('2026-05-15'); // retention=2: cutoff = 2026-03; 2026-02 is strictly older → drop $oldMonth = Carbon::create(2026, 2, 1)->startOfMonth(); $partition = createTestPartition('auth_log', $oldMonth); setRetention('auth_log', 2); expect(dropExpiredPartitionExists($partition))->toBeTrue(); $this->artisan('partitions:drop-expired')->assertSuccessful(); expect(dropExpiredPartitionExists($partition))->toBeFalse('Partition beyond retention must be dropped'); })->skip(fn () => ! authLogIsPartitioned(), 'auth_log is not partitioned (migration not applied)'); test('does not drop partition at the retention boundary (inclusive keep)', function () { Carbon::setTestNow('2026-05-15'); // retention=3: cutoff = 2026-02; 2026-02 is NOT strictly less than cutoff → keep $boundaryMonth = Carbon::create(2026, 2, 1)->startOfMonth(); $partition = createTestPartition('auth_log', $boundaryMonth); setRetention('auth_log', 3); expect(dropExpiredPartitionExists($partition))->toBeTrue(); $this->artisan('partitions:drop-expired')->assertSuccessful(); expect(dropExpiredPartitionExists($partition))->toBeTrue('Partition at boundary must NOT be dropped'); })->skip(fn () => ! authLogIsPartitioned(), 'auth_log is not partitioned (migration not applied)'); test('skips table when retention is not configured', function () { Carbon::setTestNow('2026-05-15'); setRetention('auth_log', null); // remove any retention setting $oldMonth = Carbon::create(2025, 1, 1)->startOfMonth(); $partition = createTestPartition('auth_log', $oldMonth); expect(dropExpiredPartitionExists($partition))->toBeTrue(); $this->artisan('partitions:drop-expired')->assertSuccessful(); expect(dropExpiredPartitionExists($partition))->toBeTrue('No retention config → nothing dropped'); })->skip(fn () => ! authLogIsPartitioned(), 'auth_log is not partitioned (migration not applied)'); test('skips table when retention value is 0 (safety guard)', function () { Carbon::setTestNow('2026-05-15'); $oldMonth = Carbon::create(2024, 1, 1)->startOfMonth(); $partition = createTestPartition('auth_log', $oldMonth); setRetention('auth_log', 0); expect(dropExpiredPartitionExists($partition))->toBeTrue(); $this->artisan('partitions:drop-expired')->assertSuccessful(); expect(dropExpiredPartitionExists($partition))->toBeTrue('retention=0 must be blocked — nothing dropped'); })->skip(fn () => ! authLogIsPartitioned(), 'auth_log is not partitioned (migration not applied)'); test('keeps recent partitions, drops only expired ones', function () { Carbon::setTestNow('2026-05-15'); // retention=2 → cutoff = 2026-03 // keep: 2026-05 (current), 2026-04 (1mo ago), 2026-03 (boundary) // drop: 2026-02 (3mo ago), 2026-01 (4mo ago) setRetention('auth_log', 2); $keep1 = createTestPartition('auth_log', Carbon::create(2026, 5, 1)); $keep2 = createTestPartition('auth_log', Carbon::create(2026, 4, 1)); $keep3 = createTestPartition('auth_log', Carbon::create(2026, 3, 1)); $drop1 = createTestPartition('auth_log', Carbon::create(2026, 2, 1)); $drop2 = createTestPartition('auth_log', Carbon::create(2026, 1, 1)); $this->artisan('partitions:drop-expired')->assertSuccessful(); expect(dropExpiredPartitionExists($keep1))->toBeTrue('2026-05 must be kept (current)'); expect(dropExpiredPartitionExists($keep2))->toBeTrue('2026-04 must be kept (within retention)'); expect(dropExpiredPartitionExists($keep3))->toBeTrue('2026-03 must be kept (at boundary)'); expect(dropExpiredPartitionExists($drop1))->toBeFalse('2026-02 must be dropped'); expect(dropExpiredPartitionExists($drop2))->toBeFalse('2026-01 must be dropped'); })->skip(fn () => ! authLogIsPartitioned(), 'auth_log is not partitioned (migration not applied)');