feat: add Snipe-IT asset synchronization

This commit is contained in:
2026-08-04 16:15:09 +01:00
parent 604180b735
commit 077015f17a
37 changed files with 1302 additions and 26 deletions

View File

@@ -0,0 +1,72 @@
<?php
use App\Integrations\SnipeIt\Exceptions\SnipeItApiException;
use App\Integrations\SnipeIt\SnipeItClient;
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
beforeEach(function () {
config()->set('services.snipe_it', [
'url' => 'https://inventory.example.test',
'read_token' => 'read-token-for-tests',
'timeout' => 2,
'page_size' => 2,
'retries' => 1,
'retry_delay_ms' => 0,
]);
});
it('percorre todas as páginas de equipamentos com os cabeçalhos obrigatórios', function () {
Http::fakeSequence()
->push(['total' => 3, 'rows' => [snipeClientRow(1), snipeClientRow(2)]])
->push(['total' => 3, 'rows' => [snipeClientRow(3)]]);
$pages = iterator_to_array(app(SnipeItClient::class)->hardwarePages());
expect($pages)->toHaveCount(2)
->and($pages[0]->offset)->toBe(0)
->and($pages[1]->offset)->toBe(2)
->and($pages[1]->rows[0]['id'])->toBe(3);
Http::assertSentCount(2);
Http::assertSent(function (Request $request): bool {
parse_str((string) parse_url($request->url(), PHP_URL_QUERY), $query);
return $request->hasHeader('Authorization', 'Bearer read-token-for-tests')
&& $request->hasHeader('Accept', 'application/json')
&& ($query['limit'] ?? null) === '2'
&& in_array($query['offset'] ?? null, ['0', '2'], true);
});
});
it('repete falhas transitórias e recupera', function () {
config()->set('services.snipe_it.retries', 2);
Http::fakeSequence()
->push(['message' => 'temporarily unavailable'], 503)
->push(['total' => 1, 'rows' => [snipeClientRow(1)]]);
$page = app(SnipeItClient::class)->listHardwarePage();
expect($page->total)->toBe(1);
Http::assertSentCount(2);
});
it('recusa erros transportados num HTTP 200', function () {
Http::fake([
'*' => Http::response(['status' => 'error', 'messages' => 'Unauthorized'], 200),
]);
expect(fn () => app(SnipeItClient::class)->listHardwarePage())
->toThrow(SnipeItApiException::class, 'recusou a consulta');
});
/** @return array<string, mixed> */
function snipeClientRow(int $id): array
{
return [
'id' => $id,
'asset_tag' => "EQ-{$id}",
'name' => "Equipamento {$id}",
];
}