f = suggestionFixture(); app(SupplyPlanning::class)->save($this->f['receiver'], 'policy', supplyInput([], 'suspend', $this->f['policy'])); $this->actingAs($this->f['issuer']); $policy = app(SupplyPlanning::class)->save($this->f['issuer'], 'policy', supplyInput([...supplyPolicy($this->f), 'purpose' => 'internal', 'agreement_key' => $this->f['agreement']->record_key])); $this->actingAs($this->f['receiver']); $this->f['policy'] = app(SupplyPlanning::class)->save($this->f['receiver'], 'policy', supplyInput([], 'approve', $policy)); supplyGrant($this->f['otherReceiver'], ['admin']); $this->readinessOriginalStorage = app()->storagePath(); $this->readinessStorage = $this->readinessOriginalStorage.'/framework/testing/readiness-'.Str::uuid(); File::ensureDirectoryExists($this->readinessStorage.'/framework'); app()->useStoragePath($this->readinessStorage); }); afterEach(function () { app()->useStoragePath($this->readinessOriginalStorage); File::deleteDirectory($this->readinessStorage); }); function pilotReadinessRule($test): void { $test->actingAs($test->f['issuer']); $rule = app(AutomaticPurchases::class)->save($test->f['issuer'], ['operation_key' => (string) Str::uuid(), 'version' => 0, 'action' => 'save', 'reason' => 'Parametrização sintética do diagnóstico de piloto.', 'data' => ['policy_key' => $test->f['policy']->record_key, 'requester_id' => $test->f['issuer']->id, 'cost_center_id' => $test->f['r']->cost_center_id, 'unit_price' => null, 'tax_rate' => null, 'needed_days' => 5, 'valid_until' => today()->addMonth()->toDateString()]]); $test->actingAs($test->f['receiver']); app(AutomaticPurchases::class)->save($test->f['receiver'], ['operation_key' => (string) Str::uuid(), 'record_key' => $rule->record_key, 'version' => 1, 'action' => 'approve', 'reason' => 'Activação independente da regra sintética conferida.', 'data' => []]); } test('pilot configuration export is private read only and does not include the account register', function () { $counts = []; foreach (['users', 'organization_memberships', 'organization_profiles', 'audit_logs', 'stock_movements', 'supply_records', 'requisitions'] as $table) { $counts[$table] = DB::table($table)->count(); } $this->actingAs($this->f['receiver'])->get('/stock/piloto.csv')->assertForbidden(); $response = $this->actingAs($this->f['otherReceiver'])->get('/stock/piloto.csv')->assertOk()->assertHeader('Content-Type', 'text/csv; charset=UTF-8'); $csv = $response->streamedContent(); expect($csv)->toContain('Conferido em')->toContain('A preparar')->toContain('SGC autónomo')->not->toContain($this->f['issuer']->name); foreach ($counts as $table => $count) { expect(DB::table($table)->count())->toBe($count); } }); test('pilot export renders formula-like text as text in spreadsheet readers', function () { $this->mock(WarehousePilotReadiness::class)->shouldReceive('inspect')->once()->andReturn(['checked_at' => now()->toIso8601String(), 'checks' => [['label' => '=1+1', 'ok' => false, 'detail' => "\t@SUM(1)", 'path' => null]]]); $response = $this->actingAs($this->f['otherReceiver'])->get('/stock/piloto.csv')->assertOk(); $csv = $response->streamedContent(); expect($csv)->toContain("'=1+1")->toContain("'\t@SUM(1)"); }); test('pilot diagnostics are read only and distinguish a viable synthetic warehouse scenario from client delivery and an unobserved scheduler', function () { supplyGrant($this->f['issuer'], ['warehouse_request_prepare']); supplyGrant($this->f['receiver'], ['warehouse_request_review']); supplyGrant($this->f['otherReceiver'], ['warehouse', 'warehouse_request_deliver']); pilotReadinessRule($this); $counts = []; foreach (['requisitions', 'warehouse_requests', 'warehouse_request_events', 'warehouse_reservations', 'stock_movements', 'audit_logs', 'supply_records', 'replenishment_work', 'organization_memberships', 'organization_profiles'] as $table) { $counts[$table] = DB::table($table)->count(); } $this->actingAs($this->f['otherReceiver'])->get('/stock/piloto')->assertOk()->assertHeader('Cache-Control', 'max-age=0, no-store, private')->assertInertia(fn (Assert $p) => $p->component('stock/pilot')->where('syntheticEvaluation', true)->where('report.warehouse_scenario_ready', true)->where('report.replenishment_scenario_ready', false)->where('report.client_configuration_complete', false)->where('report.checks.3.ok', true)->where('report.checks.4.ok', false)->has('report.participants.prepare', 1)->has('report.participants.deliver', 1)); foreach ($counts as $table => $count) { expect(DB::table($table)->count())->toBe($count); } }); test('having all task labels among two people does not satisfy three-way independence', function () { supplyGrant($this->f['issuer'], ['warehouse_request_prepare', 'warehouse_request_review', 'warehouse_request_deliver', 'warehouse']); supplyGrant($this->f['receiver'], ['warehouse_request_review']); $this->actingAs($this->f['otherReceiver'])->get('/stock/piloto')->assertOk()->assertInertia(fn (Assert $p) => $p->where('report.warehouse_scenario_ready', false)->where('report.checks.1.ok', false)->has('report.participants.prepare', 1)->has('report.participants.review', 2)->has('report.participants.deliver', 1)); }); test('rule inspection reuses generation authority and source validity without creating drafts', function () { pilotReadinessRule($this); $before = Requisition::query()->count(); DB::table('organization_memberships')->where('user_id', $this->f['issuer']->id)->update(['expires_at' => now()->subMinute()]); $this->actingAs($this->f['otherReceiver'])->get('/stock/piloto')->assertOk()->assertInertia(fn (Assert $p) => $p->where('report.checks.3.ok', false)); expect(Requisition::query()->count())->toBe($before); }); test('pilot diagnostics do not admit operational readers or expose accounts from another company', function () { $this->actingAs($this->f['receiver'])->get('/stock/piloto')->assertForbidden(); $foreign = receiptFixture(); supplyGrant($foreign['issuer'], ['admin']); $this->actingAs($foreign['issuer'])->get('/stock/piloto')->assertOk()->assertInertia(fn (Assert $p) => $p->has('report.registered', 3)->where('report.warehouse_scenario_ready', false)->where('report.checks.2.ok', false)->where('report.registered', fn ($people) => collect($people)->pluck('id')->sort()->values()->all() === collect([$foreign['issuer']->id, $foreign['receiver']->id, $foreign['otherReceiver']->id])->sort()->values()->all())); }); test('stale catalogue and expired automatic configuration are reported instead of being treated as ready', function () { pilotReadinessRule($this); $this->f['stock']->update(['name' => 'Cadastro actualizado depois da publicação']); $this->travel(32)->days(); $this->actingAs($this->f['otherReceiver'])->get('/stock/piloto')->assertOk()->assertInertia(fn (Assert $p) => $p->where('report.checks.2.ok', false)->where('report.checks.3.ok', false)->where('report.replenishment_scenario_ready', false)); }); test('optional scheduler telemetry is isolated and a stale or damaged file does not imply a running service', function () { $originalStorage = $this->app->storagePath(); $originalEnv = $this->app->environment(); $temporary = $originalStorage.'/framework/testing/pilot-status-'.Str::uuid(); mkdir($temporary.'/framework', 0755, true); $this->app->useStoragePath($temporary); $this->app->instance('env', 'payable-testing'); $path = $temporary.'/framework/replenishment-status.json'; $this->actingAs($this->f['otherReceiver']); try { file_put_contents($path, '{bad-json'); expect(app(ReplenishmentQueue::class)->status($this->f['otherReceiver'])['schedulerStatus'])->toBeNull(); file_put_contents($path, json_encode(['result' => 'passed', 'finished_at' => now()->subHour()->toIso8601String()], JSON_THROW_ON_ERROR)); expect(app(ReplenishmentQueue::class)->status($this->f['otherReceiver'])['schedulerRecent'])->toBeFalse(); file_put_contents($path, json_encode(['result' => 'passed', 'finished_at' => now()->toIso8601String()], JSON_THROW_ON_ERROR)); expect(app(ReplenishmentQueue::class)->status($this->f['otherReceiver'])['schedulerRecent'])->toBeTrue(); } finally { $this->app->useStoragePath($originalStorage); $this->app->instance('env', $originalEnv); unlink($path); rmdir($temporary.'/framework'); rmdir($temporary); } }); test('native scheduler observation is available outside evaluation and takes precedence over helper telemetry', function () { $originalStorage = app()->storagePath(); $originalEnv = app()->environment(); $temporary = $originalStorage.'/framework/testing/native-status-'.Str::uuid(); mkdir($temporary.'/framework', 0755, true); app()->useStoragePath($temporary); app()->instance('env', 'production'); $this->actingAs($this->f['otherReceiver']); try { app(ReplenishmentHeartbeat::class)->record(now()->toIso8601String(), 0); $status = app(ReplenishmentQueue::class)->status($this->f['otherReceiver']); expect($status['schedulerRecent'])->toBeTrue()->and($status['schedulerStatus']['result'])->toBe('passed'); app(ReplenishmentHeartbeat::class)->record(now()->toIso8601String(), 1); file_put_contents($temporary.'/framework/replenishment-status.json', json_encode(['result' => 'passed', 'finished_at' => now()->toIso8601String()], JSON_THROW_ON_ERROR)); app()->instance('env', 'payable-testing'); expect(app(ReplenishmentQueue::class)->status($this->f['otherReceiver'])['schedulerStatus']['result'])->toBe('retry_pending'); file_put_contents($temporary.'/framework/replenishment-command-status.json', '{broken'); expect(app(ReplenishmentQueue::class)->status($this->f['otherReceiver'])['schedulerStatus'])->toBeNull(); } finally { app()->useStoragePath($originalStorage); app()->instance('env', $originalEnv); File::deleteDirectory($temporary); } }); test('a recent successful scheduler does not hide durable work still awaiting recovery', function () { pilotReadinessRule($this); $this->actingAs($this->f['otherReceiver']); DB::table('replenishment_work')->where('tenant_id', $this->f['tenant']->id)->update(['processed_generation' => DB::raw('generation'), 'failures' => 0]); $originalStorage = $this->app->storagePath(); $originalEnv = $this->app->environment(); $temporary = $originalStorage.'/framework/testing/pilot-recovery-'.Str::uuid(); mkdir($temporary.'/framework', 0755, true); $this->app->useStoragePath($temporary); $this->app->instance('env', 'payable-testing'); $path = $temporary.'/framework/replenishment-status.json'; try { file_put_contents($path, json_encode(['result' => 'passed', 'finished_at' => now()->toIso8601String()], JSON_THROW_ON_ERROR)); expect(app(WarehousePilotReadiness::class)->inspect($this->f['otherReceiver'])['replenishment_scenario_ready'])->toBeTrue(); DB::table('replenishment_work')->where('tenant_id', $this->f['tenant']->id)->update(['failures' => 1, 'due_at' => now()->addHour()]); $report = app(WarehousePilotReadiness::class)->inspect($this->f['otherReceiver']); expect($report['runtime']['schedulerRecent'])->toBeTrue()->and($report['replenishment_scenario_ready'])->toBeFalse()->and($report['checks'][6]['ok'])->toBeFalse(); } finally { $this->app->useStoragePath($originalStorage); $this->app->instance('env', $originalEnv); unlink($path); rmdir($temporary.'/framework'); rmdir($temporary); } }); test('command readiness does not certify a pilot missing receipt storage or the requisition version', function (string $missing) { supplyGrant($this->f['issuer'], ['warehouse_request_prepare']); supplyGrant($this->f['receiver'], ['warehouse_request_review']); supplyGrant($this->f['otherReceiver'], ['warehouse', 'warehouse_request_deliver']); pilotReadinessRule($this); $this->actingAs($this->f['otherReceiver']); expect(app(WarehousePilotReadiness::class)->inspect($this->f['otherReceiver'])['warehouse_scenario_ready'])->toBeTrue(); $counts = [DB::table('requisitions')->count(), DB::table('stock_movements')->count(), DB::table('audit_logs')->count()]; if ($missing === 'receipts') { Schema::drop('requisition_commands'); } else { Schema::table('requisitions', fn (Blueprint $table) => $table->dropColumn('process_version')); } $report = app(WarehousePilotReadiness::class)->inspect($this->f['otherReceiver']); expect($report['checks'][0]['ok'])->toBeFalse()->and($report['warehouse_scenario_ready'])->toBeFalse()->and($report['replenishment_scenario_ready'])->toBeFalse() ->and($report['tables'][$missing === 'receipts' ? 'requisition_commands' : 'requisition_process_version'])->toBeFalse() ->and([DB::table('requisitions')->count(), DB::table('stock_movements')->count(), DB::table('audit_logs')->count()])->toBe($counts); })->with(['receipts', 'version']);