mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
added vectordb tests
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Databases\VectorDB;
|
||||
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Tests\E2E\Services\Databases\VectorDB\Permissions\DatabasesPermissionsScope;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
|
||||
class VectorDBPermissionsGuestTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideClient;
|
||||
use DatabasesPermissionsScope;
|
||||
|
||||
private $authorization;
|
||||
|
||||
public function getAuthorization(): Authorization
|
||||
{
|
||||
if (isset($this->authorization)) {
|
||||
return $this->authorization;
|
||||
}
|
||||
|
||||
$this->authorization = new Authorization();
|
||||
|
||||
return $this->authorization;
|
||||
}
|
||||
|
||||
public function createCollection(): array
|
||||
{
|
||||
$database = $this->client->call(Client::METHOD_POST, '/vectordb', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'VectorGuestDB',
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
$this->assertEquals('VectorGuestDB', $database['body']['name']);
|
||||
|
||||
$databaseId = $database['body']['$id'];
|
||||
$publicMovies = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections', $this->getServerHeader(), [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Movies',
|
||||
'dimension' => 3,
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::create(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
]);
|
||||
$privateMovies = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections', $this->getServerHeader(), [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Movies',
|
||||
'dimension' => 3,
|
||||
'permissions' => [],
|
||||
'documentSecurity' => true,
|
||||
]);
|
||||
|
||||
$publicCollection = ['id' => $publicMovies['body']['$id']];
|
||||
$privateCollection = ['id' => $privateMovies['body']['$id']];
|
||||
|
||||
return [
|
||||
'databaseId' => $databaseId,
|
||||
'publicCollectionId' => $publicCollection['id'],
|
||||
'privateCollectionId' => $privateCollection['id'],
|
||||
];
|
||||
}
|
||||
|
||||
public static function permissionsProvider(): array
|
||||
{
|
||||
return [
|
||||
[[Permission::read(Role::any())]],
|
||||
[[Permission::read(Role::users())]],
|
||||
[[Permission::update(Role::any()), Permission::delete(Role::any())]],
|
||||
[[Permission::read(Role::any()), Permission::update(Role::any()), Permission::delete(Role::any())]],
|
||||
[[Permission::read(Role::users()), Permission::update(Role::users()), Permission::delete(Role::users())]],
|
||||
[[Permission::read(Role::any()), Permission::update(Role::users()), Permission::delete(Role::users())]],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('permissionsProvider')]
|
||||
public function testReadDocuments($permissions)
|
||||
{
|
||||
$data = $this->createCollection();
|
||||
$publicCollectionId = $data['publicCollectionId'];
|
||||
$privateCollectionId = $data['privateCollectionId'];
|
||||
$databaseId = $data['databaseId'];
|
||||
|
||||
$publicResponse = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', $this->getServerHeader(), [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => [1.0, 0.0, 0.0],
|
||||
'metadata' => ['title' => 'Lorem'],
|
||||
],
|
||||
'permissions' => $permissions,
|
||||
]);
|
||||
$privateResponse = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', $this->getServerHeader(), [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => [0.0, 1.0, 0.0],
|
||||
'metadata' => ['title' => 'Lorem'],
|
||||
],
|
||||
'permissions' => $permissions,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $publicResponse['headers']['status-code']);
|
||||
$this->assertEquals(201, $privateResponse['headers']['status-code']);
|
||||
|
||||
$roles = $this->getAuthorization()->getRoles();
|
||||
$this->getAuthorization()->cleanRoles();
|
||||
|
||||
$publicDocuments = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]);
|
||||
$privateDocuments = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $publicDocuments['body']['total']);
|
||||
$this->assertEquals($permissions, $publicDocuments['body']['documents'][0]['$permissions']);
|
||||
|
||||
if (\in_array(Permission::read(Role::any()), $permissions)) {
|
||||
$this->assertEquals(1, $privateDocuments['body']['total']);
|
||||
$this->assertEquals($permissions, $privateDocuments['body']['documents'][0]['$permissions']);
|
||||
} else {
|
||||
$this->assertEquals(0, $privateDocuments['body']['total']);
|
||||
}
|
||||
|
||||
foreach ($roles as $role) {
|
||||
$this->getAuthorization()->addRole($role);
|
||||
}
|
||||
}
|
||||
|
||||
public function testWriteDocument()
|
||||
{
|
||||
$data = $this->createCollection();
|
||||
$publicCollectionId = $data['publicCollectionId'];
|
||||
$privateCollectionId = $data['privateCollectionId'];
|
||||
$databaseId = $data['databaseId'];
|
||||
|
||||
$roles = $this->getAuthorization()->getRoles();
|
||||
$this->getAuthorization()->cleanRoles();
|
||||
|
||||
$publicResponse = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => [1.0, 0.0, 0.0],
|
||||
'metadata' => ['title' => 'Lorem'],
|
||||
]
|
||||
]);
|
||||
|
||||
$publicDocumentId = $publicResponse['body']['$id'];
|
||||
$this->assertEquals(201, $publicResponse['headers']['status-code']);
|
||||
|
||||
$privateResponse = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => [0.0, 1.0, 0.0],
|
||||
'metadata' => ['title' => 'Lorem'],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $privateResponse['headers']['status-code']);
|
||||
|
||||
// Create a document in private collection with API key so we can test that update and delete are also not allowed
|
||||
$privateResponse = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', $this->getServerHeader(), [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => [0.0, 0.0, 1.0],
|
||||
'metadata' => ['title' => 'Lorem'],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $privateResponse['headers']['status-code']);
|
||||
$privateDocumentId = $privateResponse['body']['$id'];
|
||||
|
||||
$publicDocument = $this->client->call(Client::METHOD_PUT, '/vectordb/' . $databaseId . '/collections/' . $publicCollectionId . '/documents/' . $publicDocumentId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], [
|
||||
'data' => [
|
||||
'embeddings' => [0.5, 0.5, 0.0],
|
||||
'metadata' => ['title' => 'Thor: Ragnarok'],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $publicDocument['headers']['status-code']);
|
||||
$this->assertEquals('Thor: Ragnarok', $publicDocument['body']['metadata']['title']);
|
||||
|
||||
$privateDocument = $this->client->call(Client::METHOD_PUT, '/vectordb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents/' . $privateDocumentId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], [
|
||||
'data' => [
|
||||
'embeddings' => [0.2, 0.3, 0.5],
|
||||
'metadata' => ['title' => 'Thor: Ragnarok'],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $privateDocument['headers']['status-code']);
|
||||
|
||||
$publicDocument = $this->client->call(Client::METHOD_DELETE, '/vectordb/' . $databaseId . '/collections/' . $publicCollectionId . '/documents/' . $publicDocumentId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(204, $publicDocument['headers']['status-code']);
|
||||
|
||||
$privateDocument = $this->client->call(Client::METHOD_DELETE, '/vectordb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents/' . $privateDocumentId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $privateDocument['headers']['status-code']);
|
||||
|
||||
foreach ($roles as $role) {
|
||||
$this->getAuthorization()->addRole($role);
|
||||
}
|
||||
}
|
||||
|
||||
public function testWriteDocumentWithPermissions()
|
||||
{
|
||||
$database = $this->client->call(Client::METHOD_POST, '/vectordb', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'VectorGuestPermsWrite',
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
$this->assertEquals('VectorGuestPermsWrite', $database['body']['name']);
|
||||
|
||||
$databaseId = $database['body']['$id'];
|
||||
$movies = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections', $this->getServerHeader(), [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Movies',
|
||||
'dimension' => 3,
|
||||
'permissions' => [
|
||||
Permission::create(Role::any()),
|
||||
],
|
||||
'documentSecurity' => true
|
||||
]);
|
||||
|
||||
$moviesId = $movies['body']['$id'];
|
||||
|
||||
$document = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections/' . $moviesId . '/documents', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => [1.0, 0.0, 0.0],
|
||||
'metadata' => ['title' => 'Thor: Ragnarok'],
|
||||
],
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $document['headers']['status-code']);
|
||||
$this->assertEquals('Thor: Ragnarok', $document['body']['metadata']['title']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Databases\VectorDB;
|
||||
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Depends;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Tests\E2E\Services\Databases\VectorDB\Permissions\DatabasesPermissionsScope;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
|
||||
class VectorDBPermissionsMemberTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideClient;
|
||||
use DatabasesPermissionsScope;
|
||||
|
||||
public array $collections = [];
|
||||
|
||||
public function createUsers(): array
|
||||
{
|
||||
return [
|
||||
'user1' => $this->createUser('user1', 'lorem@ipsum.com'),
|
||||
'user2' => $this->createUser('user2', 'dolor@ipsum.com'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function permissionsProvider(): array
|
||||
{
|
||||
return [
|
||||
[[Permission::read(Role::any())], 1, 1, 1],
|
||||
[[Permission::read(Role::users())], 2, 2, 2],
|
||||
[[Permission::read(Role::user(ID::custom('random')))], 3, 3, 2],
|
||||
[[Permission::read(Role::user(ID::custom('lorem'))), Permission::update(Role::user('lorem')), Permission::delete(Role::user('lorem'))], 4, 4, 2],
|
||||
[[Permission::read(Role::user(ID::custom('dolor'))), Permission::update(Role::user('dolor')), Permission::delete(Role::user('dolor'))], 5, 5, 2],
|
||||
[[Permission::read(Role::user(ID::custom('dolor'))), Permission::read(Role::user('lorem')), Permission::update(Role::user('dolor')), Permission::delete(Role::user('dolor'))], 6, 6, 2],
|
||||
[[Permission::update(Role::any()), Permission::delete(Role::any())], 7, 7, 2],
|
||||
[[Permission::read(Role::any()), Permission::update(Role::any()), Permission::delete(Role::any())], 8, 8, 3],
|
||||
[[Permission::read(Role::any()), Permission::update(Role::users()), Permission::delete(Role::users())], 9, 9, 4],
|
||||
[[Permission::read(Role::user(ID::custom('user1')))], 10, 10, 5],
|
||||
[[Permission::read(Role::user(ID::custom('user1'))), Permission::read(Role::user(ID::custom('user1')))], 11, 11, 6],
|
||||
[[Permission::read(Role::users()), Permission::update(Role::users()), Permission::delete(Role::users())], 12, 12, 7],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup database
|
||||
*
|
||||
* Data providers lose object state so explicitly pass [$users, $collections] to each iteration
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testSetupDatabase(): array
|
||||
{
|
||||
$this->createUsers();
|
||||
|
||||
$db = $this->client->call(Client::METHOD_POST, '/vectordb', $this->getServerHeader(), [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Test Database',
|
||||
]);
|
||||
$this->assertEquals(201, $db['headers']['status-code']);
|
||||
|
||||
$databaseId = $db['body']['$id'];
|
||||
|
||||
$public = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections', $this->getServerHeader(), [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Movies',
|
||||
'dimension' => 3,
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::create(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'documentSecurity' => true,
|
||||
]);
|
||||
$this->assertEquals(201, $public['headers']['status-code']);
|
||||
$this->collections = ['public' => $public['body']['$id']];
|
||||
|
||||
$private = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections', $this->getServerHeader(), [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Private Movies',
|
||||
'dimension' => 3,
|
||||
'permissions' => [
|
||||
Permission::read(Role::users()),
|
||||
Permission::create(Role::users()),
|
||||
Permission::update(Role::users()),
|
||||
Permission::delete(Role::users()),
|
||||
],
|
||||
'documentSecurity' => true,
|
||||
]);
|
||||
$this->assertEquals(201, $private['headers']['status-code']);
|
||||
$this->collections['private'] = $private['body']['$id'];
|
||||
|
||||
$doconly = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections', $this->getServerHeader(), [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Document Only Movies',
|
||||
'dimension' => 3,
|
||||
'permissions' => [],
|
||||
'documentSecurity' => true,
|
||||
]);
|
||||
$this->assertEquals(201, $private['headers']['status-code']);
|
||||
$this->collections['doconly'] = $doconly['body']['$id'];
|
||||
|
||||
return [
|
||||
'users' => $this->users,
|
||||
'collections' => $this->collections,
|
||||
'databaseId' => $databaseId
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider params are passed before test dependencies.
|
||||
*/
|
||||
#[DataProvider('permissionsProvider')]
|
||||
#[Depends('testSetupDatabase')]
|
||||
public function testReadDocuments($permissions, $anyCount, $usersCount, $docOnlyCount, $data)
|
||||
{
|
||||
$users = $data['users'];
|
||||
$collections = $data['collections'];
|
||||
$databaseId = $data['databaseId'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections/' . $collections['public'] . '/documents', $this->getServerHeader(), [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => [1.0, 0.0, 0.0],
|
||||
'metadata' => ['title' => 'Lorem'],
|
||||
],
|
||||
'permissions' => $permissions
|
||||
]);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections/' . $collections['private'] . '/documents', $this->getServerHeader(), [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => [0.0, 1.0, 0.0],
|
||||
'metadata' => ['title' => 'Lorem'],
|
||||
],
|
||||
'permissions' => $permissions
|
||||
]);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections/' . $collections['doconly'] . '/documents', $this->getServerHeader(), [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => [0.0, 0.0, 1.0],
|
||||
'metadata' => ['title' => 'Lorem'],
|
||||
],
|
||||
'permissions' => $permissions
|
||||
]);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Check "any" permission collection
|
||||
*/
|
||||
$documents = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections/' . $collections['public'] . '/documents', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users['user1']['session'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $documents['headers']['status-code']);
|
||||
$this->assertEquals($anyCount, $documents['body']['total']);
|
||||
|
||||
/**
|
||||
* Check "users" permission collection
|
||||
*/
|
||||
$documents = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections/' . $collections['private'] . '/documents', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users['user1']['session'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $documents['headers']['status-code']);
|
||||
$this->assertEquals($usersCount, $documents['body']['total']);
|
||||
|
||||
/**
|
||||
* Check "user:user1" document only permission collection
|
||||
*/
|
||||
$documents = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections/' . $collections['doconly'] . '/documents', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users['user1']['session'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $documents['headers']['status-code']);
|
||||
$this->assertEquals($docOnlyCount, $documents['body']['total']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Databases\VectorDB;
|
||||
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Depends;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Tests\E2E\Services\Databases\VectorDB\Permissions\DatabasesPermissionsScope;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
|
||||
class VectorDBPermissionsTeamTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideClient;
|
||||
use DatabasesPermissionsScope;
|
||||
|
||||
public array $collections = [];
|
||||
public string $databaseId = 'testpermissiondb';
|
||||
|
||||
public function createTeams(): array
|
||||
{
|
||||
return [
|
||||
'team1' => $this->createTeam('team1', 'Team 1'),
|
||||
'team2' => $this->createTeam('team2', 'Team 2'),
|
||||
];
|
||||
}
|
||||
|
||||
public function createUsers(): array
|
||||
{
|
||||
return [
|
||||
'user1' => $this->createUser('user1', 'lorem@ipsum.com'),
|
||||
'user2' => $this->createUser('user2', 'dolor@ipsum.com'),
|
||||
'user3' => $this->createUser('user3', 'sit@ipsum.com'),
|
||||
];
|
||||
}
|
||||
|
||||
public function createCollections($teams)
|
||||
{
|
||||
$db = $this->client->call(Client::METHOD_POST, '/vectordb', $this->getServerHeader(), [
|
||||
'databaseId' => $this->databaseId,
|
||||
'name' => 'Test Database',
|
||||
]);
|
||||
$this->assertEquals(201, $db['headers']['status-code']);
|
||||
|
||||
$collection1 = $this->client->call(Client::METHOD_POST, '/vectordb/' . $this->databaseId . '/collections', $this->getServerHeader(), [
|
||||
'collectionId' => ID::custom('collection1'),
|
||||
'name' => 'Collection 1',
|
||||
'dimension' => 3,
|
||||
'permissions' => [
|
||||
Permission::read(Role::team($teams['team1']['$id'])),
|
||||
Permission::create(Role::team($teams['team1']['$id'], 'admin')),
|
||||
Permission::update(Role::team($teams['team1']['$id'], 'admin')),
|
||||
Permission::delete(Role::team($teams['team1']['$id'], 'admin')),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->collections['collection1'] = $collection1['body']['$id'];
|
||||
|
||||
$collection2 = $this->client->call(Client::METHOD_POST, '/vectordb/' . $this->databaseId . '/collections', $this->getServerHeader(), [
|
||||
'collectionId' => ID::custom('collection2'),
|
||||
'name' => 'Collection 2',
|
||||
'dimension' => 3,
|
||||
'permissions' => [
|
||||
Permission::read(Role::team($teams['team2']['$id'])),
|
||||
Permission::create(Role::team($teams['team2']['$id'], 'owner')),
|
||||
Permission::update(Role::team($teams['team2']['$id'], 'owner')),
|
||||
Permission::delete(Role::team($teams['team2']['$id'], 'owner')),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->collections['collection2'] = $collection2['body']['$id'];
|
||||
|
||||
return $this->collections;
|
||||
}
|
||||
|
||||
/*
|
||||
* $success = can $user read from $collection
|
||||
* [$user, $collection, $success]
|
||||
*/
|
||||
public static function readDocumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
['user1', 'collection1', true],
|
||||
['user2', 'collection1', false],
|
||||
['user3', 'collection1', true],
|
||||
['user1', 'collection2', false],
|
||||
['user2', 'collection2', true],
|
||||
['user3', 'collection2', true],
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
* $success = can $user write to $collection
|
||||
* [$user, $collection, $success]
|
||||
*/
|
||||
public static function writeDocumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
['user1', 'collection1', true],
|
||||
['user2', 'collection1', false],
|
||||
['user3', 'collection1', false],
|
||||
['user1', 'collection2', false],
|
||||
['user2', 'collection2', true],
|
||||
['user3', 'collection2', false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup database
|
||||
*
|
||||
* Data providers lose object state
|
||||
* so explicitly pass $users to each iteration
|
||||
* @return array $users
|
||||
*/
|
||||
public function testSetupDatabase(): array
|
||||
{
|
||||
$this->createUsers();
|
||||
$this->createTeams();
|
||||
|
||||
$this->addToTeam('user1', 'team1', ['admin']);
|
||||
$this->addToTeam('user2', 'team2', ['owner']);
|
||||
|
||||
// user3 in both teams but with no roles
|
||||
$this->addToTeam('user3', 'team1');
|
||||
$this->addToTeam('user3', 'team2');
|
||||
|
||||
$this->createCollections($this->teams);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/vectordb/' . $this->databaseId . '/collections/' . $this->collections['collection1'] . '/documents', $this->getServerHeader(), [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => [1.0, 0.0, 0.0],
|
||||
'metadata' => ['title' => 'Lorem'],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/vectordb/' . $this->databaseId . '/collections/' . $this->collections['collection2'] . '/documents', $this->getServerHeader(), [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => [0.0, 1.0, 0.0],
|
||||
'metadata' => ['title' => 'Ipsum'],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
return $this->users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider params are passed before test dependencies.
|
||||
*/
|
||||
#[Depends('testSetupDatabase')]
|
||||
#[DataProvider('readDocumentsProvider')]
|
||||
public function testReadDocuments($user, $collection, $success, $users)
|
||||
{
|
||||
$documents = $this->client->call(Client::METHOD_GET, '/vectordb/' . $this->databaseId . '/collections/' . $collection . '/documents', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users[$user]['session'],
|
||||
]);
|
||||
|
||||
if ($success) {
|
||||
$this->assertCount(1, $documents['body']['documents']);
|
||||
} else {
|
||||
$this->assertEquals(401, $documents['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
|
||||
#[Depends('testSetupDatabase')]
|
||||
#[DataProvider('writeDocumentsProvider')]
|
||||
public function testWriteDocuments($user, $collection, $success, $users)
|
||||
{
|
||||
$documents = $this->client->call(Client::METHOD_POST, '/vectordb/' . $this->databaseId . '/collections/' . $collection . '/documents', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users[$user]['session'],
|
||||
], [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => [0.2, 0.3, 0.5],
|
||||
'metadata' => ['title' => 'Ipsum'],
|
||||
],
|
||||
]);
|
||||
|
||||
if ($success) {
|
||||
$this->assertEquals(201, $documents['headers']['status-code']);
|
||||
} else {
|
||||
// 401 if user is a part of team, 404 otherwise
|
||||
$this->assertContains($documents['headers']['status-code'], [401, 404]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,528 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Databases\Transactions;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
|
||||
class VectorDBACIDTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideClient;
|
||||
|
||||
private function generateEmbeddings(int $dimensions = 3, float $value = 0.1): array
|
||||
{
|
||||
$vector = array_fill(0, $dimensions, $value);
|
||||
$vector[0] = 1.0;
|
||||
return $vector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test atomicity - all operations succeed or all fail
|
||||
*/
|
||||
public function testAtomicity(): void
|
||||
{
|
||||
// Create database
|
||||
$database = $this->client->call(Client::METHOD_POST, '/vectordb', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'AtomicityTestDB'
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
$databaseId = $database['body']['$id'];
|
||||
|
||||
// Create collection for the test
|
||||
$collection = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'AtomicityTest',
|
||||
'dimension' => 3,
|
||||
'documentSecurity' => false,
|
||||
'permissions' => [
|
||||
Permission::create(Role::any()),
|
||||
Permission::read(Role::any()),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $collection['headers']['status-code']);
|
||||
$collectionId = $collection['body']['$id'];
|
||||
|
||||
// Create a document outside the transaction
|
||||
$existingDocumentId = 'existing_doc';
|
||||
$doc1 = $this->client->call(Client::METHOD_POST, "/vectordb/{$databaseId}/collections/{$collectionId}/documents", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'documentId' => $existingDocumentId,
|
||||
'data' => [
|
||||
'embeddings' => $this->generateEmbeddings(3),
|
||||
'metadata' => ['email' => 'existing@example.com'],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $doc1['headers']['status-code']);
|
||||
|
||||
// Create transaction
|
||||
$transaction = $this->client->call(Client::METHOD_POST, '/vectordb/transactions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]));
|
||||
|
||||
$this->assertEquals(201, $transaction['headers']['status-code'], 'Transaction creation should succeed. Response: ' . json_encode($transaction));
|
||||
$this->assertArrayHasKey('$id', $transaction['body'], 'Transaction response should have $id. Response body: ' . json_encode($transaction['body']));
|
||||
$transactionId = $transaction['body']['$id'];
|
||||
|
||||
// Add operations - second create reuses an existing documentId and should cause the commit to fail
|
||||
$response = $this->client->call(Client::METHOD_POST, "/vectordb/{$databaseId}/collections/{$collectionId}/documents", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'documents' => [
|
||||
[
|
||||
'$id' => 'txn_doc_1',
|
||||
'embeddings' => $this->generateEmbeddings(3, 0.2),
|
||||
'metadata' => ['email' => 'newuser@example.com'],
|
||||
],
|
||||
[
|
||||
'$id' => $existingDocumentId,
|
||||
'embeddings' => $this->generateEmbeddings(3, 0.3),
|
||||
'metadata' => ['email' => 'duplicate@example.com'],
|
||||
],
|
||||
[
|
||||
'$id' => 'txn_doc_2',
|
||||
'embeddings' => $this->generateEmbeddings(3, 0.4),
|
||||
'metadata' => ['email' => 'should-not-exist@example.com'],
|
||||
],
|
||||
],
|
||||
'transactionId' => $transactionId,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code'], 'Adding documents via normal route should succeed. Response: ' . json_encode($response['body']));
|
||||
|
||||
// Attempt to commit - should fail due to duplicate document ID
|
||||
$response = $this->client->call(Client::METHOD_PATCH, "/vectordb/transactions/{$transactionId}", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'commit' => true
|
||||
]);
|
||||
|
||||
$this->assertEquals(409, $response['headers']['status-code']);
|
||||
|
||||
// Verify NO new documents were created (atomicity)
|
||||
$documents = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/documents", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(1, $documents['body']['total']);
|
||||
$this->assertEquals('existing@example.com', $documents['body']['documents'][0]['metadata']['email']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test consistency - schema validation and constraints
|
||||
*/
|
||||
public function testConsistency(): void
|
||||
{
|
||||
// Create database
|
||||
$database = $this->client->call(Client::METHOD_POST, '/vectordb', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'ConsistencyTestDB'
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
$databaseId = $database['body']['$id'];
|
||||
|
||||
// Create collection
|
||||
$collection = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'ConsistencyTest',
|
||||
'dimension' => 3,
|
||||
'documentSecurity' => false,
|
||||
'permissions' => [
|
||||
Permission::create(Role::any()),
|
||||
Permission::read(Role::any()),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $collection['headers']['status-code']);
|
||||
$collectionId = $collection['body']['$id'];
|
||||
|
||||
// Create transaction
|
||||
$transaction = $this->client->call(Client::METHOD_POST, '/vectordb/transactions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]));
|
||||
|
||||
$transactionId = $transaction['body']['$id'];
|
||||
|
||||
// Stage operations with valid and invalid data (embedding length mismatch)
|
||||
$response = $this->client->call(Client::METHOD_POST, "/vectordb/transactions/{$transactionId}/operations", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'operations' => [
|
||||
[
|
||||
'databaseId' => $databaseId,
|
||||
'collectionId' => $collectionId,
|
||||
'action' => 'create',
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => $this->generateEmbeddings(3, 0.2),
|
||||
'metadata' => ['name' => 'Valid User'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'databaseId' => $databaseId,
|
||||
'collectionId' => $collectionId,
|
||||
'action' => 'create',
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => $this->generateEmbeddings(2, 0.5), // Invalid dimensions
|
||||
'metadata' => ['name' => 'Invalid User'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'databaseId' => $databaseId,
|
||||
'collectionId' => $collectionId,
|
||||
'action' => 'create',
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => $this->generateEmbeddings(3, 0.6),
|
||||
'metadata' => ['name' => 'Should Not Persist'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
// Attempt to commit - should fail due to invalid embeddings
|
||||
$response = $this->client->call(Client::METHOD_PATCH, "/vectordb/transactions/{$transactionId}", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'commit' => true
|
||||
]);
|
||||
|
||||
$this->assertContains($response['headers']['status-code'], [400, 409, 500], 'Transaction commit should fail due to validation. Response: ' . json_encode($response['body']));
|
||||
|
||||
// Verify no documents were created
|
||||
$documents = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/documents", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(0, $documents['body']['total']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test isolation - concurrent transactions on same data
|
||||
*/
|
||||
public function testIsolation(): void
|
||||
{
|
||||
// Create database
|
||||
$database = $this->client->call(Client::METHOD_POST, '/vectordb', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'IsolationTestDB'
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
$databaseId = $database['body']['$id'];
|
||||
|
||||
// Create collection
|
||||
$collection = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'IsolationTest',
|
||||
'dimension' => 3,
|
||||
'documentSecurity' => false,
|
||||
'permissions' => [
|
||||
Permission::create(Role::any()),
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
],
|
||||
]);
|
||||
|
||||
$collectionId = $collection['body']['$id'];
|
||||
|
||||
// Create initial document with status metadata
|
||||
$doc = $this->client->call(Client::METHOD_POST, "/vectordb/{$databaseId}/collections/{$collectionId}/documents", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'documentId' => 'shared_doc',
|
||||
'data' => [
|
||||
'embeddings' => $this->generateEmbeddings(3),
|
||||
'metadata' => ['status' => 'pending'],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $doc['headers']['status-code']);
|
||||
|
||||
// Create first transaction
|
||||
$transaction1 = $this->client->call(Client::METHOD_POST, '/vectordb/transactions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]));
|
||||
|
||||
$this->assertEquals(201, $transaction1['headers']['status-code'], 'Transaction 1 creation should succeed');
|
||||
$this->assertArrayHasKey('$id', $transaction1['body'], 'Transaction 1 response should have $id');
|
||||
$transactionId1 = $transaction1['body']['$id'];
|
||||
|
||||
// Transaction 1: update status to approved
|
||||
$this->client->call(Client::METHOD_POST, "/vectordb/transactions/{$transactionId1}/operations", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'operations' => [
|
||||
[
|
||||
'databaseId' => $databaseId,
|
||||
'collectionId' => $collectionId,
|
||||
'action' => 'update',
|
||||
'documentId' => 'shared_doc',
|
||||
'data' => [
|
||||
'metadata' => ['status' => 'approved'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// Commit first transaction
|
||||
$response1 = $this->client->call(Client::METHOD_PATCH, "/vectordb/transactions/{$transactionId1}", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'commit' => true
|
||||
]);
|
||||
$this->assertEquals(200, $response1['headers']['status-code']);
|
||||
|
||||
// Document should reflect the first transaction's update
|
||||
$document = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/documents/shared_doc", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
$this->assertEquals('approved', $document['body']['metadata']['status']);
|
||||
|
||||
// Create second transaction after first commit
|
||||
$transaction2 = $this->client->call(Client::METHOD_POST, '/vectordb/transactions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]));
|
||||
|
||||
$this->assertEquals(201, $transaction2['headers']['status-code'], 'Transaction 2 creation should succeed');
|
||||
$this->assertArrayHasKey('$id', $transaction2['body'], 'Transaction 2 response should have $id');
|
||||
$transactionId2 = $transaction2['body']['$id'];
|
||||
|
||||
// Transaction 2: update status to declined
|
||||
$this->client->call(Client::METHOD_POST, "/vectordb/transactions/{$transactionId2}/operations", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'operations' => [
|
||||
[
|
||||
'databaseId' => $databaseId,
|
||||
'collectionId' => $collectionId,
|
||||
'action' => 'update',
|
||||
'documentId' => 'shared_doc',
|
||||
'data' => [
|
||||
'metadata' => ['status' => 'declined'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// Commit second transaction and ensure isolation guarantees
|
||||
$response2 = $this->client->call(Client::METHOD_PATCH, "/vectordb/transactions/{$transactionId2}", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'commit' => true
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response2['headers']['status-code']);
|
||||
|
||||
// Final document should reflect the second transaction's update
|
||||
$document = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/documents/shared_doc", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals('declined', $document['body']['metadata']['status']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test durability - committed data persists
|
||||
*/
|
||||
public function testDurability(): void
|
||||
{
|
||||
// Create database
|
||||
$database = $this->client->call(Client::METHOD_POST, '/vectordb', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'DurabilityTestDB'
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
$databaseId = $database['body']['$id'];
|
||||
|
||||
// Create collection
|
||||
$collection = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'DurabilityTest',
|
||||
'dimension' => 3,
|
||||
'documentSecurity' => false,
|
||||
'permissions' => [
|
||||
Permission::create(Role::any()),
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $collection['headers']['status-code']);
|
||||
$collectionId = $collection['body']['$id'];
|
||||
|
||||
// Create transaction with multiple operations
|
||||
$transaction = $this->client->call(Client::METHOD_POST, '/vectordb/transactions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]));
|
||||
|
||||
$this->assertEquals(201, $transaction['headers']['status-code'], 'Transaction creation should succeed');
|
||||
$this->assertArrayHasKey('$id', $transaction['body'], 'Transaction response should have $id');
|
||||
$transactionId = $transaction['body']['$id'];
|
||||
|
||||
// Create two documents via normal route inside transaction
|
||||
$this->client->call(Client::METHOD_POST, "/vectordb/{$databaseId}/collections/{$collectionId}/documents", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'documents' => [
|
||||
[
|
||||
'$id' => 'durable_doc_1',
|
||||
'embeddings' => $this->generateEmbeddings(3, 0.3),
|
||||
'metadata' => ['data' => 'Important data 1'],
|
||||
],
|
||||
[
|
||||
'$id' => 'durable_doc_2',
|
||||
'embeddings' => $this->generateEmbeddings(3, 0.5),
|
||||
'metadata' => ['data' => 'Important data 2'],
|
||||
],
|
||||
],
|
||||
'transactionId' => $transactionId,
|
||||
]);
|
||||
|
||||
// Update first document inside the same transaction
|
||||
$this->client->call(Client::METHOD_PATCH, "/vectordb/{$databaseId}/collections/{$collectionId}/documents/durable_doc_1", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'data' => [
|
||||
'metadata' => ['data' => 'Updated important data 1'],
|
||||
],
|
||||
'transactionId' => $transactionId,
|
||||
]);
|
||||
|
||||
// Commit transaction
|
||||
$response = $this->client->call(Client::METHOD_PATCH, "/vectordb/transactions/{$transactionId}", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'commit' => true
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code'], 'Commit should succeed. Response: ' . json_encode($response['body']));
|
||||
$this->assertEquals('committed', $response['body']['status']);
|
||||
|
||||
// Verify documents exist and have correct data
|
||||
$document1 = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/documents/durable_doc_1", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
$this->assertEquals(200, $document1['headers']['status-code']);
|
||||
$this->assertEquals('Updated important data 1', $document1['body']['metadata']['data']);
|
||||
|
||||
$document2 = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/documents/durable_doc_2", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
$this->assertEquals(200, $document2['headers']['status-code']);
|
||||
$this->assertEquals('Important data 2', $document2['body']['metadata']['data']);
|
||||
|
||||
// Further update outside transaction to ensure persistence
|
||||
$update = $this->client->call(Client::METHOD_PATCH, "/vectordb/{$databaseId}/collections/{$collectionId}/documents/durable_doc_1", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'data' => [
|
||||
'metadata' => ['data' => 'Modified outside transaction'],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals(200, $update['headers']['status-code']);
|
||||
|
||||
// Verify the update persisted
|
||||
$document1 = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/documents/durable_doc_1", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
$this->assertEquals('Modified outside transaction', $document1['body']['metadata']['data']);
|
||||
|
||||
// List all documents to verify total count
|
||||
$documents = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/documents", array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
$this->assertEquals(2, $documents['body']['total']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Databases\Transactions;
|
||||
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
use Tests\E2E\Services\Databases\VectorDB\Transactions\TransactionsBase;
|
||||
|
||||
class VectorDBTransactionsConsoleClientTest extends Scope
|
||||
{
|
||||
use TransactionsBase;
|
||||
use ProjectCustom;
|
||||
use SideConsole;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Databases\Transactions;
|
||||
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Tests\E2E\Services\Databases\VectorDB\Transactions\TransactionsBase;
|
||||
|
||||
class VectorDBTransactionsCustomClientTest extends Scope
|
||||
{
|
||||
use TransactionsBase;
|
||||
use ProjectCustom;
|
||||
use SideClient;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Databases\Transactions;
|
||||
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Tests\E2E\Services\Databases\VectorDB\Transactions\TransactionsBase;
|
||||
|
||||
class VectorDBTransactionsCustomServerTest extends Scope
|
||||
{
|
||||
use TransactionsBase;
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Tests\E2E\Services\Databases\VectorDB;
|
||||
|
||||
use PHPUnit\Framework\Attributes\Depends;
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
@@ -33,9 +34,7 @@ trait DatabasesBase
|
||||
return ['databaseId' => $database['body']['$id']];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollectionSample
|
||||
*/
|
||||
#[Depends('testCreateCollectionSample')]
|
||||
public function testCreateDocument(array $data): array
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -131,9 +130,7 @@ trait DatabasesBase
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateDocument
|
||||
*/
|
||||
#[Depends('testCreateDocument')]
|
||||
public function testGetDocument(array $data): array
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -160,9 +157,7 @@ trait DatabasesBase
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateDocument
|
||||
*/
|
||||
#[Depends('testCreateDocument')]
|
||||
public function testListDocuments(array $data): array
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -211,9 +206,7 @@ trait DatabasesBase
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateDocument
|
||||
*/
|
||||
#[Depends('testCreateDocument')]
|
||||
public function testUpsertDocument(array $data): array
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -250,9 +243,7 @@ trait DatabasesBase
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpsertDocument
|
||||
*/
|
||||
#[Depends('testUpsertDocument')]
|
||||
public function testUpdateDocument(array $data): array
|
||||
{
|
||||
// Upsert is used for update semantics
|
||||
@@ -303,9 +294,7 @@ trait DatabasesBase
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpdateDocument
|
||||
*/
|
||||
#[Depends('testUpdateDocument')]
|
||||
public function testDocumentsVectorQueries(array $data): array
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -409,9 +398,7 @@ trait DatabasesBase
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testDocumentsVectorQueries
|
||||
*/
|
||||
#[Depends('testDocumentsVectorQueries')]
|
||||
public function testDeleteDocument(array $data): void
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -444,9 +431,7 @@ trait DatabasesBase
|
||||
$this->assertEquals(200, $list['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollectionSample
|
||||
*/
|
||||
#[Depends('testCreateCollectionSample')]
|
||||
public function testDocumentPermissions(array $data): void
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -487,9 +472,7 @@ trait DatabasesBase
|
||||
$this->assertEquals(200, $withKey['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateDatabase
|
||||
*/
|
||||
#[Depends('testCreateDatabase')]
|
||||
public function testCreateCollection(array $data): array
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -556,9 +539,7 @@ trait DatabasesBase
|
||||
return ['databaseId' => $database['body']['$id']];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateDatabaseSample
|
||||
*/
|
||||
#[Depends('testCreateDatabaseSample')]
|
||||
public function testCreateCollectionSample(array $data): array
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -972,9 +953,7 @@ trait DatabasesBase
|
||||
$this->assertEquals(0, $res['body']['total']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
*/
|
||||
#[Depends('testCreateCollection')]
|
||||
public function testCreateIndexes(array $data): array
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -1023,9 +1002,7 @@ trait DatabasesBase
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateIndexes
|
||||
*/
|
||||
#[Depends('testCreateIndexes')]
|
||||
public function testListIndexes(array $data): void
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -1044,9 +1021,7 @@ trait DatabasesBase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateIndexes
|
||||
*/
|
||||
#[Depends('testCreateIndexes')]
|
||||
public function testGetIndexByKey(array $data): void
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Tests\E2E\Services\Databases\VectorDB;
|
||||
|
||||
use PHPUnit\Framework\Attributes\Depends;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
@@ -99,11 +100,7 @@ class DatabasesConsoleClientTest extends Scope
|
||||
return ['moviesId' => $movies['body']['$id'], 'databaseId' => $databaseId, 'tvShowsId' => $tvShows['body']['$id']];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
* @param array $data
|
||||
* @throws \Exception
|
||||
*/
|
||||
#[Depends('testCreateCollection')]
|
||||
public function testListCollection(array $data)
|
||||
{
|
||||
/**
|
||||
@@ -120,11 +117,7 @@ class DatabasesConsoleClientTest extends Scope
|
||||
$this->assertEquals(2, $collections['body']['total']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
* @param array $data
|
||||
* @throws \Exception
|
||||
*/
|
||||
#[Depends('testCreateCollection')]
|
||||
public function testGetCollection(array $data)
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -144,12 +137,7 @@ class DatabasesConsoleClientTest extends Scope
|
||||
$this->assertFalse($collection['body']['enabled']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
* @param array $data
|
||||
* @throws \Exception
|
||||
* @throws \Exception
|
||||
*/
|
||||
#[Depends('testCreateCollection')]
|
||||
public function testUpdateCollection(array $data)
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -172,12 +160,7 @@ class DatabasesConsoleClientTest extends Scope
|
||||
$this->assertFalse($collection['body']['enabled']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
* @param array $data
|
||||
* @throws \Exception
|
||||
* @throws \Exception
|
||||
*/
|
||||
#[Depends('testCreateCollection')]
|
||||
public function testDeleteCollection(array $data)
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -195,9 +178,7 @@ class DatabasesConsoleClientTest extends Scope
|
||||
$this->assertEquals($response['body'], "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
*/
|
||||
#[Depends('testCreateCollection')]
|
||||
public function testGetDatabaseUsage(array $data)
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -235,9 +216,7 @@ class DatabasesConsoleClientTest extends Scope
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
*/
|
||||
#[Depends('testCreateCollection')]
|
||||
public function testGetCollectionUsage(array $data)
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -279,10 +258,7 @@ class DatabasesConsoleClientTest extends Scope
|
||||
$this->assertIsArray($response['body']['documents']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
* @throws \Utopia\Database\Exception\Query
|
||||
*/
|
||||
#[Depends('testCreateCollection')]
|
||||
public function testGetCollectionLogs(array $data)
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\E2E\Services\Databases\VectorDB;
|
||||
|
||||
use PHPUnit\Framework\Attributes\Depends;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
@@ -66,9 +67,7 @@ class DatabasesCustomServerTest extends Scope
|
||||
return ['databaseId' => $db1['body']['$id']];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testListDatabases
|
||||
*/
|
||||
#[Depends('testListDatabases')]
|
||||
public function testGetDatabase(array $data): array
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -84,9 +83,7 @@ class DatabasesCustomServerTest extends Scope
|
||||
return ['databaseId' => $databaseId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testListDatabases
|
||||
*/
|
||||
#[Depends('testListDatabases')]
|
||||
public function testUpdateDatabase(array $data): array
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -103,9 +100,7 @@ class DatabasesCustomServerTest extends Scope
|
||||
return ['databaseId' => $databaseId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testListDatabases
|
||||
*/
|
||||
#[Depends('testListDatabases')]
|
||||
public function testDeleteDatabase(array $data): void
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -238,9 +233,7 @@ class DatabasesCustomServerTest extends Scope
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCollectionsCRUD
|
||||
*/
|
||||
#[Depends('testCollectionsCRUD')]
|
||||
public function testUpdateCollectionMore(array $data): array
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -272,9 +265,7 @@ class DatabasesCustomServerTest extends Scope
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCollectionsCRUD
|
||||
*/
|
||||
#[Depends('testCollectionsCRUD')]
|
||||
public function testUpdateCollectionEnabledFlag(array $data): array
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -364,9 +355,7 @@ class DatabasesCustomServerTest extends Scope
|
||||
$this->assertEquals(204, $del['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCollectionsCRUD
|
||||
*/
|
||||
#[Depends('testCollectionsCRUD')]
|
||||
public function testRecreateIndex(array $data): void
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
@@ -402,9 +391,7 @@ class DatabasesCustomServerTest extends Scope
|
||||
$this->assertEquals(204, $del['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCollectionsCRUD
|
||||
*/
|
||||
#[Depends('testCollectionsCRUD')]
|
||||
public function testIndexesCRUD(array $data): void
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Tests\E2E\Services\Databases\VectorDB\Permissions;
|
||||
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
@@ -73,7 +74,7 @@ class DatabasesPermissionsGuestTest extends Scope
|
||||
];
|
||||
}
|
||||
|
||||
public function permissionsProvider(): array
|
||||
public static function permissionsProvider(): array
|
||||
{
|
||||
return [
|
||||
[[Permission::read(Role::any())]],
|
||||
@@ -85,9 +86,7 @@ class DatabasesPermissionsGuestTest extends Scope
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider permissionsProvider
|
||||
*/
|
||||
#[DataProvider('permissionsProvider')]
|
||||
public function testReadDocuments($permissions)
|
||||
{
|
||||
$data = $this->createCollection();
|
||||
|
||||
+18
-76
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Tests\E2E\Services\Databases\VectorDB\Permissions;
|
||||
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Depends;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
@@ -26,81 +28,21 @@ class DatabasesPermissionsMemberTest extends Scope
|
||||
];
|
||||
}
|
||||
|
||||
public function permissionsProvider(): array
|
||||
public static function permissionsProvider(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'permissions' => [Permission::read(Role::any())],
|
||||
'any' => 1,
|
||||
'users' => 1,
|
||||
'doconly' => 1,
|
||||
],
|
||||
[
|
||||
'permissions' => [Permission::read(Role::users())],
|
||||
'any' => 2,
|
||||
'users' => 2,
|
||||
'doconly' => 2,
|
||||
],
|
||||
[
|
||||
'permissions' => [Permission::read(Role::user(ID::custom('random')))],
|
||||
'any' => 3,
|
||||
'users' => 3,
|
||||
'doconly' => 2,
|
||||
],
|
||||
[
|
||||
'permissions' => [Permission::read(Role::user(ID::custom('lorem'))), Permission::update(Role::user('lorem')), Permission::delete(Role::user('lorem'))],
|
||||
'any' => 4,
|
||||
'users' => 4,
|
||||
'doconly' => 2,
|
||||
],
|
||||
[
|
||||
'permissions' => [Permission::read(Role::user(ID::custom('dolor'))), Permission::update(Role::user('dolor')), Permission::delete(Role::user('dolor'))],
|
||||
'any' => 5,
|
||||
'users' => 5,
|
||||
'doconly' => 2,
|
||||
],
|
||||
[
|
||||
'permissions' => [Permission::read(Role::user(ID::custom('dolor'))), Permission::read(Role::user('lorem')), Permission::update(Role::user('dolor')), Permission::delete(Role::user('dolor'))],
|
||||
'any' => 6,
|
||||
'users' => 6,
|
||||
'doconly' => 2,
|
||||
],
|
||||
[
|
||||
'permissions' => [Permission::update(Role::any()), Permission::delete(Role::any())],
|
||||
'any' => 7,
|
||||
'users' => 7,
|
||||
'doconly' => 2,
|
||||
],
|
||||
[
|
||||
'permissions' => [Permission::read(Role::any()), Permission::update(Role::any()), Permission::delete(Role::any())],
|
||||
'any' => 8,
|
||||
'users' => 8,
|
||||
'doconly' => 3,
|
||||
],
|
||||
[
|
||||
'permissions' => [Permission::read(Role::any()), Permission::update(Role::users()), Permission::delete(Role::users())],
|
||||
'any' => 9,
|
||||
'users' => 9,
|
||||
'doconly' => 4,
|
||||
],
|
||||
[
|
||||
'permissions' => [Permission::read(Role::user(ID::custom('user1')))],
|
||||
'any' => 10,
|
||||
'users' => 10,
|
||||
'doconly' => 5,
|
||||
],
|
||||
[
|
||||
'permissions' => [Permission::read(Role::user(ID::custom('user1'))), Permission::read(Role::user(ID::custom('user1')))],
|
||||
'any' => 11,
|
||||
'users' => 11,
|
||||
'doconly' => 6,
|
||||
],
|
||||
[
|
||||
'permissions' => [Permission::read(Role::users()), Permission::update(Role::users()), Permission::delete(Role::users())],
|
||||
'any' => 12,
|
||||
'users' => 12,
|
||||
'doconly' => 7,
|
||||
],
|
||||
[[Permission::read(Role::any())], 1, 1, 1],
|
||||
[[Permission::read(Role::users())], 2, 2, 2],
|
||||
[[Permission::read(Role::user(ID::custom('random')))], 3, 3, 2],
|
||||
[[Permission::read(Role::user(ID::custom('lorem'))), Permission::update(Role::user('lorem')), Permission::delete(Role::user('lorem'))], 4, 4, 2],
|
||||
[[Permission::read(Role::user(ID::custom('dolor'))), Permission::update(Role::user('dolor')), Permission::delete(Role::user('dolor'))], 5, 5, 2],
|
||||
[[Permission::read(Role::user(ID::custom('dolor'))), Permission::read(Role::user('lorem')), Permission::update(Role::user('dolor')), Permission::delete(Role::user('dolor'))], 6, 6, 2],
|
||||
[[Permission::update(Role::any()), Permission::delete(Role::any())], 7, 7, 2],
|
||||
[[Permission::read(Role::any()), Permission::update(Role::any()), Permission::delete(Role::any())], 8, 8, 3],
|
||||
[[Permission::read(Role::any()), Permission::update(Role::users()), Permission::delete(Role::users())], 9, 9, 4],
|
||||
[[Permission::read(Role::user(ID::custom('user1')))], 10, 10, 5],
|
||||
[[Permission::read(Role::user(ID::custom('user1'))), Permission::read(Role::user(ID::custom('user1')))], 11, 11, 6],
|
||||
[[Permission::read(Role::users()), Permission::update(Role::users()), Permission::delete(Role::users())], 12, 12, 7],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -172,10 +114,10 @@ class DatabasesPermissionsMemberTest extends Scope
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider params are passed before test dependencies
|
||||
* @dataProvider permissionsProvider
|
||||
* @depends testSetupDatabase
|
||||
* Data provider params are passed before test dependencies.
|
||||
*/
|
||||
#[DataProvider('permissionsProvider')]
|
||||
#[Depends('testSetupDatabase')]
|
||||
public function testReadDocuments($permissions, $anyCount, $usersCount, $docOnlyCount, $data)
|
||||
{
|
||||
$users = $data['users'];
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Tests\E2E\Services\Databases\VectorDB\Permissions;
|
||||
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Depends;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
@@ -79,7 +81,7 @@ class DatabasesPermissionsTeamTest extends Scope
|
||||
* $success = can $user read from $collection
|
||||
* [$user, $collection, $success]
|
||||
*/
|
||||
public function readDocumentsProvider(): array
|
||||
public static function readDocumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
['user1', 'collection1', true],
|
||||
@@ -95,7 +97,7 @@ class DatabasesPermissionsTeamTest extends Scope
|
||||
* $success = can $user write to $collection
|
||||
* [$user, $collection, $success]
|
||||
*/
|
||||
public function writeDocumentsProvider(): array
|
||||
public static function writeDocumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
['user1', 'collection1', true],
|
||||
@@ -150,10 +152,10 @@ class DatabasesPermissionsTeamTest extends Scope
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider params are passed before test dependencies
|
||||
* @depends testSetupDatabase
|
||||
* @dataProvider readDocumentsProvider
|
||||
* Data provider params are passed before test dependencies.
|
||||
*/
|
||||
#[Depends('testSetupDatabase')]
|
||||
#[DataProvider('readDocumentsProvider')]
|
||||
public function testReadDocuments($user, $collection, $success, $users)
|
||||
{
|
||||
$documents = $this->client->call(Client::METHOD_GET, '/vectordb/' . $this->databaseId . '/collections/' . $collection . '/documents', [
|
||||
@@ -170,10 +172,8 @@ class DatabasesPermissionsTeamTest extends Scope
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testSetupDatabase
|
||||
* @dataProvider writeDocumentsProvider
|
||||
*/
|
||||
#[Depends('testSetupDatabase')]
|
||||
#[DataProvider('writeDocumentsProvider')]
|
||||
public function testWriteDocuments($user, $collection, $success, $users)
|
||||
{
|
||||
$documents = $this->client->call(Client::METHOD_POST, '/vectordb/' . $this->databaseId . '/collections/' . $collection . '/documents', [
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Databases;
|
||||
|
||||
use PHPUnit\Framework\Attributes\Depends;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class VectorDBConsoleClientTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideConsole;
|
||||
|
||||
public function testCreateCollection(): array
|
||||
{
|
||||
$database = $this->client->call(Client::METHOD_POST, '/vectordb', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Vector Console DB',
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
$this->assertEquals('Vector Console DB', $database['body']['name']);
|
||||
$this->assertTrue($database['body']['enabled']);
|
||||
|
||||
$databaseId = $database['body']['$id'];
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$movies = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Movies',
|
||||
'dimension' => 3,
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::create(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'documentSecurity' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $movies['headers']['status-code']);
|
||||
$this->assertEquals($movies['body']['name'], 'Movies');
|
||||
|
||||
/**
|
||||
* Test when database is disabled but can still create collections
|
||||
*/
|
||||
$database = $this->client->call(Client::METHOD_PUT, '/vectordb/' . $databaseId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Vector Console DB Updated',
|
||||
'enabled' => false,
|
||||
]);
|
||||
|
||||
$this->assertFalse($database['body']['enabled']);
|
||||
|
||||
$tvShows = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'TvShows',
|
||||
'dimension' => 3,
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::create(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'documentSecurity' => true,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Test when collection is disabled but can still modify collections
|
||||
*/
|
||||
$database = $this->client->call(Client::METHOD_PUT, '/vectordb/' . $databaseId . '/collections/' . $movies['body']['$id'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Movies',
|
||||
'enabled' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $tvShows['headers']['status-code']);
|
||||
$this->assertEquals($tvShows['body']['name'], 'TvShows');
|
||||
|
||||
return ['moviesId' => $movies['body']['$id'], 'databaseId' => $databaseId, 'tvShowsId' => $tvShows['body']['$id']];
|
||||
}
|
||||
|
||||
#[Depends('testCreateCollection')]
|
||||
public function testListCollection(array $data)
|
||||
{
|
||||
/**
|
||||
* Test when database is disabled but can still call list collections
|
||||
*/
|
||||
$databaseId = $data['databaseId'];
|
||||
|
||||
$collections = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $collections['headers']['status-code']);
|
||||
$this->assertEquals(2, $collections['body']['total']);
|
||||
}
|
||||
|
||||
#[Depends('testCreateCollection')]
|
||||
public function testGetCollection(array $data)
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
$moviesCollectionId = $data['moviesId'];
|
||||
|
||||
/**
|
||||
* Test when database and collection are disabled but can still call get collection
|
||||
*/
|
||||
$collection = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections/' . $moviesCollectionId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $collection['headers']['status-code']);
|
||||
$this->assertEquals('Movies', $collection['body']['name']);
|
||||
$this->assertEquals($moviesCollectionId, $collection['body']['$id']);
|
||||
$this->assertFalse($collection['body']['enabled']);
|
||||
}
|
||||
|
||||
#[Depends('testCreateCollection')]
|
||||
public function testUpdateCollection(array $data)
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
$moviesCollectionId = $data['moviesId'];
|
||||
|
||||
/**
|
||||
* Test When database and collection are disabled but can still call update collection
|
||||
*/
|
||||
$collection = $this->client->call(Client::METHOD_PUT, '/vectordb/' . $databaseId . '/collections/' . $moviesCollectionId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Movies Updated',
|
||||
'enabled' => false
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $collection['headers']['status-code']);
|
||||
$this->assertEquals('Movies Updated', $collection['body']['name']);
|
||||
$this->assertEquals($moviesCollectionId, $collection['body']['$id']);
|
||||
$this->assertFalse($collection['body']['enabled']);
|
||||
}
|
||||
|
||||
#[Depends('testCreateCollection')]
|
||||
public function testDeleteCollection(array $data)
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
$tvShowsId = $data['tvShowsId'];
|
||||
|
||||
/**
|
||||
* Test when database and collection are disabled but can still call delete collection
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/vectordb/' . $databaseId . '/collections/' . $tvShowsId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
$this->assertEquals($response['body'], "");
|
||||
}
|
||||
|
||||
#[Depends('testCreateCollection')]
|
||||
public function testGetDatabaseUsage(array $data)
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/usage', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()), [
|
||||
'range' => '32h'
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/usage', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()), [
|
||||
'range' => '24h'
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(11, count($response['body']));
|
||||
$this->assertEquals('24h', $response['body']['range']);
|
||||
$this->assertIsNumeric($response['body']['documentsTotal']);
|
||||
$this->assertIsNumeric($response['body']['collectionsTotal']);
|
||||
$this->assertIsArray($response['body']['collections']);
|
||||
$this->assertIsArray($response['body']['documents']);
|
||||
}
|
||||
|
||||
|
||||
#[Depends('testCreateCollection')]
|
||||
public function testGetCollectionUsage(array $data)
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections/' . $data['moviesId'] . '/usage', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()), [
|
||||
'range' => '32h'
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections/randomCollectionId/usage', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()), [
|
||||
'range' => '24h'
|
||||
]);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections/' . $data['moviesId'] . '/usage', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()), [
|
||||
'range' => '24h'
|
||||
]);
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(3, count($response['body']));
|
||||
$this->assertEquals('24h', $response['body']['range']);
|
||||
$this->assertIsNumeric($response['body']['documentsTotal']);
|
||||
$this->assertIsArray($response['body']['documents']);
|
||||
}
|
||||
|
||||
#[Depends('testCreateCollection')]
|
||||
public function testGetCollectionLogs(array $data)
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $logs['headers']['status-code']);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [Query::limit(1)->toString()]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $logs['headers']['status-code']);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [Query::offset(1)->toString()]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $logs['headers']['status-code']);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [Query::offset(1)->toString(), Query::limit(1)->toString()]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $logs['headers']['status-code']);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Databases;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Tests\E2E\Services\Databases\VectorDB\DatabasesBase;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
|
||||
class VectorDBCustomClientTest extends Scope
|
||||
{
|
||||
use DatabasesBase;
|
||||
use ProjectCustom;
|
||||
use SideClient;
|
||||
|
||||
public function testAllowedPermissions(): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$database = $this->client->call(Client::METHOD_POST, '/vectordb', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Test Database'
|
||||
]);
|
||||
|
||||
$databaseId = $database['body']['$id'];
|
||||
|
||||
// Collection aliases write to create, update, delete
|
||||
$movies = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Movies',
|
||||
'dimension' => 3,
|
||||
'documentSecurity' => true,
|
||||
'permissions' => [
|
||||
Permission::write(Role::user($this->getUser()['$id'])),
|
||||
],
|
||||
]);
|
||||
|
||||
$moviesId = $movies['body']['$id'];
|
||||
|
||||
$this->assertContains(Permission::create(Role::user($this->getUser()['$id'])), $movies['body']['$permissions']);
|
||||
$this->assertContains(Permission::update(Role::user($this->getUser()['$id'])), $movies['body']['$permissions']);
|
||||
$this->assertContains(Permission::delete(Role::user($this->getUser()['$id'])), $movies['body']['$permissions']);
|
||||
|
||||
// VectorDB uses fixed schema (embeddings, metadata). No attribute creation needed.
|
||||
|
||||
// Document aliases write to update, delete
|
||||
$document1 = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => [1.0, 0.0, 0.0],
|
||||
'metadata' => ['k' => 'v'],
|
||||
],
|
||||
'permissions' => [
|
||||
Permission::write(Role::user($this->getUser()['$id'])),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertNotContains(Permission::create(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']);
|
||||
$this->assertContains(Permission::update(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']);
|
||||
$this->assertContains(Permission::delete(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
// Document does not allow create permission
|
||||
$document2 = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => [0.0, 1.0, 0.0],
|
||||
'metadata' => ['k' => 'v'],
|
||||
],
|
||||
'permissions' => [
|
||||
Permission::create(Role::user($this->getUser()['$id'])),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $document2['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testUpdateWithoutPermission(): array
|
||||
{
|
||||
// As a part of preparation, we get ID of currently logged-in user
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
$userId = $response['body']['$id'];
|
||||
|
||||
$database = $this->client->call(Client::METHOD_POST, '/vectordb', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'databaseId' => ID::custom('permissionCheckDatabase'),
|
||||
'name' => 'Test Database',
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
$this->assertEquals('Test Database', $database['body']['name']);
|
||||
|
||||
$databaseId = $database['body']['$id'];
|
||||
// Create collection
|
||||
$response = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'collectionId' => ID::custom('permissionCheck'),
|
||||
'name' => 'permissionCheck',
|
||||
'dimension' => 3,
|
||||
'permissions' => [],
|
||||
'documentSecurity' => true,
|
||||
]);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
// Creating document by server, give read permission to our user + some other user
|
||||
$response = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections/permissionCheck/documents', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'documentId' => ID::custom('permissionCheckDocument'),
|
||||
'data' => [
|
||||
'embeddings' => [1.0, 0.0, 0.0],
|
||||
'metadata' => ['name' => 'AppwriteBeginner'],
|
||||
],
|
||||
'permissions' => [
|
||||
Permission::read(Role::user(ID::custom('user2'))),
|
||||
Permission::read(Role::user($userId)),
|
||||
Permission::update(Role::user($userId)),
|
||||
Permission::delete(Role::user($userId)),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
// Update document
|
||||
// This is the point of this test. We should be allowed to do this action, and it should not fail on permission check
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/vectordb/' . $databaseId . '/collections/permissionCheck/documents/permissionCheckDocument', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()), [
|
||||
'data' => [
|
||||
'embeddings' => [0.0, 1.0, 0.0],
|
||||
'metadata' => ['name' => 'AppwriteExpert'],
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
// Get name of the document, should be the new one
|
||||
$response = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections/permissionCheck/documents/permissionCheckDocument', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals("AppwriteExpert", $response['body']['metadata']['name']);
|
||||
|
||||
// Cleanup to prevent collision with other tests
|
||||
// Delete collection
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/vectordb/' . $databaseId . '/collections/permissionCheck', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]));
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
|
||||
|
||||
// Wait for database worker to finish deleting collection
|
||||
sleep(2);
|
||||
|
||||
// Make sure collection has been deleted
|
||||
$response = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections/permissionCheck', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]));
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,963 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Databases;
|
||||
|
||||
use PHPUnit\Framework\Attributes\Depends;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Tests\E2E\Services\Databases\VectorDB\DatabasesBase;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
|
||||
class VectorDBCustomServerTest extends Scope
|
||||
{
|
||||
use DatabasesBase;
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
|
||||
public function testListDatabases(): array
|
||||
{
|
||||
$db1 = $this->client->call(Client::METHOD_POST, '/vectordb', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'databaseId' => ID::custom('first'),
|
||||
'name' => 'Test 1',
|
||||
]);
|
||||
$this->assertEquals(201, $db1['headers']['status-code']);
|
||||
$this->assertEquals('Test 1', $db1['body']['name']);
|
||||
$this->assertEquals('vectordb', $db1['body']['type']);
|
||||
// Validate database response model fields on create
|
||||
$this->assertArrayHasKey('$id', $db1['body']);
|
||||
$this->assertArrayHasKey('$createdAt', $db1['body']);
|
||||
$this->assertArrayHasKey('$updatedAt', $db1['body']);
|
||||
$this->assertArrayHasKey('enabled', $db1['body']);
|
||||
|
||||
$db2 = $this->client->call(Client::METHOD_POST, '/vectordb', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'databaseId' => ID::custom('second'),
|
||||
'name' => 'Test 2',
|
||||
]);
|
||||
$this->assertEquals(201, $db2['headers']['status-code']);
|
||||
$this->assertEquals('Test 2', $db2['body']['name']);
|
||||
$this->assertEquals('vectordb', $db2['body']['type']);
|
||||
|
||||
$list = $this->client->call(Client::METHOD_GET, '/vectordb', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(200, $list['headers']['status-code']);
|
||||
$this->assertIsInt($list['body']['total']);
|
||||
$this->assertGreaterThanOrEqual(2, $list['body']['total']);
|
||||
$this->assertIsArray($list['body']['databases']);
|
||||
$this->assertArrayHasKey('$id', $list['body']['databases'][0]);
|
||||
$this->assertArrayHasKey('name', $list['body']['databases'][0]);
|
||||
$this->assertArrayHasKey('type', $list['body']['databases'][0]);
|
||||
|
||||
return ['databaseId' => $db1['body']['$id']];
|
||||
}
|
||||
|
||||
#[Depends('testListDatabases')]
|
||||
public function testGetDatabase(array $data): array
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
$res = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(200, $res['headers']['status-code']);
|
||||
$this->assertEquals($databaseId, $res['body']['$id']);
|
||||
$this->assertEquals('Test 1', $res['body']['name']);
|
||||
$this->assertEquals('vectordb', $res['body']['type']);
|
||||
return ['databaseId' => $databaseId];
|
||||
}
|
||||
|
||||
#[Depends('testListDatabases')]
|
||||
public function testUpdateDatabase(array $data): array
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
$res = $this->client->call(Client::METHOD_PUT, '/vectordb/' . $databaseId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'name' => 'Test 1 Updated',
|
||||
]);
|
||||
$this->assertEquals(200, $res['headers']['status-code']);
|
||||
$this->assertEquals('Test 1 Updated', $res['body']['name']);
|
||||
$this->assertEquals('vectordb', $res['body']['type']);
|
||||
return ['databaseId' => $databaseId];
|
||||
}
|
||||
|
||||
#[Depends('testListDatabases')]
|
||||
public function testDeleteDatabase(array $data): void
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
$del = $this->client->call(Client::METHOD_DELETE, '/vectordb/' . $databaseId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(204, $del['headers']['status-code']);
|
||||
$this->assertEquals("", $del['body']);
|
||||
|
||||
$get = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(404, $get['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testCollectionsCRUD(): array
|
||||
{
|
||||
// Create database for collections tests
|
||||
$database = $this->client->call(Client::METHOD_POST, '/vectordb', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Collections DB',
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
$databaseId = $database['body']['$id'];
|
||||
|
||||
// Create two collections
|
||||
$col1 = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'name' => 'Test 1',
|
||||
'collectionId' => ID::custom('first'),
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::create(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'documentSecurity' => true,
|
||||
'dimension' => 3,
|
||||
]);
|
||||
$this->assertEquals(201, $col1['headers']['status-code']);
|
||||
// Validate collection response model on create
|
||||
$this->assertArrayHasKey('$id', $col1['body']);
|
||||
$this->assertArrayHasKey('$createdAt', $col1['body']);
|
||||
$this->assertArrayHasKey('$updatedAt', $col1['body']);
|
||||
$this->assertArrayHasKey('enabled', $col1['body']);
|
||||
$this->assertArrayHasKey('documentSecurity', $col1['body']);
|
||||
$this->assertArrayHasKey('dimension', $col1['body']);
|
||||
|
||||
$col2 = $this->client->call(Client::METHOD_POST, '/vectordb/' . $databaseId . '/collections', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'name' => 'Test 2',
|
||||
'collectionId' => ID::custom('second'),
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::create(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'documentSecurity' => true,
|
||||
'dimension' => 3,
|
||||
]);
|
||||
$this->assertEquals(201, $col2['headers']['status-code']);
|
||||
$this->assertArrayHasKey('$id', $col2['body']);
|
||||
$this->assertArrayHasKey('$createdAt', $col2['body']);
|
||||
$this->assertArrayHasKey('$updatedAt', $col2['body']);
|
||||
|
||||
// List collections
|
||||
$list = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(200, $list['headers']['status-code']);
|
||||
$this->assertIsInt($list['body']['total']);
|
||||
$this->assertGreaterThanOrEqual(2, $list['body']['total']);
|
||||
$this->assertIsArray($list['body']['collections']);
|
||||
$this->assertArrayHasKey('$id', $list['body']['collections'][0]);
|
||||
$this->assertArrayHasKey('name', $list['body']['collections'][0]);
|
||||
$this->assertArrayHasKey('dimension', $list['body']['collections'][0]);
|
||||
|
||||
// Get collection
|
||||
$get = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections/' . $col1['body']['$id'], [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(200, $get['headers']['status-code']);
|
||||
$this->assertEquals($col1['body']['$id'], $get['body']['$id']);
|
||||
$this->assertEquals('Test 1', $get['body']['name']);
|
||||
$this->assertEquals(3, $get['body']['dimension']);
|
||||
|
||||
// Update collection (name only)
|
||||
$upd = $this->client->call(Client::METHOD_PUT, '/vectordb/' . $databaseId . '/collections/' . $col1['body']['$id'], [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'name' => 'Test 1 Updated',
|
||||
]);
|
||||
$this->assertEquals(200, $upd['headers']['status-code']);
|
||||
$this->assertEquals('Test 1 Updated', $upd['body']['name']);
|
||||
$this->assertArrayHasKey('$updatedAt', $upd['body']);
|
||||
|
||||
// Delete collection
|
||||
$del = $this->client->call(Client::METHOD_DELETE, '/vectordb/' . $databaseId . '/collections/' . $col2['body']['$id'], [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(204, $del['headers']['status-code']);
|
||||
$this->assertEquals("", $del['body']);
|
||||
|
||||
return [
|
||||
'databaseId' => $databaseId,
|
||||
'collectionId' => $col1['body']['$id'],
|
||||
];
|
||||
}
|
||||
|
||||
#[Depends('testCollectionsCRUD')]
|
||||
public function testUpdateCollectionMore(array $data): array
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
$collectionId = $data['collectionId'];
|
||||
|
||||
// Update collection name and dimensions
|
||||
$upd = $this->client->call(Client::METHOD_PUT, '/vectordb/' . $databaseId . '/collections/' . $collectionId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'name' => 'Test 1 Renamed',
|
||||
'dimension' => 4,
|
||||
]);
|
||||
$this->assertEquals(200, $upd['headers']['status-code']);
|
||||
$this->assertEquals('Test 1 Renamed', $upd['body']['name']);
|
||||
$this->assertEquals(4, $upd['body']['dimension']);
|
||||
|
||||
// Read back to confirm
|
||||
$get = $this->client->call(Client::METHOD_GET, '/vectordb/' . $databaseId . '/collections/' . $collectionId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(200, $get['headers']['status-code']);
|
||||
$this->assertEquals('Test 1 Renamed', $get['body']['name']);
|
||||
$this->assertEquals(4, $get['body']['dimension']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
#[Depends('testCollectionsCRUD')]
|
||||
public function testUpdateCollectionEnabledFlag(array $data): array
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
$collectionId = $data['collectionId'];
|
||||
|
||||
// Disable collection
|
||||
$disable = $this->client->call(Client::METHOD_PUT, '/vectordb/' . $databaseId . '/collections/' . $collectionId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'name' => 'Updated',
|
||||
'enabled' => false,
|
||||
]);
|
||||
$this->assertEquals(200, $disable['headers']['status-code']);
|
||||
$this->assertFalse($disable['body']['enabled']);
|
||||
|
||||
// Re-enable collection
|
||||
$enable = $this->client->call(Client::METHOD_PUT, '/vectordb/' . $databaseId . '/collections/' . $collectionId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'name' => 'Updated',
|
||||
'enabled' => true,
|
||||
]);
|
||||
$this->assertEquals(200, $enable['headers']['status-code']);
|
||||
$this->assertTrue($enable['body']['enabled']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function testUpdateDatabaseNameAndEnabled(): void
|
||||
{
|
||||
// Create isolated database for this test to avoid ordering conflicts
|
||||
$create = $this->client->call(Client::METHOD_POST, '/vectordb', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Update DB',
|
||||
]);
|
||||
$this->assertEquals(201, $create['headers']['status-code']);
|
||||
$databaseId = $create['body']['$id'];
|
||||
|
||||
// Update name
|
||||
$rename = $this->client->call(Client::METHOD_PUT, '/vectordb/' . $databaseId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'name' => 'Test DB Renamed',
|
||||
]);
|
||||
$this->assertEquals(200, $rename['headers']['status-code']);
|
||||
$this->assertEquals('Test DB Renamed', $rename['body']['name']);
|
||||
|
||||
// Toggle enabled off then on
|
||||
$disable = $this->client->call(Client::METHOD_PUT, '/vectordb/' . $databaseId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'name' => 'Test DB Renamed',
|
||||
'enabled' => false,
|
||||
]);
|
||||
$this->assertEquals(200, $disable['headers']['status-code']);
|
||||
$this->assertFalse($disable['body']['enabled']);
|
||||
|
||||
$enable = $this->client->call(Client::METHOD_PUT, '/vectordb/' . $databaseId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'name' => 'Test DB Renamed',
|
||||
'enabled' => true,
|
||||
]);
|
||||
$this->assertEquals(200, $enable['headers']['status-code']);
|
||||
$this->assertTrue($enable['body']['enabled']);
|
||||
|
||||
// Cleanup
|
||||
$del = $this->client->call(Client::METHOD_DELETE, '/vectordb/' . $databaseId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(204, $del['headers']['status-code']);
|
||||
}
|
||||
|
||||
#[Depends('testCollectionsCRUD')]
|
||||
public function testRecreateIndex(array $data): void
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
$collectionId = $data['collectionId'];
|
||||
|
||||
// Create a new index variant
|
||||
$create = $this->client->call(Client::METHOD_POST, "/vectordb/{$databaseId}/collections/{$collectionId}/indexes", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'key' => 'embedding_euclidean_v2',
|
||||
'type' => Database::INDEX_HNSW_EUCLIDEAN,
|
||||
'attributes' => ['embeddings']
|
||||
]);
|
||||
$this->assertEquals(202, $create['headers']['status-code']);
|
||||
|
||||
// Ensure it exists
|
||||
$get = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/indexes/embedding_euclidean_v2", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(200, $get['headers']['status-code']);
|
||||
$this->assertEquals('embedding_euclidean_v2', $get['body']['key']);
|
||||
|
||||
// Delete it
|
||||
$del = $this->client->call(Client::METHOD_DELETE, "/vectordb/{$databaseId}/collections/{$collectionId}/indexes/embedding_euclidean_v2", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(204, $del['headers']['status-code']);
|
||||
}
|
||||
|
||||
#[Depends('testCollectionsCRUD')]
|
||||
public function testIndexesCRUD(array $data): void
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
$collectionId = $data['collectionId'];
|
||||
|
||||
// Create indexes
|
||||
$eu = $this->client->call(Client::METHOD_POST, "/vectordb/{$databaseId}/collections/{$collectionId}/indexes", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'key' => 'embedding_euclidean',
|
||||
'type' => Database::INDEX_HNSW_EUCLIDEAN,
|
||||
'attributes' => ['embeddings']
|
||||
]);
|
||||
$this->assertEquals(202, $eu['headers']['status-code']);
|
||||
|
||||
$dot = $this->client->call(Client::METHOD_POST, "/vectordb/{$databaseId}/collections/{$collectionId}/indexes", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'key' => 'embedding_dot',
|
||||
'type' => Database::INDEX_HNSW_DOT,
|
||||
'attributes' => ['embeddings']
|
||||
]);
|
||||
$this->assertEquals(202, $dot['headers']['status-code']);
|
||||
|
||||
$cos = $this->client->call(Client::METHOD_POST, "/vectordb/{$databaseId}/collections/{$collectionId}/indexes", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'key' => 'embedding_cosine',
|
||||
'type' => Database::INDEX_HNSW_COSINE,
|
||||
'attributes' => ['embeddings']
|
||||
]);
|
||||
$this->assertEquals(202, $cos['headers']['status-code']);
|
||||
|
||||
// List indexes
|
||||
$list = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/indexes", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(200, $list['headers']['status-code']);
|
||||
$this->assertIsArray($list['body']['indexes']);
|
||||
$keys = array_map(fn ($i) => $i['key'], $list['body']['indexes']);
|
||||
$this->assertContains('embedding_euclidean', $keys);
|
||||
$this->assertContains('embedding_dot', $keys);
|
||||
$this->assertContains('embedding_cosine', $keys);
|
||||
|
||||
// Get index by key
|
||||
$get = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/indexes/embedding_euclidean", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(200, $get['headers']['status-code']);
|
||||
$this->assertEquals('embedding_euclidean', $get['body']['key']);
|
||||
$this->assertEquals(Database::INDEX_HNSW_EUCLIDEAN, $get['body']['type']);
|
||||
|
||||
// Delete index
|
||||
$del = $this->client->call(Client::METHOD_DELETE, "/vectordb/{$databaseId}/collections/{$collectionId}/indexes/embedding_dot", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(204, $del['headers']['status-code']);
|
||||
sleep(4);
|
||||
// Ensure it's gone
|
||||
$getMissing = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/indexes/embedding_dot", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(404, $getMissing['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testBulkCreate(): array
|
||||
{
|
||||
// Setup: create isolated database and collection
|
||||
$db = $this->client->call(Client::METHOD_POST, '/vectordb', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'BulkDBCreate'
|
||||
]);
|
||||
$this->assertEquals(201, $db['headers']['status-code']);
|
||||
$databaseId = $db['body']['$id'];
|
||||
|
||||
$col = $this->client->call(Client::METHOD_POST, "/vectordb/{$databaseId}/collections", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'BulkColCreate',
|
||||
'documentSecurity' => true,
|
||||
'dimension' => 3,
|
||||
'permissions' => [Permission::read(Role::any())]
|
||||
]);
|
||||
$this->assertEquals(201, $col['headers']['status-code']);
|
||||
$collectionId = $col['body']['$id'];
|
||||
|
||||
$docs = [
|
||||
[
|
||||
'embeddings' => [1.0, 0.0, 0.0],
|
||||
'metadata' => ['group' => 'bulkA'],
|
||||
'$permissions' => [Permission::read(Role::any())]
|
||||
],
|
||||
[
|
||||
'embeddings' => [0.0, 1.0, 0.0],
|
||||
'metadata' => ['group' => 'bulkB'],
|
||||
'$permissions' => [Permission::read(Role::any())]
|
||||
],
|
||||
];
|
||||
|
||||
$res = $this->client->call(Client::METHOD_POST, "/vectordb/{$databaseId}/collections/{$collectionId}/documents", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'documents' => $docs
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $res['headers']['status-code']);
|
||||
$this->assertIsInt($res['body']['total'] ?? 0);
|
||||
$this->assertGreaterThanOrEqual(2, $res['body']['total']);
|
||||
$this->assertIsArray($res['body']['documents']);
|
||||
$this->assertCount(2, $res['body']['documents']);
|
||||
|
||||
$ids = array_map(fn ($d) => $d['$id'], $res['body']['documents']);
|
||||
$this->assertNotEmpty($ids[0]);
|
||||
$this->assertNotEmpty($ids[1]);
|
||||
|
||||
// Fetch and validate persisted data via GET
|
||||
foreach ($ids as $i => $id) {
|
||||
$get = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/documents/{$id}", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(200, $get['headers']['status-code']);
|
||||
$this->assertEquals($id, $get['body']['$id']);
|
||||
$this->assertIsArray($get['body']['embeddings']);
|
||||
$this->assertCount(3, $get['body']['embeddings']);
|
||||
$this->assertArrayHasKey('group', $get['body']['metadata']);
|
||||
}
|
||||
|
||||
return [ 'databaseId' => $databaseId, 'collectionId' => $collectionId, 'bulkIds' => $ids ];
|
||||
}
|
||||
|
||||
public function testCreateTextEmbeddingsSuccessAndErrors(): void
|
||||
{
|
||||
// Setup new database and collection
|
||||
$db = $this->client->call(Client::METHOD_POST, '/vectordb', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'EmbedDB',
|
||||
]);
|
||||
$this->assertEquals(201, $db['headers']['status-code']);
|
||||
$databaseId = $db['body']['$id'];
|
||||
|
||||
$col = $this->client->call(Client::METHOD_POST, "/vectordb/{$databaseId}/collections", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'EmbedCol',
|
||||
'documentSecurity' => true,
|
||||
'dimension' => 3,
|
||||
'permissions' => [Permission::read(Role::any())]
|
||||
]);
|
||||
$this->assertEquals(201, $col['headers']['status-code']);
|
||||
$collectionId = $col['body']['$id'];
|
||||
|
||||
// Success: two embeddings
|
||||
$this->assertEventually(function () {
|
||||
$ok = $this->client->call(Client::METHOD_POST, "/vectordb/embeddings/text", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'model' => 'embeddinggemma',
|
||||
'texts' => [
|
||||
'hello world',
|
||||
'second sentence',
|
||||
],
|
||||
]);
|
||||
$this->assertEquals(200, $ok['headers']['status-code']);
|
||||
$this->assertIsInt($ok['body']['total'] ?? 0);
|
||||
$this->assertEquals(2, $ok['body']['total']);
|
||||
$this->assertIsArray($ok['body']['embeddings']);
|
||||
$this->assertCount(2, $ok['body']['embeddings']);
|
||||
foreach ($ok['body']['embeddings'] as $embed) {
|
||||
$this->assertIsString($embed['model']);
|
||||
$this->assertIsInt($embed['dimension']);
|
||||
$this->assertIsArray($embed['embedding']);
|
||||
$this->assertGreaterThan(0, count($embed['embedding']));
|
||||
$this->assertArrayHasKey('error', $embed);
|
||||
}
|
||||
}, 3000, 100);
|
||||
|
||||
// Error: missing texts payload
|
||||
$missingTexts = $this->client->call(Client::METHOD_POST, "/vectordb/embeddings/text", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], []);
|
||||
$this->assertEquals(400, $missingTexts['headers']['status-code']);
|
||||
|
||||
// Error: invalid texts item type (must be strings)
|
||||
$invalidItem = $this->client->call(Client::METHOD_POST, "/vectordb/embeddings/text", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'model' => 'embeddinggemma',
|
||||
'texts' => [
|
||||
'valid text',
|
||||
123, // invalid, not a string
|
||||
],
|
||||
]);
|
||||
$this->assertEquals(400, $invalidItem['headers']['status-code']);
|
||||
|
||||
// Error: unknown embedding model
|
||||
$unknownModel = $this->client->call(Client::METHOD_POST, "/vectordb/embeddings/text", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'model' => 'nonexistent-model',
|
||||
'texts' => ['hello'],
|
||||
]);
|
||||
$this->assertEquals(400, $unknownModel['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testBulkUpsert(): void
|
||||
{
|
||||
// Setup fresh db/collection
|
||||
$db = $this->client->call(Client::METHOD_POST, '/vectordb', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [ 'databaseId' => ID::unique(), 'name' => 'BulkDBUpsert' ]);
|
||||
$this->assertEquals(201, $db['headers']['status-code']);
|
||||
$databaseId = $db['body']['$id'];
|
||||
|
||||
$col = $this->client->call(Client::METHOD_POST, "/vectordb/{$databaseId}/collections", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'BulkColUpsert',
|
||||
'documentSecurity' => true,
|
||||
'dimension' => 3,
|
||||
'permissions' => [Permission::read(Role::any())]
|
||||
]);
|
||||
$this->assertEquals(201, $col['headers']['status-code']);
|
||||
$collectionId = $col['body']['$id'];
|
||||
|
||||
$docs = [
|
||||
[
|
||||
'embeddings' => [0.5, 0.5, 0.0],
|
||||
'metadata' => ['group' => 'bulkA', 'updated' => true],
|
||||
'$permissions' => [Permission::read(Role::any())]
|
||||
],
|
||||
[
|
||||
'embeddings' => [0.2, 0.8, 0.0],
|
||||
'metadata' => ['group' => 'bulkB', 'updated' => true],
|
||||
'$permissions' => [Permission::read(Role::any())]
|
||||
],
|
||||
];
|
||||
|
||||
$res = $this->client->call(Client::METHOD_PUT, "/vectordb/{$databaseId}/collections/{$collectionId}/documents", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'documents' => $docs
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $res['headers']['status-code']);
|
||||
$this->assertIsArray($res['body']['documents']);
|
||||
$this->assertCount(2, $res['body']['documents']);
|
||||
$this->assertTrue($res['body']['documents'][0]['metadata']['updated']);
|
||||
$this->assertTrue($res['body']['documents'][1]['metadata']['updated']);
|
||||
|
||||
// Fetch and validate updated content
|
||||
$ids = array_map(fn ($d) => $d['$id'], $res['body']['documents']);
|
||||
foreach ($ids as $id) {
|
||||
$get = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/documents/{$id}", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(200, $get['headers']['status-code']);
|
||||
$this->assertTrue($get['body']['metadata']['updated']);
|
||||
}
|
||||
|
||||
// Perform another bulk upsert to mutate the same documents
|
||||
$docs2 = [
|
||||
[ 'embeddings' => [0.6, 0.4, 0.0], 'metadata' => ['updatedAgain' => true] ],
|
||||
[ 'embeddings' => [0.3, 0.7, 0.0], 'metadata' => ['updatedAgain' => true] ],
|
||||
];
|
||||
$res2 = $this->client->call(Client::METHOD_PUT, "/vectordb/{$databaseId}/collections/{$collectionId}/documents", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'documents' => $docs2
|
||||
]);
|
||||
$this->assertEquals(200, $res2['headers']['status-code']);
|
||||
$this->assertIsArray($res2['body']['documents']);
|
||||
$this->assertCount(2, $res2['body']['documents']);
|
||||
|
||||
// Fetch again and assert second update persisted
|
||||
$ids2 = array_map(fn ($d) => $d['$id'], $res2['body']['documents']);
|
||||
foreach ($ids2 as $id) {
|
||||
$get2 = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/documents/{$id}", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(200, $get2['headers']['status-code']);
|
||||
$this->assertTrue($get2['body']['metadata']['updatedAgain']);
|
||||
}
|
||||
}
|
||||
|
||||
public function testBulkUpdate(): void
|
||||
{
|
||||
// Setup: create db/collection and two docs
|
||||
$db = $this->client->call(Client::METHOD_POST, '/vectordb', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [ 'databaseId' => ID::unique(), 'name' => 'BulkDBUpdate' ]);
|
||||
$this->assertEquals(201, $db['headers']['status-code']);
|
||||
$databaseId = $db['body']['$id'];
|
||||
|
||||
$col = $this->client->call(Client::METHOD_POST, "/vectordb/{$databaseId}/collections", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'BulkColUpdate',
|
||||
'documentSecurity' => true,
|
||||
'dimension' => 3,
|
||||
'permissions' => [Permission::read(Role::any())]
|
||||
]);
|
||||
$this->assertEquals(201, $col['headers']['status-code']);
|
||||
$collectionId = $col['body']['$id'];
|
||||
|
||||
$seed = $this->client->call(Client::METHOD_PUT, "/vectordb/{$databaseId}/collections/{$collectionId}/documents", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'documents' => [
|
||||
['embeddings' => [1.0,0.0,0.0], 'metadata' => ['seed' => 1], '$permissions' => [Permission::read(Role::any())]],
|
||||
['embeddings' => [0.0,1.0,0.0], 'metadata' => ['seed' => 2], '$permissions' => [Permission::read(Role::any())]]
|
||||
]
|
||||
]);
|
||||
$this->assertEquals(200, $seed['headers']['status-code']);
|
||||
$ids = array_map(fn ($d) => $d['$id'], $seed['body']['documents']);
|
||||
|
||||
$res = $this->client->call(Client::METHOD_PATCH, "/vectordb/{$databaseId}/collections/{$collectionId}/documents", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'data' => [ 'metadata' => ['bulkUpdated' => true] ],
|
||||
'queries' => [
|
||||
\Utopia\Database\Query::equal('$id', $ids)->toString()
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $res['headers']['status-code']);
|
||||
$this->assertIsArray($res['body']['documents']);
|
||||
$this->assertCount(2, $res['body']['documents']);
|
||||
foreach ($res['body']['documents'] as $doc) {
|
||||
$this->assertTrue($doc['metadata']['bulkUpdated']);
|
||||
}
|
||||
|
||||
// Fetch by IDs and assert update persisted
|
||||
foreach ($ids as $id) {
|
||||
$get = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/documents/{$id}", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(200, $get['headers']['status-code']);
|
||||
$this->assertTrue($get['body']['metadata']['bulkUpdated']);
|
||||
}
|
||||
}
|
||||
|
||||
public function testBulkDelete(): void
|
||||
{
|
||||
// Setup: create db/collection and two docs
|
||||
$db = $this->client->call(Client::METHOD_POST, '/vectordb', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [ 'databaseId' => ID::unique(), 'name' => 'BulkDBDelete' ]);
|
||||
$this->assertEquals(201, $db['headers']['status-code']);
|
||||
$databaseId = $db['body']['$id'];
|
||||
|
||||
$col = $this->client->call(Client::METHOD_POST, "/vectordb/{$databaseId}/collections", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'BulkColDelete',
|
||||
'documentSecurity' => true,
|
||||
'dimension' => 3,
|
||||
'permissions' => [Permission::read(Role::any())]
|
||||
]);
|
||||
$this->assertEquals(201, $col['headers']['status-code']);
|
||||
$collectionId = $col['body']['$id'];
|
||||
|
||||
$seed = $this->client->call(Client::METHOD_PUT, "/vectordb/{$databaseId}/collections/{$collectionId}/documents", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'documents' => [
|
||||
['embeddings' => [1.0,0.0,0.0], 'metadata' => ['seed' => 1], '$permissions' => [Permission::read(Role::any())]],
|
||||
['embeddings' => [0.0,1.0,0.0], 'metadata' => ['seed' => 2], '$permissions' => [Permission::read(Role::any())]]
|
||||
]
|
||||
]);
|
||||
$this->assertEquals(200, $seed['headers']['status-code']);
|
||||
$ids = array_map(fn ($d) => $d['$id'], $seed['body']['documents']);
|
||||
|
||||
$res = $this->client->call(Client::METHOD_DELETE, "/vectordb/{$databaseId}/collections/{$collectionId}/documents", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'queries' => [
|
||||
\Utopia\Database\Query::equal('$id', $ids)->toString()
|
||||
]
|
||||
]);
|
||||
$this->assertEquals(200, $res['headers']['status-code']);
|
||||
$this->assertIsInt($res['body']['total'] ?? 0);
|
||||
$this->assertGreaterThanOrEqual(2, $res['body']['total']);
|
||||
|
||||
// Ensure they are deleted
|
||||
foreach ($ids as $id) {
|
||||
$get = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/documents/{$id}", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals(404, $get['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
|
||||
public function testCustomTimestamps(): void
|
||||
{
|
||||
// Setup: create database and collection
|
||||
$db = $this->client->call(Client::METHOD_POST, '/vectordb', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'TimestampTestDB'
|
||||
]);
|
||||
$this->assertEquals(201, $db['headers']['status-code']);
|
||||
$databaseId = $db['body']['$id'];
|
||||
|
||||
$col = $this->client->call(Client::METHOD_POST, "/vectordb/{$databaseId}/collections", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'TimestampTestCollection',
|
||||
'documentSecurity' => true,
|
||||
'dimension' => 1536,
|
||||
'permissions' => [Permission::read(Role::any())]
|
||||
]);
|
||||
$this->assertEquals(201, $col['headers']['status-code']);
|
||||
$collectionId = $col['body']['$id'];
|
||||
|
||||
// Test: Create document with custom timestamps using PUT (upsert)
|
||||
$customCreatedAt = '1970-01-01T00:00:00.000+00:00';
|
||||
$customUpdatedAt = '1970-01-01T00:00:00.000+00:00';
|
||||
$vector = array_fill(0, 1536, 0.0);
|
||||
$vector[0] = 1.0;
|
||||
$documentId = ID::unique();
|
||||
|
||||
$doc = $this->client->call(Client::METHOD_POST, "/vectordb/{$databaseId}/collections/{$collectionId}/documents", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'documentId' => $documentId,
|
||||
'data' => [
|
||||
'$createdAt' => $customCreatedAt,
|
||||
'$updatedAt' => $customUpdatedAt,
|
||||
'embeddings' => $vector,
|
||||
'metadata' => ['test' => 'custom_timestamps']
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $doc['headers']['status-code']);
|
||||
$documentId = $doc['body']['$id'];
|
||||
$this->assertNotEmpty($documentId);
|
||||
|
||||
// Verify timestamps were set correctly
|
||||
$this->assertEquals($customCreatedAt, $doc['body']['$createdAt'], 'CreatedAt should match custom timestamp');
|
||||
$this->assertEquals($customUpdatedAt, $doc['body']['$updatedAt'], 'UpdatedAt should match custom timestamp');
|
||||
|
||||
// Fetch document and verify timestamps persist
|
||||
$fetched = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $fetched['headers']['status-code']);
|
||||
$this->assertEquals($customCreatedAt, $fetched['body']['$createdAt'], 'CreatedAt should persist after fetch');
|
||||
$this->assertEquals($customUpdatedAt, $fetched['body']['$updatedAt'], 'UpdatedAt should persist after fetch');
|
||||
|
||||
// Test: Update document with new custom timestamps
|
||||
$newCustomUpdatedAt = '2000-01-01T12:00:00.000+00:00';
|
||||
$vector2 = array_fill(0, 1536, 0.0);
|
||||
$vector2[1] = 1.0;
|
||||
|
||||
$updated = $this->client->call(Client::METHOD_PUT, "/vectordb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'data' => [
|
||||
'$createdAt' => $customCreatedAt, // Keep original createdAt
|
||||
'$updatedAt' => $newCustomUpdatedAt, // Update updatedAt
|
||||
'embeddings' => $vector2,
|
||||
'metadata' => ['test' => 'updated_timestamps']
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $updated['headers']['status-code']);
|
||||
$this->assertEquals($customCreatedAt, $updated['body']['$createdAt'], 'CreatedAt should remain unchanged');
|
||||
$this->assertEquals($newCustomUpdatedAt, $updated['body']['$updatedAt'], 'UpdatedAt should be updated to new custom timestamp');
|
||||
|
||||
// Final verification
|
||||
$final = $this->client->call(Client::METHOD_GET, "/vectordb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $final['headers']['status-code']);
|
||||
$this->assertEquals($customCreatedAt, $final['body']['$createdAt'], 'CreatedAt should persist through updates');
|
||||
$this->assertEquals($newCustomUpdatedAt, $final['body']['$updatedAt'], 'UpdatedAt should reflect the latest custom timestamp');
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user