[ [ 'domainIdentifier' => '100', 'title' => 'Gottesdienst Sonntag', 'startDate' => '2026-03-08T10:00:00+00:00', 'note' => 'Probe', ], ], 'meta' => [ 'pagination' => [ 'lastPage' => 1, ], ], ]; $songPayload = [ 'data' => [ 'songId' => '1', 'name' => 'Way Maker', 'ccli' => '7115744', 'arrangements' => [ ['id' => '11', 'name' => 'Normal', 'isDefault' => true], ], 'lyrics' => [ 'type' => 'text', 'cclid' => '7115744', 'lyricParts' => [ ['key' => 'v1', 'text' => "Du bist hier"], ], ], ], ]; $mockClient = new CtsApiSpikeMockClient([ '/api/events' => [new Response(200, [], json_encode($eventsPayload, JSON_THROW_ON_ERROR))], '/api/songs/1' => [new Response(200, [], json_encode($songPayload, JSON_THROW_ON_ERROR))], ]); $result = \App\Cts\CtsApiSpikeSync::run( apiUrl: 'https://example.church.tools', apiToken: 'token-abc', fromDate: '2026-03-01', songId: 1, client: $mockClient, ); expect($result['events']['count'])->toBe(1) ->and($result['events']['first']['title'])->toBe('Gottesdienst Sonntag') ->and($result['song']['hasCcli'])->toBeTrue() ->and($result['song']['hasLyrics'])->toBeTrue() ->and($result['song']['arrangements_count'])->toBe(1) ->and($result['auth']['method'])->toBe('setApiKey'); $eventsCall = $mockClient->firstCallFor('GET', '/api/events'); expect($eventsCall['options']['query']['from'])->toBe('2026-03-01') ->and($eventsCall['options']['query']['page'])->toBe(1) ->and(CTConfig::getRequestConfig()['query']['login_token'])->toBe('token-abc'); }); it('returns auth blocker when API token is missing', function () { $result = \App\Cts\CtsApiSpikeSync::run( apiUrl: 'https://example.church.tools', apiToken: '', fromDate: '2026-03-01', songId: 1, client: new CtsApiSpikeMockClient([]), ); expect($result['auth']['ok'])->toBeFalse() ->and($result['auth']['blocker'])->toContain('CTS_API_TOKEN'); }); final class CtsApiSpikeMockClient extends CTClient { private array $responsesByUri; private array $calls = []; public function __construct(array $responsesByUri) { $this->responsesByUri = $responsesByUri; } public function get($uri, array $options = []): ResponseInterface { $this->calls[] = [ 'method' => 'GET', 'uri' => $uri, 'options' => $options, ]; if (! isset($this->responsesByUri[$uri]) || $this->responsesByUri[$uri] === []) { return new Response(404, [], json_encode(['data' => []], JSON_THROW_ON_ERROR)); } return array_shift($this->responsesByUri[$uri]); } public function firstCallFor(string $method, string $uri): array { foreach ($this->calls as $call) { if ($call['method'] === $method && $call['uri'] === $uri) { return $call; } } throw new RuntimeException("No call recorded for {$method} {$uri}"); } }