mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge remote-tracking branch 'origin/1.9.x' into pr-12288
# Conflicts: # composer.lock
This commit is contained in:
@@ -177,6 +177,10 @@ trait ProjectCustom
|
||||
'project.policies.write',
|
||||
'templates.read',
|
||||
'templates.write',
|
||||
'insights.read',
|
||||
'insights.write',
|
||||
'reports.read',
|
||||
'reports.write',
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Advisor;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
trait AdvisorBase
|
||||
{
|
||||
protected function serverHeaders(): array
|
||||
{
|
||||
return [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getReport(string $reportId, ?array $headers = null): array
|
||||
{
|
||||
return $this->client->call(Client::METHOD_GET, '/reports/' . $reportId, $headers ?? $this->serverHeaders());
|
||||
}
|
||||
|
||||
protected function listReports(array $params = [], ?array $headers = null): array
|
||||
{
|
||||
return $this->client->call(Client::METHOD_GET, '/reports', $headers ?? $this->serverHeaders(), $params);
|
||||
}
|
||||
|
||||
protected function getInsight(string $reportId, string $insightId, ?array $headers = null): array
|
||||
{
|
||||
return $this->client->call(Client::METHOD_GET, '/reports/' . $reportId . '/insights/' . $insightId, $headers ?? $this->serverHeaders());
|
||||
}
|
||||
|
||||
protected function listInsights(string $reportId, array $params = [], ?array $headers = null): array
|
||||
{
|
||||
return $this->client->call(Client::METHOD_GET, '/reports/' . $reportId . '/insights', $headers ?? $this->serverHeaders(), $params);
|
||||
}
|
||||
|
||||
public function testListReports(): void
|
||||
{
|
||||
$list = $this->listReports();
|
||||
|
||||
$this->assertSame(200, $list['headers']['status-code']);
|
||||
$this->assertArrayHasKey('reports', $list['body']);
|
||||
$this->assertArrayHasKey('total', $list['body']);
|
||||
$this->assertIsArray($list['body']['reports']);
|
||||
}
|
||||
|
||||
public function testGetReportMissing(): void
|
||||
{
|
||||
$missing = $this->getReport(ID::unique());
|
||||
|
||||
$this->assertSame(404, $missing['headers']['status-code']);
|
||||
$this->assertSame('report_not_found', $missing['body']['type']);
|
||||
}
|
||||
|
||||
public function testListInsightsMissingReport(): void
|
||||
{
|
||||
$missing = $this->listInsights(ID::unique());
|
||||
|
||||
$this->assertSame(404, $missing['headers']['status-code']);
|
||||
$this->assertSame('report_not_found', $missing['body']['type']);
|
||||
}
|
||||
|
||||
public function testGetInsightMissingReport(): void
|
||||
{
|
||||
$missing = $this->getInsight(ID::unique(), ID::unique());
|
||||
|
||||
$this->assertSame(404, $missing['headers']['status-code']);
|
||||
$this->assertSame('report_not_found', $missing['body']['type']);
|
||||
}
|
||||
|
||||
public function testReportsCreateAndUpdateNotExposed(): void
|
||||
{
|
||||
$create = $this->client->call(Client::METHOD_POST, '/reports', $this->serverHeaders(), [
|
||||
'reportId' => ID::unique(),
|
||||
'type' => 'audit',
|
||||
'title' => 'Read-only check',
|
||||
'targetType' => 'sites',
|
||||
'target' => 'home',
|
||||
]);
|
||||
$this->assertSame(404, $create['headers']['status-code']);
|
||||
|
||||
$update = $this->client->call(Client::METHOD_PATCH, '/reports/' . ID::unique(), $this->serverHeaders(), [
|
||||
'title' => 'Read-only check',
|
||||
]);
|
||||
$this->assertSame(404, $update['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testDeleteReportMissing(): void
|
||||
{
|
||||
$delete = $this->client->call(Client::METHOD_DELETE, '/reports/' . ID::unique(), $this->serverHeaders());
|
||||
$this->assertSame(404, $delete['headers']['status-code']);
|
||||
$this->assertSame('report_not_found', $delete['body']['type']);
|
||||
}
|
||||
|
||||
public function testInsightsCreateUpdateDeleteNotExposed(): void
|
||||
{
|
||||
$create = $this->client->call(
|
||||
Client::METHOD_POST,
|
||||
'/reports/' . ID::unique() . '/insights',
|
||||
$this->serverHeaders(),
|
||||
[]
|
||||
);
|
||||
$this->assertSame(404, $create['headers']['status-code']);
|
||||
|
||||
$update = $this->client->call(
|
||||
Client::METHOD_PATCH,
|
||||
'/reports/' . ID::unique() . '/insights/' . ID::unique(),
|
||||
$this->serverHeaders(),
|
||||
['status' => 'dismissed']
|
||||
);
|
||||
$this->assertSame(404, $update['headers']['status-code']);
|
||||
|
||||
$delete = $this->client->call(
|
||||
Client::METHOD_DELETE,
|
||||
'/reports/' . ID::unique() . '/insights/' . ID::unique(),
|
||||
$this->serverHeaders()
|
||||
);
|
||||
$this->assertSame(404, $delete['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Advisor;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
class AdvisorCustomServerTest extends Scope
|
||||
{
|
||||
use AdvisorBase;
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
|
||||
public function testReadWithAdvisorScopes(): void
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
||||
$userKey = $this->getNewKey([
|
||||
// Advisor read APIs are protected by the underlying report/insight resource scopes.
|
||||
'insights.read',
|
||||
'reports.read',
|
||||
]);
|
||||
|
||||
$listed = $this->client->call(
|
||||
Client::METHOD_GET,
|
||||
'/reports',
|
||||
[
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $userKey,
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertSame(200, $listed['headers']['status-code']);
|
||||
|
||||
$create = $this->client->call(
|
||||
Client::METHOD_POST,
|
||||
'/reports',
|
||||
[
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $userKey,
|
||||
],
|
||||
[
|
||||
'reportId' => ID::unique(),
|
||||
'type' => 'audit',
|
||||
'title' => 'Read-only check',
|
||||
'targetType' => 'sites',
|
||||
'target' => 'home',
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertSame(404, $create['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
@@ -171,8 +171,8 @@ trait ProxyBase
|
||||
|
||||
$siteId = $this->setupSite()['siteId'];
|
||||
|
||||
$ruleId = $this->setupRedirectRule($domain, 'https://jsonplaceholder.typicode.com/todos/1', 301, 'site', $siteId);
|
||||
$this->assertNotEmpty($ruleId);
|
||||
$ruleId301 = $this->setupRedirectRule($domain, 'https://jsonplaceholder.typicode.com/todos/1', 301, 'site', $siteId);
|
||||
$this->assertNotEmpty($ruleId301);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/todos/1');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
@@ -187,8 +187,8 @@ trait ProxyBase
|
||||
$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);
|
||||
$ruleId307 = $this->setupRedirectRule($domain, 'https://jsonplaceholder.typicode.com/todos/1', 307, 'site', $siteId);
|
||||
$this->assertNotEmpty($ruleId307);
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://appwrite.test');
|
||||
@@ -209,7 +209,8 @@ trait ProxyBase
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertEquals(2, $rules['body']['total']);
|
||||
|
||||
$this->cleanupRule($ruleId);
|
||||
$this->cleanupRule($ruleId301);
|
||||
$this->cleanupRule($ruleId307);
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Advisor\Validator;
|
||||
|
||||
use Appwrite\Advisor\Validator\CTAs;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class CTAsTest extends TestCase
|
||||
{
|
||||
public function testRejectsNonArray(): void
|
||||
{
|
||||
$validator = new CTAs();
|
||||
|
||||
$this->assertFalse($validator->isValid('not-an-array'));
|
||||
$this->assertFalse($validator->isValid(42));
|
||||
$this->assertFalse($validator->isValid(null));
|
||||
}
|
||||
|
||||
public function testAcceptsEmptyArray(): void
|
||||
{
|
||||
$validator = new CTAs();
|
||||
|
||||
$this->assertTrue($validator->isValid([]));
|
||||
}
|
||||
|
||||
public function testAcceptsCompleteEntry(): void
|
||||
{
|
||||
$validator = new CTAs();
|
||||
|
||||
$this->assertTrue($validator->isValid([[
|
||||
'label' => 'Create missing index',
|
||||
'service' => 'tablesDB',
|
||||
'method' => 'createIndex',
|
||||
'params' => [
|
||||
'databaseId' => 'main',
|
||||
'tableId' => 'orders',
|
||||
],
|
||||
]]));
|
||||
}
|
||||
|
||||
public function testAcceptsEntryWithoutParams(): void
|
||||
{
|
||||
$validator = new CTAs();
|
||||
|
||||
$this->assertTrue($validator->isValid([[
|
||||
'label' => 'Create missing index',
|
||||
'service' => 'tablesDB',
|
||||
'method' => 'createIndex',
|
||||
]]));
|
||||
}
|
||||
|
||||
public function testRejectsEntryMissingRequiredKeys(): void
|
||||
{
|
||||
$validator = new CTAs();
|
||||
|
||||
$this->assertFalse($validator->isValid([['label' => 'x']]));
|
||||
$this->assertFalse($validator->isValid([['label' => 'x', 'service' => 'tablesDB']]));
|
||||
$this->assertFalse($validator->isValid([['label' => 'x', 'method' => 'createIndex']]));
|
||||
}
|
||||
|
||||
public function testRejectsEntryWithEmptyStrings(): void
|
||||
{
|
||||
$validator = new CTAs();
|
||||
|
||||
$this->assertFalse($validator->isValid([[
|
||||
'label' => '',
|
||||
'service' => 'tablesDB',
|
||||
'method' => 'createIndex',
|
||||
]]));
|
||||
}
|
||||
|
||||
public function testRejectsEntryWithNonStringFields(): void
|
||||
{
|
||||
$validator = new CTAs();
|
||||
|
||||
$this->assertFalse($validator->isValid([[
|
||||
'label' => 123,
|
||||
'service' => 'tablesDB',
|
||||
'method' => 'createIndex',
|
||||
]]));
|
||||
}
|
||||
|
||||
public function testRejectsEntryWithScalarParams(): void
|
||||
{
|
||||
$validator = new CTAs();
|
||||
|
||||
$this->assertFalse($validator->isValid([[
|
||||
'label' => 'Create missing index',
|
||||
'service' => 'tablesDB',
|
||||
'method' => 'createIndex',
|
||||
'params' => 'not-a-map',
|
||||
]]));
|
||||
}
|
||||
|
||||
public function testReportsArrayType(): void
|
||||
{
|
||||
$validator = new CTAs();
|
||||
|
||||
$this->assertTrue($validator->isArray());
|
||||
$this->assertSame($validator::TYPE_ARRAY, $validator->getType());
|
||||
}
|
||||
|
||||
public function testRejectsMoreThanMaxCount(): void
|
||||
{
|
||||
$validator = new CTAs(maxCount: 3);
|
||||
|
||||
$entries = [];
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$entries[] = [
|
||||
'label' => 'Label ' . $i,
|
||||
'service' => 'tablesDB',
|
||||
'method' => 'createIndex',
|
||||
];
|
||||
}
|
||||
|
||||
$this->assertFalse($validator->isValid($entries));
|
||||
$this->assertStringContainsString('maximum of 3', $validator->getDescription());
|
||||
}
|
||||
|
||||
public function testAcceptsExactlyMaxCount(): void
|
||||
{
|
||||
$validator = new CTAs(maxCount: 3);
|
||||
|
||||
$entries = [];
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$entries[] = [
|
||||
'label' => 'Label ' . $i,
|
||||
'service' => 'tablesDB',
|
||||
'method' => 'createIndex',
|
||||
];
|
||||
}
|
||||
|
||||
$this->assertTrue($validator->isValid($entries));
|
||||
}
|
||||
|
||||
public function testAcceptsObjectParams(): void
|
||||
{
|
||||
$validator = new CTAs();
|
||||
|
||||
$entry = [
|
||||
'label' => 'Create missing index',
|
||||
'service' => 'tablesDB',
|
||||
'method' => 'createIndex',
|
||||
'params' => new \stdClass(),
|
||||
];
|
||||
|
||||
$this->assertTrue($validator->isValid([$entry]));
|
||||
}
|
||||
|
||||
public function testRejectsEntryWithEmptyService(): void
|
||||
{
|
||||
$validator = new CTAs();
|
||||
|
||||
$this->assertFalse($validator->isValid([[
|
||||
'label' => 'Create missing index',
|
||||
'service' => '',
|
||||
'method' => 'createIndex',
|
||||
]]));
|
||||
}
|
||||
|
||||
public function testRejectsEntryWithEmptyMethod(): void
|
||||
{
|
||||
$validator = new CTAs();
|
||||
|
||||
$this->assertFalse($validator->isValid([[
|
||||
'label' => 'Create missing index',
|
||||
'service' => 'tablesDB',
|
||||
'method' => '',
|
||||
]]));
|
||||
}
|
||||
|
||||
public function testRejectsUnknownService(): void
|
||||
{
|
||||
$validator = new CTAs();
|
||||
|
||||
$this->assertFalse($validator->isValid([[
|
||||
'label' => 'Create missing index',
|
||||
'service' => 'nonExistentService',
|
||||
'method' => 'createIndex',
|
||||
]]));
|
||||
$this->assertStringContainsString('service', $validator->getDescription());
|
||||
}
|
||||
|
||||
public function testRejectsUnknownMethod(): void
|
||||
{
|
||||
$validator = new CTAs();
|
||||
|
||||
$this->assertFalse($validator->isValid([[
|
||||
'label' => 'Create missing index',
|
||||
'service' => 'tablesDB',
|
||||
'method' => 'nonExistentMethod',
|
||||
]]));
|
||||
$this->assertStringContainsString('method', $validator->getDescription());
|
||||
}
|
||||
|
||||
public function testAcceptsCustomAllowedLists(): void
|
||||
{
|
||||
$validator = new CTAs(
|
||||
allowedServices: ['custom'],
|
||||
allowedMethods: ['doThing'],
|
||||
);
|
||||
|
||||
$this->assertTrue($validator->isValid([[
|
||||
'label' => 'Custom action',
|
||||
'service' => 'custom',
|
||||
'method' => 'doThing',
|
||||
]]));
|
||||
|
||||
$this->assertFalse($validator->isValid([[
|
||||
'label' => 'Custom action',
|
||||
'service' => 'tablesDB',
|
||||
'method' => 'doThing',
|
||||
]]));
|
||||
}
|
||||
|
||||
public function testDefaultMaxCountIsSixteen(): void
|
||||
{
|
||||
$validator = new CTAs();
|
||||
|
||||
$this->assertSame(CTAs::MAX_COUNT_DEFAULT, 16);
|
||||
|
||||
$entries = [];
|
||||
for ($i = 0; $i < 16; $i++) {
|
||||
$entries[] = [
|
||||
'label' => 'Label ' . $i,
|
||||
'service' => 'tablesDB',
|
||||
'method' => 'createIndex',
|
||||
];
|
||||
}
|
||||
|
||||
$this->assertTrue($validator->isValid($entries));
|
||||
|
||||
$entries[] = [
|
||||
'label' => 'Label 16',
|
||||
'service' => 'tablesDB',
|
||||
'method' => 'createIndex',
|
||||
];
|
||||
|
||||
$this->assertFalse($validator->isValid($entries));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user