Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions src/v4/Endpoint/Reports/ReportStatusResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,33 @@ public function __construct(
) {
}

/**
* True once Bring has finished generating the report.
*
* Bring's Reports API reports progress as `NOT_DONE` while the report is
* still building and `DONE` once it is ready to download (see the legacy
* {@see \Crakter\BringApi\Clients\Reports\StatusOfReport::checkStatus()},
* written against the live API). The earlier `COMPLETED`/`READY` check
* never matched a real response, so every polled report looked perpetually
* unfinished and callers' pending-report tables filled with rows that were
* never collected.
*/
public function isReady(): bool
{
return strtoupper($this->status) === 'COMPLETED' || strtoupper($this->status) === 'READY';
return strtoupper($this->status) === 'DONE';
}

/** @param array<mixed, mixed> $decoded */
public static function fromArray(array $decoded): self
{
// Bring returns the finished report's location as `xmlUrl` / `xlsUrl`
// (there is no `downloadUrl` field); fall back through them so the
// parsed URL isn't perpetually null on a real response.
$downloadUrl = $decoded['downloadUrl'] ?? $decoded['xmlUrl'] ?? $decoded['xlsUrl'] ?? null;

return new self(
status: (string) ($decoded['status'] ?? ''),
downloadUrl: isset($decoded['downloadUrl']) ? (string) $decoded['downloadUrl'] : null,
downloadUrl: null !== $downloadUrl ? (string) $downloadUrl : null,
raw: $decoded,
);
}
Expand Down
95 changes: 95 additions & 0 deletions tests/v4/Endpoint/Reports/ReportStatusResponseTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

namespace Bring\Api\Tests\Endpoint\Reports;

use Bring\Api\Endpoint\Reports\ReportStatusResponse;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

#[CoversClass(ReportStatusResponse::class)]
final class ReportStatusResponseTest extends TestCase
{
public function testDoneReportIsReady(): void
{
// Bring's real "finished" status is DONE (see the legacy
// StatusOfReport::checkStatus() written against the live API).
$response = ReportStatusResponse::fromArray(['status' => 'DONE']);

self::assertTrue($response->isReady());
}

public function testNotDoneReportIsNotReady(): void
{
// NOT_DONE is what Bring returns while the report is still generating.
$response = ReportStatusResponse::fromArray(['status' => 'NOT_DONE']);

self::assertFalse($response->isReady());
}

/**
* Regression: the previous implementation only recognised COMPLETED / READY —
* values Bring never returns — so every polled report looked perpetually
* unfinished and pending-report tables filled with rows that were never
* collected.
*/
#[DataProvider('provideStatusesThatBringNeverReadiesOn')]
public function testStatusesBringNeverReturnsAreNotReady(string $status): void
{
self::assertFalse(ReportStatusResponse::fromArray(['status' => $status])->isReady());
}

/** @return iterable<string, array{string}> */
public static function provideStatusesThatBringNeverReadiesOn(): iterable
{
yield 'completed (never emitted by Bring)' => ['COMPLETED'];
yield 'ready (never emitted by Bring)' => ['READY'];
yield 'empty' => [''];
}

public function testIsReadyIsCaseInsensitive(): void
{
self::assertTrue(ReportStatusResponse::fromArray(['status' => 'done'])->isReady());
}

public function testMissingStatusIsNotReady(): void
{
$response = ReportStatusResponse::fromArray([]);

self::assertSame('', $response->status);
self::assertFalse($response->isReady());
}

public function testDownloadUrlFallsBackToXmlUrl(): void
{
// The real status.json exposes the finished report as xmlUrl / xlsUrl,
// not downloadUrl, so the parsed URL must fall back through them.
$response = ReportStatusResponse::fromArray([
'status' => 'DONE',
'xmlUrl' => 'https://www.mybring.com/reports/api/report/abc.xml',
'xlsUrl' => 'https://www.mybring.com/reports/api/report/abc.xls',
]);

self::assertSame('https://www.mybring.com/reports/api/report/abc.xml', $response->downloadUrl);
}

public function testExplicitDownloadUrlWins(): void
{
$response = ReportStatusResponse::fromArray([
'status' => 'DONE',
'downloadUrl' => 'https://example.test/explicit',
'xmlUrl' => 'https://www.mybring.com/reports/api/report/abc.xml',
]);

self::assertSame('https://example.test/explicit', $response->downloadUrl);
}

public function testDownloadUrlNullWhenAbsent(): void
{
$response = ReportStatusResponse::fromArray(['status' => 'NOT_DONE']);

self::assertNull($response->downloadUrl);
}
}