'pk', 'label' => 'string', 'body' => 'text', 'amount' => 'number', 'active' => 'bool', 'created_at' => 'timestamp', ]); $insert = self::db()->prepare( 'INSERT INTO smoke_types (label, body, amount, active, created_at) VALUES (?, ?, ?, ?, ?)' ); $insert->execute(['widget', 'a longer free-form body', 12.5, true, '2026-09-05 12:34:56']); $row = self::db()->query('SELECT * FROM smoke_types')->fetch(); $this->assertSame(1, (int) $row['id']); $this->assertSame('widget', $row['label']); $this->assertSame('a longer free-form body', $row['body']); $this->assertEquals(12.5, (float) $row['amount']); $this->assertTrue(filter_var($row['active'], FILTER_VALIDATE_BOOLEAN)); $this->assertStringStartsWith('2026-09-05 12:34:56', (string) $row['created_at']); } #[Test] public function saveAssignsPrimaryKeyAndGetByIdRoundTrips(): void { self::createTable('users', [ 'id' => 'pk', 'username' => 'string', 'email' => 'string', 'bio' => 'text', 'age' => 'number', 'is_active' => 'bool', 'last_login' => 'timestamp', ]); $user = new User(); $user->username = 'keyjay'; $user->email = 'kj@duckbrain.dev'; $user->bio = 'framework keeper'; $user->age = 29; $user->isActive = true; $user->lastLogin = new DateTime('2026-09-05 10:00:00'); $user->save(); $this->assertSame(1, $user->id, 'save() should assign the generated primary key'); $found = User::getById(1); $this->assertNotNull($found); $this->assertSame('keyjay', $found->username); $this->assertSame(29, $found->age); $this->assertTrue($found->isActive); $this->assertInstanceOf(DateTime::class, $found->lastLogin); $this->assertSame('2026-09-05 10:00:00', $found->lastLogin->format('Y-m-d H:i:s')); } #[Test] public function unknownLogicalTypeFails(): void { $this->expectException(InvalidArgumentException::class); self::createTable('smoke_bad', ['x' => 'money']); } }