Merge branch '1.9.x' into presence-api

This commit is contained in:
Jake Barnby
2026-05-07 19:13:57 +12:00
committed by GitHub
99 changed files with 3900 additions and 1414 deletions
+1
View File
@@ -380,6 +380,7 @@ function computeFlow(ctx) {
api('GET', '/functions/runtimes', null, ctx.sessionHeaders, [200], 'functions.runtimes.list');
api('GET', '/functions/specifications', null, ctx.apiHeaders, [200], 'functions.specifications.list');
const functionVariable = api('POST', `/functions/${functionId}/variables`, {
variableId: 'unique()',
key: 'BENCHMARK',
value: 'true',
secret: false,
+2 -2
View File
@@ -173,8 +173,8 @@ trait ProjectCustom
'oauth2.write',
'mocks.read',
'mocks.write',
'policies.read',
'policies.write',
'project.policies.read',
'project.policies.write',
'templates.read',
'templates.write',
],
@@ -4163,4 +4163,72 @@ class AccountCustomClientTest extends Scope
$this->assertEquals(401, $verification3['headers']['status-code']);
}
public function testRefreshEmailPasswordSession(): void
{
$email = uniqid() . 'user@localhost.test';
$account = $this->client->call(Client::METHOD_POST, '/account', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'userId' => ID::unique(),
'email' => $email,
'password' => 'password',
]);
$this->assertEquals(201, $account['headers']['status-code']);
$session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => $email,
'password' => 'password',
]);
$this->assertEquals(201, $session['headers']['status-code']);
$this->assertNotEmpty($session['body']['$id']);
$sessionId = $session['body']['$id'];
$cookie = 'a_session_' . $this->getProject()['$id'] . '=' .$session['cookies']['a_session_' . $this->getProject()['$id']];
$session = $this->client->call(Client::METHOD_GET, '/account/sessions/current', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => $cookie,
]));
$this->assertEquals(200, $session['headers']['status-code']);
$this->assertNotEmpty($session['body']['expire']);
$expiryBefore = $session['body']['expire'];
\sleep(3); // Small delay to ensure expiry an expand
$session = $this->client->call(Client::METHOD_PATCH, '/account/sessions/' . $sessionId, array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => $cookie,
]));
$this->assertEquals(200, $session['headers']['status-code']);
$this->assertNotEmpty($session['body']['expire']);
$expiryAfter = $session['body']['expire'];
$this->assertGreaterThan(\strtotime($expiryBefore), \strtotime($expiryAfter));
$session = $this->client->call(Client::METHOD_GET, '/account/sessions/current', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => $cookie,
]));
$this->assertEquals(200, $session['headers']['status-code']);
$this->assertEquals(\strtotime($expiryAfter), \strtotime($session['body']['expire']));
}
}
@@ -7,8 +7,10 @@ use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideConsole;
use Utopia\Console;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
class FunctionsConsoleClientTest extends Scope
{
@@ -70,6 +72,7 @@ class FunctionsConsoleClientTest extends Scope
$variable = $this->createVariable(
$functionId,
[
'variableId' => ID::unique(),
'key' => 'APP_TEST',
'value' => 'TESTINGVALUE',
'secret' => false
@@ -82,6 +85,7 @@ class FunctionsConsoleClientTest extends Scope
$secretVariable = $this->createVariable(
$functionId,
[
'variableId' => ID::unique(),
'key' => 'APP_TEST_1',
'value' => 'TESTINGVALUE_1',
'secret' => true
@@ -196,6 +200,7 @@ class FunctionsConsoleClientTest extends Scope
$variable = $this->createVariable(
$functionId,
[
'variableId' => ID::unique(),
'key' => 'APP_TEST',
'value' => 'TESTINGVALUE',
'secret' => false
@@ -208,6 +213,7 @@ class FunctionsConsoleClientTest extends Scope
$variable = $this->createVariable(
$functionId,
[
'variableId' => ID::unique(),
'key' => 'APP_TEST_1',
'value' => 'TESTINGVALUE_1',
'secret' => true
@@ -226,6 +232,7 @@ class FunctionsConsoleClientTest extends Scope
$variable = $this->createVariable(
$functionId,
[
'variableId' => ID::unique(),
'key' => 'APP_TEST',
'value' => 'ANOTHERTESTINGVALUE',
'secret' => false
@@ -234,10 +241,47 @@ class FunctionsConsoleClientTest extends Scope
$this->assertEquals(409, $variable['headers']['status-code']);
// Test for invalid variableId
$variable = $this->createVariable(
$functionId,
[
'variableId' => '!invalid-id!',
'key' => 'INVALID_ID_KEY',
'value' => 'value',
]
);
$this->assertEquals(400, $variable['headers']['status-code']);
// Test for duplicate variableId
$duplicateVariableId = ID::unique();
$variable = $this->createVariable(
$functionId,
[
'variableId' => $duplicateVariableId,
'key' => 'DUP_ID_KEY_1',
'value' => 'value1',
]
);
$this->assertEquals(201, $variable['headers']['status-code']);
$duplicate = $this->createVariable(
$functionId,
[
'variableId' => $duplicateVariableId,
'key' => 'DUP_ID_KEY_2',
'value' => 'value2',
]
);
$this->assertEquals(409, $duplicate['headers']['status-code']);
// Test for invalid key
$variable = $this->createVariable(
$functionId,
[
'variableId' => ID::unique(),
'key' => str_repeat("A", 256),
'value' => 'TESTINGVALUE'
]
@@ -249,6 +293,7 @@ class FunctionsConsoleClientTest extends Scope
$variable = $this->createVariable(
$functionId,
[
'variableId' => ID::unique(),
'key' => 'LONGKEY',
'value' => str_repeat("#", 8193),
]
@@ -283,6 +328,150 @@ class FunctionsConsoleClientTest extends Scope
*/
}
public function testListVariablesWithLimit(): void
{
// Create a fresh function for this test
$function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Test List Variables With Limit',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
]);
$this->assertEquals(201, $function['headers']['status-code']);
$functionId = $function['body']['$id'];
$variable1 = $this->createVariable($functionId, [
'variableId' => ID::unique(),
'key' => 'LIMIT_KEY_1',
'value' => 'limit-value-1',
]);
$this->assertEquals(201, $variable1['headers']['status-code']);
$variable2 = $this->createVariable($functionId, [
'variableId' => ID::unique(),
'key' => 'LIMIT_KEY_2',
'value' => 'limit-value-2',
]);
$this->assertEquals(201, $variable2['headers']['status-code']);
// List with limit of 1
$response = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/variables', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::limit(1)->toString(),
],
'total' => true,
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(1, $response['body']['variables']);
$this->assertGreaterThanOrEqual(2, $response['body']['total']);
$this->cleanupFunction($functionId);
}
public function testListVariablesWithoutTotal(): void
{
// Create a fresh function for this test
$function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Test List Variables Without Total',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
]);
$this->assertEquals(201, $function['headers']['status-code']);
$functionId = $function['body']['$id'];
$variable = $this->createVariable($functionId, [
'variableId' => ID::unique(),
'key' => 'NO_TOTAL_KEY',
'value' => 'no-total-value',
]);
$this->assertEquals(201, $variable['headers']['status-code']);
// List with total=false
$response = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/variables', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'total' => false,
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(0, $response['body']['total']);
$this->assertGreaterThanOrEqual(1, \count($response['body']['variables']));
$this->cleanupFunction($functionId);
}
public function testListVariablesCursorPagination(): void
{
// Create a fresh function for this test
$function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Test List Variables Cursor Pagination',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
]);
$this->assertEquals(201, $function['headers']['status-code']);
$functionId = $function['body']['$id'];
$variable1 = $this->createVariable($functionId, [
'variableId' => ID::unique(),
'key' => 'CURSOR_KEY_1',
'value' => 'cursor-value-1',
]);
$this->assertEquals(201, $variable1['headers']['status-code']);
$variable2 = $this->createVariable($functionId, [
'variableId' => ID::unique(),
'key' => 'CURSOR_KEY_2',
'value' => 'cursor-value-2',
]);
$this->assertEquals(201, $variable2['headers']['status-code']);
// Get first page with limit 1
$page1 = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/variables', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::limit(1)->toString(),
],
'total' => true,
]);
$this->assertEquals(200, $page1['headers']['status-code']);
$this->assertCount(1, $page1['body']['variables']);
$cursorId = $page1['body']['variables'][0]['$id'];
// Get next page using cursor
$page2 = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/variables', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::limit(1)->toString(),
Query::cursorAfter(new Document(['$id' => $cursorId]))->toString(),
],
'total' => true,
]);
$this->assertEquals(200, $page2['headers']['status-code']);
$this->assertCount(1, $page2['body']['variables']);
$this->assertNotEquals($cursorId, $page2['body']['variables'][0]['$id']);
$this->cleanupFunction($functionId);
}
public function testGetVariable(): void
{
$data = $this->setupTestVariables();
@@ -337,6 +526,7 @@ class FunctionsConsoleClientTest extends Scope
$functionId = $function['body']['$id'];
$variable = $this->createVariable($functionId, [
'variableId' => ID::unique(),
'key' => 'APP_TEST',
'value' => 'TESTINGVALUE',
'secret' => false
@@ -345,6 +535,7 @@ class FunctionsConsoleClientTest extends Scope
$variableId = $variable['body']['$id'];
$secretVariable = $this->createVariable($functionId, [
'variableId' => ID::unique(),
'key' => 'APP_TEST_1',
'value' => 'TESTINGVALUE_1',
'secret' => true
@@ -457,6 +648,7 @@ class FunctionsConsoleClientTest extends Scope
* Test for FAILURE
*/
// Update with no parameters should fail with 400
$response = $this->client->call(Client::METHOD_PUT, '/functions/' . $functionId . '/variables/' . $variableId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -464,6 +656,7 @@ class FunctionsConsoleClientTest extends Scope
$this->assertEquals(400, $response['headers']['status-code']);
// Update with only value should succeed
$response = $this->client->call(Client::METHOD_PUT, '/functions/' . $functionId . '/variables/' . $variableId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -471,7 +664,7 @@ class FunctionsConsoleClientTest extends Scope
'value' => 'TESTINGVALUEUPDATED_2'
]);
$this->assertEquals(400, $response['headers']['status-code']);
$this->assertEquals(200, $response['headers']['status-code']);
$longKey = str_repeat("A", 256);
$response = $this->client->call(Client::METHOD_PUT, '/functions/' . $functionId . '/variables/' . $variableId, array_merge([
@@ -496,6 +689,110 @@ class FunctionsConsoleClientTest extends Scope
$this->assertEquals(400, $response['headers']['status-code']);
}
public function testUpdateVariableKey(): void
{
// Create a fresh function and variable for this test
$function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Test Update Variable Key',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
]);
$this->assertEquals(201, $function['headers']['status-code']);
$functionId = $function['body']['$id'];
$variable = $this->createVariable($functionId, [
'variableId' => ID::unique(),
'key' => 'KEY_BEFORE',
'value' => 'unchanged-value',
'secret' => false
]);
$this->assertEquals(201, $variable['headers']['status-code']);
$variableId = $variable['body']['$id'];
// Update only key (key is nullable, but we provide a new key)
$response = $this->client->call(Client::METHOD_PUT, '/functions/' . $functionId . '/variables/' . $variableId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'key' => 'KEY_AFTER',
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('KEY_AFTER', $response['body']['key']);
$this->assertEquals('unchanged-value', $response['body']['value']);
$this->cleanupFunction($functionId);
}
public function testUpdateVariableValueOnly(): void
{
// Create a fresh function and variable for this test
$function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Test Update Variable Value',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
]);
$this->assertEquals(201, $function['headers']['status-code']);
$functionId = $function['body']['$id'];
$variable = $this->createVariable($functionId, [
'variableId' => ID::unique(),
'key' => 'UNCHANGED_KEY',
'value' => 'value-before',
'secret' => false
]);
$this->assertEquals(201, $variable['headers']['status-code']);
$variableId = $variable['body']['$id'];
// Update only value
$response = $this->client->call(Client::METHOD_PUT, '/functions/' . $functionId . '/variables/' . $variableId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'value' => 'value-after',
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('UNCHANGED_KEY', $response['body']['key']);
$this->assertEquals('value-after', $response['body']['value']);
$this->cleanupFunction($functionId);
}
public function testUpdateVariableNotFound(): void
{
// Create a fresh function for this test
$function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Test Update Variable Not Found',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
]);
$this->assertEquals(201, $function['headers']['status-code']);
$functionId = $function['body']['$id'];
$response = $this->client->call(Client::METHOD_PUT, '/functions/' . $functionId . '/variables/non-existent-id', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'key' => 'NEW_KEY',
'value' => 'new-value',
]);
$this->assertEquals(404, $response['headers']['status-code']);
$this->assertEquals('variable_not_found', $response['body']['type']);
$this->cleanupFunction($functionId);
}
public function testDeleteVariable(): void
{
// Create a fresh function and variables for this test since it deletes them
@@ -512,6 +809,7 @@ class FunctionsConsoleClientTest extends Scope
$functionId = $function['body']['$id'];
$variable = $this->createVariable($functionId, [
'variableId' => ID::unique(),
'key' => 'APP_TEST',
'value' => 'TESTINGVALUE',
'secret' => false
@@ -520,6 +818,7 @@ class FunctionsConsoleClientTest extends Scope
$variableId = $variable['body']['$id'];
$secretVariable = $this->createVariable($functionId, [
'variableId' => ID::unique(),
'key' => 'APP_TEST_1',
'value' => 'TESTINGVALUE_1',
'secret' => true
@@ -585,6 +884,7 @@ class FunctionsConsoleClientTest extends Scope
// create variable
$variable = $this->createVariable($functionId, [
'variableId' => ID::unique(),
'key' => 'CUSTOM_VARIABLE',
'value' => 'a_secret_value',
'secret' => true,
@@ -53,14 +53,17 @@ class FunctionsCustomServerTest extends Scope
$functionId = $function['body']['$id'] ?? '';
$variable = $this->createVariable($functionId, [
'variableId' => 'unique()',
'key' => 'funcKey1',
'value' => 'funcValue1',
]);
$variable2 = $this->createVariable($functionId, [
'variableId' => 'unique()',
'key' => 'funcKey2',
'value' => 'funcValue2',
]);
$variable3 = $this->createVariable($functionId, [
'variableId' => 'unique()',
'key' => 'funcKey3',
'value' => 'funcValue3',
]);
@@ -109,6 +112,7 @@ class FunctionsCustomServerTest extends Scope
// Create a variable for later tests
$variable = $this->createVariable($functionId, [
'variableId' => 'unique()',
'key' => 'GLOBAL_VARIABLE',
'value' => 'Global Variable Value',
]);
@@ -278,14 +282,17 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(10, $function['body']['timeout']);
$variable = $this->createVariable($functionId, [
'variableId' => 'unique()',
'key' => 'funcKey1',
'value' => 'funcValue1',
]);
$variable2 = $this->createVariable($functionId, [
'variableId' => 'unique()',
'key' => 'funcKey2',
'value' => 'funcValue2',
]);
$variable3 = $this->createVariable($functionId, [
'variableId' => 'unique()',
'key' => 'funcKey3',
'value' => 'funcValue3',
]);
@@ -521,6 +528,7 @@ class FunctionsCustomServerTest extends Scope
// Create a variable for later tests
$variable = $this->createVariable($functionId, [
'variableId' => 'unique()',
'key' => 'GLOBAL_VARIABLE',
'value' => 'Global Variable Value',
]);
@@ -2011,6 +2019,7 @@ class FunctionsCustomServerTest extends Scope
]);
$variable = $this->createVariable($functionId, [
'variableId' => 'unique()',
'key' => 'CUSTOM_VARIABLE',
'value' => 'variable'
]);
@@ -55,10 +55,10 @@ class FunctionsClientTest extends Scope
$query = '
mutation createVariables($functionId: String!) {
var1: functionsCreateVariable(functionId: $functionId, key: "name", value: "John Doe") {
var1: functionsCreateVariable(functionId: $functionId, variableId: "unique()", key: "name", value: "John Doe") {
_id
}
var2: functionsCreateVariable(functionId: $functionId, key: "age", value: "42") {
var2: functionsCreateVariable(functionId: $functionId, variableId: "unique()", key: "age", value: "42") {
_id
}
}
@@ -55,10 +55,10 @@ class FunctionsServerTest extends Scope
$query = '
mutation createVariables($functionId: String!) {
var1: functionsCreateVariable(functionId: $functionId, key: "name", value: "John Doe") {
var1: functionsCreateVariable(functionId: $functionId, variableId: "unique()", key: "name", value: "John Doe") {
_id
}
var2: functionsCreateVariable(functionId: $functionId, key: "age", value: "42") {
var2: functionsCreateVariable(functionId: $functionId, variableId: "unique()", key: "age", value: "42") {
_id
}
}
@@ -1096,6 +1096,7 @@ trait MigrationsBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
'x-appwrite-response-format' => '1.9.3'
], [
'key' => 'TEST_VAR',
'value' => 'test_value',
@@ -1256,7 +1257,6 @@ trait MigrationsBase
'max' => 65,
'required' => true,
]);
$this->assertEquals(202, $response['headers']['status-code']);
$this->assertEquals($response['body']['key'], 'age');
$this->assertEquals($response['body']['type'], 'integer');
@@ -1573,6 +1573,19 @@ trait MigrationsBase
$this->assertEquals(202, $varchar['headers']['status-code']);
$bigint = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/bigint', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'key' => 'bigint',
'min' => 2147483648,
'max' => 9223372036854775807,
'required' => false,
]);
$this->assertEquals(202, $bigint['headers']['status-code']);
$mediumtext = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/mediumtext', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -1623,6 +1636,7 @@ trait MigrationsBase
'mediumtext' => 'mediumText',
'longtext' => 'longText',
'varchar' => 'varchar',
'bigint' => 2147483648 + $i,
]
]);
@@ -1711,6 +1725,8 @@ trait MigrationsBase
$this->assertStringContainsString('mediumText', $csvData, 'CSV should contain the medium column header');
$this->assertStringContainsString('longText', $csvData, 'CSV should contain the long text column header');
$this->assertStringContainsString('varchar', $csvData, 'CSV should contain the varchar column header');
$this->assertStringContainsString('bigint', $csvData, 'CSV should contain the bigint column header');
$this->assertStringContainsString('2147483649', $csvData, 'CSV should contain bigint test data');
// Cleanup
$this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, [
+217 -55
View File
@@ -5,6 +5,7 @@ namespace Tests\E2E\Services\Project;
use PHPUnit\Framework\Attributes\Before;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\E2E\Client;
use Utopia\Database\Query;
trait OAuth2Base
{
@@ -172,6 +173,40 @@ trait OAuth2Base
$this->assertNotContains('mock-unverified', $ids);
}
public function testListOAuth2ProvidersTotalFalse(): void
{
$response = $this->listOAuth2Providers(total: false);
$this->assertSame(200, $response['headers']['status-code']);
$this->assertSame(0, $response['body']['total']);
$this->assertGreaterThan(0, \count($response['body']['providers']));
}
public function testListOAuth2ProvidersWithLimit(): void
{
$response = $this->listOAuth2Providers([
Query::limit(1)->toString(),
]);
$this->assertSame(200, $response['headers']['status-code']);
$this->assertCount(1, $response['body']['providers']);
$this->assertGreaterThan(1, $response['body']['total']);
}
public function testListOAuth2ProvidersWithOffset(): void
{
$listAll = $this->listOAuth2Providers();
$this->assertSame(200, $listAll['headers']['status-code']);
$listOffset = $this->listOAuth2Providers([
Query::offset(1)->toString(),
]);
$this->assertSame(200, $listOffset['headers']['status-code']);
$this->assertCount(\count($listAll['body']['providers']) - 1, $listOffset['body']['providers']);
$this->assertSame($listAll['body']['total'], $listOffset['body']['total']);
}
// =========================================================================
// Get OAuth2 provider
// =========================================================================
@@ -188,6 +223,28 @@ trait OAuth2Base
$this->assertSame('', $response['body']['clientSecret']);
}
public function testGetOAuth2ProviderWithAlias(): void
{
// The action declares the canonical param name as `providerId` and
// registers `provider` as an alias so that older SDK versions that
// send the provider in the query string continue to work.
$headers = [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
];
$headers = \array_merge($headers, $this->getHeaders());
// Call with `provider` in query string (legacy behaviour)
$response = $this->client->call(
Client::METHOD_GET,
'/project/oauth2/github?provider=github',
$headers,
);
$this->assertSame(200, $response['headers']['status-code']);
$this->assertSame('github', $response['body']['$id']);
}
public function testGetOAuth2ProviderClientSecretWriteOnly(): void
{
$this->updateOAuth2('amazon', [
@@ -221,19 +278,23 @@ trait OAuth2Base
public function testGetOAuth2ProviderUnsupported(): void
{
// The `providerId` param is validated by a WhiteList of registered
// OAuth2 provider keys, so an unknown value is rejected at validation
// time — before the action runs — and surfaces as a generic argument
// error rather than `project_provider_unsupported`.
$response = $this->getOAuth2Provider('not-a-real-provider');
$this->assertSame(400, $response['headers']['status-code']);
$this->assertSame('project_provider_unsupported', $response['body']['type']);
$this->assertSame('general_argument_invalid', $response['body']['type']);
}
public function testGetOAuth2ProviderRegisteredInConfigButNoUpdateClass(): void
{
// `mock` is present in oAuthProviders config (enabled: true) but is NOT
// registered in Base::getProviderActions(). Get::action has two
// separate `unsupported` throw branches — testGetOAuth2ProviderUnsupported
// covers the first (provider missing from config); this covers the
// second (provider in config but missing from the action registry).
// `mock` is present in oAuthProviders config (enabled: true) but is
// NOT registered in Base::getProviderActions(). It passes the
// WhiteList validator (which only checks config membership) and
// reaches the action body, where the action-registry check throws
// `project_provider_unsupported`.
$response = $this->getOAuth2Provider('mock');
$this->assertSame(400, $response['headers']['status-code']);
@@ -1590,8 +1651,8 @@ trait OAuth2Base
$this->assertSame(200, $response['headers']['status-code']);
$this->assertSame('https://idp.example.com/.well-known/openid-configuration', $response['body']['wellKnownURL']);
$this->assertArrayHasKey('authorizationURL', $response['body']);
$this->assertArrayHasKey('tokenUrl', $response['body']);
$this->assertArrayHasKey('userInfoUrl', $response['body']);
$this->assertArrayHasKey('tokenURL', $response['body']);
$this->assertArrayHasKey('userInfoURL', $response['body']);
// Cleanup
$this->updateOAuth2('oidc', [
@@ -1599,8 +1660,8 @@ trait OAuth2Base
'clientSecret' => '',
'wellKnownURL' => '',
'authorizationURL' => '',
'tokenUrl' => '',
'userInfoUrl' => '',
'tokenURL' => '',
'userInfoURL' => '',
'enabled' => false,
]);
}
@@ -1611,15 +1672,15 @@ trait OAuth2Base
'clientId' => 'oidc-discovery',
'clientSecret' => 'oidc-discovery-secret',
'authorizationURL' => 'https://idp.example.com/oauth2/authorize',
'tokenUrl' => 'https://idp.example.com/oauth2/token',
'userInfoUrl' => 'https://idp.example.com/oauth2/userinfo',
'tokenURL' => 'https://idp.example.com/oauth2/token',
'userInfoURL' => 'https://idp.example.com/oauth2/userinfo',
'enabled' => false,
]);
$this->assertSame(200, $response['headers']['status-code']);
$this->assertSame('https://idp.example.com/oauth2/authorize', $response['body']['authorizationURL']);
$this->assertSame('https://idp.example.com/oauth2/token', $response['body']['tokenUrl']);
$this->assertSame('https://idp.example.com/oauth2/userinfo', $response['body']['userInfoUrl']);
$this->assertSame('https://idp.example.com/oauth2/token', $response['body']['tokenURL']);
$this->assertSame('https://idp.example.com/oauth2/userinfo', $response['body']['userInfoURL']);
// Cleanup
$this->updateOAuth2('oidc', [
@@ -1627,8 +1688,8 @@ trait OAuth2Base
'clientSecret' => '',
'wellKnownURL' => '',
'authorizationURL' => '',
'tokenUrl' => '',
'userInfoUrl' => '',
'tokenURL' => '',
'userInfoURL' => '',
'enabled' => false,
]);
}
@@ -1640,8 +1701,8 @@ trait OAuth2Base
'clientSecret' => '',
'wellKnownURL' => '',
'authorizationURL' => '',
'tokenUrl' => '',
'userInfoUrl' => '',
'tokenURL' => '',
'userInfoURL' => '',
'enabled' => false,
]);
@@ -1670,8 +1731,8 @@ trait OAuth2Base
'clientSecret' => '',
'wellKnownURL' => '',
'authorizationURL' => '',
'tokenUrl' => '',
'userInfoUrl' => '',
'tokenURL' => '',
'userInfoURL' => '',
'enabled' => false,
]);
@@ -1679,7 +1740,7 @@ trait OAuth2Base
'clientId' => 'oidc-partial',
'clientSecret' => 'oidc-partial-secret',
'authorizationURL' => 'https://idp.example.com/oauth2/authorize',
'tokenUrl' => 'https://idp.example.com/oauth2/token',
'tokenURL' => 'https://idp.example.com/oauth2/token',
'enabled' => true,
]);
@@ -1692,8 +1753,8 @@ trait OAuth2Base
'clientSecret' => '',
'wellKnownURL' => '',
'authorizationURL' => '',
'tokenUrl' => '',
'userInfoUrl' => '',
'tokenURL' => '',
'userInfoURL' => '',
'enabled' => false,
]);
}
@@ -1724,8 +1785,8 @@ trait OAuth2Base
'clientSecret' => '',
'wellKnownURL' => '',
'authorizationURL' => '',
'tokenUrl' => '',
'userInfoUrl' => '',
'tokenURL' => '',
'userInfoURL' => '',
'enabled' => false,
]);
}
@@ -1755,8 +1816,8 @@ trait OAuth2Base
'clientSecret' => '',
'wellKnownURL' => '',
'authorizationURL' => '',
'tokenUrl' => '',
'userInfoUrl' => '',
'tokenURL' => '',
'userInfoURL' => '',
'enabled' => false,
]);
}
@@ -1770,8 +1831,8 @@ trait OAuth2Base
'clientSecret' => '',
'wellKnownURL' => '',
'authorizationURL' => '',
'tokenUrl' => '',
'userInfoUrl' => '',
'tokenURL' => '',
'userInfoURL' => '',
'enabled' => false,
]);
@@ -1780,7 +1841,7 @@ trait OAuth2Base
'clientId' => 'oidc-split-discovery',
'clientSecret' => 'oidc-split-discovery-secret',
'authorizationURL' => 'https://idp.example.com/oauth2/authorize',
'tokenUrl' => 'https://idp.example.com/oauth2/token',
'tokenURL' => 'https://idp.example.com/oauth2/token',
'enabled' => false,
]);
@@ -1788,19 +1849,19 @@ trait OAuth2Base
// state must include the two stored URLs + the new one to satisfy
// the all-three-discovery-URLs branch of the enable check.
$enable = $this->updateOAuth2('oidc', [
'userInfoUrl' => 'https://idp.example.com/oauth2/userinfo',
'userInfoURL' => 'https://idp.example.com/oauth2/userinfo',
'enabled' => true,
]);
$this->assertSame(200, $enable['headers']['status-code']);
$this->assertTrue($enable['body']['enabled']);
// Confirm all three URLs ended up persisted (merge wrote the new
// userInfoUrl while preserving the previously stored two).
// userInfoURL while preserving the previously stored two).
$get = $this->getOAuth2Provider('oidc');
$this->assertSame(200, $get['headers']['status-code']);
$this->assertSame('https://idp.example.com/oauth2/authorize', $get['body']['authorizationURL']);
$this->assertSame('https://idp.example.com/oauth2/token', $get['body']['tokenUrl']);
$this->assertSame('https://idp.example.com/oauth2/userinfo', $get['body']['userInfoUrl']);
$this->assertSame('https://idp.example.com/oauth2/token', $get['body']['tokenURL']);
$this->assertSame('https://idp.example.com/oauth2/userinfo', $get['body']['userInfoURL']);
// Cleanup
$this->updateOAuth2('oidc', [
@@ -1808,8 +1869,8 @@ trait OAuth2Base
'clientSecret' => '',
'wellKnownURL' => '',
'authorizationURL' => '',
'tokenUrl' => '',
'userInfoUrl' => '',
'tokenURL' => '',
'userInfoURL' => '',
'enabled' => false,
]);
}
@@ -1822,8 +1883,8 @@ trait OAuth2Base
'clientSecret' => 'oidc-clear-then-enable-secret',
'wellKnownURL' => 'https://idp.example.com/.well-known/openid-configuration',
'authorizationURL' => '',
'tokenUrl' => '',
'userInfoUrl' => '',
'tokenURL' => '',
'userInfoURL' => '',
'enabled' => false,
]);
@@ -1846,8 +1907,8 @@ trait OAuth2Base
'clientSecret' => '',
'wellKnownURL' => '',
'authorizationURL' => '',
'tokenUrl' => '',
'userInfoUrl' => '',
'tokenURL' => '',
'userInfoURL' => '',
'enabled' => false,
]);
}
@@ -1868,16 +1929,16 @@ trait OAuth2Base
$switch = $this->updateOAuth2('oidc', [
'wellKnownURL' => '',
'authorizationURL' => 'https://idp.example.com/oauth2/authorize',
'tokenUrl' => 'https://idp.example.com/oauth2/token',
'userInfoUrl' => 'https://idp.example.com/oauth2/userinfo',
'tokenURL' => 'https://idp.example.com/oauth2/token',
'userInfoURL' => 'https://idp.example.com/oauth2/userinfo',
'enabled' => true,
]);
$this->assertSame(200, $switch['headers']['status-code']);
$this->assertTrue($switch['body']['enabled']);
$this->assertSame('', $switch['body']['wellKnownURL']);
$this->assertSame('https://idp.example.com/oauth2/authorize', $switch['body']['authorizationURL']);
$this->assertSame('https://idp.example.com/oauth2/token', $switch['body']['tokenUrl']);
$this->assertSame('https://idp.example.com/oauth2/userinfo', $switch['body']['userInfoUrl']);
$this->assertSame('https://idp.example.com/oauth2/token', $switch['body']['tokenURL']);
$this->assertSame('https://idp.example.com/oauth2/userinfo', $switch['body']['userInfoURL']);
// Cleanup
$this->updateOAuth2('oidc', [
@@ -1885,8 +1946,8 @@ trait OAuth2Base
'clientSecret' => '',
'wellKnownURL' => '',
'authorizationURL' => '',
'tokenUrl' => '',
'userInfoUrl' => '',
'tokenURL' => '',
'userInfoURL' => '',
'enabled' => false,
]);
}
@@ -1900,23 +1961,23 @@ trait OAuth2Base
'clientSecret' => 'oidc-clear-secret',
'wellKnownURL' => 'https://idp.example.com/.well-known/openid-configuration',
'authorizationURL' => 'https://idp.example.com/oauth2/authorize',
'tokenUrl' => 'https://idp.example.com/oauth2/token',
'userInfoUrl' => 'https://idp.example.com/oauth2/userinfo',
'tokenURL' => 'https://idp.example.com/oauth2/token',
'userInfoURL' => 'https://idp.example.com/oauth2/userinfo',
'enabled' => false,
]);
$response = $this->updateOAuth2('oidc', [
'wellKnownURL' => '',
'authorizationURL' => '',
'tokenUrl' => '',
'userInfoUrl' => '',
'tokenURL' => '',
'userInfoURL' => '',
]);
$this->assertSame(200, $response['headers']['status-code']);
$this->assertSame('', $response['body']['wellKnownURL']);
$this->assertSame('', $response['body']['authorizationURL']);
$this->assertSame('', $response['body']['tokenUrl']);
$this->assertSame('', $response['body']['userInfoUrl']);
$this->assertSame('', $response['body']['tokenURL']);
$this->assertSame('', $response['body']['userInfoURL']);
// Cleanup
$this->updateOAuth2('oidc', [
@@ -1926,6 +1987,96 @@ trait OAuth2Base
]);
}
public function testUpdateOAuth2OidcBackwardCompatibleResponseFormat(): void
{
// Reset to clean state
$this->updateOAuth2('oidc', [
'clientId' => '',
'clientSecret' => '',
'wellKnownURL' => '',
'authorizationURL' => '',
'tokenURL' => '',
'userInfoURL' => '',
'enabled' => false,
]);
$headers = [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-response-format' => '1.9.3',
];
$headers = \array_merge($headers, $this->getHeaders());
// Update using OLD param names (aliases must still work)
$response = $this->client->call(
Client::METHOD_PATCH,
'/project/oauth2/oidc',
$headers,
[
'clientId' => 'oidc-compat-client',
'clientSecret' => 'oidc-compat-secret',
'tokenUrl' => 'https://idp.example.com/oauth2/token',
'userInfoUrl' => 'https://idp.example.com/oauth2/userinfo',
'enabled' => false,
],
);
$this->assertSame(200, $response['headers']['status-code']);
$this->assertArrayHasKey('tokenUrl', $response['body']);
$this->assertArrayHasKey('userInfoUrl', $response['body']);
$this->assertArrayNotHasKey('tokenURL', $response['body']);
$this->assertArrayNotHasKey('userInfoURL', $response['body']);
$this->assertSame('https://idp.example.com/oauth2/token', $response['body']['tokenUrl']);
$this->assertSame('https://idp.example.com/oauth2/userinfo', $response['body']['userInfoUrl']);
// GET with 1.9.3 format must also return old param names
$get = $this->client->call(
Client::METHOD_GET,
'/project/oauth2/oidc',
$headers,
);
$this->assertSame(200, $get['headers']['status-code']);
$this->assertArrayHasKey('tokenUrl', $get['body']);
$this->assertArrayHasKey('userInfoUrl', $get['body']);
$this->assertArrayNotHasKey('tokenURL', $get['body']);
$this->assertArrayNotHasKey('userInfoURL', $get['body']);
$this->assertSame('https://idp.example.com/oauth2/token', $get['body']['tokenUrl']);
$this->assertSame('https://idp.example.com/oauth2/userinfo', $get['body']['userInfoUrl']);
// LIST with 1.9.3 format must also return old param names for OIDC
$list = $this->client->call(
Client::METHOD_GET,
'/project/oauth2',
$headers,
);
$this->assertSame(200, $list['headers']['status-code']);
$oidcEntry = null;
foreach ($list['body']['providers'] as $provider) {
if ($provider['$id'] === 'oidc') {
$oidcEntry = $provider;
break;
}
}
$this->assertNotNull($oidcEntry, 'OIDC provider missing from listOAuth2Providers response');
$this->assertArrayHasKey('tokenUrl', $oidcEntry);
$this->assertArrayHasKey('userInfoUrl', $oidcEntry);
$this->assertArrayNotHasKey('tokenURL', $oidcEntry);
$this->assertArrayNotHasKey('userInfoURL', $oidcEntry);
$this->assertSame('https://idp.example.com/oauth2/token', $oidcEntry['tokenUrl']);
$this->assertSame('https://idp.example.com/oauth2/userinfo', $oidcEntry['userInfoUrl']);
// Cleanup
$this->updateOAuth2('oidc', [
'clientId' => '',
'clientSecret' => '',
'tokenURL' => '',
'userInfoURL' => '',
'enabled' => false,
]);
}
// =========================================================================
// Update Okta (clientId + clientSecret + optional domain/authServer)
// =========================================================================
@@ -2573,7 +2724,7 @@ trait OAuth2Base
);
}
protected function getOAuth2Provider(string $provider, bool $authenticated = true): mixed
protected function getOAuth2Provider(string $providerId, bool $authenticated = true): mixed
{
$headers = [
'content-type' => 'application/json',
@@ -2586,13 +2737,23 @@ trait OAuth2Base
return $this->client->call(
Client::METHOD_GET,
'/project/oauth2/' . $provider,
'/project/oauth2/' . $providerId,
$headers,
);
}
protected function listOAuth2Providers(bool $authenticated = true): mixed
protected function listOAuth2Providers(?array $queries = null, ?bool $total = null, bool $authenticated = true): mixed
{
$params = [];
if ($queries !== null) {
$params['queries'] = $queries;
}
if ($total !== null) {
$params['total'] = $total;
}
$headers = [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -2606,6 +2767,7 @@ trait OAuth2Base
Client::METHOD_GET,
'/project/oauth2',
$headers,
$params,
);
}
}
@@ -89,6 +89,7 @@ class WebhooksCustomServerTest extends Scope
$this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-response-format' => '1.9.3',
], $this->getHeaders()), [
'key' => 'key1',
'value' => 'value1',
@@ -699,6 +700,7 @@ class WebhooksCustomServerTest extends Scope
$variable = $this->client->call(Client::METHOD_POST, '/functions/' . $id . '/variables', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-response-format' => '1.9.3',
], $this->getHeaders()), [
'key' => 'key1',
'value' => 'value1',
@@ -6813,6 +6813,7 @@ class ProjectsConsoleClientTest extends Scope
'x-appwrite-mode' => 'admin',
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $token,
], [
'variableId' => $variableId,
'key' => 'APP_TEST_' . $variableId,
'value' => 'TESTINGVALUE',
'secret' => false
@@ -6832,6 +6833,7 @@ class ProjectsConsoleClientTest extends Scope
'x-appwrite-mode' => 'admin',
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $token,
], [
'variableId' => $variableId,
'key' => 'APP_TEST_' . $variableId,
'value' => 'TESTINGVALUE',
'secret' => false
+689 -234
View File
@@ -2,298 +2,753 @@
namespace Tests\E2E\Services\Proxy;
use Appwrite\ID;
use Appwrite\Tests\Async;
use CURLFile;
use Tests\E2E\Client;
use Utopia\Console;
use Utopia\Database\Query;
use Utopia\System\System;
trait ProxyBase
{
use Async;
use ProxyHelpers;
protected function listRules(array $params = []): mixed
protected function tearDown(): void
{
$rule = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), $params);
return $rule;
}
protected function createAPIRule(string $domain): mixed
{
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/api', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'domain' => $domain,
// Cleanup for testRuleVerification test
// Required as it uses static domain name
$rules = $this->listRules([
'queries' => [
Query::endsWith('domain', 'webapp.com')->toString(),
Query::limit(1000)->toString(),
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
foreach ($rules['body']['rules'] as $rule) {
$ruleId = $rule['$id'];
$response = $this->deleteRule($ruleId);
$this->assertEquals(204, $response['headers']['status-code']);
}
return $rule;
if ($rules['body']['total'] > 0) {
$rules = $this->listRules([
'queries' => [
Query::endsWith('domain', 'webapp.com')->toString(),
Query::limit(1)->toString()
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(0, count($rules['body']['rules']));
$this->assertEquals(0, $rules['body']['total']);
}
}
protected function updateRuleVerification(string $ruleId): mixed
{
$rule = $this->client->call(Client::METHOD_PATCH, '/proxy/rules/' . $ruleId . '/verification', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
return $rule;
}
protected function createSiteRule(string $domain, string $siteId, string $branch = ''): mixed
{
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/site', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'domain' => $domain,
'siteId' => $siteId,
'branch' => $branch,
]);
return $rule;
}
protected function getRule(string $ruleId): mixed
{
$rule = $this->client->call(Client::METHOD_GET, '/proxy/rules/' . $ruleId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
return $rule;
}
protected function createRedirectRule(string $domain, string $url, int $statusCode, string $resourceType, string $resourceId): mixed
{
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/redirect', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'domain' => $domain,
'url' => $url,
'statusCode' => $statusCode,
'resourceType' => $resourceType,
'resourceId' => $resourceId,
]);
return $rule;
}
protected function createFunctionRule(string $domain, string $functionId, string $branch = ''): mixed
{
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/function', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'domain' => $domain,
'functionId' => $functionId,
'branch' => $branch,
]);
return $rule;
}
protected function deleteRule(string $ruleId): mixed
{
$rule = $this->client->call(Client::METHOD_DELETE, '/proxy/rules/' . $ruleId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
return $rule;
}
protected function setupAPIRule(string $domain): string
public function testCreateRule(): void
{
$domain = \uniqid() . '-api.myapp.com';
$rule = $this->createAPIRule($domain);
$this->assertEquals(201, $rule['headers']['status-code'], 'Failed to setup rule: ' . \json_encode($rule));
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals($domain, $rule['body']['domain']);
$this->assertEquals('manual', $rule['body']['trigger']);
$this->assertArrayHasKey('$id', $rule['body']);
$this->assertArrayHasKey('domain', $rule['body']);
$this->assertArrayHasKey('type', $rule['body']);
$this->assertArrayHasKey('redirectUrl', $rule['body']);
$this->assertArrayHasKey('redirectStatusCode', $rule['body']);
$this->assertArrayHasKey('deploymentResourceType', $rule['body']);
$this->assertArrayHasKey('deploymentId', $rule['body']);
$this->assertArrayHasKey('deploymentResourceId', $rule['body']);
$this->assertArrayHasKey('deploymentVcsProviderBranch', $rule['body']);
$this->assertArrayHasKey('logs', $rule['body']);
$this->assertArrayHasKey('renewAt', $rule['body']);
return $rule['body']['$id'];
}
$ruleId = $rule['body']['$id'];
protected function setupRedirectRule(string $domain, string $url, int $statusCode, string $resourceType, string $resourceId): string
{
$rule = $this->createRedirectRule($domain, $url, $statusCode, $resourceType, $resourceId);
$rule = $this->createAPIRule($domain);
$this->assertEquals(409, $rule['headers']['status-code']);
$this->assertEquals(201, $rule['headers']['status-code'], 'Failed to setup rule: ' . \json_encode($rule));
return $rule['body']['$id'];
}
protected function setupFunctionRule(string $domain, string $functionId, string $branch = ''): string
{
$rule = $this->createFunctionRule($domain, $functionId, $branch);
$this->assertEquals(201, $rule['headers']['status-code'], 'Failed to setup rule: ' . \json_encode($rule));
return $rule['body']['$id'];
}
protected function setupSiteRule(string $domain, string $siteId, string $branch = ''): string
{
$rule = $this->createSiteRule($domain, $siteId, $branch);
$this->assertEquals(201, $rule['headers']['status-code'], 'Failed to setup rule: ' . \json_encode($rule));
return $rule['body']['$id'];
}
protected function cleanupRule(string $ruleId): void
{
$rule = $this->deleteRule($ruleId);
$this->assertEquals(204, $rule['headers']['status-code'], 'Failed to cleanup rule: ' . \json_encode($rule));
$this->assertEquals(204, $rule['headers']['status-code']);
}
protected function cleanupSite(string $siteId): void
public function testCreateRuleSetup(): void
{
$site = $this->client->call(Client::METHOD_DELETE, '/sites/' . $siteId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$this->assertEquals(204, $site['headers']['status-code'], 'Failed to cleanup site: ' . \json_encode($site));
$ruleId = $this->setupAPIRule(\uniqid() . '-api2.myapp.com');
$this->cleanupRule($ruleId);
}
protected function cleanupFunction(string $functionId): void
public function testCreateRuleApex(): void
{
$function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$this->assertEquals(204, $function['headers']['status-code'], 'Failed to cleanup function: ' . \json_encode($function));
$domain = \uniqid() . '.com';
$rule = $this->createAPIRule($domain);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('unverified', $rule['body']['status']);
}
protected function setupSite(): mixed
public function testCreateRuleVcs(): void
{
// Site
$site = $this->client->call(Client::METHOD_POST, '/sites', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]), [
'siteId' => ID::unique(),
'name' => 'Proxy site',
'framework' => 'other',
'adapter' => 'static',
'buildRuntime' => 'static-1',
'outputDirectory' => './',
'buildCommand' => '',
'installCommand' => '',
'fallbackFile' => '',
]);
$domain = \uniqid() . '-vcs.myapp.com';
$this->assertEquals($site['headers']['status-code'], 201, 'Setup site failed with status code: ' . $site['headers']['status-code'] . ' and response: ' . json_encode($site['body'], JSON_PRETTY_PRINT));
$setup = $this->setupSite();
$siteId = $setup['siteId'];
$deploymentId = $setup['deploymentId'];
$siteId = $site['body']['$id'];
$this->assertNotEmpty($siteId);
$this->assertNotEmpty($deploymentId);
// Deployment
$deployment = $this->client->call(Client::METHOD_POST, '/sites/' . $siteId . '/deployments', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]), [
'code' => $this->packageSite('static'),
'activate' => 'true'
]);
$rule = $this->createSiteRule('commit-' . $domain, $siteId);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->cleanupRule($rule['body']['$id']);
$this->assertEquals($deployment['headers']['status-code'], 202, 'Setup deployment failed with status code: ' . $deployment['headers']['status-code'] . ' and response: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
$deploymentId = $deployment['body']['$id'] ?? '';
$rule = $this->createSiteRule('branch-' . $domain, $siteId);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->cleanupRule($rule['body']['$id']);
$this->assertEventually(function () use ($siteId, $deploymentId) {
$site = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]));
$this->assertEquals($deploymentId, $site['body']['deploymentId'], 'Deployment is not activated, deployment: ' . json_encode($site['body'], JSON_PRETTY_PRINT));
}, 120000, 500);
$rule = $this->createSiteRule('anything-' . $domain, $siteId);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->cleanupRule($rule['body']['$id']);
return ['siteId' => $siteId, 'deploymentId' => $deploymentId];
$sitesDomain = \explode(',', System::getEnv('_APP_DOMAIN_SITES', ''))[0];
$domain = \uniqid() . '-vcs.' . $sitesDomain;
$rule = $this->createSiteRule('commit-' . $domain, $siteId);
$this->assertEquals(400, $rule['headers']['status-code']);
$rule = $this->createSiteRule('branch-' . $domain, $siteId);
$this->assertEquals(400, $rule['headers']['status-code']);
$rule = $this->createSiteRule('subdomain.anything-' . $domain, $siteId);
$this->assertEquals(400, $rule['headers']['status-code']);
$rule = $this->createSiteRule('anything-' . $domain, $siteId);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->cleanupRule($rule['body']['$id']);
}
protected function setupFunction(): mixed
public function testCreateAPIRule(): void
{
// Function
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]), [
'functionId' => ID::unique(),
'runtime' => 'node-22',
'name' => 'Proxy Function',
'entrypoint' => 'index.js',
'commands' => '',
'execute' => ['any']
$domain = \uniqid() . '-api.custom.localhost';
$proxyClient = new Client();
$proxyClient->setEndpoint('http://appwrite.test');
$proxyClient->addHeader('x-appwrite-hostname', $domain);
$response = $proxyClient->call(Client::METHOD_GET, '/versions');
$this->assertEquals(401, $response['headers']['status-code']);
$ruleId = $this->setupAPIRule($domain);
$this->assertNotEmpty($ruleId);
$response = $proxyClient->call(Client::METHOD_GET, '/versions');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(APP_VERSION_STABLE, $response['body']['server']);
$this->cleanupRule($ruleId);
$rule = $this->createAPIRule('http://' . $domain);
$this->assertEquals(400, $rule['headers']['status-code']);
$rule = $this->createAPIRule('https://' . $domain);
$this->assertEquals(400, $rule['headers']['status-code']);
$rule = $this->createAPIRule('wss://' . $domain);
$this->assertEquals(400, $rule['headers']['status-code']);
$rule = $this->createAPIRule($domain . '/some-path');
$this->assertEquals(400, $rule['headers']['status-code']);
}
public function testCreateRedirectRule(): void
{
$domain = \uniqid() . '-redirect.custom.localhost';
$proxyClient = new Client();
$proxyClient->setEndpoint('http://appwrite.test');
$proxyClient->addHeader('x-appwrite-hostname', $domain);
$response = $proxyClient->call(Client::METHOD_GET, '/todos/1');
$this->assertEquals(401, $response['headers']['status-code']);
$siteId = $this->setupSite()['siteId'];
$ruleId = $this->setupRedirectRule($domain, 'https://jsonplaceholder.typicode.com/todos/1', 301, 'site', $siteId);
$this->assertNotEmpty($ruleId);
$response = $proxyClient->call(Client::METHOD_GET, '/todos/1');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(1, $response['body']['id']);
$response = $proxyClient->call(Client::METHOD_GET, '/');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(1, $response['body']['id']);
$response = $proxyClient->call(Client::METHOD_GET, '/', followRedirects: false);
$this->assertEquals(301, $response['headers']['status-code']);
$this->assertEquals('https://jsonplaceholder.typicode.com/todos/1', $response['headers']['location']);
$domain = \uniqid() . '-redirect-307.custom.localhost';
$ruleId = $this->setupRedirectRule($domain, 'https://jsonplaceholder.typicode.com/todos/1', 307, 'site', $siteId);
$this->assertNotEmpty($ruleId);
$proxyClient = new Client();
$proxyClient->setEndpoint('http://appwrite.test');
$proxyClient->addHeader('x-appwrite-hostname', $domain);
$response = $proxyClient->call(Client::METHOD_GET, '/', followRedirects: false);
$this->assertEquals(307, $response['headers']['status-code']);
$this->assertEquals('https://jsonplaceholder.typicode.com/todos/1', $response['headers']['location']);
$rules = $this->listRules([
'queries' => [
Query::equal('type', ['redirect'])->toString(),
Query::equal('trigger', ['manual'])->toString(),
Query::equal('deploymentResourceType', ['site'])->toString(),
Query::equal('deploymentResourceId', [$siteId])->toString(),
],
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(2, $rules['body']['total']);
$this->assertEquals($function['headers']['status-code'], 201, 'Setup function failed with status code: ' . $function['headers']['status-code'] . ' and response: ' . json_encode($function['body'], JSON_PRETTY_PRINT));
$this->cleanupSite($siteId);
$this->cleanupRule($ruleId);
}
$functionId = $function['body']['$id'];
public function testCreateFunctionRule(): void
{
$domain = \uniqid() . '-function.custom.localhost';
// Deployment
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]), [
'code' => $this->packageFunction('basic'),
'activate' => 'true'
]);
$proxyClient = new Client();
$proxyClient->setEndpoint('http://appwrite.test');
$proxyClient->addHeader('x-appwrite-hostname', $domain);
$this->assertEquals($deployment['headers']['status-code'], 202, 'Setup deployment failed with status code: ' . $deployment['headers']['status-code'] . ' and response: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
$deploymentId = $deployment['body']['$id'] ?? '';
$response = $proxyClient->call(Client::METHOD_GET, '/ping');
$this->assertEquals(401, $response['headers']['status-code']);
$setup = $this->setupFunction();
$functionId = $setup['functionId'];
$deploymentId = $setup['deploymentId'];
$this->assertNotEmpty($functionId);
$this->assertNotEmpty($deploymentId);
$ruleId = $this->setupFunctionRule($domain, $functionId);
$this->assertNotEmpty($ruleId);
$response = $proxyClient->call(Client::METHOD_GET, '/ping');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($functionId, $response['body']['APPWRITE_FUNCTION_ID']);
$this->cleanupRule($ruleId);
$this->cleanupFunction($functionId);
$this->assertEventually(function () use ($functionId, $deploymentId) {
$function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]));
$this->assertEquals($deploymentId, $function['body']['deploymentId'], 'Deployment is not activated, deployment: ' . json_encode($function['body'], JSON_PRETTY_PRINT));
}, 100000, 500);
$rules = $this->listRules([
'queries' => [
Query::limit(1)->toString(),
Query::equal('type', ['deployment'])->toString(),
Query::equal('deploymentResourceType', ['function'])->toString(),
Query::equal('deploymentResourceId', [$functionId])->toString(),
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(0, $rules['body']['total']);
$this->assertCount(0, $rules['body']['rules']);
return ['functionId' => $functionId, 'deploymentId' => $deploymentId];
$rules = $this->listRules([
'queries' => [
Query::limit(1)->toString(),
Query::equal('type', ['deployment'])->toString(),
Query::equal('deploymentId', [$deploymentId])->toString()
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(0, $rules['body']['total']);
$this->assertCount(0, $rules['body']['rules']);
});
}
private function packageSite(string $site): CURLFile
public function testCreateSiteRule(): void
{
$stdout = '';
$stderr = '';
$domain = \uniqid() . '-site.custom.localhost';
$folderPath = realpath(__DIR__ . '/../../../resources/sites') . "/$site";
$tarPath = "$folderPath/code.tar.gz";
$proxyClient = new Client();
$proxyClient->setEndpoint('http://appwrite.test');
$proxyClient->addHeader('x-appwrite-hostname', $domain);
Console::execute("cd $folderPath && tar --exclude code.tar.gz --exclude node_modules -czf code.tar.gz .", '', $stdout, $stderr);
$response = $proxyClient->call(Client::METHOD_GET, '/contact');
$this->assertEquals(401, $response['headers']['status-code']);
if (filesize($tarPath) > 1024 * 1024 * 5) {
throw new \Exception('Code package is too large. Use the chunked upload method instead.');
$setup = $this->setupSite();
$siteId = $setup['siteId'];
$deploymentId = $setup['deploymentId'];
$this->assertNotEmpty($siteId);
$this->assertNotEmpty($deploymentId);
$ruleId = $this->setupSiteRule($domain, $siteId);
$this->assertNotEmpty($ruleId);
$rule = $this->getRule($ruleId);
$this->assertSame(200, $rule['headers']['status-code']);
$this->assertSame('unverified', $rule['body']['status']);
$response = $proxyClient->call(Client::METHOD_GET, '/contact');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertStringContainsString('Contact page', $response['body']);
// Wildcard domains automatically get verified status
$domains = [
\uniqid() . '.sites.localhost',
\uniqid() . '.rebranded.localhost',
];
foreach ($domains as $domain) {
$wildcardRuleId = $this->setupSiteRule($domain, $siteId);
$this->assertNotEmpty($wildcardRuleId);
$rule = $this->getRule($wildcardRuleId);
$this->assertSame(200, $rule['headers']['status-code']);
$this->assertSame('verified', $rule['body']['status']);
$this->cleanupRule($wildcardRuleId);
}
return new CURLFile($tarPath, 'application/x-gzip', \basename($tarPath));
$rules = $this->listRules([
'queries' => [
Query::limit(1)->toString(),
Query::equal('trigger', ['deployment'])->toString(),
Query::equal('type', ['deployment'])->toString(),
Query::equal('deploymentResourceType', ['site'])->toString(),
Query::equal('deploymentResourceId', [$siteId])->toString(),
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertGreaterThan(0, $rules['body']['total']);
$this->cleanupRule($ruleId);
$this->cleanupSite($siteId);
$this->assertEventually(function () use ($siteId, $deploymentId) {
$rules = $this->listRules([
'queries' => [
Query::limit(1)->toString(),
Query::equal('type', ['deployment'])->toString(),
Query::equal('deploymentResourceType', ['site'])->toString(),
Query::equal('deploymentResourceId', [$siteId])->toString(),
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(0, $rules['body']['total']);
$this->assertCount(0, $rules['body']['rules']);
$rules = $this->listRules([
'queries' => [
Query::limit(1)->toString(),
Query::equal('type', ['deployment'])->toString(),
Query::equal('deploymentId', [$deploymentId])->toString()
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(0, $rules['body']['total']);
$this->assertCount(0, $rules['body']['rules']);
});
}
private function packageFunction(string $function): CURLFile
public function testCreateSiteBranchRule(): void
{
$stdout = '';
$stderr = '';
$domain = \uniqid() . '-site-branch.custom.localhost';
$folderPath = realpath(__DIR__ . '/../../../resources/functions') . "/$function";
$tarPath = "$folderPath/code.tar.gz";
$setup = $this->setupSite();
$siteId = $setup['siteId'];
$deploymentId = $setup['deploymentId'];
Console::execute("cd $folderPath && tar --exclude code.tar.gz --exclude node_modules -czf code.tar.gz .", '', $stdout, $stderr);
$this->assertNotEmpty($siteId);
$this->assertNotEmpty($deploymentId);
if (filesize($tarPath) > 1024 * 1024 * 5) {
throw new \Exception('Code package is too large. Use the chunked upload method instead.');
$ruleId = $this->setupSiteRule($domain, $siteId, 'dev');
$this->assertNotEmpty($ruleId);
$rule = $this->getRule($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->cleanupRule($ruleId);
}
public function testCreateFunctionBranchRule(): void
{
$domain = \uniqid() . '-function-branch.custom.localhost';
$setup = $this->setupFunction();
$functionId = $setup['functionId'];
$deploymentId = $setup['deploymentId'];
$this->assertNotEmpty($functionId);
$this->assertNotEmpty($deploymentId);
$ruleId = $this->setupFunctionRule($domain, $functionId, 'dev');
$this->assertNotEmpty($ruleId);
$rule = $this->getRule($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->cleanupRule($ruleId);
$this->cleanupFunction($functionId);
}
public function testUpdateRule(): void
{
// Create function appwrite-network domain
$functionsDomain = \explode(',', System::getEnv('_APP_DOMAIN_FUNCTIONS', ''))[0];
$domain = \uniqid() . '-cname-api.' . $functionsDomain;
$rule = $this->createAPIRule($domain);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('verified', $rule['body']['status']);
$this->cleanupRule($rule['body']['$id']);
// Create site appwrite-network domain
$sitesDomain = \explode(',', System::getEnv('_APP_DOMAIN_SITES', ''))[0];
$domain = \uniqid() . '-cname-api.' . $sitesDomain;
$rule = $this->createAPIRule($domain);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('verified', $rule['body']['status']);
$this->cleanupRule($rule['body']['$id']);
// Create + update
$domain = \uniqid() . '-cname-api.custom.com';
$rule = $this->createAPIRule($domain);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('unverified', $rule['body']['status']);
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleStatus($ruleId);
$this->assertEquals(400, $rule['headers']['status-code']);
$this->cleanupRule($ruleId);
}
public function testGetRule()
{
$domain = \uniqid() . '-get.custom.localhost';
$ruleId = $this->setupAPIRule($domain);
$this->assertNotEmpty($ruleId);
$rule = $this->getRule($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->assertEquals($domain, $rule['body']['domain']);
$this->assertEquals('manual', $rule['body']['trigger']);
$this->assertArrayHasKey('$id', $rule['body']);
$this->assertArrayHasKey('domain', $rule['body']);
$this->assertArrayHasKey('type', $rule['body']);
$this->assertArrayHasKey('redirectUrl', $rule['body']);
$this->assertArrayHasKey('redirectStatusCode', $rule['body']);
$this->assertArrayHasKey('deploymentResourceType', $rule['body']);
$this->assertArrayHasKey('deploymentId', $rule['body']);
$this->assertArrayHasKey('deploymentResourceId', $rule['body']);
$this->assertArrayHasKey('deploymentVcsProviderBranch', $rule['body']);
$this->assertArrayHasKey('logs', $rule['body']);
$this->assertArrayHasKey('renewAt', $rule['body']);
$this->cleanupRule($ruleId);
}
public function testListRules()
{
$rules = $this->listRules();
$this->assertEquals(200, $rules['headers']['status-code']);
foreach ($rules['body']['rules'] as $rule) {
$rule = $this->deleteRule($rule['$id']);
$this->assertEquals(204, $rule['headers']['status-code']);
}
return new CURLFile($tarPath, 'application/x-gzip', \basename($tarPath));
$rules = $this->listRules();
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(0, $rules['body']['total']);
$this->assertCount(0, $rules['body']['rules']);
$rule1Domain = \uniqid() . '-list1.custom.localhost';
$rule1Id = $this->setupAPIRule($rule1Domain);
$this->assertNotEmpty($rule1Id);
$rules = $this->listRules();
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(1, $rules['body']['total']);
$this->assertCount(1, $rules['body']['rules']);
$this->assertEquals($rule1Domain, $rules['body']['rules'][0]['domain']);
$this->assertEquals('manual', $rules['body']['rules'][0]['trigger']);
$this->assertArrayHasKey('$id', $rules['body']['rules'][0]);
$this->assertArrayHasKey('domain', $rules['body']['rules'][0]);
$this->assertArrayHasKey('type', $rules['body']['rules'][0]);
$this->assertArrayHasKey('redirectUrl', $rules['body']['rules'][0]);
$this->assertArrayHasKey('redirectStatusCode', $rules['body']['rules'][0]);
$this->assertArrayHasKey('deploymentResourceType', $rules['body']['rules'][0]);
$this->assertArrayHasKey('deploymentId', $rules['body']['rules'][0]);
$this->assertArrayHasKey('deploymentResourceId', $rules['body']['rules'][0]);
$this->assertArrayHasKey('deploymentVcsProviderBranch', $rules['body']['rules'][0]);
$this->assertArrayHasKey('logs', $rules['body']['rules'][0]);
$this->assertArrayHasKey('renewAt', $rules['body']['rules'][0]);
$rule2Domain = \uniqid() . '-list1.custom.localhost';
$rule2Id = $this->setupAPIRule($rule2Domain);
$this->assertNotEmpty($rule2Id);
$rules = $this->listRules();
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(2, $rules['body']['total']);
$this->assertCount(2, $rules['body']['rules']);
$rules = $this->listRules([
'queries' => [
Query::limit(1)->toString()
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(2, $rules['body']['total']);
$this->assertCount(1, $rules['body']['rules']);
$rules = $this->listRules([
'queries' => [
Query::equal('$id', [$rule1Id])->toString()
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertCount(1, $rules['body']['rules']);
$this->assertEquals($rule1Domain, $rules['body']['rules'][0]['domain']);
$rules = $this->listRules([
'queries' => [
Query::orderDesc('$id')->toString()
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertCount(2, $rules['body']['rules']);
$this->assertEquals($rule2Id, $rules['body']['rules'][0]['$id']);
$rules = $this->listRules([
'queries' => [
Query::equal('domain', [$rule2Domain])->toString()
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertCount(1, $rules['body']['rules']);
$this->assertEquals($rule2Id, $rules['body']['rules'][0]['$id']);
$rules = $this->listRules([
'search' => $rule1Domain,
'queries' => [ Query::orderDesc('$createdAt')->toString() ]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$ruleIds = \array_column($rules['body']['rules'], '$id');
$this->assertContains($rule1Id, $ruleIds);
$rules = $this->listRules([
'search' => $rule2Domain,
'queries' => [ Query::orderDesc('$createdAt')->toString() ]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$ruleIds = \array_column($rules['body']['rules'], '$id');
$this->assertContains($rule2Id, $ruleIds);
$rules = $this->listRules([
'search' => $rule1Id,
'queries' => [ Query::orderDesc('$createdAt')->toString() ]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$ruleDomains = \array_column($rules['body']['rules'], 'domain');
$this->assertContains($rule1Domain, $ruleDomains);
$rules = $this->listRules([
'search' => $rule2Id,
'queries' => [ Query::orderDesc('$createdAt')->toString() ]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$ruleDomains = \array_column($rules['body']['rules'], 'domain');
$this->assertContains($rule2Domain, $ruleDomains);
$rules = $this->listRules();
$this->assertEquals(200, $rules['headers']['status-code']);
foreach ($rules['body']['rules'] as $rule) {
$rule = $this->deleteRule($rule['$id']);
$this->assertEquals(204, $rule['headers']['status-code']);
}
$rules = $this->listRules();
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(0, $rules['body']['total']);
$this->assertCount(0, $rules['body']['rules']);
}
public function testRuleVerification(): void
{
// 1. Site rule can verify
$site = $this->setupSite();
$siteId = $site['siteId'];
$rule = $this->createSiteRule('stage-site.webapp.com', $siteId);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('verifying', $rule['body']['status']);
$this->assertEmpty($rule['body']['logs']);
$this->assertNotEmpty($rule['body']['$id']);
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleStatus($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->assertEquals($ruleId, $rule['body']['$id']);
$this->assertEquals('verifying', $rule['body']['status']);
$this->assertEmpty($rule['body']['logs']);
$this->cleanupRule($rule['body']['$id']);
$this->cleanupSite($siteId);
// 2. Function rule can verify
$function = $this->setupFunction();
$functionId = $function['functionId'];
$rule = $this->createFunctionRule('stage-function.webapp.com', $functionId);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('verifying', $rule['body']['status']);
$this->assertEmpty($rule['body']['logs']);
$this->cleanupRule($rule['body']['$id']);
$rule = $this->createAPIRule('stage-site.webapp.com');
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('unverified', $rule['body']['status']);
$this->assertStringContainsString('has incorrect CNAME value', $rule['body']['logs']);
$this->cleanupRule($rule['body']['$id']);
$this->cleanupFunction($functionId);
// 3. Wrong A record fails to verify
$rule = $this->createAPIRule('wrong-a-webapp.com');
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('unverified', $rule['body']['status']);
$this->assertStringContainsString('is missing CNAME record', $rule['body']['logs']);
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleStatus($ruleId);
$this->assertEquals(400, $rule['headers']['status-code']);
$this->assertStringContainsString('is missing CNAME record', $rule['body']['message']);
$rule = $this->getRule($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->assertEquals('unverified', $rule['body']['status']);
$this->cleanupRule($ruleId);
// 4. Correct A record can verify
$rule = $this->createAPIRule('webapp.com');
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('verifying', $rule['body']['status']);
$this->assertEmpty($rule['body']['logs']);
$this->cleanupRule($rule['body']['$id']);
// 5. Correct CNAME record can verify (no CAA record)
$rule = $this->createAPIRule('stage.webapp.com');
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('verifying', $rule['body']['status']);
$this->assertEmpty($rule['body']['logs']);
$this->cleanupRule($rule['body']['$id']);
// 6. Missing CNAME record fails to verify
$rule = $this->createAPIRule('stage-missing-cname.webapp.com');
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('unverified', $rule['body']['status']);
$this->assertStringContainsString('is missing CNAME record', $rule['body']['logs']);
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleStatus($ruleId);
$this->assertEquals(400, $rule['headers']['status-code']);
$this->assertStringContainsString('is missing CNAME record', $rule['body']['message']);
$rule = $this->getRule($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->assertEquals('unverified', $rule['body']['status']);
$this->cleanupRule($ruleId);
// 7. Wrong CNAME record fails to verify
$rule = $this->createAPIRule('stage-wrong-cname.webapp.com');
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('unverified', $rule['body']['status']);
$this->assertStringContainsString('has incorrect CNAME value', $rule['body']['logs']);
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleStatus($ruleId);
$this->assertEquals(400, $rule['headers']['status-code']);
$this->assertStringContainsString('has incorrect CNAME value', $rule['body']['message']);
$rule = $this->getRule($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->assertEquals('unverified', $rule['body']['status']);
$this->cleanupRule($ruleId);
// 8. Wrong CAA record fails to verify
$rule = $this->createAPIRule('stage-wrong-caa.webapp.com');
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('unverified', $rule['body']['status']);
$this->assertStringContainsString('has incorrect CAA value', $rule['body']['logs']);
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleStatus($ruleId);
$this->assertEquals(400, $rule['headers']['status-code']);
$this->assertStringContainsString('has incorrect CAA value', $rule['body']['message']);
$rule = $this->getRule($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->assertEquals('unverified', $rule['body']['status']);
$this->cleanupRule($ruleId);
// 9. Correct CAA record can verify
$rule = $this->createAPIRule('stage-correct-caa.webapp.com');
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('verifying', $rule['body']['status']);
$this->assertEmpty($rule['body']['logs']);
$this->cleanupRule($rule['body']['$id']);
}
public function testUpdateRuleVerificationWithSameDataUpdatesTimestamp(): void
{
$domain = \uniqid() . '-timestamp-test.webapp.com';
$rule = $this->createAPIRule($domain);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('unverified', $rule['body']['status']);
$this->assertNotEmpty($rule['body']['logs']);
$ruleId = $rule['body']['$id'];
$initialUpdatedAt = $rule['body']['$updatedAt'];
$initiallogs = $rule['body']['logs'];
sleep(1);
$updatedRule = $this->updateRuleStatus($ruleId);
$this->assertEquals(400, $updatedRule['headers']['status-code']);
$this->assertStringContainsString($initiallogs, $updatedRule['body']['message']);
$ruleAfterUpdate = $this->getRule($ruleId);
$this->assertEquals(200, $ruleAfterUpdate['headers']['status-code']);
$this->assertEquals('unverified', $ruleAfterUpdate['body']['status']);
$this->assertEquals($initiallogs, $ruleAfterUpdate['body']['logs']);
$this->assertNotEquals($initialUpdatedAt, $ruleAfterUpdate['body']['$updatedAt']);
$initialTime = new \DateTime($initialUpdatedAt);
$updatedTime = new \DateTime($ruleAfterUpdate['body']['$updatedAt']);
$this->assertGreaterThan($initialTime, $updatedTime);
$this->cleanupRule($ruleId);
}
}
@@ -0,0 +1,14 @@
<?php
namespace Tests\E2E\Services\Proxy;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideConsole;
class ProxyConsoleClientTest extends Scope
{
use ProxyBase;
use ProjectCustom;
use SideConsole;
}
@@ -2,758 +2,13 @@
namespace Tests\E2E\Services\Proxy;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideServer;
use Utopia\Database\Query;
use Utopia\System\System;
class ProxyCustomServerTest extends Scope
{
use ProxyBase;
use ProjectCustom;
use SideServer;
protected function tearDown(): void
{
// Cleanup for testRuleVerification test
// Required as it uses static domain name
$rules = $this->listRules([
'queries' => [
Query::endsWith('domain', 'webapp.com')->toString(),
Query::limit(1000)->toString(),
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
foreach ($rules['body']['rules'] as $rule) {
$ruleId = $rule['$id'];
$response = $this->deleteRule($ruleId);
$this->assertEquals(204, $response['headers']['status-code']);
}
if ($rules['body']['total'] > 0) {
$rules = $this->listRules([
'queries' => [
Query::endsWith('domain', 'webapp.com')->toString(),
Query::limit(1)->toString()
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(0, count($rules['body']['rules']));
$this->assertEquals(0, $rules['body']['total']);
}
}
public function testCreateRule(): void
{
$domain = \uniqid() . '-api.myapp.com';
$rule = $this->createAPIRule($domain);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals($domain, $rule['body']['domain']);
$this->assertEquals('manual', $rule['body']['trigger']);
$this->assertArrayHasKey('$id', $rule['body']);
$this->assertArrayHasKey('domain', $rule['body']);
$this->assertArrayHasKey('type', $rule['body']);
$this->assertArrayHasKey('redirectUrl', $rule['body']);
$this->assertArrayHasKey('redirectStatusCode', $rule['body']);
$this->assertArrayHasKey('deploymentResourceType', $rule['body']);
$this->assertArrayHasKey('deploymentId', $rule['body']);
$this->assertArrayHasKey('deploymentResourceId', $rule['body']);
$this->assertArrayHasKey('deploymentVcsProviderBranch', $rule['body']);
$this->assertArrayHasKey('logs', $rule['body']);
$this->assertArrayHasKey('renewAt', $rule['body']);
$ruleId = $rule['body']['$id'];
$rule = $this->createAPIRule($domain);
$this->assertEquals(409, $rule['headers']['status-code']);
$rule = $this->deleteRule($ruleId);
$this->assertEquals(204, $rule['headers']['status-code']);
}
public function testCreateRuleSetup(): void
{
$ruleId = $this->setupAPIRule(\uniqid() . '-api2.myapp.com');
$this->cleanupRule($ruleId);
}
public function testCreateRuleApex(): void
{
$domain = \uniqid() . '.com';
$rule = $this->createAPIRule($domain);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('created', $rule['body']['status']);
}
public function testCreateRuleVcs(): void
{
$domain = \uniqid() . '-vcs.myapp.com';
$setup = $this->setupSite();
$siteId = $setup['siteId'];
$deploymentId = $setup['deploymentId'];
$this->assertNotEmpty($siteId);
$this->assertNotEmpty($deploymentId);
$rule = $this->createSiteRule('commit-' . $domain, $siteId);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->cleanupRule($rule['body']['$id']);
$rule = $this->createSiteRule('branch-' . $domain, $siteId);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->cleanupRule($rule['body']['$id']);
$rule = $this->createSiteRule('anything-' . $domain, $siteId);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->cleanupRule($rule['body']['$id']);
$sitesDomain = \explode(',', System::getEnv('_APP_DOMAIN_SITES', ''))[0];
$domain = \uniqid() . '-vcs.' . $sitesDomain;
$rule = $this->createSiteRule('commit-' . $domain, $siteId);
$this->assertEquals(400, $rule['headers']['status-code']);
$rule = $this->createSiteRule('branch-' . $domain, $siteId);
$this->assertEquals(400, $rule['headers']['status-code']);
$rule = $this->createSiteRule('subdomain.anything-' . $domain, $siteId);
$this->assertEquals(400, $rule['headers']['status-code']);
$rule = $this->createSiteRule('anything-' . $domain, $siteId);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->cleanupRule($rule['body']['$id']);
}
public function testCreateAPIRule(): void
{
$domain = \uniqid() . '-api.custom.localhost';
$proxyClient = new Client();
$proxyClient->setEndpoint('http://appwrite.test');
$proxyClient->addHeader('x-appwrite-hostname', $domain);
$response = $proxyClient->call(Client::METHOD_GET, '/versions');
$this->assertEquals(401, $response['headers']['status-code']);
$ruleId = $this->setupAPIRule($domain);
$this->assertNotEmpty($ruleId);
$response = $proxyClient->call(Client::METHOD_GET, '/versions');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(APP_VERSION_STABLE, $response['body']['server']);
$this->cleanupRule($ruleId);
$rule = $this->createAPIRule('http://' . $domain);
$this->assertEquals(400, $rule['headers']['status-code']);
$rule = $this->createAPIRule('https://' . $domain);
$this->assertEquals(400, $rule['headers']['status-code']);
$rule = $this->createAPIRule('wss://' . $domain);
$this->assertEquals(400, $rule['headers']['status-code']);
$rule = $this->createAPIRule($domain . '/some-path');
$this->assertEquals(400, $rule['headers']['status-code']);
}
public function testCreateRedirectRule(): void
{
$domain = \uniqid() . '-redirect.custom.localhost';
$proxyClient = new Client();
$proxyClient->setEndpoint('http://appwrite.test');
$proxyClient->addHeader('x-appwrite-hostname', $domain);
$response = $proxyClient->call(Client::METHOD_GET, '/todos/1');
$this->assertEquals(401, $response['headers']['status-code']);
$siteId = $this->setupSite()['siteId'];
$ruleId = $this->setupRedirectRule($domain, 'https://jsonplaceholder.typicode.com/todos/1', 301, 'site', $siteId);
$this->assertNotEmpty($ruleId);
$response = $proxyClient->call(Client::METHOD_GET, '/todos/1');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(1, $response['body']['id']);
$response = $proxyClient->call(Client::METHOD_GET, '/');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(1, $response['body']['id']);
$response = $proxyClient->call(Client::METHOD_GET, '/', followRedirects: false);
$this->assertEquals(301, $response['headers']['status-code']);
$this->assertEquals('https://jsonplaceholder.typicode.com/todos/1', $response['headers']['location']);
$domain = \uniqid() . '-redirect-307.custom.localhost';
$ruleId = $this->setupRedirectRule($domain, 'https://jsonplaceholder.typicode.com/todos/1', 307, 'site', $siteId);
$this->assertNotEmpty($ruleId);
$proxyClient = new Client();
$proxyClient->setEndpoint('http://appwrite.test');
$proxyClient->addHeader('x-appwrite-hostname', $domain);
$response = $proxyClient->call(Client::METHOD_GET, '/', followRedirects: false);
$this->assertEquals(307, $response['headers']['status-code']);
$this->assertEquals('https://jsonplaceholder.typicode.com/todos/1', $response['headers']['location']);
$rules = $this->listRules([
'queries' => [
Query::equal('type', ['redirect'])->toString(),
Query::equal('trigger', ['manual'])->toString(),
Query::equal('deploymentResourceType', ['site'])->toString(),
Query::equal('deploymentResourceId', [$siteId])->toString(),
],
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(2, $rules['body']['total']);
$this->cleanupSite($siteId);
$this->cleanupRule($ruleId);
}
public function testCreateFunctionRule(): void
{
$domain = \uniqid() . '-function.custom.localhost';
$proxyClient = new Client();
$proxyClient->setEndpoint('http://appwrite.test');
$proxyClient->addHeader('x-appwrite-hostname', $domain);
$response = $proxyClient->call(Client::METHOD_GET, '/ping');
$this->assertEquals(401, $response['headers']['status-code']);
$setup = $this->setupFunction();
$functionId = $setup['functionId'];
$deploymentId = $setup['deploymentId'];
$this->assertNotEmpty($functionId);
$this->assertNotEmpty($deploymentId);
$ruleId = $this->setupFunctionRule($domain, $functionId);
$this->assertNotEmpty($ruleId);
$response = $proxyClient->call(Client::METHOD_GET, '/ping');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($functionId, $response['body']['APPWRITE_FUNCTION_ID']);
$this->cleanupRule($ruleId);
$this->cleanupFunction($functionId);
$this->assertEventually(function () use ($functionId, $deploymentId) {
$rules = $this->listRules([
'queries' => [
Query::limit(1)->toString(),
Query::equal('type', ['deployment'])->toString(),
Query::equal('deploymentResourceType', ['function'])->toString(),
Query::equal('deploymentResourceId', [$functionId])->toString(),
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(0, $rules['body']['total']);
$this->assertCount(0, $rules['body']['rules']);
$rules = $this->listRules([
'queries' => [
Query::limit(1)->toString(),
Query::equal('type', ['deployment'])->toString(),
Query::equal('deploymentId', [$deploymentId])->toString()
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(0, $rules['body']['total']);
$this->assertCount(0, $rules['body']['rules']);
});
}
public function testCreateSiteRule(): void
{
$domain = \uniqid() . '-site.custom.localhost';
$proxyClient = new Client();
$proxyClient->setEndpoint('http://appwrite.test');
$proxyClient->addHeader('x-appwrite-hostname', $domain);
$response = $proxyClient->call(Client::METHOD_GET, '/contact');
$this->assertEquals(401, $response['headers']['status-code']);
$setup = $this->setupSite();
$siteId = $setup['siteId'];
$deploymentId = $setup['deploymentId'];
$this->assertNotEmpty($siteId);
$this->assertNotEmpty($deploymentId);
$ruleId = $this->setupSiteRule($domain, $siteId);
$this->assertNotEmpty($ruleId);
$rule = $this->getRule($ruleId);
$this->assertSame(200, $rule['headers']['status-code']);
$this->assertSame('created', $rule['body']['status']);
$response = $proxyClient->call(Client::METHOD_GET, '/contact');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertStringContainsString('Contact page', $response['body']);
// Wildcard domains automatically get verified status
$domains = [
\uniqid() . '.sites.localhost',
\uniqid() . '.rebranded.localhost',
];
foreach ($domains as $domain) {
$wildcardRuleId = $this->setupSiteRule($domain, $siteId);
$this->assertNotEmpty($wildcardRuleId);
$rule = $this->getRule($wildcardRuleId);
$this->assertSame(200, $rule['headers']['status-code']);
$this->assertSame('verified', $rule['body']['status']);
$this->cleanupRule($wildcardRuleId);
}
$rules = $this->listRules([
'queries' => [
Query::limit(1)->toString(),
Query::equal('trigger', ['deployment'])->toString(),
Query::equal('type', ['deployment'])->toString(),
Query::equal('deploymentResourceType', ['site'])->toString(),
Query::equal('deploymentResourceId', [$siteId])->toString(),
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertGreaterThan(0, $rules['body']['total']);
$this->cleanupRule($ruleId);
$this->cleanupSite($siteId);
$this->assertEventually(function () use ($siteId, $deploymentId) {
$rules = $this->listRules([
'queries' => [
Query::limit(1)->toString(),
Query::equal('type', ['deployment'])->toString(),
Query::equal('deploymentResourceType', ['site'])->toString(),
Query::equal('deploymentResourceId', [$siteId])->toString(),
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(0, $rules['body']['total']);
$this->assertCount(0, $rules['body']['rules']);
$rules = $this->listRules([
'queries' => [
Query::limit(1)->toString(),
Query::equal('type', ['deployment'])->toString(),
Query::equal('deploymentId', [$deploymentId])->toString()
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(0, $rules['body']['total']);
$this->assertCount(0, $rules['body']['rules']);
});
}
public function testCreateSiteBranchRule(): void
{
$domain = \uniqid() . '-site-branch.custom.localhost';
$setup = $this->setupSite();
$siteId = $setup['siteId'];
$deploymentId = $setup['deploymentId'];
$this->assertNotEmpty($siteId);
$this->assertNotEmpty($deploymentId);
$ruleId = $this->setupSiteRule($domain, $siteId, 'dev');
$this->assertNotEmpty($ruleId);
$rule = $this->getRule($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->cleanupRule($ruleId);
}
public function testCreateFunctionBranchRule(): void
{
$domain = \uniqid() . '-function-branch.custom.localhost';
$setup = $this->setupFunction();
$functionId = $setup['functionId'];
$deploymentId = $setup['deploymentId'];
$this->assertNotEmpty($functionId);
$this->assertNotEmpty($deploymentId);
$ruleId = $this->setupFunctionRule($domain, $functionId, 'dev');
$this->assertNotEmpty($ruleId);
$rule = $this->getRule($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->cleanupRule($ruleId);
$this->cleanupFunction($functionId);
}
public function testUpdateRule(): void
{
// Create function appwrite-network domain
$functionsDomain = \explode(',', System::getEnv('_APP_DOMAIN_FUNCTIONS', ''))[0];
$domain = \uniqid() . '-cname-api.' . $functionsDomain;
$rule = $this->createAPIRule($domain);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('verified', $rule['body']['status']);
$this->cleanupRule($rule['body']['$id']);
// Create site appwrite-network domain
$sitesDomain = \explode(',', System::getEnv('_APP_DOMAIN_SITES', ''))[0];
$domain = \uniqid() . '-cname-api.' . $sitesDomain;
$rule = $this->createAPIRule($domain);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('verified', $rule['body']['status']);
$this->cleanupRule($rule['body']['$id']);
// Create + update
$domain = \uniqid() . '-cname-api.custom.com';
$rule = $this->createAPIRule($domain);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('created', $rule['body']['status']);
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleVerification($ruleId);
$this->assertEquals(400, $rule['headers']['status-code']);
$this->cleanupRule($ruleId);
}
public function testGetRule()
{
$domain = \uniqid() . '-get.custom.localhost';
$ruleId = $this->setupAPIRule($domain);
$this->assertNotEmpty($ruleId);
$rule = $this->getRule($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->assertEquals($domain, $rule['body']['domain']);
$this->assertEquals('manual', $rule['body']['trigger']);
$this->assertArrayHasKey('$id', $rule['body']);
$this->assertArrayHasKey('domain', $rule['body']);
$this->assertArrayHasKey('type', $rule['body']);
$this->assertArrayHasKey('redirectUrl', $rule['body']);
$this->assertArrayHasKey('redirectStatusCode', $rule['body']);
$this->assertArrayHasKey('deploymentResourceType', $rule['body']);
$this->assertArrayHasKey('deploymentId', $rule['body']);
$this->assertArrayHasKey('deploymentResourceId', $rule['body']);
$this->assertArrayHasKey('deploymentVcsProviderBranch', $rule['body']);
$this->assertArrayHasKey('logs', $rule['body']);
$this->assertArrayHasKey('renewAt', $rule['body']);
$this->cleanupRule($ruleId);
}
public function testListRules()
{
$rules = $this->listRules();
$this->assertEquals(200, $rules['headers']['status-code']);
foreach ($rules['body']['rules'] as $rule) {
$rule = $this->deleteRule($rule['$id']);
$this->assertEquals(204, $rule['headers']['status-code']);
}
$rules = $this->listRules();
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(0, $rules['body']['total']);
$this->assertCount(0, $rules['body']['rules']);
$rule1Domain = \uniqid() . '-list1.custom.localhost';
$rule1Id = $this->setupAPIRule($rule1Domain);
$this->assertNotEmpty($rule1Id);
$rules = $this->listRules();
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(1, $rules['body']['total']);
$this->assertCount(1, $rules['body']['rules']);
$this->assertEquals($rule1Domain, $rules['body']['rules'][0]['domain']);
$this->assertEquals('manual', $rules['body']['rules'][0]['trigger']);
$this->assertArrayHasKey('$id', $rules['body']['rules'][0]);
$this->assertArrayHasKey('domain', $rules['body']['rules'][0]);
$this->assertArrayHasKey('type', $rules['body']['rules'][0]);
$this->assertArrayHasKey('redirectUrl', $rules['body']['rules'][0]);
$this->assertArrayHasKey('redirectStatusCode', $rules['body']['rules'][0]);
$this->assertArrayHasKey('deploymentResourceType', $rules['body']['rules'][0]);
$this->assertArrayHasKey('deploymentId', $rules['body']['rules'][0]);
$this->assertArrayHasKey('deploymentResourceId', $rules['body']['rules'][0]);
$this->assertArrayHasKey('deploymentVcsProviderBranch', $rules['body']['rules'][0]);
$this->assertArrayHasKey('logs', $rules['body']['rules'][0]);
$this->assertArrayHasKey('renewAt', $rules['body']['rules'][0]);
$rule2Domain = \uniqid() . '-list1.custom.localhost';
$rule2Id = $this->setupAPIRule($rule2Domain);
$this->assertNotEmpty($rule2Id);
$rules = $this->listRules();
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(2, $rules['body']['total']);
$this->assertCount(2, $rules['body']['rules']);
$rules = $this->listRules([
'queries' => [
Query::limit(1)->toString()
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(2, $rules['body']['total']);
$this->assertCount(1, $rules['body']['rules']);
$rules = $this->listRules([
'queries' => [
Query::equal('$id', [$rule1Id])->toString()
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertCount(1, $rules['body']['rules']);
$this->assertEquals($rule1Domain, $rules['body']['rules'][0]['domain']);
$rules = $this->listRules([
'queries' => [
Query::orderDesc('$id')->toString()
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertCount(2, $rules['body']['rules']);
$this->assertEquals($rule2Id, $rules['body']['rules'][0]['$id']);
$rules = $this->listRules([
'queries' => [
Query::equal('domain', [$rule2Domain])->toString()
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertCount(1, $rules['body']['rules']);
$this->assertEquals($rule2Id, $rules['body']['rules'][0]['$id']);
$rules = $this->listRules([
'search' => $rule1Domain,
'queries' => [ Query::orderDesc('$createdAt')->toString() ]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$ruleIds = \array_column($rules['body']['rules'], '$id');
$this->assertContains($rule1Id, $ruleIds);
$rules = $this->listRules([
'search' => $rule2Domain,
'queries' => [ Query::orderDesc('$createdAt')->toString() ]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$ruleIds = \array_column($rules['body']['rules'], '$id');
$this->assertContains($rule2Id, $ruleIds);
$rules = $this->listRules([
'search' => $rule1Id,
'queries' => [ Query::orderDesc('$createdAt')->toString() ]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$ruleDomains = \array_column($rules['body']['rules'], 'domain');
$this->assertContains($rule1Domain, $ruleDomains);
$rules = $this->listRules([
'search' => $rule2Id,
'queries' => [ Query::orderDesc('$createdAt')->toString() ]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$ruleDomains = \array_column($rules['body']['rules'], 'domain');
$this->assertContains($rule2Domain, $ruleDomains);
$rules = $this->listRules();
$this->assertEquals(200, $rules['headers']['status-code']);
foreach ($rules['body']['rules'] as $rule) {
$rule = $this->deleteRule($rule['$id']);
$this->assertEquals(204, $rule['headers']['status-code']);
}
$rules = $this->listRules();
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(0, $rules['body']['total']);
$this->assertCount(0, $rules['body']['rules']);
}
public function testRuleVerification(): void
{
// 1. Site rule can verify
$site = $this->setupSite();
$siteId = $site['siteId'];
$rule = $this->createSiteRule('stage-site.webapp.com', $siteId);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('verifying', $rule['body']['status']);
$this->assertEmpty($rule['body']['logs']);
$this->assertNotEmpty($rule['body']['$id']);
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleVerification($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->assertEquals($ruleId, $rule['body']['$id']);
$this->assertEquals('verifying', $rule['body']['status']);
$this->assertEmpty($rule['body']['logs']);
$this->cleanupRule($rule['body']['$id']);
$this->cleanupSite($siteId);
// 2. Function rule can verify
$function = $this->setupFunction();
$functionId = $function['functionId'];
$rule = $this->createFunctionRule('stage-function.webapp.com', $functionId);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('verifying', $rule['body']['status']);
$this->assertEmpty($rule['body']['logs']);
$this->cleanupRule($rule['body']['$id']);
$rule = $this->createAPIRule('stage-site.webapp.com');
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('created', $rule['body']['status']);
$this->assertStringContainsString('has incorrect CNAME value', $rule['body']['logs']);
$this->cleanupRule($rule['body']['$id']);
$this->cleanupFunction($functionId);
// 3. Wrong A record fails to verify
$rule = $this->createAPIRule('wrong-a-webapp.com');
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('created', $rule['body']['status']);
$this->assertStringContainsString('is missing CNAME record', $rule['body']['logs']);
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleVerification($ruleId);
$this->assertEquals(400, $rule['headers']['status-code']);
$this->assertStringContainsString('is missing CNAME record', $rule['body']['message']);
$rule = $this->getRule($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->assertEquals('created', $rule['body']['status']);
$this->cleanupRule($ruleId);
// 4. Correct A record can verify
$rule = $this->createAPIRule('webapp.com');
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('verifying', $rule['body']['status']);
$this->assertEmpty($rule['body']['logs']);
$this->cleanupRule($rule['body']['$id']);
// 5. Correct CNAME record can verify (no CAA record)
$rule = $this->createAPIRule('stage.webapp.com');
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('verifying', $rule['body']['status']);
$this->assertEmpty($rule['body']['logs']);
$this->cleanupRule($rule['body']['$id']);
// 6. Missing CNAME record fails to verify
$rule = $this->createAPIRule('stage-missing-cname.webapp.com');
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('created', $rule['body']['status']);
$this->assertStringContainsString('is missing CNAME record', $rule['body']['logs']);
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleVerification($ruleId);
$this->assertEquals(400, $rule['headers']['status-code']);
$this->assertStringContainsString('is missing CNAME record', $rule['body']['message']);
$rule = $this->getRule($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->assertEquals('created', $rule['body']['status']);
$this->cleanupRule($ruleId);
// 7. Wrong CNAME record fails to verify
$rule = $this->createAPIRule('stage-wrong-cname.webapp.com');
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('created', $rule['body']['status']);
$this->assertStringContainsString('has incorrect CNAME value', $rule['body']['logs']);
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleVerification($ruleId);
$this->assertEquals(400, $rule['headers']['status-code']);
$this->assertStringContainsString('has incorrect CNAME value', $rule['body']['message']);
$rule = $this->getRule($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->assertEquals('created', $rule['body']['status']);
$this->cleanupRule($ruleId);
// 8. Wrong CAA record fails to verify
$rule = $this->createAPIRule('stage-wrong-caa.webapp.com');
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('created', $rule['body']['status']);
$this->assertStringContainsString('has incorrect CAA value', $rule['body']['logs']);
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleVerification($ruleId);
$this->assertEquals(400, $rule['headers']['status-code']);
$this->assertStringContainsString('has incorrect CAA value', $rule['body']['message']);
$rule = $this->getRule($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->assertEquals('created', $rule['body']['status']);
$this->cleanupRule($ruleId);
// 9. Correct CAA record can verify
$rule = $this->createAPIRule('stage-correct-caa.webapp.com');
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('verifying', $rule['body']['status']);
$this->assertEmpty($rule['body']['logs']);
$this->cleanupRule($rule['body']['$id']);
}
public function testUpdateRuleVerificationWithSameDataUpdatesTimestamp(): void
{
$domain = \uniqid() . '-timestamp-test.webapp.com';
$rule = $this->createAPIRule($domain);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals('created', $rule['body']['status']);
$this->assertNotEmpty($rule['body']['logs']);
$ruleId = $rule['body']['$id'];
$initialUpdatedAt = $rule['body']['$updatedAt'];
$initiallogs = $rule['body']['logs'];
sleep(1);
$updatedRule = $this->updateRuleVerification($ruleId);
$this->assertEquals(400, $updatedRule['headers']['status-code']);
$this->assertStringContainsString($initiallogs, $updatedRule['body']['message']);
$ruleAfterUpdate = $this->getRule($ruleId);
$this->assertEquals(200, $ruleAfterUpdate['headers']['status-code']);
$this->assertEquals('created', $ruleAfterUpdate['body']['status']);
$this->assertEquals($initiallogs, $ruleAfterUpdate['body']['logs']);
$this->assertNotEquals($initialUpdatedAt, $ruleAfterUpdate['body']['$updatedAt']);
$initialTime = new \DateTime($initialUpdatedAt);
$updatedTime = new \DateTime($ruleAfterUpdate['body']['$updatedAt']);
$this->assertGreaterThan($initialTime, $updatedTime);
$this->cleanupRule($ruleId);
}
}
+293
View File
@@ -0,0 +1,293 @@
<?php
namespace Tests\E2E\Services\Proxy;
use Appwrite\ID;
use Appwrite\Tests\Async;
use CURLFile;
use Tests\E2E\Client;
use Utopia\Console;
trait ProxyHelpers
{
use Async;
protected function listRules(array $params = []): mixed
{
$rule = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), $params);
return $rule;
}
protected function createAPIRule(string $domain): mixed
{
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/api', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'domain' => $domain,
]);
return $rule;
}
protected function updateRuleStatus(string $ruleId): mixed
{
$rule = $this->client->call(Client::METHOD_PATCH, '/proxy/rules/' . $ruleId . '/status', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
return $rule;
}
protected function createSiteRule(string $domain, string $siteId, string $branch = ''): mixed
{
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/site', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'domain' => $domain,
'siteId' => $siteId,
'branch' => $branch,
]);
return $rule;
}
protected function getRule(string $ruleId): mixed
{
$rule = $this->client->call(Client::METHOD_GET, '/proxy/rules/' . $ruleId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
return $rule;
}
protected function createRedirectRule(string $domain, string $url, int $statusCode, string $resourceType, string $resourceId): mixed
{
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/redirect', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'domain' => $domain,
'url' => $url,
'statusCode' => $statusCode,
'resourceType' => $resourceType,
'resourceId' => $resourceId,
]);
return $rule;
}
protected function createFunctionRule(string $domain, string $functionId, string $branch = ''): mixed
{
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/function', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'domain' => $domain,
'functionId' => $functionId,
'branch' => $branch,
]);
return $rule;
}
protected function deleteRule(string $ruleId): mixed
{
$rule = $this->client->call(Client::METHOD_DELETE, '/proxy/rules/' . $ruleId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
return $rule;
}
protected function setupAPIRule(string $domain): string
{
$rule = $this->createAPIRule($domain);
$this->assertEquals(201, $rule['headers']['status-code'], 'Failed to setup rule: ' . \json_encode($rule));
return $rule['body']['$id'];
}
protected function setupRedirectRule(string $domain, string $url, int $statusCode, string $resourceType, string $resourceId): string
{
$rule = $this->createRedirectRule($domain, $url, $statusCode, $resourceType, $resourceId);
$this->assertEquals(201, $rule['headers']['status-code'], 'Failed to setup rule: ' . \json_encode($rule));
return $rule['body']['$id'];
}
protected function setupFunctionRule(string $domain, string $functionId, string $branch = ''): string
{
$rule = $this->createFunctionRule($domain, $functionId, $branch);
$this->assertEquals(201, $rule['headers']['status-code'], 'Failed to setup rule: ' . \json_encode($rule));
return $rule['body']['$id'];
}
protected function setupSiteRule(string $domain, string $siteId, string $branch = ''): string
{
$rule = $this->createSiteRule($domain, $siteId, $branch);
$this->assertEquals(201, $rule['headers']['status-code'], 'Failed to setup rule: ' . \json_encode($rule));
return $rule['body']['$id'];
}
protected function cleanupRule(string $ruleId): void
{
$rule = $this->deleteRule($ruleId);
$this->assertEquals(204, $rule['headers']['status-code'], 'Failed to cleanup rule: ' . \json_encode($rule));
}
protected function cleanupSite(string $siteId): void
{
$site = $this->client->call(Client::METHOD_DELETE, '/sites/' . $siteId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$this->assertEquals(204, $site['headers']['status-code'], 'Failed to cleanup site: ' . \json_encode($site));
}
protected function cleanupFunction(string $functionId): void
{
$function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$this->assertEquals(204, $function['headers']['status-code'], 'Failed to cleanup function: ' . \json_encode($function));
}
protected function setupSite(): mixed
{
// Site
$site = $this->client->call(Client::METHOD_POST, '/sites', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'siteId' => ID::unique(),
'name' => 'Proxy site',
'framework' => 'other',
'adapter' => 'static',
'buildRuntime' => 'static-1',
'outputDirectory' => './',
'buildCommand' => '',
'installCommand' => '',
'fallbackFile' => '',
]);
$this->assertEquals($site['headers']['status-code'], 201, 'Setup site failed with status code: ' . $site['headers']['status-code'] . ' and response: ' . json_encode($site['body'], JSON_PRETTY_PRINT));
$siteId = $site['body']['$id'];
// Deployment
$deployment = $this->client->call(Client::METHOD_POST, '/sites/' . $siteId . '/deployments', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'code' => $this->packageSite('static'),
'activate' => 'true'
]);
$this->assertEquals($deployment['headers']['status-code'], 202, 'Setup deployment failed with status code: ' . $deployment['headers']['status-code'] . ' and response: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEventually(function () use ($siteId, $deploymentId) {
$site = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals($deploymentId, $site['body']['deploymentId'], 'Deployment is not activated, deployment: ' . json_encode($site['body'], JSON_PRETTY_PRINT));
}, 120000, 500);
return ['siteId' => $siteId, 'deploymentId' => $deploymentId];
}
protected function setupFunction(): mixed
{
// Function
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'functionId' => ID::unique(),
'runtime' => 'node-22',
'name' => 'Proxy Function',
'entrypoint' => 'index.js',
'commands' => '',
'execute' => ['any']
]);
$this->assertEquals($function['headers']['status-code'], 201, 'Setup function failed with status code: ' . $function['headers']['status-code'] . ' and response: ' . json_encode($function['body'], JSON_PRETTY_PRINT));
$functionId = $function['body']['$id'];
// Deployment
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'code' => $this->packageFunction('basic'),
'activate' => 'true'
]);
$this->assertEquals($deployment['headers']['status-code'], 202, 'Setup deployment failed with status code: ' . $deployment['headers']['status-code'] . ' and response: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEventually(function () use ($functionId, $deploymentId) {
$function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals($deploymentId, $function['body']['deploymentId'], 'Deployment is not activated, deployment: ' . json_encode($function['body'], JSON_PRETTY_PRINT));
}, 100000, 500);
return ['functionId' => $functionId, 'deploymentId' => $deploymentId];
}
private function packageSite(string $site): CURLFile
{
$stdout = '';
$stderr = '';
$folderPath = realpath(__DIR__ . '/../../../resources/sites') . "/$site";
$tarPath = "$folderPath/code.tar.gz";
Console::execute("cd $folderPath && tar --exclude code.tar.gz --exclude node_modules -czf code.tar.gz .", '', $stdout, $stderr);
if (filesize($tarPath) > 1024 * 1024 * 5) {
throw new \Exception('Code package is too large. Use the chunked upload method instead.');
}
return new CURLFile($tarPath, 'application/x-gzip', \basename($tarPath));
}
private function packageFunction(string $function): CURLFile
{
$stdout = '';
$stderr = '';
$folderPath = realpath(__DIR__ . '/../../../resources/functions') . "/$function";
$tarPath = "$folderPath/code.tar.gz";
Console::execute("cd $folderPath && tar --exclude code.tar.gz --exclude node_modules -czf code.tar.gz .", '', $stdout, $stderr);
if (filesize($tarPath) > 1024 * 1024 * 5) {
throw new \Exception('Code package is too large. Use the chunked upload method instead.');
}
return new CURLFile($tarPath, 'application/x-gzip', \basename($tarPath));
}
}
@@ -104,14 +104,17 @@ class SitesCustomServerTest extends Scope
$this->assertEquals('./', $site['body']['outputDirectory']);
$variable = $this->createVariable($siteId, [
'variableId' => ID::unique(),
'key' => 'siteKey1',
'value' => 'siteValue1',
]);
$variable2 = $this->createVariable($siteId, [
'variableId' => ID::unique(),
'key' => 'siteKey2',
'value' => 'siteValue2',
]);
$variable3 = $this->createVariable($siteId, [
'variableId' => ID::unique(),
'key' => 'siteKey3',
'value' => 'siteValue3',
]);
@@ -211,6 +214,7 @@ class SitesCustomServerTest extends Scope
$this->assertEquals('Test Site', $site['body']['name']);
$variable = $this->createVariable($siteId, [
'variableId' => ID::unique(),
'key' => 'siteKey1',
'value' => 'siteValue1',
'secret' => false,
@@ -223,6 +227,7 @@ class SitesCustomServerTest extends Scope
$this->assertEquals(false, $variable['body']['secret']);
$variable2 = $this->createVariable($siteId, [
'variableId' => ID::unique(),
'key' => 'siteKey2',
'value' => 'siteValue2',
'secret' => false,
@@ -235,6 +240,7 @@ class SitesCustomServerTest extends Scope
$this->assertEquals(false, $variable2['body']['secret']);
$secretVariable = $this->createVariable($siteId, [
'variableId' => ID::unique(),
'key' => 'siteKey3',
'value' => 'siteValue3',
'secret' => true,
@@ -330,6 +336,316 @@ class SitesCustomServerTest extends Scope
$this->cleanupSite($siteId);
}
public function testListVariablesWithLimit(): void
{
$site = $this->createSite([
'buildRuntime' => 'node-22',
'fallbackFile' => '',
'framework' => 'other',
'name' => 'Test List Variables Limit',
'outputDirectory' => './',
'siteId' => ID::unique()
]);
$siteId = $site['body']['$id'] ?? '';
$this->assertEquals(201, $site['headers']['status-code']);
$variable1 = $this->createVariable($siteId, [
'variableId' => ID::unique(),
'key' => 'LIMIT_KEY_1',
'value' => 'limit-value-1',
]);
$this->assertEquals(201, $variable1['headers']['status-code']);
$variable2 = $this->createVariable($siteId, [
'variableId' => ID::unique(),
'key' => 'LIMIT_KEY_2',
'value' => 'limit-value-2',
]);
$this->assertEquals(201, $variable2['headers']['status-code']);
// List with limit of 1
$response = $this->listVariables($siteId, [
'queries' => [
Query::limit(1)->toString(),
],
'total' => true,
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(1, $response['body']['variables']);
$this->assertGreaterThanOrEqual(2, $response['body']['total']);
$this->cleanupSite($siteId);
}
public function testListVariablesWithoutTotal(): void
{
$site = $this->createSite([
'buildRuntime' => 'node-22',
'fallbackFile' => '',
'framework' => 'other',
'name' => 'Test List Variables No Total',
'outputDirectory' => './',
'siteId' => ID::unique()
]);
$siteId = $site['body']['$id'] ?? '';
$this->assertEquals(201, $site['headers']['status-code']);
$variable = $this->createVariable($siteId, [
'variableId' => ID::unique(),
'key' => 'NO_TOTAL_KEY',
'value' => 'no-total-value',
]);
$this->assertEquals(201, $variable['headers']['status-code']);
// List with total=false
$response = $this->listVariables($siteId, [
'total' => false,
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(0, $response['body']['total']);
$this->assertGreaterThanOrEqual(1, \count($response['body']['variables']));
$this->cleanupSite($siteId);
}
public function testListVariablesCursorPagination(): void
{
$site = $this->createSite([
'buildRuntime' => 'node-22',
'fallbackFile' => '',
'framework' => 'other',
'name' => 'Test List Variables Cursor',
'outputDirectory' => './',
'siteId' => ID::unique()
]);
$siteId = $site['body']['$id'] ?? '';
$this->assertEquals(201, $site['headers']['status-code']);
$variable1 = $this->createVariable($siteId, [
'variableId' => ID::unique(),
'key' => 'CURSOR_KEY_1',
'value' => 'cursor-value-1',
]);
$this->assertEquals(201, $variable1['headers']['status-code']);
$variable2 = $this->createVariable($siteId, [
'variableId' => ID::unique(),
'key' => 'CURSOR_KEY_2',
'value' => 'cursor-value-2',
]);
$this->assertEquals(201, $variable2['headers']['status-code']);
// Get first page with limit 1
$page1 = $this->listVariables($siteId, [
'queries' => [
Query::limit(1)->toString(),
],
'total' => true,
]);
$this->assertEquals(200, $page1['headers']['status-code']);
$this->assertCount(1, $page1['body']['variables']);
$cursorId = $page1['body']['variables'][0]['$id'];
// Get next page using cursor
$page2 = $this->listVariables($siteId, [
'queries' => [
Query::limit(1)->toString(),
Query::cursorAfter(new Document(['$id' => $cursorId]))->toString(),
],
'total' => true,
]);
$this->assertEquals(200, $page2['headers']['status-code']);
$this->assertCount(1, $page2['body']['variables']);
$this->assertNotEquals($cursorId, $page2['body']['variables'][0]['$id']);
$this->cleanupSite($siteId);
}
public function testUpdateVariableKey(): void
{
$site = $this->createSite([
'buildRuntime' => 'node-22',
'fallbackFile' => '',
'framework' => 'other',
'name' => 'Test Update Variable Key',
'outputDirectory' => './',
'siteId' => ID::unique()
]);
$siteId = $site['body']['$id'] ?? '';
$this->assertEquals(201, $site['headers']['status-code']);
$variable = $this->createVariable($siteId, [
'variableId' => ID::unique(),
'key' => 'KEY_BEFORE',
'value' => 'unchanged-value',
'secret' => false
]);
$this->assertEquals(201, $variable['headers']['status-code']);
$variableId = $variable['body']['$id'];
// Update only key
$response = $this->updateVariable($siteId, $variableId, [
'key' => 'KEY_AFTER',
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('KEY_AFTER', $response['body']['key']);
$this->assertEquals('unchanged-value', $response['body']['value']);
$this->cleanupSite($siteId);
}
public function testUpdateVariableValueOnly(): void
{
$site = $this->createSite([
'buildRuntime' => 'node-22',
'fallbackFile' => '',
'framework' => 'other',
'name' => 'Test Update Variable Value',
'outputDirectory' => './',
'siteId' => ID::unique()
]);
$siteId = $site['body']['$id'] ?? '';
$this->assertEquals(201, $site['headers']['status-code']);
$variable = $this->createVariable($siteId, [
'variableId' => ID::unique(),
'key' => 'UNCHANGED_KEY',
'value' => 'value-before',
'secret' => false
]);
$this->assertEquals(201, $variable['headers']['status-code']);
$variableId = $variable['body']['$id'];
// Update only value
$response = $this->updateVariable($siteId, $variableId, [
'value' => 'value-after',
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('UNCHANGED_KEY', $response['body']['key']);
$this->assertEquals('value-after', $response['body']['value']);
$this->cleanupSite($siteId);
}
public function testUpdateVariableNoOp(): void
{
$site = $this->createSite([
'buildRuntime' => 'node-22',
'fallbackFile' => '',
'framework' => 'other',
'name' => 'Test Update Variable NoOp',
'outputDirectory' => './',
'siteId' => ID::unique()
]);
$siteId = $site['body']['$id'] ?? '';
$this->assertEquals(201, $site['headers']['status-code']);
$variable = $this->createVariable($siteId, [
'variableId' => ID::unique(),
'key' => 'NOOP_KEY',
'value' => 'noop-value',
'secret' => false
]);
$this->assertEquals(201, $variable['headers']['status-code']);
$variableId = $variable['body']['$id'];
// Update with no parameters should fail with 400
$response = $this->updateVariable($siteId, $variableId, []);
$this->assertEquals(400, $response['headers']['status-code']);
$this->cleanupSite($siteId);
}
public function testUpdateVariableNotFound(): void
{
$site = $this->createSite([
'buildRuntime' => 'node-22',
'fallbackFile' => '',
'framework' => 'other',
'name' => 'Test Update Variable Not Found',
'outputDirectory' => './',
'siteId' => ID::unique()
]);
$siteId = $site['body']['$id'] ?? '';
$this->assertEquals(201, $site['headers']['status-code']);
$response = $this->updateVariable($siteId, 'non-existent-id', [
'key' => 'NEW_KEY',
'value' => 'new-value',
]);
$this->assertEquals(404, $response['headers']['status-code']);
$this->assertEquals('variable_not_found', $response['body']['type']);
$this->cleanupSite($siteId);
}
public function testCreateVariableInvalidId(): void
{
$site = $this->createSite([
'buildRuntime' => 'node-22',
'fallbackFile' => '',
'framework' => 'other',
'name' => 'Test Invalid Variable ID',
'outputDirectory' => './',
'siteId' => ID::unique()
]);
$siteId = $site['body']['$id'] ?? '';
$this->assertEquals(201, $site['headers']['status-code']);
$variable = $this->createVariable($siteId, [
'variableId' => '!invalid-id!',
'key' => 'INVALID_ID_KEY',
'value' => 'value',
]);
$this->assertEquals(400, $variable['headers']['status-code']);
$this->cleanupSite($siteId);
}
public function testCreateVariableDuplicateId(): void
{
$site = $this->createSite([
'buildRuntime' => 'node-22',
'fallbackFile' => '',
'framework' => 'other',
'name' => 'Test Duplicate Variable ID',
'outputDirectory' => './',
'siteId' => ID::unique()
]);
$siteId = $site['body']['$id'] ?? '';
$this->assertEquals(201, $site['headers']['status-code']);
$variableId = ID::unique();
$variable = $this->createVariable($siteId, [
'variableId' => $variableId,
'key' => 'DUP_ID_KEY_1',
'value' => 'value1',
]);
$this->assertEquals(201, $variable['headers']['status-code']);
// Attempt to create with same ID
$duplicate = $this->createVariable($siteId, [
'variableId' => $variableId,
'key' => 'DUP_ID_KEY_2',
'value' => 'value2',
]);
$this->assertEquals(409, $duplicate['headers']['status-code']);
$this->assertEquals('variable_already_exists', $duplicate['body']['type']);
$this->cleanupSite($siteId);
}
// This is first Sites test with Proxy
// If this fails, it may not be related to variables; but Router flow failing
public function testVariablesE2E(): void
@@ -351,6 +667,7 @@ class SitesCustomServerTest extends Scope
$domain = $this->setupSiteDomain($siteId);
$secretVariable = $this->createVariable($siteId, [
'variableId' => ID::unique(),
'key' => 'name',
'value' => 'Appwrite',
]);
@@ -0,0 +1,482 @@
<?php
namespace Tests\E2E\Services\TablesDB;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ApiTablesDB;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\SchemaPolling;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideServer;
use Tests\E2E\Traits\DatabasesUrlHelpers;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
class DatabasesNumericTypesTest extends Scope
{
use ProjectCustom;
use SideServer;
use ApiTablesDB;
use DatabasesUrlHelpers;
use SchemaPolling;
private static array $setupCache = [];
/**
* Setup database, table, and numeric columns for parallel-safe tests.
*/
protected function setupDatabaseAndTable(): array
{
$cacheKey = $this->getProject()['$id'] ?? 'default';
if (!empty(self::$setupCache[$cacheKey])) {
return self::$setupCache[$cacheKey];
}
$projectId = $this->getProject()['$id'];
$apiKey = $this->getProject()['apiKey'];
$headers = [
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
'x-appwrite-key' => $apiKey,
];
$database = $this->client->call(Client::METHOD_POST, '/tablesdb', $headers, [
'databaseId' => ID::unique(),
'name' => 'Numeric Types Test Database',
]);
$this->assertEquals(201, $database['headers']['status-code']);
$databaseId = $database['body']['$id'];
$table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', $headers, [
'tableId' => ID::unique(),
'name' => 'Numeric Types Table',
'rowSecurity' => true,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
],
]);
$this->assertEquals(201, $table['headers']['status-code']);
$tableId = $table['body']['$id'];
// Create integer column
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/integer', $headers, [
'key' => 'integer_field',
'required' => false,
'min' => -10,
'max' => 10,
'default' => 0,
]);
// Create bigint column
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/bigint', $headers, [
'key' => 'bigint_field',
'required' => false,
'min' => -9007199254740991,
'max' => 9007199254740991,
'default' => 9007199254740000,
]);
// Create unsigned integer column
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/integer', $headers, [
'key' => 'unsigned_int_field',
'required' => false,
'min' => 0,
'max' => 100,
'default' => 0,
'signed' => false,
]);
// Create unsigned bigint column
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/bigint', $headers, [
'key' => 'unsigned_bigint_field',
'required' => false,
'min' => 0,
'max' => 9223372036854775807,
'default' => 0,
'signed' => false,
]);
// Cache before waiting so that if waitForAllAttributes times out,
// subsequent calls don't try to re-create the same columns (causing 409)
self::$setupCache[$cacheKey] = [
'databaseId' => $databaseId,
'tableId' => $tableId,
];
// Wait for all columns to be available
$this->waitForAllAttributes($databaseId, $tableId);
return self::$setupCache[$cacheKey];
}
/**
* Setup database/table without caching so mutations (update/delete) don't
* affect other tests that might be executed in a different order.
*/
protected function setupFreshDatabaseAndTable(): array
{
$projectId = $this->getProject()['$id'];
$apiKey = $this->getProject()['apiKey'];
$headers = [
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
'x-appwrite-key' => $apiKey,
];
$database = $this->client->call(Client::METHOD_POST, '/tablesdb', $headers, [
'databaseId' => ID::unique(),
'name' => 'Numeric Types Test Database',
]);
$this->assertEquals(201, $database['headers']['status-code']);
$databaseId = $database['body']['$id'];
$table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', $headers, [
'tableId' => ID::unique(),
'name' => 'Numeric Types Table',
'rowSecurity' => true,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
],
]);
$this->assertEquals(201, $table['headers']['status-code']);
$tableId = $table['body']['$id'];
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/integer', $headers, [
'key' => 'integer_field',
'required' => false,
'min' => -10,
'max' => 10,
'default' => 0,
]);
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/bigint', $headers, [
'key' => 'bigint_field',
'required' => false,
'min' => -9007199254740991,
'max' => 9007199254740991,
'default' => 9007199254740000,
]);
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/integer', $headers, [
'key' => 'unsigned_int_field',
'required' => false,
'max' => 100,
'default' => 0,
'signed' => false,
]);
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/bigint', $headers, [
'key' => 'unsigned_bigint_field',
'required' => false,
'max' => 9223372036854775807,
'default' => 0,
'signed' => false,
]);
$this->waitForAllAttributes($databaseId, $tableId);
return [
'databaseId' => $databaseId,
'tableId' => $tableId,
];
}
public function testCreateDatabase(): void
{
$database = $this->client->call(Client::METHOD_POST, '/tablesdb', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'databaseId' => ID::unique(),
'name' => 'Numeric Types Test Database',
]);
$this->assertEquals(201, $database['headers']['status-code']);
}
public function testCreateTable(): void
{
$data = $this->setupDatabaseAndTable();
$table = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $data['databaseId'] . '/tables/' . $data['tableId'], [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $table['headers']['status-code']);
$this->assertEquals($data['tableId'], $table['body']['$id']);
}
public function testGetIntegerAndBigIntColumns(): void
{
$data = $this->setupDatabaseAndTable();
$databaseId = $data['databaseId'];
$tableId = $data['tableId'];
$integerColumn = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/integer_field', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $integerColumn['headers']['status-code']);
$this->assertEquals('integer_field', $integerColumn['body']['key']);
$this->assertEquals('integer', $integerColumn['body']['type']);
$this->assertEquals(false, $integerColumn['body']['required']);
$this->assertEquals(false, $integerColumn['body']['array']);
$this->assertEquals(-10, $integerColumn['body']['min']);
$this->assertEquals(10, $integerColumn['body']['max']);
$this->assertEquals(0, $integerColumn['body']['default']);
$bigintColumn = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/bigint_field', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $bigintColumn['headers']['status-code']);
$this->assertEquals('bigint_field', $bigintColumn['body']['key']);
$this->assertEquals('bigint', $bigintColumn['body']['type']);
$this->assertEquals(false, $bigintColumn['body']['required']);
$this->assertEquals(false, $bigintColumn['body']['array']);
$this->assertEquals(-9007199254740991, $bigintColumn['body']['min']);
$this->assertEquals(9007199254740991, $bigintColumn['body']['max']);
$this->assertEquals(9007199254740000, $bigintColumn['body']['default']);
}
public function testGetUnsignedIntegerAndBigIntColumns(): void
{
$data = $this->setupDatabaseAndTable();
$databaseId = $data['databaseId'];
$tableId = $data['tableId'];
$unsignedIntColumn = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/unsigned_int_field', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $unsignedIntColumn['headers']['status-code']);
$this->assertEquals('unsigned_int_field', $unsignedIntColumn['body']['key']);
$this->assertEquals('integer', $unsignedIntColumn['body']['type']);
$this->assertEquals(false, $unsignedIntColumn['body']['required']);
$this->assertEquals(false, $unsignedIntColumn['body']['array']);
$this->assertEquals(false, $unsignedIntColumn['body']['signed']);
$this->assertEquals(0, $unsignedIntColumn['body']['min']);
$this->assertEquals(100, $unsignedIntColumn['body']['max']);
$this->assertEquals(0, $unsignedIntColumn['body']['default']);
$unsignedBigintColumn = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/unsigned_bigint_field', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $unsignedBigintColumn['headers']['status-code']);
$this->assertEquals('unsigned_bigint_field', $unsignedBigintColumn['body']['key']);
$this->assertEquals('bigint', $unsignedBigintColumn['body']['type']);
$this->assertEquals(false, $unsignedBigintColumn['body']['required']);
$this->assertEquals(false, $unsignedBigintColumn['body']['array']);
$this->assertEquals(false, $unsignedBigintColumn['body']['signed']);
$this->assertEquals(0, $unsignedBigintColumn['body']['min']);
$this->assertEquals(9223372036854775807, $unsignedBigintColumn['body']['max']);
$this->assertEquals(0, $unsignedBigintColumn['body']['default']);
}
public function testListColumnsWithNumericTypes(): void
{
$data = $this->setupDatabaseAndTable();
$databaseId = $data['databaseId'];
$tableId = $data['tableId'];
$columns = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $columns['headers']['status-code']);
$this->assertIsArray($columns['body']['columns']);
$this->assertGreaterThan(0, $columns['body']['total']);
$columnKeys = array_map(fn ($col) => $col['key'], $columns['body']['columns']);
$this->assertContains('integer_field', $columnKeys);
$this->assertContains('bigint_field', $columnKeys);
$this->assertContains('unsigned_int_field', $columnKeys);
$this->assertContains('unsigned_bigint_field', $columnKeys);
$columnTypeByKey = [];
foreach ($columns['body']['columns'] as $col) {
$columnTypeByKey[$col['key']] = $col['type'];
}
$this->assertEquals('integer', $columnTypeByKey['integer_field']);
$this->assertEquals('bigint', $columnTypeByKey['bigint_field']);
$this->assertEquals('integer', $columnTypeByKey['unsigned_int_field']);
$this->assertEquals('bigint', $columnTypeByKey['unsigned_bigint_field']);
}
public function testCreateRowWithIntegerAndBigIntTypes(): void
{
$data = $this->setupDatabaseAndTable();
$databaseId = $data['databaseId'];
$tableId = $data['tableId'];
$row = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'rowId' => ID::unique(),
'data' => [
'integer_field' => 5,
'bigint_field' => 456,
'unsigned_int_field' => 50,
'unsigned_bigint_field' => 9007199254740000,
],
'permissions' => [
Permission::read(Role::any()),
],
]);
$this->assertEquals(201, $row['headers']['status-code']);
$this->assertEquals(5, $row['body']['integer_field']);
$this->assertEquals(456, $row['body']['bigint_field']);
$this->assertEquals(50, $row['body']['unsigned_int_field']);
$this->assertEquals(9007199254740000, $row['body']['unsigned_bigint_field']);
}
public function testUpdateIntegerAndBigIntColumns(): void
{
$data = $this->setupFreshDatabaseAndTable();
$databaseId = $data['databaseId'];
$tableId = $data['tableId'];
// Update integer column
$updateInteger = $this->client->call(
Client::METHOD_PATCH,
'/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/integer/integer_field',
[
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
],
[
'required' => false,
'min' => -20,
'max' => 20,
'default' => 3,
]
);
$this->assertEquals(200, $updateInteger['headers']['status-code']);
$this->assertEventually(function () use ($databaseId, $tableId) {
$column = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/integer_field', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $column['headers']['status-code']);
$this->assertEquals(-20, $column['body']['min']);
$this->assertEquals(20, $column['body']['max']);
$this->assertEquals(3, $column['body']['default']);
}, 30000, 250);
// Update bigint column
$updateBigint = $this->client->call(
Client::METHOD_PATCH,
'/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/bigint/bigint_field',
[
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
],
[
'required' => false,
'min' => -999,
'max' => 999,
'default' => 10,
]
);
$this->assertEquals(200, $updateBigint['headers']['status-code']);
$this->assertEventually(function () use ($databaseId, $tableId) {
$column = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/bigint_field', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $column['headers']['status-code']);
$this->assertEquals(-999, $column['body']['min']);
$this->assertEquals(999, $column['body']['max']);
$this->assertEquals(10, $column['body']['default']);
}, 30000, 250);
}
public function testDeleteIntegerAndBigIntColumns(): void
{
$data = $this->setupFreshDatabaseAndTable();
$databaseId = $data['databaseId'];
$tableId = $data['tableId'];
// Delete integer column
$deleteInteger = $this->client->call(
Client::METHOD_DELETE,
'/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/integer_field',
[
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]
);
$this->assertEquals(204, $deleteInteger['headers']['status-code']);
$this->assertEventually(function () use ($databaseId, $tableId) {
$column = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/integer_field', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(404, $column['headers']['status-code']);
}, 30000, 250);
// Delete bigint column
$deleteBigint = $this->client->call(
Client::METHOD_DELETE,
'/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/bigint_field',
[
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]
);
$this->assertEquals(204, $deleteBigint['headers']['status-code']);
$this->assertEventually(function () use ($databaseId, $tableId) {
$column = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/bigint_field', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(404, $column['headers']['status-code']);
}, 30000, 250);
}
}