Bring feature work for OperationRun phase composite progress into `platform-dev`. This PR contains the merged session commits and spec artifacts. Notes: - Session branch was merged into `272-operationrun-phase-composite-progress` locally and pushed. - Please review specs and tests under `specs/272-operationrun-phase-composite-progress/`. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #330
221 lines
7.4 KiB
PHP
221 lines
7.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\OperationRun;
|
|
use App\Support\OpsUx\OperationRunProgressContract;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
it('returns counted progress for running runs with trustworthy processed totals', function (): void {
|
|
$run = OperationRun::factory()->create([
|
|
'status' => 'running',
|
|
'outcome' => 'pending',
|
|
'summary_counts' => [
|
|
'total' => 10,
|
|
'processed' => 4,
|
|
],
|
|
'started_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$progress = OperationRunProgressContract::forRun($run);
|
|
|
|
expect($progress['capability'])->toBe('counted')
|
|
->and($progress['display'])->toBe('counted')
|
|
->and($progress['processed'])->toBe(4)
|
|
->and($progress['total'])->toBe(10)
|
|
->and($progress['percent'])->toBe(40)
|
|
->and($progress['label'])->toBe('4 / 10 processed (40%)');
|
|
});
|
|
|
|
it('clamps counted progress into a truthful visible range', function (): void {
|
|
$run = OperationRun::factory()->create([
|
|
'status' => 'running',
|
|
'outcome' => 'pending',
|
|
'summary_counts' => [
|
|
'total' => 10,
|
|
'processed' => 15,
|
|
],
|
|
'started_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$progress = OperationRunProgressContract::forRun($run);
|
|
|
|
expect($progress['capability'])->toBe('counted')
|
|
->and($progress['processed'])->toBe(10)
|
|
->and($progress['total'])->toBe(10)
|
|
->and($progress['percent'])->toBe(100)
|
|
->and($progress['label'])->toBe('10 / 10 processed (100%)');
|
|
});
|
|
|
|
it('keeps queued runs activity only even when planned totals exist', function (): void {
|
|
$run = OperationRun::factory()->create([
|
|
'status' => 'queued',
|
|
'outcome' => 'pending',
|
|
'summary_counts' => [
|
|
'total' => 10,
|
|
'processed' => 0,
|
|
],
|
|
]);
|
|
|
|
$progress = OperationRunProgressContract::forRun($run);
|
|
|
|
expect($progress['capability'])->toBe('activity')
|
|
->and($progress['display'])->toBe('indeterminate')
|
|
->and($progress['label'])->toBe('Waiting for worker.')
|
|
->and($progress['percent'])->toBeNull();
|
|
});
|
|
|
|
it('returns no progress for terminal runs even when retained counts exist', function (): void {
|
|
$run = OperationRun::factory()->create([
|
|
'status' => 'completed',
|
|
'outcome' => 'succeeded',
|
|
'summary_counts' => [
|
|
'total' => 10,
|
|
'processed' => 10,
|
|
],
|
|
'started_at' => now()->subMinutes(2),
|
|
'completed_at' => now()->subSecond(),
|
|
]);
|
|
|
|
$progress = OperationRunProgressContract::forRun($run);
|
|
|
|
expect($progress['capability'])->toBe('none')
|
|
->and($progress['display'])->toBe('none')
|
|
->and($progress['label'])->toBeNull()
|
|
->and($progress['percent'])->toBeNull();
|
|
});
|
|
|
|
it('does not let outcome counters masquerade as counted progress', function (): void {
|
|
$run = OperationRun::factory()->create([
|
|
'status' => 'running',
|
|
'outcome' => 'pending',
|
|
'summary_counts' => [
|
|
'succeeded' => 4,
|
|
'failed' => 1,
|
|
'skipped' => 2,
|
|
],
|
|
'started_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$progress = OperationRunProgressContract::forRun($run);
|
|
|
|
expect($progress['capability'])->toBe('activity')
|
|
->and($progress['display'])->toBe('indeterminate')
|
|
->and($progress['label'])->toBe('Progress details pending.')
|
|
->and($progress['percent'])->toBeNull();
|
|
});
|
|
|
|
it('classifies repo-real baseline evidence capture runs as phased fallback', function (): void {
|
|
$run = OperationRun::factory()->create([
|
|
'type' => 'baseline_capture',
|
|
'status' => 'running',
|
|
'outcome' => 'pending',
|
|
'context' => [
|
|
'baseline_capture' => [
|
|
'evidence_capture' => [
|
|
'requested' => 10,
|
|
'succeeded' => 3,
|
|
'skipped' => 1,
|
|
],
|
|
'resume_token' => 'resume-123',
|
|
],
|
|
],
|
|
'started_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$progress = OperationRunProgressContract::forRun($run);
|
|
|
|
expect($progress['capability'])->toBe('phased')
|
|
->and($progress['display'])->toBe('indeterminate')
|
|
->and($progress['label'])->toBe('Capturing evidence.')
|
|
->and($progress['percent'])->toBeNull();
|
|
});
|
|
|
|
it('uses canonical phase metadata when present for phased runs', function (): void {
|
|
$run = OperationRun::factory()->create([
|
|
'type' => 'baseline_compare',
|
|
'status' => 'running',
|
|
'outcome' => 'pending',
|
|
'context' => [
|
|
'progress' => [
|
|
'phase' => [
|
|
'key' => 'persisting',
|
|
'label' => 'Saving comparison results.',
|
|
],
|
|
],
|
|
],
|
|
'started_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$progress = OperationRunProgressContract::forRun($run);
|
|
|
|
expect($progress['capability'])->toBe('phased')
|
|
->and($progress['display'])->toBe('indeterminate')
|
|
->and($progress['label'])->toBe('Saving comparison results.')
|
|
->and($progress['percent'])->toBeNull();
|
|
});
|
|
|
|
it('classifies aggregate multi-run work as composite fallback', function (): void {
|
|
$run = OperationRun::factory()->create([
|
|
'status' => 'running',
|
|
'outcome' => 'pending',
|
|
'summary_counts' => [
|
|
'operation_count' => 3,
|
|
],
|
|
'started_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$progress = OperationRunProgressContract::forRun($run);
|
|
|
|
expect($progress['capability'])->toBe('composite')
|
|
->and($progress['display'])->toBe('indeterminate')
|
|
->and($progress['label'])->toBe('Composite progress pending.')
|
|
->and($progress['percent'])->toBeNull();
|
|
});
|
|
|
|
it('derives a tenant review composite label from aggregate operation truth', function (): void {
|
|
$run = OperationRun::factory()->create([
|
|
'type' => 'tenant.review.compose',
|
|
'status' => 'running',
|
|
'outcome' => 'pending',
|
|
'summary_counts' => [
|
|
'operation_count' => 3,
|
|
],
|
|
'started_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$progress = OperationRunProgressContract::forRun($run);
|
|
|
|
expect($progress['capability'])->toBe('composite')
|
|
->and($progress['display'])->toBe('indeterminate')
|
|
->and($progress['label'])->toBe('Review composition is aggregating 3 operations.')
|
|
->and($progress['percent'])->toBeNull();
|
|
});
|
|
|
|
it('uses explicit composite attention hints when present', function (): void {
|
|
$run = OperationRun::factory()->create([
|
|
'type' => 'tenant.review.compose',
|
|
'status' => 'running',
|
|
'outcome' => 'pending',
|
|
'summary_counts' => [
|
|
'operation_count' => 4,
|
|
],
|
|
'context' => [
|
|
'progress' => [
|
|
'composite' => [
|
|
'label' => 'Review composition is aggregating 4 operations. 1 failed operation currently needs review.',
|
|
],
|
|
],
|
|
],
|
|
'started_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$progress = OperationRunProgressContract::forRun($run);
|
|
|
|
expect($progress['capability'])->toBe('composite')
|
|
->and($progress['display'])->toBe('indeterminate')
|
|
->and($progress['label'])->toBe('Review composition is aggregating 4 operations. 1 failed operation currently needs review.')
|
|
->and($progress['percent'])->toBeNull();
|
|
}); |