Skip to content

Add test infrastructure + cover list-provider default sort #4

Description

@schmunk42

Context

feature/default-time-sort (commit 278a523) makes the essential list
resources default to their native time field descending (newest first)
when the client sends no explicit sort. Flowable has no updated_at, so each
resource maps to its own field:

Resource Endpoint Default sort
Task runtime/tasks createTime
Deployment repository/deployments deployTime
Process Instance runtime/process-instances startTime
Historic Process Instance history/historic-process-instances startTime
Historic Task history/historic-task-instances endTime

An explicit client sort/order still wins; resources passing no default stay
sort-free (backwards compatible). Logic lives in
AbstractFlowableProvider::listQuery() (optional $defaultSort/$defaultOrder).

Problem

The bundle has no test infrastructure yet — require-dev lists
phpunit/phpunit: ^12.0, but there is no autoload-dev, no PHPUnit config, no
tests/ directory and no CI. The new default-sort behaviour is therefore
unverified inside this repo. (It was verified end-to-end in the consuming app
mlr/za7-fogu-api against a live engine, but that test should live here, not in
the app.)

Task

  • Add autoload-dev PSR-4 Dmstr\Flowable\Tests\tests/ to composer.json
  • Add a phpunit.dist.xml
  • Add tests/State/DefaultSortTest.php (see below) covering the default-sort,
    the client override, order sanitisation and the sort-free fallback
  • Wire CI (GitHub Actions) to run the suite on push / PR
  • Grow coverage from here for the other providers/state processors

Ready-to-drop-in test

<?php

declare(strict_types=1);

namespace Dmstr\Flowable\Tests\State;

use Dmstr\Flowable\Client\FlowableClientLocator;
use Dmstr\Flowable\State\AbstractFlowableProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Pins the default-sort behaviour of the Flowable list providers
 * (AbstractFlowableProvider::listQuery). Flowable has no `updated_at`, so each
 * essential resource defaults to its own time field (newest first): tasks →
 * createTime, deployments → deployTime, (historic) process instances →
 * startTime, historic tasks → endTime. The default must be overridable by an
 * explicit client `sort`/`order`, and resources without a default must stay
 * sort-free (backwards compatible).
 */
final class DefaultSortTest extends TestCase
{
    public function testDefaultSortAppliedDescendingWhenClientRequestsNone(): void
    {
        $query = $this->build(['createTime'], [], 'createTime');

        self::assertSame('createTime', $query['sort']);
        self::assertSame('desc', $query['order']);
    }

    public function testExplicitClientSortOverridesDefault(): void
    {
        $query = $this->build([], ['sort' => 'name', 'order' => 'asc'], 'createTime');

        self::assertSame('name', $query['sort']);
        self::assertSame('asc', $query['order']);
    }

    public function testExplicitOrderRespectedWithDefaultSort(): void
    {
        $query = $this->build([], ['order' => 'asc'], 'startTime');

        self::assertSame('startTime', $query['sort']);
        self::assertSame('asc', $query['order']);
    }

    public function testInvalidOrderFallsBackToDescending(): void
    {
        $query = $this->build([], ['sort' => 'startTime', 'order' => 'sideways'], null);

        self::assertSame('startTime', $query['sort']);
        self::assertSame('desc', $query['order']);
    }

    public function testNoDefaultAndNoClientSortLeavesQuerySortFree(): void
    {
        $query = $this->build([], [], null);

        self::assertArrayNotHasKey('sort', $query);
        self::assertArrayNotHasKey('order', $query);
        self::assertSame(0, $query['start']);
        self::assertSame(30, $query['size']);
    }

    /**
     * @param list<string> $whitelist
     * @param array<string,scalar> $requestQuery
     * @return array<string,scalar>
     */
    private function build(array $whitelist, array $requestQuery, ?string $defaultSort): array
    {
        $stack = new RequestStack();
        $stack->push(new Request($requestQuery));

        // listQuery never touches the locator — a bare instance is enough to
        // satisfy the (final) locator dependency of the constructor.
        $locator = (new \ReflectionClass(FlowableClientLocator::class))->newInstanceWithoutConstructor();

        $provider = new class($locator, $stack) extends AbstractFlowableProvider {
            /**
             * @param list<string> $whitelist
             * @return array<string,scalar>
             */
            public function expose(array $whitelist, ?string $defaultSort): array
            {
                return $this->listQuery($whitelist, $defaultSort);
            }
        };

        return $provider->expose($whitelist, $defaultSort);
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions