* @license MIT */ declare(strict_types=1); namespace Test\Integration\PostgreSQL; use BitBadger\PDODocument\{AutoId, Configuration, Custom, Document, DocumentException, Field, Find, Query}; use BitBadger\PDODocument\Mapper\ArrayMapper; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; use Test\Integration\{NumDocument, SubDocument, TestDocument}; /** * PostgreSQL integration tests for the Document class */ #[TestDox('Document (PostgreSQL integration)')] class DocumentTest extends TestCase { /** @var string Database name for throwaway database */ private string $dbName; protected function setUp(): void { parent::setUp(); $this->dbName = ThrowawayDb::create(); } protected function tearDown(): void { ThrowawayDb::destroy($this->dbName); parent::tearDown(); } #[TestDox('Insert succeeds for array no auto ID')] public function testInsertSucceedsForArrayNoAutoId(): void { Document::insert(ThrowawayDb::TABLE, ['id' => 'turkey', 'sub' => ['foo' => 'gobble', 'bar' => 'gobble']]); $tryDoc = Find::byId(ThrowawayDb::TABLE, 'turkey', TestDocument::class); $this->assertTrue($tryDoc->isDefined(), 'There should have been a document inserted'); $doc = $tryDoc->get(); $this->assertEquals('turkey', $doc->id, 'The ID was incorrect'); $this->assertEquals('', $doc->value, 'The value was incorrect'); $this->assertEquals(0, $doc->num_value, 'The numeric value was incorrect'); $this->assertNotNull($doc->sub, 'The sub-document should not have been null'); $this->assertEquals('gobble', $doc->sub->foo, 'The sub-document foo property was incorrect'); $this->assertEquals('gobble', $doc->sub->bar, 'The sub-document bar property was incorrect'); } #[TestDox('Insert succeeds for array with auto number ID not provided')] public function testInsertSucceedsForArrayWithAutoNumberIdNotProvided(): void { Configuration::$autoId = AutoId::Number; try { Custom::nonQuery('DELETE FROM ' . ThrowawayDb::TABLE, []); Document::insert(ThrowawayDb::TABLE, ['id' => 0, 'value' => 'new', 'num_value' => 8]); $doc = Custom::single('SELECT data FROM ' . ThrowawayDb::TABLE, [], new ArrayMapper()); $this->assertTrue($doc->isDefined(), 'There should have been a document returned'); $obj = json_decode($doc->get()['data']); $this->assertEquals(1, $obj->id, 'The ID 1 should have been auto-generated'); Document::insert(ThrowawayDb::TABLE, ['id' => 0, 'value' => 'again', 'num_value' => 7]); $doc = Custom::single('SELECT data FROM ' . ThrowawayDb::TABLE . " WHERE " . Query::whereById(docId: 2), [':id' => 2], new ArrayMapper()); $this->assertTrue($doc->isDefined(), 'There should have been a document returned'); $obj = json_decode($doc->get()['data']); $this->assertEquals(2, $obj->id, 'The ID 2 should have been auto-generated'); } finally { Configuration::$autoId = AutoId::None; } } #[TestDox('Insert succeeds for array with auto number ID with ID provided')] public function testInsertSucceedsForArrayWithAutoNumberIdWithIdProvided(): void { Configuration::$autoId = AutoId::Number; try { Custom::nonQuery('DELETE FROM ' . ThrowawayDb::TABLE, []); Document::insert(ThrowawayDb::TABLE, ['id' => 7, 'value' => 'new', 'num_value' => 8]); $doc = Custom::single('SELECT data FROM ' . ThrowawayDb::TABLE, [], new ArrayMapper()); $this->assertTrue($doc->isDefined(), 'There should have been a document returned'); $obj = json_decode($doc->get()['data']); $this->assertEquals(7, $obj->id, 'The ID 7 should have been stored'); } finally { Configuration::$autoId = AutoId::None; } } #[TestDox('Insert succeeds for array with auto UUID ID not provided')] public function testInsertSucceedsForArrayWithAutoUuidIdNotProvided(): void { Configuration::$autoId = AutoId::UUID; try { Custom::nonQuery('DELETE FROM ' . ThrowawayDb::TABLE, []); Document::insert(ThrowawayDb::TABLE, ['id' => '', 'num_value' => 5]); $doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('num_value', 5)], TestDocument::class); $this->assertTrue($doc->isDefined(), 'There should have been a document returned'); $this->assertNotEmpty($doc->get()->id, 'The ID should have been auto-generated'); } finally { Configuration::$autoId = AutoId::None; } } #[TestDox('Insert succeeds for array with auto UUID ID with ID provided')] public function testInsertSucceedsForArrayWithAutoUuidIdWithIdProvided(): void { Configuration::$autoId = AutoId::UUID; try { Custom::nonQuery('DELETE FROM ' . ThrowawayDb::TABLE, []); $uuid = AutoId::generateUUID(); Document::insert(ThrowawayDb::TABLE, ['id' => $uuid, 'value' => 'uuid', 'num_value' => 12]); $doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('num_value', 12)], TestDocument::class); $this->assertTrue($doc->isDefined(), 'There should have been a document returned'); $this->assertEquals($uuid, $doc->get()->id, 'The ID should not have been changed'); } finally { Configuration::$autoId = AutoId::None; } } #[TestDox('Insert succeeds for array with auto string ID not provided')] public function testInsertSucceedsForArrayWithAutoStringIdNotProvided(): void { Configuration::$autoId = AutoId::RandomString; Configuration::$idStringLength = 6; try { Custom::nonQuery('DELETE FROM ' . ThrowawayDb::TABLE, []); Document::insert(ThrowawayDb::TABLE, ['id' => '', 'value' => 'new', 'num_value' => 8]); $doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('num_value', 8)], TestDocument::class); $this->assertTrue($doc->isDefined(), 'There should have been a document returned'); $this->assertEquals(6, strlen($doc->get()->id), 'The ID should have been auto-generated and had 6 characters'); } finally { Configuration::$autoId = AutoId::None; Configuration::$idStringLength = 16; } } #[TestDox('Insert succeeds for array with auto string ID with ID provided')] public function testInsertSucceedsForArrayWithAutoStringIdWithIdProvided(): void { Configuration::$autoId = AutoId::RandomString; try { Custom::nonQuery('DELETE FROM ' . ThrowawayDb::TABLE, []); Document::insert(ThrowawayDb::TABLE, ['id' => 'my-key', 'value' => 'old', 'num_value' => 3]); $doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('num_value', 3)], TestDocument::class); $this->assertTrue($doc->isDefined(), 'There should have been a document returned'); $this->assertEquals('my-key', $doc->get()->id, 'The ID should not have been changed'); } finally { Configuration::$autoId = AutoId::None; } } #[TestDox('Insert succeeds for object no auto ID')] public function testInsertSucceedsForObjectNoAutoId(): void { Document::insert(ThrowawayDb::TABLE, new TestDocument('turkey', sub: new SubDocument('gobble', 'gobble'))); $tryDoc = Find::byId(ThrowawayDb::TABLE, 'turkey', TestDocument::class); $this->assertTrue($tryDoc->isDefined(), 'There should have been a document inserted'); $doc = $tryDoc->get(); $this->assertEquals('turkey', $doc->id, 'The ID was incorrect'); $this->assertEquals('', $doc->value, 'The value was incorrect'); $this->assertEquals(0, $doc->num_value, 'The numeric value was incorrect'); $this->assertNotNull($doc->sub, 'The sub-document should not have been null'); $this->assertEquals('gobble', $doc->sub->foo, 'The sub-document foo property was incorrect'); $this->assertEquals('gobble', $doc->sub->bar, 'The sub-document bar property was incorrect'); } #[TestDox('Insert succeeds for object with auto number ID not provided')] public function testInsertSucceedsForObjectWithAutoNumberIdNotProvided(): void { Configuration::$autoId = AutoId::Number; try { Custom::nonQuery('DELETE FROM ' . ThrowawayDb::TABLE, []); Document::insert(ThrowawayDb::TABLE, new NumDocument(value: 'taco')); $doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('value', 'taco')], NumDocument::class); $this->assertTrue($doc->isDefined(), 'There should have been a document returned'); $this->assertEquals(1, $doc->get()->id, 'The ID 1 should have been auto-generated'); Document::insert(ThrowawayDb::TABLE, new NumDocument(value: 'burrito')); $doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('value', 'burrito')], NumDocument::class); $this->assertTrue($doc->isDefined(), 'There should have been a document returned'); $this->assertEquals(2, $doc->get()->id, 'The ID 2 should have been auto-generated'); } finally { Configuration::$autoId = AutoId::None; } } #[TestDox('Insert succeeds for object with auto number ID with ID provided')] public function testInsertSucceedsForObjectWithAutoNumberIdWithIdProvided(): void { Configuration::$autoId = AutoId::Number; try { Custom::nonQuery('DELETE FROM ' . ThrowawayDb::TABLE, []); Document::insert(ThrowawayDb::TABLE, new NumDocument(64, 'large')); $doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('value', 'large')], NumDocument::class); $this->assertTrue($doc->isDefined(), 'There should have been a document returned'); $this->assertEquals(64, $doc->get()->id, 'The ID 64 should have been stored'); } finally { Configuration::$autoId = AutoId::None; } } #[TestDox('Insert succeeds for object with auto UUID ID not provided')] public function testInsertSucceedsForObjectWithAutoUuidIdNotProvided(): void { Configuration::$autoId = AutoId::UUID; try { Custom::nonQuery('DELETE FROM ' . ThrowawayDb::TABLE, []); Document::insert(ThrowawayDb::TABLE, new TestDocument(value: 'something', num_value: 9)); $doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EX('value')], TestDocument::class); $this->assertTrue($doc->isDefined(), 'There should have been a document returned'); $this->assertNotEmpty($doc->get()->id, 'The ID should have been auto-generated'); } finally { Configuration::$autoId = AutoId::None; } } #[TestDox('Insert succeeds for object with auto UUID ID with ID provided')] public function testInsertSucceedsForObjectWithAutoUuidIdWithIdProvided(): void { Configuration::$autoId = AutoId::UUID; try { Custom::nonQuery('DELETE FROM ' . ThrowawayDb::TABLE, []); $uuid = AutoId::generateUUID(); Document::insert(ThrowawayDb::TABLE, new TestDocument($uuid, num_value: 14)); $doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('num_value', 14)], TestDocument::class); $this->assertTrue($doc->isDefined(), 'There should have been a document returned'); $this->assertEquals($uuid, $doc->get()->id, 'The ID should not have been changed'); } finally { Configuration::$autoId = AutoId::None; } } #[TestDox('Insert succeeds for object with auto string ID not provided')] public function testInsertSucceedsForObjectWithAutoStringIdNotProvided(): void { Configuration::$autoId = AutoId::RandomString; Configuration::$idStringLength = 40; try { Custom::nonQuery('DELETE FROM ' . ThrowawayDb::TABLE, []); Document::insert(ThrowawayDb::TABLE, new TestDocument(num_value: 55)); $doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('num_value', 55)], TestDocument::class); $this->assertTrue($doc->isDefined(), 'There should have been a document returned'); $this->assertEquals(40, strlen($doc->get()->id), 'The ID should have been auto-generated and had 40 characters'); } finally { Configuration::$autoId = AutoId::None; Configuration::$idStringLength = 16; } } #[TestDox('Insert succeeds for object with auto string ID with ID provided')] public function testInsertSucceedsForObjectWithAutoStringIdWithIdProvided(): void { Configuration::$autoId = AutoId::RandomString; try { Custom::nonQuery('DELETE FROM ' . ThrowawayDb::TABLE, []); Document::insert(ThrowawayDb::TABLE, new TestDocument('my-key', num_value: 3)); $doc = Find::firstByFields(ThrowawayDb::TABLE, [Field::EQ('num_value', 3)], TestDocument::class); $this->assertTrue($doc->isDefined(), 'There should have been a document returned'); $this->assertEquals('my-key', $doc->get()->id, 'The ID should not have been changed'); } finally { Configuration::$autoId = AutoId::None; } } public function testInsertFailsForDuplicateKey(): void { $this->expectException(DocumentException::class); Document::insert(ThrowawayDb::TABLE, new TestDocument('one')); } public function testSaveSucceedsWhenADocumentIsInserted(): void { Document::save(ThrowawayDb::TABLE, new TestDocument('test', sub: new SubDocument('a', 'b'))); $doc = Find::byId(ThrowawayDb::TABLE, 'one', TestDocument::class); $this->assertTrue($doc->isDefined(), 'There should have been a document returned'); } public function testSaveSucceedsWhenADocumentIsUpdated(): void { Document::save(ThrowawayDb::TABLE, new TestDocument('two', num_value: 44)); $tryDoc = Find::byId(ThrowawayDb::TABLE, 'two', TestDocument::class); $this->assertTrue($tryDoc->isDefined(), 'There should have been a document returned'); $doc = $tryDoc->get(); $this->assertEquals(44, $doc->num_value, 'The numeric value was not updated'); $this->assertNull($doc->sub, 'The sub-document should have been null'); } public function testUpdateSucceedsWhenReplacingADocument(): void { Document::update(ThrowawayDb::TABLE, 'one', new TestDocument('one', 'howdy', 8, new SubDocument('y', 'z'))); $tryDoc = Find::byId(ThrowawayDb::TABLE, 'one', TestDocument::class); $this->assertNotFalse($tryDoc->isDefined(), 'There should have been a document returned'); $doc = $tryDoc->get(); $this->assertEquals('howdy', $doc->value, 'The value was incorrect'); $this->assertEquals(8, $doc->num_value, 'The numeric value was incorrect'); $this->assertNotNull($doc->sub, 'The sub-document should not have been null'); $this->assertEquals('y', $doc->sub->foo, 'The sub-document foo property was incorrect'); $this->assertEquals('z', $doc->sub->bar, 'The sub-document bar property was incorrect'); } public function testUpdateSucceedsWhenNoDocumentIsReplaced(): void { Document::update(ThrowawayDb::TABLE, 'two-hundred', new TestDocument('200')); $doc = Find::byId(ThrowawayDb::TABLE, 'two-hundred', TestDocument::class); $this->assertTrue($doc->isEmpty(), 'There should not have been a document returned'); } }