diff --git a/lib/Dossiq/CaseTypeContributionProvider.php b/lib/Dossiq/CaseTypeContributionProvider.php new file mode 100644 index 000000000..fa5a884d1 --- /dev/null +++ b/lib/Dossiq/CaseTypeContributionProvider.php @@ -0,0 +1,95 @@ + + * @copyright 2026 Conduction B.V. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * @link https://pipelinq.nl + * + * SPDX-License-Identifier: EUPL-1.2 + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * + * @spec openspec/specs/request-management/spec.md#requirement-request-status-lifecycle-mvp + */ + +declare(strict_types=1); + +namespace OCA\Pipelinq\Dossiq; + +/** + * Declares the case types pipelinq contributes to dossiq. + * + * Pure data: no I/O, no container, nothing that can fail. A provider that + * throws costs every other app its case types, so this one cannot. + * + * @spec openspec/specs/request-management/spec.md#requirement-request-status-lifecycle-mvp + */ +class CaseTypeContributionProvider { + + /** + * The case types pipelinq contributes. + * + * @return array> The declarations. + * + * @spec openspec/specs/request-management/spec.md#requirement-request-status-lifecycle-mvp + */ + public function getCaseTypes(): array { + return [ + [ + 'identifier' => 'pipelinq-ticket', + 'title' => 'Ticket', + 'description' => 'Customer contact handled by pipelinq: a request, a complaint or a logged interaction.', + 'register' => 'pipelinq', + 'schema' => 'ticket', + // The discriminator unify-ticket-supertype introduced. Kept as + // ONE case type with a subtype rather than three case types, + // because the storage is one schema and dossiq should not be + // told a different shape than the data has. + 'discriminator' => 'ticketType', + 'subtypes' => [ + [ + 'value' => 'request', + 'title' => 'Request', + ], + [ + 'value' => 'complaint', + 'title' => 'Complaint', + ], + [ + 'value' => 'interaction', + 'title' => 'Contact moment', + ], + ], + 'assigneeProperty' => 'assignee', + 'statusProperty' => 'status', + ], + ]; + }//end getCaseTypes() +}//end class diff --git a/tests/Unit/Dossiq/CaseTypeContributionProviderTest.php b/tests/Unit/Dossiq/CaseTypeContributionProviderTest.php new file mode 100644 index 000000000..d4dcf9ada --- /dev/null +++ b/tests/Unit/Dossiq/CaseTypeContributionProviderTest.php @@ -0,0 +1,141 @@ + + * @copyright 2026 Conduction B.V. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * @link https://pipelinq.nl + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + * + * @spec openspec/specs/request-management/spec.md#requirement-request-status-lifecycle-mvp + */ + +declare(strict_types=1); + +namespace OCA\Pipelinq\Tests\Unit\Dossiq; + +use OCA\Pipelinq\Dossiq\CaseTypeContributionProvider; +use PHPUnit\Framework\TestCase; +use ReflectionClass; + +/** + * Tests for CaseTypeContributionProvider. + */ +class CaseTypeContributionProviderTest extends TestCase { + + /** + * The provider under test. + * + * @var CaseTypeContributionProvider + */ + private CaseTypeContributionProvider $provider; + + /** + * Build the provider directly, with no container and no dossiq. + * + * @return void + */ + protected function setUp(): void { + $this->provider = new CaseTypeContributionProvider(); + }//end setUp() + + /** + * The class takes no constructor dependencies. + * + * This is what lets dossiq construct it through the server container while + * pipelinq stays installable alone. A dependency added here would fail no + * compiler; it would fail at discovery time, on someone else's instance. + * + * @return void + */ + public function testTakesNoConstructorDependencies(): void { + $constructor = (new ReflectionClass(CaseTypeContributionProvider::class))->getConstructor(); + + $this->assertTrue( + ($constructor === null || $constructor->getNumberOfParameters() === 0), + 'the provider must construct without arguments' + ); + }//end testTakesNoConstructorDependencies() + + /** + * The class imports nothing and implements nothing. + * + * @return void + */ + public function testIsPlain(): void { + $reflected = new ReflectionClass(CaseTypeContributionProvider::class); + + $this->assertSame([], $reflected->getInterfaceNames(), 'the provider must implement no interface'); + + $source = (string)file_get_contents((string)$reflected->getFileName()); + $this->assertSame( + 0, + preg_match_all('/^use\s+/m', $source), + 'the provider must import nothing: importing dossiq would break pipelinq without it' + ); + }//end testIsPlain() + + /** + * The probed method keeps its name. + * + * Dossiq narrows on `method_exists($provider, 'getCaseTypes')`. Renaming it + * makes the provider silently invisible: no error, no case types, and + * nothing in any log naming pipelinq. + * + * @return void + */ + public function testExposesTheProbedMethod(): void { + $this->assertTrue(method_exists($this->provider, 'getCaseTypes')); + }//end testExposesTheProbedMethod() + + /** + * Ticket is contributed with the fields the registry requires. + * + * @return void + */ + public function testContributesTicketWithIdentifierAndTitle(): void { + $types = $this->provider->getCaseTypes(); + + $this->assertCount(1, $types); + $this->assertSame('pipelinq-ticket', $types[0]['identifier']); + $this->assertNotSame('', trim((string)$types[0]['title'])); + $this->assertSame('ticket', $types[0]['schema']); + }//end testContributesTicketWithIdentifierAndTitle() + + /** + * The three kinds stay one case type with a discriminator. + * + * Declaring three case types would re-split, on dossiq's side, exactly what + * unify-ticket-supertype joined into one `ticket` schema. + * + * @return void + */ + public function testKeepsTheThreeKindsAsSubtypesOfOneCaseType(): void { + $types = $this->provider->getCaseTypes(); + + $this->assertSame('ticketType', $types[0]['discriminator']); + $this->assertSame( + ['request', 'complaint', 'interaction'], + array_column($types[0]['subtypes'], 'value') + ); + }//end testKeepsTheThreeKindsAsSubtypesOfOneCaseType() +}//end class