mirror of
https://github.com/appwrite/appwrite
synced 2026-05-22 16:38:32 +00:00
Merge pull request #10990 from appwrite/project-queries
Allow queries on projects xlist
This commit is contained in:
commit
3e96f9c2f1
5 changed files with 395 additions and 12 deletions
|
|
@ -107,9 +107,11 @@ class Action extends UtopiaAction
|
|||
}
|
||||
}
|
||||
|
||||
public function disableSubqueries()
|
||||
public function disableSubqueries(array $filters = []): void
|
||||
{
|
||||
$filters = $this->filters;
|
||||
if (empty($filters)) {
|
||||
$filters = $this->filters;
|
||||
}
|
||||
|
||||
foreach ($filters as $filter) {
|
||||
Database::addFilter(
|
||||
|
|
@ -189,6 +191,11 @@ class Action extends UtopiaAction
|
|||
}
|
||||
}
|
||||
|
||||
// found a wildcard, return!
|
||||
if (\in_array('*', $attributes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$responseModel = $response->getModel($model);
|
||||
foreach ($responseModel->getRules() as $ruleName => $rule) {
|
||||
if (\str_starts_with($ruleName, '$')) {
|
||||
|
|
|
|||
|
|
@ -3,19 +3,21 @@
|
|||
namespace Appwrite\Platform\Modules\Projects\Http\Projects;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Projects;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
|
@ -24,6 +26,10 @@ use Utopia\Validator\Text;
|
|||
class XList extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
// cached mapping of columns to their subQuery filters
|
||||
private static ?array $attributeToSubQueryFilters = null;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'listProjects';
|
||||
|
|
@ -61,12 +67,13 @@ class XList extends Action
|
|||
->param('queries', [], $this->getQueriesValidator(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Projects::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(array $queries, string $search, bool $includeTotal, Response $response, Database $dbForPlatform)
|
||||
public function action(array $queries, string $search, bool $includeTotal, Request $request, Response $response, Database $dbForPlatform)
|
||||
{
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
|
|
@ -103,16 +110,89 @@ class XList extends Action
|
|||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
try {
|
||||
$projects = $dbForPlatform->find('projects', $queries);
|
||||
$selectQueries = Query::groupByType($queries)['selections'] ?? [];
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
$projects = $this->find($dbForPlatform, $queries, $selectQueries);
|
||||
$total = $includeTotal ? $dbForPlatform->count('projects', $filterQueries, APP_LIMIT_COUNT) : 0;
|
||||
} catch (Order $e) {
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
|
||||
}
|
||||
|
||||
$this->applySelectQueries($request, $response, Response::MODEL_PROJECT);
|
||||
$response->dynamic(new Document([
|
||||
'projects' => $projects,
|
||||
'total' => $total,
|
||||
]), Response::MODEL_PROJECT_LIST);
|
||||
}
|
||||
|
||||
// Build mapping of columns to their subQuery filters
|
||||
private static function getAttributeToSubQueryFilters(): array
|
||||
{
|
||||
if (self::$attributeToSubQueryFilters !== null) {
|
||||
return self::$attributeToSubQueryFilters;
|
||||
}
|
||||
|
||||
self::$attributeToSubQueryFilters = [];
|
||||
|
||||
$collections = Config::getParam('collections', []);
|
||||
$projectAttributes = $collections['platform']['projects']['attributes'] ?? [];
|
||||
|
||||
foreach ($projectAttributes as $attribute) {
|
||||
$attributeId = $attribute['$id'] ?? null;
|
||||
$filters = $attribute['filters'] ?? [];
|
||||
|
||||
if ($attributeId === null || empty($filters)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// extract only subQuery filters
|
||||
$subQueryFilters = \array_filter($filters, function ($filter) {
|
||||
return \str_starts_with($filter, 'subQuery');
|
||||
});
|
||||
|
||||
if (!empty($subQueryFilters)) {
|
||||
self::$attributeToSubQueryFilters[$attributeId] = \array_values($subQueryFilters);
|
||||
}
|
||||
}
|
||||
|
||||
return self::$attributeToSubQueryFilters;
|
||||
}
|
||||
|
||||
private function find(Database $dbForPlatform, array $queries, array $selectQueries): array
|
||||
{
|
||||
if (empty($selectQueries)) {
|
||||
return $dbForPlatform->find('projects', $queries);
|
||||
}
|
||||
|
||||
$selectedAttributes = [];
|
||||
foreach ($selectQueries as $query) {
|
||||
foreach ($query->getValues() as $value) {
|
||||
$selectedAttributes[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (\in_array('*', $selectedAttributes)) {
|
||||
return $dbForPlatform->find('projects', $queries);
|
||||
}
|
||||
|
||||
$filtersToSkipMap = [];
|
||||
$selectedAttributesMap = \array_flip($selectedAttributes);
|
||||
$attributeToSubQueryFilters = self::getAttributeToSubQueryFilters();
|
||||
|
||||
foreach ($attributeToSubQueryFilters as $attributeName => $subQueryFilters) {
|
||||
if (!isset($selectedAttributesMap[$attributeName])) {
|
||||
foreach ($subQueryFilters as $filter) {
|
||||
$filtersToSkipMap[$filter] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$filtersToSkip = \array_keys($filtersToSkipMap);
|
||||
|
||||
return empty($filtersToSkip)
|
||||
? $dbForPlatform->find('projects', $queries)
|
||||
: $dbForPlatform->skipFilters(fn () => $dbForPlatform->find('projects', $queries), $filtersToSkip);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,4 +17,9 @@ class Projects extends Base
|
|||
{
|
||||
parent::__construct('projects', self::ALLOWED_ATTRIBUTES);
|
||||
}
|
||||
|
||||
public function isSelectQueryAllowed(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -341,6 +341,20 @@ class Project extends Model
|
|||
*/
|
||||
public function filter(Document $document): Document
|
||||
{
|
||||
$this->expandSmtpFields($document);
|
||||
$this->expandServiceFields($document);
|
||||
$this->expandAuthFields($document);
|
||||
$this->expandOAuthProviders($document);
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
private function expandSmtpFields(Document $document): void
|
||||
{
|
||||
if (!$document->isSet('smtp')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// SMTP
|
||||
$smtp = $document->getAttribute('smtp', []);
|
||||
$document->setAttribute('smtpEnabled', $smtp['enabled'] ?? false);
|
||||
|
|
@ -352,8 +366,14 @@ class Project extends Model
|
|||
$document->setAttribute('smtpUsername', $smtp['username'] ?? '');
|
||||
$document->setAttribute('smtpPassword', $smtp['password'] ?? '');
|
||||
$document->setAttribute('smtpSecure', $smtp['secure'] ?? '');
|
||||
}
|
||||
|
||||
private function expandServiceFields(Document $document): void
|
||||
{
|
||||
if (!$document->isSet('services')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Services
|
||||
$values = $document->getAttribute('services', []);
|
||||
$services = Config::getParam('services', []);
|
||||
|
||||
|
|
@ -365,8 +385,14 @@ class Project extends Model
|
|||
$value = $values[$key] ?? true;
|
||||
$document->setAttribute('serviceStatusFor' . ucfirst($key), $value);
|
||||
}
|
||||
}
|
||||
|
||||
private function expandAuthFields(Document $document): void
|
||||
{
|
||||
if (!$document->isSet('auths')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Auth
|
||||
$authValues = $document->getAttribute('auths', []);
|
||||
$auth = Config::getParam('auth', []);
|
||||
|
||||
|
|
@ -383,13 +409,19 @@ class Project extends Model
|
|||
$document->setAttribute('authMembershipsMfa', $authValues['membershipsMfa'] ?? true);
|
||||
$document->setAttribute('authInvalidateSessions', $authValues['invalidateSessions'] ?? false);
|
||||
|
||||
foreach ($auth as $index => $method) {
|
||||
foreach ($auth as $method) {
|
||||
$key = $method['key'];
|
||||
$value = $authValues[$key] ?? true;
|
||||
$document->setAttribute('auth' . ucfirst($key), $value);
|
||||
}
|
||||
}
|
||||
|
||||
private function expandOAuthProviders(Document $document): void
|
||||
{
|
||||
if (!$document->isSet('oAuthProviders')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// OAuth Providers
|
||||
$providers = Config::getParam('oAuthProviders', []);
|
||||
$providerValues = $document->getAttribute('oAuthProviders', []);
|
||||
$projectProviders = [];
|
||||
|
|
@ -410,7 +442,5 @@ class Project extends Model
|
|||
}
|
||||
|
||||
$document->setAttribute('oAuthProviders', $projectProviders);
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -436,6 +436,267 @@ class ProjectsConsoleClientTest extends Scope
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @group projectsCRUD
|
||||
*/
|
||||
public function testListProjectsQuerySelect(): void
|
||||
{
|
||||
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'teamId' => ID::unique(),
|
||||
'name' => 'Query Select Test Team',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $team['headers']['status-code']);
|
||||
$teamId = $team['body']['$id'];
|
||||
|
||||
$project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'projectId' => ID::unique(),
|
||||
'name' => 'Query Select Test Project',
|
||||
'teamId' => $teamId,
|
||||
'region' => System::getEnv('_APP_REGION', 'default')
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $project['headers']['status-code']);
|
||||
$projectId = $project['body']['$id'];
|
||||
|
||||
/**
|
||||
* Test Query.select - basic fields
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::select(['$id', 'name'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertGreaterThan(0, count($response['body']['projects']));
|
||||
|
||||
$project = $response['body']['projects'][0];
|
||||
$this->assertArrayHasKey('$id', $project);
|
||||
$this->assertArrayHasKey('name', $project);
|
||||
$this->assertArrayNotHasKey('platforms', $project);
|
||||
$this->assertArrayNotHasKey('webhooks', $project);
|
||||
$this->assertArrayNotHasKey('keys', $project);
|
||||
$this->assertArrayNotHasKey('devKeys', $project);
|
||||
$this->assertArrayNotHasKey('oAuthProviders', $project);
|
||||
$this->assertArrayNotHasKey('smtpEnabled', $project);
|
||||
$this->assertArrayNotHasKey('smtpHost', $project);
|
||||
$this->assertArrayNotHasKey('authLimit', $project);
|
||||
$this->assertArrayNotHasKey('authDuration', $project);
|
||||
|
||||
/**
|
||||
* Test Query.select - multiple fields
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::select(['$id', 'name', 'teamId', 'description', '$createdAt', '$updatedAt'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertGreaterThan(0, count($response['body']['projects']));
|
||||
|
||||
$project = $response['body']['projects'][0];
|
||||
$this->assertArrayHasKey('$id', $project);
|
||||
$this->assertArrayHasKey('name', $project);
|
||||
$this->assertArrayHasKey('teamId', $project);
|
||||
$this->assertArrayHasKey('description', $project);
|
||||
$this->assertArrayHasKey('$createdAt', $project);
|
||||
$this->assertArrayHasKey('$updatedAt', $project);
|
||||
$this->assertArrayNotHasKey('platforms', $project);
|
||||
$this->assertArrayNotHasKey('webhooks', $project);
|
||||
$this->assertArrayNotHasKey('keys', $project);
|
||||
$this->assertArrayNotHasKey('devKeys', $project);
|
||||
$this->assertArrayNotHasKey('oAuthProviders', $project);
|
||||
$this->assertArrayNotHasKey('smtpEnabled', $project);
|
||||
$this->assertArrayNotHasKey('authLimit', $project);
|
||||
|
||||
/**
|
||||
* Test Query.select combined with filters
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::select(['$id', 'name', 'teamId'])->toString(),
|
||||
Query::equal('name', ['Query Select Test Project'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertCount(1, $response['body']['projects']);
|
||||
|
||||
$project = $response['body']['projects'][0];
|
||||
$this->assertArrayHasKey('$id', $project);
|
||||
$this->assertArrayHasKey('name', $project);
|
||||
$this->assertArrayHasKey('teamId', $project);
|
||||
$this->assertEquals('Query Select Test Project', $project['name']);
|
||||
$this->assertEquals($teamId, $project['teamId']);
|
||||
$this->assertArrayNotHasKey('platforms', $project);
|
||||
$this->assertArrayNotHasKey('webhooks', $project);
|
||||
$this->assertArrayNotHasKey('keys', $project);
|
||||
$this->assertArrayNotHasKey('devKeys', $project);
|
||||
$this->assertArrayNotHasKey('oAuthProviders', $project);
|
||||
$this->assertArrayNotHasKey('smtpEnabled', $project);
|
||||
$this->assertArrayNotHasKey('authLimit', $project);
|
||||
|
||||
/**
|
||||
* Test Query.select combined with limit
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::select(['$id', 'name'])->toString(),
|
||||
Query::limit(2)->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertLessThanOrEqual(2, count($response['body']['projects']));
|
||||
|
||||
foreach ($response['body']['projects'] as $p) {
|
||||
$this->assertArrayHasKey('$id', $p);
|
||||
$this->assertArrayHasKey('name', $p);
|
||||
$this->assertArrayNotHasKey('platforms', $p);
|
||||
$this->assertArrayNotHasKey('webhooks', $p);
|
||||
$this->assertArrayNotHasKey('keys', $p);
|
||||
$this->assertArrayNotHasKey('devKeys', $p);
|
||||
$this->assertArrayNotHasKey('oAuthProviders', $p);
|
||||
$this->assertArrayNotHasKey('smtpEnabled', $p);
|
||||
$this->assertArrayNotHasKey('authLimit', $p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Query.select with subquery attributes (platforms, webhooks, etc.)
|
||||
* When explicitly selected, subqueries should still run
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::select(['$id', 'name', 'platforms'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertGreaterThan(0, count($response['body']['projects']));
|
||||
|
||||
$project = $response['body']['projects'][0];
|
||||
$this->assertArrayHasKey('$id', $project);
|
||||
$this->assertArrayHasKey('name', $project);
|
||||
$this->assertArrayHasKey('platforms', $project);
|
||||
$this->assertIsArray($project['platforms']);
|
||||
$this->assertArrayNotHasKey('webhooks', $project);
|
||||
$this->assertArrayNotHasKey('keys', $project);
|
||||
$this->assertArrayNotHasKey('devKeys', $project);
|
||||
$this->assertArrayNotHasKey('oAuthProviders', $project);
|
||||
$this->assertArrayNotHasKey('smtpEnabled', $project);
|
||||
$this->assertArrayNotHasKey('authLimit', $project);
|
||||
|
||||
/**
|
||||
* Test Query.select with expanded attributes
|
||||
* webhooks and keys should load their subquery data when selected
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::select(['$id', 'name', 'webhooks', 'keys'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertGreaterThan(0, count($response['body']['projects']));
|
||||
|
||||
$project = $response['body']['projects'][0];
|
||||
$this->assertArrayHasKey('$id', $project);
|
||||
$this->assertArrayHasKey('name', $project);
|
||||
$this->assertArrayHasKey('webhooks', $project);
|
||||
$this->assertArrayHasKey('keys', $project);
|
||||
$this->assertIsArray($project['webhooks']);
|
||||
$this->assertIsArray($project['keys']);
|
||||
$this->assertArrayNotHasKey('platforms', $project);
|
||||
$this->assertArrayNotHasKey('devKeys', $project);
|
||||
$this->assertArrayNotHasKey('smtpEnabled', $project);
|
||||
$this->assertArrayNotHasKey('authLimit', $project);
|
||||
|
||||
/**
|
||||
* Test Query.select with wildcard '*'
|
||||
* Should return all fields like no select query
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::select(['*'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertGreaterThan(0, count($response['body']['projects']));
|
||||
|
||||
$project = $response['body']['projects'][0];
|
||||
$this->assertArrayHasKey('$id', $project);
|
||||
$this->assertArrayHasKey('name', $project);
|
||||
$this->assertArrayHasKey('teamId', $project);
|
||||
$this->assertArrayHasKey('platforms', $project);
|
||||
$this->assertArrayHasKey('webhooks', $project);
|
||||
$this->assertArrayHasKey('keys', $project);
|
||||
$this->assertArrayHasKey('devKeys', $project);
|
||||
$this->assertArrayHasKey('oAuthProviders', $project);
|
||||
$this->assertArrayHasKey('smtpEnabled', $project);
|
||||
$this->assertArrayHasKey('smtpHost', $project);
|
||||
$this->assertArrayHasKey('authLimit', $project);
|
||||
$this->assertArrayHasKey('authDuration', $project);
|
||||
|
||||
/**
|
||||
* Test Query.select with invalid attribute
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::select(['$id', 'invalidAttribute'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
$this->assertEquals('Invalid `queries` param: Invalid query: Attribute not found in schema: invalidAttribute', $response['body']['message']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $projectId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testGetProject(): void
|
||||
{
|
||||
// Create a team
|
||||
|
|
|
|||
Loading…
Reference in a new issue