mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
363 lines
14 KiB
PHP
363 lines
14 KiB
PHP
<?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 DatabasesTransactionsTest extends Scope
|
|
{
|
|
use ProjectCustom;
|
|
use SideClient;
|
|
|
|
/**
|
|
* Test creating a transaction
|
|
*/
|
|
public function testCreate(): array
|
|
{
|
|
// Create database first
|
|
$database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $this->getProject()['apiKey']
|
|
]), [
|
|
'databaseId' => ID::unique(),
|
|
'name' => 'TransactionTestDatabase'
|
|
]);
|
|
|
|
$this->assertEquals(201, $database['headers']['status-code']);
|
|
$databaseId = $database['body']['$id'];
|
|
|
|
// Test creating a transaction with default TTL
|
|
$response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()));
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertArrayHasKey('$id', $response['body']);
|
|
$this->assertArrayHasKey('status', $response['body']);
|
|
$this->assertArrayHasKey('operations', $response['body']);
|
|
$this->assertArrayHasKey('expiresAt', $response['body']);
|
|
$this->assertEquals('pending', $response['body']['status']);
|
|
$this->assertEquals(0, $response['body']['operations']);
|
|
|
|
$transactionId1 = $response['body']['$id'];
|
|
|
|
// Test creating a transaction with custom TTL
|
|
$response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'ttl' => 900
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertEquals('pending', $response['body']['status']);
|
|
|
|
$expiresAt = new \DateTime($response['body']['expiresAt']);
|
|
$now = new \DateTime();
|
|
$diff = $expiresAt->getTimestamp() - $now->getTimestamp();
|
|
$this->assertGreaterThan(800, $diff);
|
|
$this->assertLessThan(1000, $diff);
|
|
|
|
$transactionId2 = $response['body']['$id'];
|
|
|
|
// Test invalid TTL values
|
|
$response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'ttl' => 30 // Below minimum
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
$response = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'ttl' => 4000 // Above maximum
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
return [
|
|
'databaseId' => $databaseId,
|
|
'transactionId1' => $transactionId1,
|
|
'transactionId2' => $transactionId2
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @depends testCreate
|
|
*/
|
|
public function testAddOperations(array $data): array
|
|
{
|
|
$databaseId = $data['databaseId'];
|
|
$transactionId = $data['transactionId1'];
|
|
|
|
// Create a collection for testing
|
|
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $this->getProject()['apiKey']
|
|
]), [
|
|
'collectionId' => ID::unique(),
|
|
'name' => 'TransactionOperationsTest',
|
|
'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'];
|
|
|
|
// Add attributes
|
|
$attribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $this->getProject()['apiKey']
|
|
]), [
|
|
'key' => 'name',
|
|
'size' => 256,
|
|
'required' => true,
|
|
]);
|
|
|
|
$this->assertEquals(202, $attribute['headers']['status-code']);
|
|
|
|
// Wait for attribute to be created
|
|
sleep(2);
|
|
|
|
// Add valid operations
|
|
$response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'operations' => [
|
|
[
|
|
'databaseId' => $databaseId,
|
|
'collectionId' => $collectionId,
|
|
'action' => 'create',
|
|
'documentId' => 'doc1',
|
|
'data' => [
|
|
'$id' => 'doc1',
|
|
'name' => 'Test Document 1'
|
|
]
|
|
],
|
|
[
|
|
'databaseId' => $databaseId,
|
|
'collectionId' => $collectionId,
|
|
'action' => 'create',
|
|
'documentId' => 'doc2',
|
|
'data' => [
|
|
'$id' => 'doc2',
|
|
'name' => 'Test Document 2'
|
|
]
|
|
]
|
|
]
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertEquals(2, $response['body']['operations']);
|
|
|
|
// Test adding more operations
|
|
$response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'operations' => [
|
|
[
|
|
'databaseId' => $databaseId,
|
|
'collectionId' => $collectionId,
|
|
'action' => 'update',
|
|
'documentId' => 'doc1',
|
|
'data' => [
|
|
'name' => 'Updated Document 1'
|
|
]
|
|
]
|
|
]
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
$this->assertEquals(3, $response['body']['operations']);
|
|
|
|
// Test invalid database ID
|
|
$response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'operations' => [
|
|
[
|
|
'databaseId' => 'invalid_database',
|
|
'collectionId' => $collectionId,
|
|
'action' => 'create',
|
|
'data' => ['name' => 'Test']
|
|
]
|
|
]
|
|
]);
|
|
|
|
$this->assertEquals(404, $response['headers']['status-code']);
|
|
|
|
// Test invalid collection ID
|
|
$response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'operations' => [
|
|
[
|
|
'databaseId' => $databaseId,
|
|
'collectionId' => 'invalid_collection',
|
|
'action' => 'create',
|
|
'data' => ['name' => 'Test']
|
|
]
|
|
]
|
|
]);
|
|
|
|
$this->assertEquals(404, $response['headers']['status-code']);
|
|
|
|
return array_merge($data, [
|
|
'collectionId' => $collectionId
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @depends testAddOperations
|
|
*/
|
|
public function testCommit(array $data): void
|
|
{
|
|
$databaseId = $data['databaseId'];
|
|
$collectionId = $data['collectionId'];
|
|
$transactionId = $data['transactionId1'];
|
|
|
|
// Commit the transaction
|
|
$response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'commit' => true
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertEquals('committed', $response['body']['status']);
|
|
|
|
// Verify documents were created
|
|
$documents = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()));
|
|
|
|
$this->assertEquals(200, $documents['headers']['status-code']);
|
|
$this->assertEquals(2, $documents['body']['total']);
|
|
|
|
// Verify the update was applied
|
|
$doc1Found = false;
|
|
foreach ($documents['body']['documents'] as $doc) {
|
|
if ($doc['$id'] === 'doc1') {
|
|
$this->assertEquals('Updated Document 1', $doc['name']);
|
|
$doc1Found = true;
|
|
}
|
|
}
|
|
$this->assertTrue($doc1Found, 'Document doc1 should exist with updated name');
|
|
|
|
// Test committing already committed transaction
|
|
$response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'commit' => true
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
}
|
|
|
|
/**
|
|
* @depends testCreate
|
|
*/
|
|
public function testRollback(array $data): void
|
|
{
|
|
$databaseId = $data['databaseId'];
|
|
$transactionId = $data['transactionId2'];
|
|
|
|
// Create a collection for rollback test
|
|
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $this->getProject()['apiKey']
|
|
]), [
|
|
'collectionId' => ID::unique(),
|
|
'name' => 'TransactionRollbackTest',
|
|
'documentSecurity' => false,
|
|
'permissions' => [
|
|
Permission::create(Role::any()),
|
|
Permission::read(Role::any()),
|
|
Permission::update(Role::any()),
|
|
Permission::delete(Role::any()),
|
|
],
|
|
]);
|
|
|
|
$collectionId = $collection['body']['$id'];
|
|
|
|
// Add attribute
|
|
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-key' => $this->getProject()['apiKey']
|
|
]), [
|
|
'key' => 'value',
|
|
'size' => 256,
|
|
'required' => true,
|
|
]);
|
|
|
|
sleep(2);
|
|
|
|
// Add operations
|
|
$response = $this->client->call(Client::METHOD_POST, "/databases/transactions/{$transactionId}/operations", array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'operations' => [
|
|
[
|
|
'databaseId' => $databaseId,
|
|
'collectionId' => $collectionId,
|
|
'action' => 'create',
|
|
'data' => [
|
|
'$id' => 'rollback_doc',
|
|
'value' => 'Should not exist'
|
|
]
|
|
]
|
|
]
|
|
]);
|
|
|
|
$this->assertEquals(201, $response['headers']['status-code']);
|
|
|
|
// Rollback the transaction
|
|
$response = $this->client->call(Client::METHOD_PATCH, "/databases/transactions/{$transactionId}", array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'rollback' => true
|
|
]);
|
|
|
|
$this->assertEquals(200, $response['headers']['status-code']);
|
|
$this->assertEquals('rolledBack', $response['body']['status']);
|
|
|
|
// Verify no documents were created
|
|
$documents = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()));
|
|
|
|
$this->assertEquals(200, $documents['headers']['status-code']);
|
|
$this->assertEquals(0, $documents['body']['total']);
|
|
}
|
|
} |