89 lines
4.9 KiB
PHP
89 lines
4.9 KiB
PHP
<?php
|
|
|
|
use App\Services\OperationalHealth;
|
|
use App\Services\ReceiptDocumentHealth;
|
|
use App\Services\ReplenishmentHeartbeat;
|
|
use App\Services\ReplenishmentQueue;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\File;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
|
|
require_once __DIR__.'/../Support/ReceiptFixture.php';
|
|
|
|
beforeEach(function () {
|
|
$this->f = receiptFixture();
|
|
auth()->logout();
|
|
$this->originalStorage = app()->storagePath();
|
|
$this->temporaryStorage = $this->originalStorage.'/framework/testing/operations-'.Str::uuid();
|
|
File::ensureDirectoryExists($this->temporaryStorage.'/framework');
|
|
File::ensureDirectoryExists($this->temporaryStorage.'/documents');
|
|
app()->useStoragePath($this->temporaryStorage);
|
|
Storage::set('receipt_documents', Storage::build(['driver' => 'local', 'root' => $this->temporaryStorage.'/documents', 'throw' => true]));
|
|
app(ReplenishmentHeartbeat::class)->record(now()->toIso8601String(), 0);
|
|
});
|
|
|
|
afterEach(function () {
|
|
app()->useStoragePath($this->originalStorage);
|
|
File::deleteDirectory($this->temporaryStorage);
|
|
});
|
|
|
|
test('operational health integrates native inspections without changing facts or heartbeat', function () {
|
|
$before = [];
|
|
foreach (['requisitions', 'stock_movements', 'audit_logs', 'replenishment_work', 'receipt_documents', 'approval_steps'] as $table) {
|
|
$before[$table] = DB::table($table)->orderBy('id')->get()->toArray();
|
|
}
|
|
$heartbeat = File::get(storage_path('framework/replenishment-command-status.json'));
|
|
expect(Artisan::call('sgc:operations-check', ['--json' => true]))->toBe(0);
|
|
$output = Artisan::output();
|
|
$result = json_decode($output, true, 32, JSON_THROW_ON_ERROR);
|
|
expect($result['schema_version'])->toBe(1)->and($result['ok'])->toBeTrue()->and($result['complete'])->toBeTrue()
|
|
->and($result['checks']['replenishment']['result']['work']['total'])->toBe(0)
|
|
->and($result['checks']['receipt_documents']['result']['documents_checked'])->toBe(0);
|
|
expect($output)->not->toContain($this->temporaryStorage, 'tenant_id', 'password');
|
|
expect(File::get(storage_path('framework/replenishment-command-status.json')))->toBe($heartbeat);
|
|
foreach ($before as $table => $rows) {
|
|
expect(DB::table($table)->orderBy('id')->get()->toArray())->toEqual($rows);
|
|
}
|
|
});
|
|
|
|
test('operational health continues the other inspection after a private failure', function () {
|
|
$this->mock(ReplenishmentQueue::class)->shouldReceive('health')->once()->andThrow(new RuntimeException('PRIVATE database password and path'));
|
|
expect(Artisan::call('sgc:operations-check', ['--json' => true]))->toBe(1);
|
|
$output = Artisan::output();
|
|
$result = json_decode($output, true, 32, JSON_THROW_ON_ERROR);
|
|
expect($result['ok'])->toBeFalse()->and($result['complete'])->toBeFalse()
|
|
->and($result['checks']['replenishment']['status'])->toBe('unavailable')
|
|
->and($result['checks']['receipt_documents']['status'])->toBe('passed');
|
|
expect($output)->not->toContain('PRIVATE', 'password', $this->temporaryStorage);
|
|
});
|
|
|
|
test('operational health reports incomplete document coverage and preserves files', function () {
|
|
Storage::disk('receipt_documents')->put('one.bin', 'one');
|
|
Storage::disk('receipt_documents')->put('two.bin', 'two');
|
|
expect(Artisan::call('sgc:operations-check', ['--json' => true, '--document-limit' => '1']))->toBe(1);
|
|
$result = json_decode(Artisan::output(), true, 32, JSON_THROW_ON_ERROR);
|
|
expect($result['complete'])->toBeFalse()->and($result['checks']['receipt_documents']['status'])->toBe('incomplete')
|
|
->and($result['checks']['receipt_documents']['result']['reasons'])->toContain('limit_reached');
|
|
expect(Storage::disk('receipt_documents')->allFiles())->toHaveCount(2);
|
|
});
|
|
|
|
test('operational health distinguishes complete attention from successful operation', function () {
|
|
File::delete(storage_path('framework/replenishment-command-status.json'));
|
|
$result = app(OperationalHealth::class)->inspect();
|
|
expect($result['ok'])->toBeFalse()->and($result['complete'])->toBeTrue()
|
|
->and($result['checks']['replenishment']['status'])->toBe('attention')
|
|
->and($result['checks']['receipt_documents']['status'])->toBe('passed');
|
|
$this->artisan('sgc:operations-check')->expectsOutput('Operação: verificações requerem atenção.')->assertExitCode(1);
|
|
});
|
|
|
|
test('operational health rejects invalid limits and interactive identities before inspection', function () {
|
|
$this->mock(ReceiptDocumentHealth::class)->shouldNotReceive('inspect');
|
|
expect(Artisan::call('sgc:operations-check', ['--document-limit' => '10001']))->toBe(2);
|
|
expect(fn () => app(OperationalHealth::class)->inspect(0))->toThrow(LogicException::class);
|
|
$this->actingAs($this->f['receiver']);
|
|
expect(fn () => app(OperationalHealth::class)->inspect())->toThrow(HttpException::class);
|
|
});
|