mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
feat(insights): validate insight and project documents in CTA actions
Replace the no-op `fn () => true` validators (gated by skipValidation) on the `insight` and `project` params with dedicated InsightDocument and ProjectDocument validators. They check that the injected value is a non-empty Document with the expected attributes, so a misconfigured dispatcher or unbound injection fails fast with a useful message. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
69c637c72d
commit
4d560bdff2
@@ -7,6 +7,8 @@ use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Insights\CTA\Action;
|
||||
use Appwrite\Insights\Validator\CTAParams\DatabasesCreateIndex as DatabasesCreateIndexParams;
|
||||
use Appwrite\Insights\Validator\InsightDocument as InsightDocumentValidator;
|
||||
use Appwrite\Insights\Validator\ProjectDocument as ProjectDocumentValidator;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
@@ -28,8 +30,8 @@ class Create extends Action
|
||||
->desc('Create a database index from an insight CTA.')
|
||||
->label('scope', 'collections.write')
|
||||
->param('params', [], new DatabasesCreateIndexParams(), 'CTA params describing the index to create.')
|
||||
->param('insight', null, fn () => true, 'Parent insight document.', skipValidation: true)
|
||||
->param('project', null, fn () => true, 'Project document.', skipValidation: true)
|
||||
->param('insight', null, new InsightDocumentValidator(), 'Parent insight document.')
|
||||
->param('project', null, new ProjectDocumentValidator(), 'Project document.')
|
||||
->inject('dbForProject')
|
||||
->inject('getDatabasesDB')
|
||||
->inject('queueForDatabase')
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Insights\Validator;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Validator;
|
||||
|
||||
class InsightDocument extends Validator
|
||||
{
|
||||
protected string $message = 'Value must be a non-empty insight Document with `type` and `ctas` attributes.';
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_OBJECT;
|
||||
}
|
||||
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (!$value instanceof Document) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($value->isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$type = $value->getAttribute('type');
|
||||
if (!\is_string($type) || $type === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!\is_array($value->getAttribute('ctas', []))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Insights\Validator;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Validator;
|
||||
|
||||
class ProjectDocument extends Validator
|
||||
{
|
||||
protected string $message = 'Value must be a non-empty project Document with an `$id`.';
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_OBJECT;
|
||||
}
|
||||
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (!$value instanceof Document) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($value->isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $value->getId() !== '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Insights\Validator;
|
||||
|
||||
use Appwrite\Insights\Validator\InsightDocument;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class InsightDocumentTest extends TestCase
|
||||
{
|
||||
public function testAcceptsValidInsight(): void
|
||||
{
|
||||
$validator = new InsightDocument();
|
||||
$insight = new Document([
|
||||
'$id' => 'insight-1',
|
||||
'type' => 'databaseIndex',
|
||||
'ctas' => [],
|
||||
]);
|
||||
|
||||
$this->assertTrue($validator->isValid($insight));
|
||||
}
|
||||
|
||||
public function testRejectsNonDocument(): void
|
||||
{
|
||||
$validator = new InsightDocument();
|
||||
|
||||
$this->assertFalse($validator->isValid('not a document'));
|
||||
$this->assertFalse($validator->isValid(null));
|
||||
$this->assertFalse($validator->isValid(['type' => 'databaseIndex']));
|
||||
}
|
||||
|
||||
public function testRejectsEmptyDocument(): void
|
||||
{
|
||||
$validator = new InsightDocument();
|
||||
|
||||
$this->assertFalse($validator->isValid(new Document()));
|
||||
}
|
||||
|
||||
public function testRejectsMissingType(): void
|
||||
{
|
||||
$validator = new InsightDocument();
|
||||
$insight = new Document([
|
||||
'$id' => 'insight-1',
|
||||
'ctas' => [],
|
||||
]);
|
||||
|
||||
$this->assertFalse($validator->isValid($insight));
|
||||
}
|
||||
|
||||
public function testRejectsNonArrayCtas(): void
|
||||
{
|
||||
$validator = new InsightDocument();
|
||||
$insight = new Document([
|
||||
'$id' => 'insight-1',
|
||||
'type' => 'databaseIndex',
|
||||
'ctas' => 'not-an-array',
|
||||
]);
|
||||
|
||||
$this->assertFalse($validator->isValid($insight));
|
||||
}
|
||||
|
||||
public function testReportsObjectType(): void
|
||||
{
|
||||
$validator = new InsightDocument();
|
||||
|
||||
$this->assertSame('object', $validator->getType());
|
||||
$this->assertFalse($validator->isArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Insights\Validator;
|
||||
|
||||
use Appwrite\Insights\Validator\ProjectDocument;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class ProjectDocumentTest extends TestCase
|
||||
{
|
||||
public function testAcceptsValidProject(): void
|
||||
{
|
||||
$validator = new ProjectDocument();
|
||||
$project = new Document([
|
||||
'$id' => 'project-1',
|
||||
'name' => 'Test',
|
||||
]);
|
||||
|
||||
$this->assertTrue($validator->isValid($project));
|
||||
}
|
||||
|
||||
public function testRejectsNonDocument(): void
|
||||
{
|
||||
$validator = new ProjectDocument();
|
||||
|
||||
$this->assertFalse($validator->isValid('not a document'));
|
||||
$this->assertFalse($validator->isValid(null));
|
||||
$this->assertFalse($validator->isValid(['$id' => 'project-1']));
|
||||
}
|
||||
|
||||
public function testRejectsEmptyDocument(): void
|
||||
{
|
||||
$validator = new ProjectDocument();
|
||||
|
||||
$this->assertFalse($validator->isValid(new Document()));
|
||||
}
|
||||
|
||||
public function testRejectsMissingId(): void
|
||||
{
|
||||
$validator = new ProjectDocument();
|
||||
$project = new Document(['name' => 'Test']);
|
||||
|
||||
$this->assertFalse($validator->isValid($project));
|
||||
}
|
||||
|
||||
public function testReportsObjectType(): void
|
||||
{
|
||||
$validator = new ProjectDocument();
|
||||
|
||||
$this->assertSame('object', $validator->getType());
|
||||
$this->assertFalse($validator->isArray());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user