toContain('auth_log') ->toContain('activity_log') ->toContain('tenant_operations_log') ->toContain('balance_transactions') ->toContain('pd_processing_log') ->toContain('saas_admin_audit_log'); expect(count($tables))->toBe(6); }); it('AuditChainConfig::rowExpression builds ROW expression with NULL::bytea at log_hash position', function (): void { $expr = AuditChainConfig::rowExpression('auth_log'); expect($expr)->toStartWith('ROW(') ->toContain('NULL::bytea') ->not->toContain('t.__log_hash__'); }); it('AuditChainConfig::rowExpression produces same result for all six tables', function (): void { foreach (array_keys(AuditChainConfig::TABLES) as $table) { $expr = AuditChainConfig::rowExpression($table); expect($expr) ->toStartWith('ROW(') ->toContain('NULL::bytea') ->not->toContain('t.__log_hash__'); } }); it('AuditChainConfig::rowExpression throws for unknown table', function (): void { AuditChainConfig::rowExpression('nonexistent_table'); })->throws(InvalidArgumentException::class); it('VerifyAuditChains command class exists and is registered', function (): void { expect(class_exists(VerifyAuditChains::class))->toBeTrue(); }); it('VerifyAuditChains does not have private TABLE_CONFIG const after ADR-018 refactor', function (): void { $reflection = new ReflectionClass(VerifyAuditChains::class); $constants = $reflection->getReflectionConstants(); $names = array_map(fn ($c) => $c->getName(), $constants); // After Task 2 refactor, TABLE_CONFIG should be removed (delegated to AuditChainConfig::TABLES) expect($names)->not->toContain('TABLE_CONFIG'); });