ids of alice, bob, carol, dave */ 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', ]); self::createTable('posts', [ 'id' => 'pk', 'user_id' => 'number', 'title' => 'string', 'content' => 'text', 'rating' => 'number', ]); $rows = [ ['alice', 'alice@duckbrain.dev', 'first', 20, true], ['bob', 'bob@duckbrain.dev', 'second', 30, true], ['carol', null, 'third', 40, false], ['dave', 'dave@duckbrain.dev', null, 50, true], ]; foreach ($rows 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->save(); self::$ids[] = (int) $user->id; } // A post owned by alice only, for the EXISTS tests. $post = new \Tests\Models\Post(); $post->userId = self::$ids[0]; $post->title = 'hello world'; $post->content = 'body'; $post->rating = 4.5; $post->save(); } /** @return list */ private static function sortedIds(array $instances): array { $ids = array_map(static fn (Model $m): int => (int) $m->id, $instances); sort($ids); return $ids; } #[Test] public function whereEqualityAndOperatorForms(): void { $this->assertSame( [self::$ids[1]], self::sortedIds(User::where('username', 'bob')->get()) ); $this->assertSame( [self::$ids[2], self::$ids[3]], self::sortedIds(User::where('age', '>', '35')->get()) ); } #[Test] public function andNestsConditions(): void { $this->assertSame( [self::$ids[3]], self::sortedIds(User::where('age', '>', '25')->and('username', 'dave')->get()) ); } #[Test] public function orCombinesAlternativeConditions(): void { $this->assertSame( [self::$ids[0], self::$ids[3]], self::sortedIds(User::where('username', 'alice')->or('username', 'dave')->get()) ); } #[Test] public function whereInAndWhereNotIn(): void { $in = self::sortedIds(User::whereIn('username', ['alice', 'carol'])->get()); $this->assertSame([self::$ids[0], self::$ids[2]], $in); $notIn = self::sortedIds(User::whereNotIn('username', ['alice', 'carol'])->get()); $this->assertSame([self::$ids[1], self::$ids[3]], $notIn); } #[Test] public function whereNullAndWhereNotNull(): void { $this->assertSame( [self::$ids[3]], self::sortedIds(User::whereNull('bio')->get()) ); $this->assertSame( [self::$ids[2]], self::sortedIds(User::whereNull('email')->get()) ); $this->assertCount(3, User::whereNotNull('email')->get()); } #[Test] public function whereExistsAndWhereNotExists(): void { $correlated = 'SELECT 1 FROM posts WHERE posts.user_id = users.id'; $this->assertSame( [self::$ids[0]], self::sortedIds(User::whereExists($correlated)->get()) ); $this->assertSame( [self::$ids[1], self::$ids[2], self::$ids[3]], self::sortedIds(User::whereNotExists($correlated)->get()) ); } #[Test] public function selectAndFromRestrictTheProjection(): void { $rows = User::select('username')->from('users')->where('username', 'bob')->get(); $this->assertCount(1, $rows); $this->assertSame('bob', $rows[0]->username); } #[Test] public function limitSingleAndOffsetForms(): void { $firstTwo = User::orderBy('id', 'ASC')->limit(2)->get(); $this->assertSame( [self::$ids[0], self::$ids[1]], array_map(static fn (Model $m): int => (int) $m->id, $firstTwo) ); // limit(offset, quantity) -> skip the first two. $lastTwo = User::orderBy('id', 'ASC')->limit(2, 2)->get(); $this->assertSame( [self::$ids[2], self::$ids[3]], array_map(static fn (Model $m): int => (int) $m->id, $lastTwo) ); } #[Test] public function orderByColumnAscendingAndDescending(): void { $asc = array_map(static fn (Model $m): string => (string) $m->age, User::orderBy('age', 'ASC')->get()); $this->assertSame(['20', '30', '40', '50'], array_map('strval', array_map('intval', $asc))); $descFirst = User::orderBy('age', 'DESC')->getFirst(); $this->assertSame('dave', $descFirst->username); } #[Test] public function countWithAndWithoutConditions(): void { $this->assertSame(4, User::count()); $this->assertSame(3, User::where('age', '>', '25')->count()); } #[Test] public function countWithUseLimit(): void { $this->assertSame(2, User::orderBy('id', 'ASC')->limit(2)->count(true, true)); } #[Test] public function countWithoutResetKeepsTheOriginalQuery(): void { User::where('age', '>', '25'); $count = User::count(false); $this->assertSame(3, $count); $this->assertSame( [self::$ids[1], self::$ids[2], self::$ids[3]], self::sortedIds(User::get()), 'count(false) must leave the where active; get() consumes it' ); } #[Test] public function queryStateResetsAfterExecution(): void { $one = User::where('id', self::$ids[0])->getFirst(); $this->assertSame('alice', $one->username); $this->assertCount(4, User::all(), 'where must not leak into the next query'); $this->assertCount(4, User::get()); } #[Test] public function orderByRandReturnsEveryRow(): void { // The readme matrix promises orderBy ok on the three engines. // Model emits literal RAND(); sqlite and PostgreSQL know RANDOM(). // This test documents the promise; failures are findings, not noise. $this->assertCount(4, User::orderBy('RAND')->get()); } }