Files
api/services/nginx/app/tests/Unit/Invoicing/InvoiceCollectionBulkActionSafetyTest.php
T
Jeppe B c795df4aad Add invoice period review workflow (#336)
Improve the superuser invoice-period review API, stale-preview protection, queue visibility, review blockers, and e-conomic eligibility.
2026-08-02 19:20:50 +02:00

95 lines
3.8 KiB
PHP

<?php
app_require('classes/invoice_collection_bulk_action_service.php');
use classes\invoice_collection_bulk_action_service;
it('rejects stale bulk-action previews with a content digest and conflict response', function (): void {
$service = (string)file_get_contents(app_path('classes/invoice_collection_bulk_action_service.php'));
$route = (string)file_get_contents(app_path('routes/orderInvoicesRoute.php'));
expect($service)
->toContain('$preview[\'content_digest\'] = $this->previewContentDigest($preview);')
->toContain('hash_equals((string)($cached[\'preview\'][\'content_digest\'] ?? \'\'), $freshDigest)')
->toContain('throw new invoice_collection_bulk_action_conflict(')
->and($route)
->toContain('catch (invoice_collection_bulk_action_conflict $e)')
->toContain('$response->error($e->getMessage(), 409);');
});
it('changes the stale-preview digest when an export-relevant line changes', function (): void {
global $db;
$previousDb = $GLOBALS['db'] ?? null;
$hadDb = array_key_exists('db', $GLOBALS);
$db = new class {
public array $rows = [];
public function query(string $sql): object
{
expect($sql)
->toContain('oi.price')
->toContain('oi.quantity')
->toContain('p.economic_product_id')
->toContain('o.reference AS order_reference');
return new class($this->rows) {
private int $index = 0;
public function __construct(private readonly array $rows)
{
}
public function fetch_assoc(): ?array
{
return $this->rows[$this->index++] ?? null;
}
};
}
};
try {
$service = (new ReflectionClass(invoice_collection_bulk_action_service::class))->newInstanceWithoutConstructor();
$method = (new ReflectionClass(invoice_collection_bulk_action_service::class))
->getMethod('collectionExportContentDigest');
$db->rows = [[
'order_id' => 91,
'order_item_id' => 191,
'product_id' => 7,
'price' => 100,
'quantity' => 1,
'product_name' => 'Wash',
'economic_product_id' => 700,
]];
$before = $method->invoke($service, 41);
$db->rows[0]['price'] = 125;
$after = $method->invoke($service, 41);
expect($before)->toMatch('/^[a-f0-9]{64}$/')
->and($after)->toMatch('/^[a-f0-9]{64}$/')
->and($after)->not->toBe($before);
} finally {
if ($hadDb) {
$db = $previousDb;
} else {
unset($GLOBALS['db']);
}
}
});
it('enqueues bulk E-conomic transfers durably and deduplicates active jobs by global target', function (): void {
$service = (string)file_get_contents(app_path('classes/invoice_collection_bulk_action_service.php'));
$queue = (string)file_get_contents(app_path('classes/economic_transfer_queue.php'));
expect($service)
->toContain('self::ACTION_QUEUE_ECONOMIC => $this->applyQueueEconomic(')
->toContain('economic_transfer_queue::TYPE_COLLECTED_INVOICE_EXPORT')
->toContain("'queue_job_ids'")
->toContain('public static function assertCollectionCanQueueEconomic(')
->toContain('(new economic())->assertCustomerNumberIsNotDraft(')
->and($queue)
->toContain('Active work is unique by transfer type and business target across all requesting users.')
->toContain('CAST(JSON_UNQUOTE(JSON_EXTRACT(payload_json, \'$json_path\')) AS UNSIGNED) = ?')
->not->toContain("CAST(JSON_UNQUOTE(JSON_EXTRACT(payload_json, '$" . "json_path')) AS UNSIGNED) = ?\n AND created_by = ?");
});