mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c124171d6 | ||
|
|
176d916dc3 | ||
|
|
d64d7ece12 | ||
|
|
fc3589c129 | ||
|
|
0cdf7e1840 | ||
|
|
2b652a2b72 | ||
|
|
18fc760077 | ||
|
|
6c35e86d98 | ||
|
|
b0d51cd25f | ||
|
|
8f993428de | ||
|
|
b9274e97e1 | ||
|
|
93cfb8fd8c | ||
|
|
abbc5f9097 | ||
|
|
c6f4fc041f | ||
|
|
cdddd438f5 | ||
|
|
d51130ec8f | ||
|
|
01c2d0f04b | ||
|
|
01ccee520b | ||
|
|
f25d3df7af |
+10
-4
@@ -74,7 +74,7 @@
|
||||
"utopia-php/locale": "0.8.*",
|
||||
"utopia-php/logger": "0.8.*",
|
||||
"utopia-php/messaging": "0.22.*",
|
||||
"utopia-php/migration": "1.*",
|
||||
"utopia-php/migration": "dev-add-project-variable-migration as 1.12.0",
|
||||
"utopia-php/platform": "^1.0@RC",
|
||||
"utopia-php/pools": "1.*",
|
||||
"utopia-php/span": "1.1.*",
|
||||
@@ -112,11 +112,17 @@
|
||||
"ext-phpiredis": "*"
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
},
|
||||
"platform": {},
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": true,
|
||||
"tbachert/spi": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"name": "migration",
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/utopia-php/migration"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Generated
+36
-10
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "9377e1b56bca8dbaf213ee3572ca15c0",
|
||||
"content-hash": "5bf93473859f86fbf9438b17cb660331",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -4606,16 +4606,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/migration",
|
||||
"version": "1.12.0",
|
||||
"version": "dev-add-project-variable-migration",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/migration.git",
|
||||
"reference": "3ee6e12af256726bddc3a0402c94535132abecc6"
|
||||
"reference": "3376bed6f797a8d48f0107e9a0d8e40745d6914b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/3ee6e12af256726bddc3a0402c94535132abecc6",
|
||||
"reference": "3ee6e12af256726bddc3a0402c94535132abecc6",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/3376bed6f797a8d48f0107e9a0d8e40745d6914b",
|
||||
"reference": "3376bed6f797a8d48f0107e9a0d8e40745d6914b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4641,7 +4641,25 @@
|
||||
"Utopia\\Migration\\": "src/Migration"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Utopia\\Tests\\": "tests/Migration"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": [
|
||||
"./vendor/bin/phpunit"
|
||||
],
|
||||
"lint": [
|
||||
"./vendor/bin/pint --test"
|
||||
],
|
||||
"format": [
|
||||
"./vendor/bin/pint"
|
||||
],
|
||||
"check": [
|
||||
"./vendor/bin/phpstan analyse --level 3 src tests --memory-limit 2G"
|
||||
]
|
||||
},
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
@@ -4654,10 +4672,10 @@
|
||||
"utopia"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/migration/issues",
|
||||
"source": "https://github.com/utopia-php/migration/tree/1.12.0"
|
||||
"source": "https://github.com/utopia-php/migration/tree/add-project-variable-migration",
|
||||
"issues": "https://github.com/utopia-php/migration/issues"
|
||||
},
|
||||
"time": "2026-05-14T07:30:09+00:00"
|
||||
"time": "2026-05-14T19:00:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/mongo",
|
||||
@@ -8564,10 +8582,18 @@
|
||||
"time": "2024-11-07T12:36:22+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
"aliases": [
|
||||
{
|
||||
"package": "utopia-php/migration",
|
||||
"version": "dev-add-project-variable-migration",
|
||||
"alias": "1.12.0",
|
||||
"alias_normalized": "1.12.0.0"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "dev",
|
||||
"stability-flags": {
|
||||
"utopia-php/http": 5,
|
||||
"utopia-php/migration": 20,
|
||||
"utopia-php/platform": 5
|
||||
},
|
||||
"prefer-stable": true,
|
||||
|
||||
@@ -59,6 +59,18 @@ class MigrationReport extends Model
|
||||
'default' => 0,
|
||||
'example' => 5,
|
||||
])
|
||||
->addRule(Resource::TYPE_API_KEY, [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Number of API keys to be migrated.',
|
||||
'default' => 0,
|
||||
'example' => 5,
|
||||
])
|
||||
->addRule(Resource::TYPE_PROJECT_VARIABLE, [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Number of project variables to be migrated.',
|
||||
'default' => 0,
|
||||
'example' => 5,
|
||||
])
|
||||
->addRule(Resource::TYPE_SITE, [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Number of sites to be migrated.',
|
||||
|
||||
@@ -2540,6 +2540,174 @@ trait MigrationsBase
|
||||
$this->client->call(Client::METHOD_DELETE, '/project/platforms/' . $platform['$id'], $sourceHeaders);
|
||||
}
|
||||
|
||||
public function testAppwriteMigrationApiKey(): void
|
||||
{
|
||||
$sourceHeaders = [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
];
|
||||
|
||||
$destinationHeaders = [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
];
|
||||
|
||||
// Create API key on source project
|
||||
$response = $this->client->call(Client::METHOD_POST, '/project/keys', $sourceHeaders, [
|
||||
'keyId' => ID::unique(),
|
||||
'name' => 'Test API Key',
|
||||
'scopes' => ['databases.read', 'databases.write'],
|
||||
'expire' => null,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
|
||||
$apiKey = $response['body'];
|
||||
|
||||
$result = $this->performMigrationSync([
|
||||
'resources' => [
|
||||
Resource::TYPE_API_KEY,
|
||||
],
|
||||
'endpoint' => $this->webEndpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals('completed', $result['status']);
|
||||
$this->assertEquals([Resource::TYPE_API_KEY], $result['resources']);
|
||||
$this->assertArrayHasKey(Resource::TYPE_API_KEY, $result['statusCounters']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_API_KEY]['error']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_API_KEY]['pending']);
|
||||
$this->assertGreaterThanOrEqual(1, $result['statusCounters'][Resource::TYPE_API_KEY]['success']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_API_KEY]['processing']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_API_KEY]['warning']);
|
||||
|
||||
// Verify API key on destination project using the project's API key
|
||||
$response = $this->client->call(Client::METHOD_GET, '/project/keys', $destinationHeaders);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertGreaterThan(0, $response['body']['total']);
|
||||
|
||||
$foundKey = null;
|
||||
|
||||
foreach ($response['body']['keys'] as $k) {
|
||||
if ($k['name'] === 'Test API Key') {
|
||||
$foundKey = $k;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertNotNull($foundKey);
|
||||
$this->assertEquals('Test API Key', $foundKey['name']);
|
||||
$this->assertEqualsCanonicalizing(['databases.read', 'databases.write'], $foundKey['scopes']);
|
||||
$this->assertEmpty($foundKey['expire']);
|
||||
$this->assertNotEquals($apiKey['secret'], $foundKey['secret']);
|
||||
|
||||
// Cleanup on destination
|
||||
$this->client->call(Client::METHOD_DELETE, '/project/keys/' . $foundKey['$id'], $destinationHeaders);
|
||||
|
||||
// Cleanup on source
|
||||
$this->client->call(Client::METHOD_DELETE, '/project/keys/' . $apiKey['$id'], $sourceHeaders);
|
||||
}
|
||||
|
||||
public function testAppwriteMigrationProjectVariable(): void
|
||||
{
|
||||
$sourceHeaders = [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
];
|
||||
|
||||
$destinationHeaders = [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
];
|
||||
|
||||
// Source-side variable IDs and keys are uniquified so re-runs and parallel suites
|
||||
// can't trip the source-side findOne('variables', [key=...]) skip path.
|
||||
$plainKey = 'TEST_PLAIN_' . \strtoupper(ID::unique());
|
||||
$secretKey = 'TEST_SECRET_' . \strtoupper(ID::unique());
|
||||
|
||||
// Non-secret variable: value should round-trip exactly.
|
||||
$plainResp = $this->client->call(Client::METHOD_POST, '/project/variables', $sourceHeaders, [
|
||||
'variableId' => ID::unique(),
|
||||
'key' => $plainKey,
|
||||
'value' => 'plain-value',
|
||||
'secret' => false,
|
||||
]);
|
||||
$this->assertEquals(201, $plainResp['headers']['status-code']);
|
||||
$plainVariable = $plainResp['body'];
|
||||
|
||||
// Secret variable: SDK strips `value` on subsequent reads, so the migration
|
||||
// source sees empty and the destination writes empty. Test asserts that.
|
||||
$secretResp = $this->client->call(Client::METHOD_POST, '/project/variables', $sourceHeaders, [
|
||||
'variableId' => ID::unique(),
|
||||
'key' => $secretKey,
|
||||
'value' => 'real-secret-value',
|
||||
'secret' => true,
|
||||
]);
|
||||
$this->assertEquals(201, $secretResp['headers']['status-code']);
|
||||
$secretVariable = $secretResp['body'];
|
||||
|
||||
$result = $this->performMigrationSync([
|
||||
'resources' => [
|
||||
Resource::TYPE_PROJECT_VARIABLE,
|
||||
],
|
||||
'endpoint' => $this->webEndpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals('completed', $result['status']);
|
||||
$this->assertEquals([Resource::TYPE_PROJECT_VARIABLE], $result['resources']);
|
||||
$this->assertArrayHasKey(Resource::TYPE_PROJECT_VARIABLE, $result['statusCounters']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_PROJECT_VARIABLE]['error']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_PROJECT_VARIABLE]['pending']);
|
||||
$this->assertGreaterThanOrEqual(2, $result['statusCounters'][Resource::TYPE_PROJECT_VARIABLE]['success']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_PROJECT_VARIABLE]['processing']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_PROJECT_VARIABLE]['warning']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/project/variables', $destinationHeaders);
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
$foundPlain = null;
|
||||
$foundSecret = null;
|
||||
foreach ($response['body']['variables'] as $v) {
|
||||
if ($v['key'] === $plainKey) {
|
||||
$foundPlain = $v;
|
||||
} elseif ($v['key'] === $secretKey) {
|
||||
$foundSecret = $v;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertNotNull($foundPlain, 'Plain variable not found on destination');
|
||||
$this->assertEquals($plainKey, $foundPlain['key']);
|
||||
$this->assertEquals('plain-value', $foundPlain['value']);
|
||||
$this->assertFalse($foundPlain['secret']);
|
||||
|
||||
$this->assertNotNull($foundSecret, 'Secret variable not found on destination');
|
||||
$this->assertEquals($secretKey, $foundSecret['key']);
|
||||
// Secret variables: source SDK never returned the real value, so the destination
|
||||
// also stores empty. The original 'real-secret-value' must not have leaked.
|
||||
$this->assertNotEquals('real-secret-value', $foundSecret['value']);
|
||||
$this->assertTrue($foundSecret['secret']);
|
||||
|
||||
// Cleanup on destination
|
||||
$this->client->call(Client::METHOD_DELETE, '/project/variables/' . $foundPlain['$id'], $destinationHeaders);
|
||||
$this->client->call(Client::METHOD_DELETE, '/project/variables/' . $foundSecret['$id'], $destinationHeaders);
|
||||
|
||||
// Cleanup on source
|
||||
$this->client->call(Client::METHOD_DELETE, '/project/variables/' . $plainVariable['$id'], $sourceHeaders);
|
||||
$this->client->call(Client::METHOD_DELETE, '/project/variables/' . $secretVariable['$id'], $sourceHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import documents from a CSV file.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user