*/ private static array $ids = []; public static function setUpBeforeClass(): void { self::createTable('users', [ 'id' => 'pk', 'username' => 'string', 'email' => 'string', 'bio' => 'text', 'age' => 'number', 'is_active' => 'bool', 'last_login' => 'timestamp', ]); $seed = [ ['alice', 'alice@duckbrain.dev', 'alpha keeper', 20, true], ['bob', 'bob@duckbrain.dev', 'beta reader', 30, true], ['carol', 'carol@duckbrain.dev', 'gamma dreamer', 40, false], ['dave', 'dave@duckbrain.dev', 'delta builder', 50, true], ]; foreach ($seed as [$username, $email, $bio, $age, $active]) { $user = new User(); $user->username = $username; $user->email = $email; $user->bio = $bio; $user->age = $age; $user->isActive = $active; $user->lastLogin = new DateTime('2026-09-05 10:00:00'); $user->save(); self::$ids[$username] = (int) $user->id; } } /** @return list */ private static function idsOf(array $rows): array { $ids = array_map(static fn (User $u): int => (int) $u->id, $rows); sort($ids); return $ids; } #[Test] public function searchMatchesAcrossAllDefaultColumns(): void { // 'ali' occurs only in alice's username/bio space. $this->assertSame( [self::$ids['alice']], self::idsOf(User::search('ali')->get()) ); } #[Test] public function searchFindsNumericValueThroughTextCasting(): void { // The number column must be matched by its textual form: sqlite // casts REAL to '30.0' while mysql/pgsql yield '30'; both contain 30. $this->assertSame( [self::$ids['bob']], self::idsOf(User::search('30')->get()) ); } #[Test] public function searchHonorsExplicitColumnList(): void { $this->assertSame( [self::$ids['alice']], self::idsOf(User::search('lpha', ['bio'])->get()) ); $this->assertSame( [self::$ids['carol']], self::idsOf(User::search('arol', ['username', 'email'])->get()) ); } #[Test] public function searchCombinesWithWhere(): void { $this->assertSame( [self::$ids['alice']], self::idsOf(User::where('is_active', '1')->search('li')->get()) ); } #[Test] public function everyColumnTypeFiltersConsistently(): void { $this->assertSame([self::$ids['alice']], self::idsOf(User::where('id', self::$ids['alice'])->get())); $this->assertSame([self::$ids['bob']], self::idsOf(User::where('email', 'bob@duckbrain.dev')->get())); $this->assertSame([self::$ids['carol']], self::idsOf(User::where('is_active', '0')->get())); $this->assertCount(4, User::where('last_login', '>', '2026-09-05 09:00:00')->get()); $this->assertSame([self::$ids['dave']], self::idsOf(User::where('age', '>=', '50')->get())); } #[Test] public function booleanFalseSurvivesTheRoundTrip(): void { // Promise of the matrix: typed properties hydrate faithfully on the // three engines. PostgreSQL reports boolean false as 'f' and the // core casts nothing -> this is the R2 probe. $carol = User::getById(self::$ids['carol']); $this->assertNotNull($carol); $this->assertFalse($carol->isActive, 'inactive user must hydrate as bool false'); } }