mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
fix: scope file caching to Database tests only, revert for non-Database tests
The previous file caching approach cached getRoot(), getUser(), getProject(), and getConsoleVariables() globally. This caused all test methods in a class to share the same project, breaking non-Database tests that expect isolated state (Account 401s, Storage 500s, Users 404s, etc.). Now file caching is only applied in Database/Transaction test setup chains: - ensureSharedProject() in DatabasesBase, TransactionsBase, TransactionPermissionsBase creates and file-caches both the project AND user so all methods share consistent project + user state (needed for collection permissions) - Non-Database tests (Account, Storage, Users, etc.) create their own isolated projects per-process as before Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,9 +28,7 @@ trait ProjectCustom
|
||||
return $this->createNewProject();
|
||||
}
|
||||
|
||||
self::$project = $this->withFileCache('project_' . static::class, function () {
|
||||
return $this->createNewProject();
|
||||
});
|
||||
self::$project = $this->createNewProject();
|
||||
|
||||
return self::$project;
|
||||
}
|
||||
|
||||
+67
-79
@@ -125,18 +125,16 @@ abstract class Scope extends TestCase
|
||||
return self::$consoleVariables;
|
||||
}
|
||||
|
||||
self::$consoleVariables = $this->withFileCache('console_variables', function () {
|
||||
$root = $this->getRoot();
|
||||
$root = $this->getRoot();
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/console/variables', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
'cookie' => 'a_session_console=' . $root['session'],
|
||||
]);
|
||||
$response = $this->client->call(Client::METHOD_GET, '/console/variables', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
'cookie' => 'a_session_console=' . $root['session'],
|
||||
]);
|
||||
|
||||
return $response['body'] ?? [];
|
||||
});
|
||||
self::$consoleVariables = $response['body'] ?? [];
|
||||
|
||||
return self::$consoleVariables;
|
||||
}
|
||||
@@ -468,41 +466,39 @@ abstract class Scope extends TestCase
|
||||
return self::$root;
|
||||
}
|
||||
|
||||
self::$root = $this->withFileCache('root_' . static::class, function () {
|
||||
// Use more entropy to avoid collisions in parallel test execution
|
||||
$email = uniqid('', true) . getmypid() . bin2hex(random_bytes(4)) . '@localhost.test';
|
||||
$password = 'password';
|
||||
$name = 'User Name';
|
||||
// Use more entropy to avoid collisions in parallel test execution
|
||||
$email = uniqid('', true) . getmypid() . bin2hex(random_bytes(4)) . '@localhost.test';
|
||||
$password = 'password';
|
||||
$name = 'User Name';
|
||||
|
||||
$root = $this->client->call(Client::METHOD_POST, '/account', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
]);
|
||||
$root = $this->client->call(Client::METHOD_POST, '/account', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $root['headers']['status-code']);
|
||||
$this->assertEquals(201, $root['headers']['status-code']);
|
||||
|
||||
$session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
$session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
return [
|
||||
'$id' => ID::custom($root['body']['$id']),
|
||||
'name' => $root['body']['name'],
|
||||
'email' => $root['body']['email'],
|
||||
'session' => $session['cookies']['a_session_console'],
|
||||
];
|
||||
});
|
||||
self::$root = [
|
||||
'$id' => ID::custom($root['body']['$id']),
|
||||
'name' => $root['body']['name'],
|
||||
'email' => $root['body']['email'],
|
||||
'session' => $session['cookies']['a_session_console'],
|
||||
];
|
||||
|
||||
return self::$root;
|
||||
}
|
||||
@@ -523,48 +519,40 @@ abstract class Scope extends TestCase
|
||||
return self::$user[$projectId];
|
||||
}
|
||||
|
||||
$createUser = function () use ($projectId) {
|
||||
// Use more entropy to avoid collisions in parallel test execution
|
||||
$email = uniqid('', true) . getmypid() . bin2hex(random_bytes(4)) . '@localhost.test';
|
||||
$password = 'password';
|
||||
$name = 'User Name';
|
||||
// Use more entropy to avoid collisions in parallel test execution
|
||||
$email = uniqid('', true) . getmypid() . bin2hex(random_bytes(4)) . '@localhost.test';
|
||||
$password = 'password';
|
||||
$name = 'User Name';
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/account', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
]);
|
||||
$user = $this->client->call(Client::METHOD_POST, '/account', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $user['headers']['status-code']);
|
||||
$this->assertEquals(201, $user['headers']['status-code']);
|
||||
|
||||
$session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
$session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
return [
|
||||
'$id' => ID::custom($user['body']['$id']),
|
||||
'name' => $user['body']['name'],
|
||||
'email' => $user['body']['email'],
|
||||
'session' => $session['cookies']['a_session_' . $projectId],
|
||||
'sessionId' => $session['body']['$id'],
|
||||
];
|
||||
};
|
||||
|
||||
if ($fresh) {
|
||||
self::$user[$projectId] = $createUser();
|
||||
} else {
|
||||
self::$user[$projectId] = $this->withFileCache('user_' . static::class, $createUser);
|
||||
}
|
||||
self::$user[$projectId] = [
|
||||
'$id' => ID::custom($user['body']['$id']),
|
||||
'name' => $user['body']['name'],
|
||||
'email' => $user['body']['email'],
|
||||
'session' => $session['cookies']['a_session_' . $projectId],
|
||||
'sessionId' => $session['body']['$id'],
|
||||
];
|
||||
|
||||
return self::$user[$projectId];
|
||||
}
|
||||
|
||||
@@ -33,6 +33,37 @@ trait DatabasesBase
|
||||
private static array $oneToManyCache = [];
|
||||
private static array $fulltextDocsCache = [];
|
||||
|
||||
/**
|
||||
* Ensure all Database test methods share the same project and user via file cache.
|
||||
* In ParaTest --functional mode, each test method runs in its own process.
|
||||
* Without this, each process would create a new project, and the file-cached
|
||||
* database/collection/attribute IDs would be invalid for the new project.
|
||||
* The user is also cached because collection permissions reference the user's ID.
|
||||
*/
|
||||
protected function ensureSharedProject(): void
|
||||
{
|
||||
// If we already have a project in static cache, use it
|
||||
if (!empty(self::$project)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// File-cache the project + user so all methods in this test class share them
|
||||
$cached = $this->withFileCache('db_project_' . static::class, function () {
|
||||
$project = $this->createNewProject();
|
||||
// Temporarily set the project so getUser() can create a user in this project
|
||||
self::$project = $project;
|
||||
$user = $this->getUser();
|
||||
return [
|
||||
'_project' => $project,
|
||||
'_user' => $user,
|
||||
];
|
||||
});
|
||||
|
||||
self::$project = $cached['_project'];
|
||||
$projectId = self::$project['$id'];
|
||||
self::$user[$projectId] = $cached['_user'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for current test instance (based on project ID)
|
||||
*/
|
||||
@@ -47,6 +78,8 @@ trait DatabasesBase
|
||||
*/
|
||||
protected function setupDatabase(): array
|
||||
{
|
||||
$this->ensureSharedProject();
|
||||
|
||||
$cacheKey = $this->getCacheKey();
|
||||
if (!empty(self::$databaseCache[$cacheKey])) {
|
||||
return self::$databaseCache[$cacheKey];
|
||||
|
||||
@@ -24,11 +24,37 @@ trait TransactionPermissionsBase
|
||||
parent::setUpBeforeClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure all Transaction permission test methods share the same project and user via file cache.
|
||||
*/
|
||||
protected function ensureSharedProject(): void
|
||||
{
|
||||
if (!empty(self::$project)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cached = $this->withFileCache('db_project_' . static::class, function () {
|
||||
$project = $this->createNewProject();
|
||||
self::$project = $project;
|
||||
$user = $this->getUser();
|
||||
return [
|
||||
'_project' => $project,
|
||||
'_user' => $user,
|
||||
];
|
||||
});
|
||||
|
||||
self::$project = $cached['_project'];
|
||||
$projectId = self::$project['$id'];
|
||||
self::$user[$projectId] = $cached['_user'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the permissions database if not already done
|
||||
*/
|
||||
protected function ensurePermissionsDatabase(): void
|
||||
{
|
||||
$this->ensureSharedProject();
|
||||
|
||||
if (!empty(self::$permissionsDatabase)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,11 +18,37 @@ trait TransactionsBase
|
||||
protected static string $sharedCollectionId = '';
|
||||
protected static bool $sharedSetupDone = false;
|
||||
|
||||
/**
|
||||
* Ensure all Transaction test methods share the same project and user via file cache.
|
||||
*/
|
||||
protected function ensureSharedProject(): void
|
||||
{
|
||||
if (!empty(self::$project)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cached = $this->withFileCache('db_project_' . static::class, function () {
|
||||
$project = $this->createNewProject();
|
||||
self::$project = $project;
|
||||
$user = $this->getUser();
|
||||
return [
|
||||
'_project' => $project,
|
||||
'_user' => $user,
|
||||
];
|
||||
});
|
||||
|
||||
self::$project = $cached['_project'];
|
||||
$projectId = self::$project['$id'];
|
||||
self::$user[$projectId] = $cached['_user'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a shared database for tests that don't need isolation
|
||||
*/
|
||||
protected function getSharedDatabase(): string
|
||||
{
|
||||
$this->ensureSharedProject();
|
||||
|
||||
if (!empty(self::$sharedDatabaseId)) {
|
||||
return self::$sharedDatabaseId;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user