'pk', 'username' => 'string', 'email' => 'string', 'bio' => 'text', 'age' => 'number', 'is_active' => 'bool', 'last_login' => 'timestamp', ]); } private static function seed(string $username): User { $user = new User(); $user->username = $username; $user->email = $username . '@duckbrain.dev'; $user->age = 25; $user->save(); return $user; } #[Test] public function commitMakesPendingWritesVisible(): void { User::beginTransaction(); $user = self::seed('committed_alice'); $this->assertTrue(User::commit()); $this->assertNotNull(User::getById($user->id)); } #[Test] public function rollBackDiscardsPendingWrites(): void { User::beginTransaction(); $user = self::seed('rolled_bob'); $this->assertTrue(User::rollBack()); $this->assertNull(User::getById($user->id)); } #[Test] public function aFailingQueryRollsBackTheOpenTransaction(): void { User::beginTransaction(); $user = self::seed('wrecked_carol'); try { (new ReflectionMethod(User::class, 'query')) ->invokeArgs(null, ['SELECT * FROM table_that_does_not_exist']); $this->fail('a failing query inside a transaction must throw'); } catch (Exception $e) { $this->assertStringContainsString('Error at query to database', $e->getMessage()); } $this->assertFalse(self::db()->inTransaction(), 'the exception path must close the transaction'); $this->assertNull(User::where('username', 'wrecked_carol')->getFirst(), 'pending insert must be undone'); } }