appwrite/tests/unit/Advisor/Validator/CTAsTest.php

242 lines
6.4 KiB
PHP
Raw Permalink Normal View History

<?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());
}
refactor(insights): metadata-only CTAs, platform DB, reports parent Address review feedback on PR #12194: - Pivot CTAs to pure descriptors (id/label/action/params). Drop the server-side execution layer: Action interface, registry, the databases.indexes.create CTA action, the params validator, the /v1/insights/:id/ctas/:id/executions endpoint, the InsightCTAExecution model, the INSIGHT_CTA_* errors, and the corresponding events. The console invokes the existing public API directly with the descriptor's action + params. - Restore Databases\Indexes\Action.php to its pre-CTA shape and inline the index-create body back into Create.php (the createIndex helper was added solely for CTA reuse). - Move insights collection from project DB to platform DB and add a parent reports collection alongside it. Insights carry projectId / projectInternalId for tenant scoping and an optional reportId for grouping. List endpoints filter by projectInternalId; Get/Update/ Delete also enforce project ownership before touching the document. - New Reports module with full CRUD (Create/Get/XList/Update/Delete), Report response model, Reports query validator, REPORT_NOT_FOUND / REPORT_ALREADY_EXISTS errors, reports.read / reports.write scopes, and reports.* event tree. Delete cascades to child insights. - Update.php now mutates the loaded document via setAttribute (instead of passing a partial new Document), reuses CTAsValidator (instead of the looser ArrayList<JSON> + isset check), and rejects duplicate CTA ids. - Create.php enforces unique CTA ids during normalization. - CTAsValidator gained a configurable maxCount (default 16) so the Create path matches the Update path and the DB column size, and oversized payloads return a clean 400. - Validator\Queries\Insights adds status and reportId to ALLOWED_ATTRIBUTES so dismissal / report workflows are filterable. - Realtime channel parser guards $parts[1] for both insights and reports event names. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 01:46:07 +00:00
public function testRejectsMoreThanMaxCount(): void
{
$validator = new CTAs(maxCount: 3);
$entries = [];
for ($i = 0; $i < 4; $i++) {
$entries[] = [
'label' => 'Label ' . $i,
'service' => 'tablesDB',
'method' => 'createIndex',
refactor(insights): metadata-only CTAs, platform DB, reports parent Address review feedback on PR #12194: - Pivot CTAs to pure descriptors (id/label/action/params). Drop the server-side execution layer: Action interface, registry, the databases.indexes.create CTA action, the params validator, the /v1/insights/:id/ctas/:id/executions endpoint, the InsightCTAExecution model, the INSIGHT_CTA_* errors, and the corresponding events. The console invokes the existing public API directly with the descriptor's action + params. - Restore Databases\Indexes\Action.php to its pre-CTA shape and inline the index-create body back into Create.php (the createIndex helper was added solely for CTA reuse). - Move insights collection from project DB to platform DB and add a parent reports collection alongside it. Insights carry projectId / projectInternalId for tenant scoping and an optional reportId for grouping. List endpoints filter by projectInternalId; Get/Update/ Delete also enforce project ownership before touching the document. - New Reports module with full CRUD (Create/Get/XList/Update/Delete), Report response model, Reports query validator, REPORT_NOT_FOUND / REPORT_ALREADY_EXISTS errors, reports.read / reports.write scopes, and reports.* event tree. Delete cascades to child insights. - Update.php now mutates the loaded document via setAttribute (instead of passing a partial new Document), reuses CTAsValidator (instead of the looser ArrayList<JSON> + isset check), and rejects duplicate CTA ids. - Create.php enforces unique CTA ids during normalization. - CTAsValidator gained a configurable maxCount (default 16) so the Create path matches the Update path and the DB column size, and oversized payloads return a clean 400. - Validator\Queries\Insights adds status and reportId to ALLOWED_ATTRIBUTES so dismissal / report workflows are filterable. - Realtime channel parser guards $parts[1] for both insights and reports event names. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 01:46:07 +00:00
];
}
$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',
refactor(insights): metadata-only CTAs, platform DB, reports parent Address review feedback on PR #12194: - Pivot CTAs to pure descriptors (id/label/action/params). Drop the server-side execution layer: Action interface, registry, the databases.indexes.create CTA action, the params validator, the /v1/insights/:id/ctas/:id/executions endpoint, the InsightCTAExecution model, the INSIGHT_CTA_* errors, and the corresponding events. The console invokes the existing public API directly with the descriptor's action + params. - Restore Databases\Indexes\Action.php to its pre-CTA shape and inline the index-create body back into Create.php (the createIndex helper was added solely for CTA reuse). - Move insights collection from project DB to platform DB and add a parent reports collection alongside it. Insights carry projectId / projectInternalId for tenant scoping and an optional reportId for grouping. List endpoints filter by projectInternalId; Get/Update/ Delete also enforce project ownership before touching the document. - New Reports module with full CRUD (Create/Get/XList/Update/Delete), Report response model, Reports query validator, REPORT_NOT_FOUND / REPORT_ALREADY_EXISTS errors, reports.read / reports.write scopes, and reports.* event tree. Delete cascades to child insights. - Update.php now mutates the loaded document via setAttribute (instead of passing a partial new Document), reuses CTAsValidator (instead of the looser ArrayList<JSON> + isset check), and rejects duplicate CTA ids. - Create.php enforces unique CTA ids during normalization. - CTAsValidator gained a configurable maxCount (default 16) so the Create path matches the Update path and the DB column size, and oversized payloads return a clean 400. - Validator\Queries\Insights adds status and reportId to ALLOWED_ATTRIBUTES so dismissal / report workflows are filterable. - Realtime channel parser guards $parts[1] for both insights and reports event names. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 01:46:07 +00:00
];
}
$this->assertTrue($validator->isValid($entries));
}
test(insights): full e2e + per-engine CTA action mapping - Restructure InsightsBase trait with explicit helper methods (createInsight/getInsight/listInsights/updateInsight/deleteInsight, createReport/getReport/listReports/updateReport/deleteReport, plus sampleInsight/sampleCTA factories) — same shape ProxyBase uses. - Add coverage for: report CRUD + duplicate-id rejection, invalid type rejection, list filtering by all allowed attributes, cursor pagination + missing-cursor, update preserving untouched fields, CTA validation edge cases (duplicate ids, empty fields, count > 16), dismissal round-trip + status filter, report cascade delete, unauthorized access (no server key), empty-result list. - Engine-specific insight types (tablesDBIndex, documentsDBIndex, vectorsDBIndex, plus the legacy databaseIndex) so the CTA's `action` can map to the matching public API: databases.createIndex, tablesDB.createIndex, documentsDB.createIndex, vectorsDB.createIndex. dataProvider drives the engine matrix and asserts the right action lands in the persisted CTA. Constants for each action name live in app/init/constants.php. - InsightCTA model docs spell out which action belongs to which engine and that the params keys differ between APIs (tableId/columns for tablesDB vs collectionId/attributes for the legacy / DocumentsDB / VectorsDB APIs). - Insight model `type` description now lists every engine variant. - CTAsTest gains coverage for object-shaped params, empty-action and empty-label rejection, and the default 16-entry cap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 04:25:49 +00:00
public function testAcceptsObjectParams(): void
{
$validator = new CTAs();
$entry = [
'label' => 'Create missing index',
'service' => 'tablesDB',
'method' => 'createIndex',
test(insights): full e2e + per-engine CTA action mapping - Restructure InsightsBase trait with explicit helper methods (createInsight/getInsight/listInsights/updateInsight/deleteInsight, createReport/getReport/listReports/updateReport/deleteReport, plus sampleInsight/sampleCTA factories) — same shape ProxyBase uses. - Add coverage for: report CRUD + duplicate-id rejection, invalid type rejection, list filtering by all allowed attributes, cursor pagination + missing-cursor, update preserving untouched fields, CTA validation edge cases (duplicate ids, empty fields, count > 16), dismissal round-trip + status filter, report cascade delete, unauthorized access (no server key), empty-result list. - Engine-specific insight types (tablesDBIndex, documentsDBIndex, vectorsDBIndex, plus the legacy databaseIndex) so the CTA's `action` can map to the matching public API: databases.createIndex, tablesDB.createIndex, documentsDB.createIndex, vectorsDB.createIndex. dataProvider drives the engine matrix and asserts the right action lands in the persisted CTA. Constants for each action name live in app/init/constants.php. - InsightCTA model docs spell out which action belongs to which engine and that the params keys differ between APIs (tableId/columns for tablesDB vs collectionId/attributes for the legacy / DocumentsDB / VectorsDB APIs). - Insight model `type` description now lists every engine variant. - CTAsTest gains coverage for object-shaped params, empty-action and empty-label rejection, and the default 16-entry cap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 04:25:49 +00:00
'params' => new \stdClass(),
];
$this->assertTrue($validator->isValid([$entry]));
}
public function testRejectsEntryWithEmptyService(): void
test(insights): full e2e + per-engine CTA action mapping - Restructure InsightsBase trait with explicit helper methods (createInsight/getInsight/listInsights/updateInsight/deleteInsight, createReport/getReport/listReports/updateReport/deleteReport, plus sampleInsight/sampleCTA factories) — same shape ProxyBase uses. - Add coverage for: report CRUD + duplicate-id rejection, invalid type rejection, list filtering by all allowed attributes, cursor pagination + missing-cursor, update preserving untouched fields, CTA validation edge cases (duplicate ids, empty fields, count > 16), dismissal round-trip + status filter, report cascade delete, unauthorized access (no server key), empty-result list. - Engine-specific insight types (tablesDBIndex, documentsDBIndex, vectorsDBIndex, plus the legacy databaseIndex) so the CTA's `action` can map to the matching public API: databases.createIndex, tablesDB.createIndex, documentsDB.createIndex, vectorsDB.createIndex. dataProvider drives the engine matrix and asserts the right action lands in the persisted CTA. Constants for each action name live in app/init/constants.php. - InsightCTA model docs spell out which action belongs to which engine and that the params keys differ between APIs (tableId/columns for tablesDB vs collectionId/attributes for the legacy / DocumentsDB / VectorsDB APIs). - Insight model `type` description now lists every engine variant. - CTAsTest gains coverage for object-shaped params, empty-action and empty-label rejection, and the default 16-entry cap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 04:25:49 +00:00
{
$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' => '',
test(insights): full e2e + per-engine CTA action mapping - Restructure InsightsBase trait with explicit helper methods (createInsight/getInsight/listInsights/updateInsight/deleteInsight, createReport/getReport/listReports/updateReport/deleteReport, plus sampleInsight/sampleCTA factories) — same shape ProxyBase uses. - Add coverage for: report CRUD + duplicate-id rejection, invalid type rejection, list filtering by all allowed attributes, cursor pagination + missing-cursor, update preserving untouched fields, CTA validation edge cases (duplicate ids, empty fields, count > 16), dismissal round-trip + status filter, report cascade delete, unauthorized access (no server key), empty-result list. - Engine-specific insight types (tablesDBIndex, documentsDBIndex, vectorsDBIndex, plus the legacy databaseIndex) so the CTA's `action` can map to the matching public API: databases.createIndex, tablesDB.createIndex, documentsDB.createIndex, vectorsDB.createIndex. dataProvider drives the engine matrix and asserts the right action lands in the persisted CTA. Constants for each action name live in app/init/constants.php. - InsightCTA model docs spell out which action belongs to which engine and that the params keys differ between APIs (tableId/columns for tablesDB vs collectionId/attributes for the legacy / DocumentsDB / VectorsDB APIs). - Insight model `type` description now lists every engine variant. - CTAsTest gains coverage for object-shaped params, empty-action and empty-label rejection, and the default 16-entry cap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 04:25:49 +00:00
]]));
}
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',
]]));
}
test(insights): full e2e + per-engine CTA action mapping - Restructure InsightsBase trait with explicit helper methods (createInsight/getInsight/listInsights/updateInsight/deleteInsight, createReport/getReport/listReports/updateReport/deleteReport, plus sampleInsight/sampleCTA factories) — same shape ProxyBase uses. - Add coverage for: report CRUD + duplicate-id rejection, invalid type rejection, list filtering by all allowed attributes, cursor pagination + missing-cursor, update preserving untouched fields, CTA validation edge cases (duplicate ids, empty fields, count > 16), dismissal round-trip + status filter, report cascade delete, unauthorized access (no server key), empty-result list. - Engine-specific insight types (tablesDBIndex, documentsDBIndex, vectorsDBIndex, plus the legacy databaseIndex) so the CTA's `action` can map to the matching public API: databases.createIndex, tablesDB.createIndex, documentsDB.createIndex, vectorsDB.createIndex. dataProvider drives the engine matrix and asserts the right action lands in the persisted CTA. Constants for each action name live in app/init/constants.php. - InsightCTA model docs spell out which action belongs to which engine and that the params keys differ between APIs (tableId/columns for tablesDB vs collectionId/attributes for the legacy / DocumentsDB / VectorsDB APIs). - Insight model `type` description now lists every engine variant. - CTAsTest gains coverage for object-shaped params, empty-action and empty-label rejection, and the default 16-entry cap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 04:25:49 +00:00
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',
test(insights): full e2e + per-engine CTA action mapping - Restructure InsightsBase trait with explicit helper methods (createInsight/getInsight/listInsights/updateInsight/deleteInsight, createReport/getReport/listReports/updateReport/deleteReport, plus sampleInsight/sampleCTA factories) — same shape ProxyBase uses. - Add coverage for: report CRUD + duplicate-id rejection, invalid type rejection, list filtering by all allowed attributes, cursor pagination + missing-cursor, update preserving untouched fields, CTA validation edge cases (duplicate ids, empty fields, count > 16), dismissal round-trip + status filter, report cascade delete, unauthorized access (no server key), empty-result list. - Engine-specific insight types (tablesDBIndex, documentsDBIndex, vectorsDBIndex, plus the legacy databaseIndex) so the CTA's `action` can map to the matching public API: databases.createIndex, tablesDB.createIndex, documentsDB.createIndex, vectorsDB.createIndex. dataProvider drives the engine matrix and asserts the right action lands in the persisted CTA. Constants for each action name live in app/init/constants.php. - InsightCTA model docs spell out which action belongs to which engine and that the params keys differ between APIs (tableId/columns for tablesDB vs collectionId/attributes for the legacy / DocumentsDB / VectorsDB APIs). - Insight model `type` description now lists every engine variant. - CTAsTest gains coverage for object-shaped params, empty-action and empty-label rejection, and the default 16-entry cap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 04:25:49 +00:00
];
}
$this->assertTrue($validator->isValid($entries));
$entries[] = [
'label' => 'Label 16',
'service' => 'tablesDB',
'method' => 'createIndex',
test(insights): full e2e + per-engine CTA action mapping - Restructure InsightsBase trait with explicit helper methods (createInsight/getInsight/listInsights/updateInsight/deleteInsight, createReport/getReport/listReports/updateReport/deleteReport, plus sampleInsight/sampleCTA factories) — same shape ProxyBase uses. - Add coverage for: report CRUD + duplicate-id rejection, invalid type rejection, list filtering by all allowed attributes, cursor pagination + missing-cursor, update preserving untouched fields, CTA validation edge cases (duplicate ids, empty fields, count > 16), dismissal round-trip + status filter, report cascade delete, unauthorized access (no server key), empty-result list. - Engine-specific insight types (tablesDBIndex, documentsDBIndex, vectorsDBIndex, plus the legacy databaseIndex) so the CTA's `action` can map to the matching public API: databases.createIndex, tablesDB.createIndex, documentsDB.createIndex, vectorsDB.createIndex. dataProvider drives the engine matrix and asserts the right action lands in the persisted CTA. Constants for each action name live in app/init/constants.php. - InsightCTA model docs spell out which action belongs to which engine and that the params keys differ between APIs (tableId/columns for tablesDB vs collectionId/attributes for the legacy / DocumentsDB / VectorsDB APIs). - Insight model `type` description now lists every engine variant. - CTAsTest gains coverage for object-shaped params, empty-action and empty-label rejection, and the default 16-entry cap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 04:25:49 +00:00
];
$this->assertFalse($validator->isValid($entries));
}
}