Skip to content

Commit 307c183

Browse files
[cross-repo from server#312] Conformance blocker: expand replay coverage beyond current smoke (#638)
1 parent 97790a9 commit 307c183

2 files changed

Lines changed: 143 additions & 2 deletions

File tree

src/V2/Support/WorkflowStepHistory.php

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
namespace Workflow\V2\Support;
66

7+
use Workflow\V2\Activity;
78
use Workflow\V2\Enums\HistoryEventType;
89
use Workflow\V2\Exceptions\HistoryEventShapeMismatchException;
910
use Workflow\V2\Models\WorkflowHistoryEvent;
1011
use Workflow\V2\Models\WorkflowRun;
12+
use Workflow\V2\Workflow;
1113

1214
final class WorkflowStepHistory
1315
{
@@ -201,7 +203,10 @@ private static function detailMismatchForSequence(
201203

202204
$recorded = self::recordedDetail($event, $expectedField);
203205

204-
if ($recorded === null || $recorded === $expected) {
206+
if (
207+
$recorded === null
208+
|| in_array($recorded, self::expectedDetailCandidates($expectedField, $expected), true)
209+
) {
205210
continue;
206211
}
207212

@@ -238,13 +243,53 @@ private static function recordedDetail(WorkflowHistoryEvent $event, string $fiel
238243
return $value;
239244
}
240245

246+
if ($field === 'activity_type') {
247+
return self::stringValue($event->payload['activity_class'] ?? null);
248+
}
249+
241250
if ($field === 'child_workflow_type') {
242-
return self::stringValue($event->payload['workflow_type'] ?? null);
251+
foreach (['workflow_type', 'child_workflow_class', 'workflow_class'] as $fallbackField) {
252+
$fallback = self::stringValue($event->payload[$fallbackField] ?? null);
253+
254+
if ($fallback !== null) {
255+
return $fallback;
256+
}
257+
}
243258
}
244259

245260
return null;
246261
}
247262

263+
/**
264+
* @return list<string>
265+
*/
266+
private static function expectedDetailCandidates(string $field, string $expected): array
267+
{
268+
$candidates = [$expected];
269+
270+
if ($field === 'activity_type' && is_subclass_of($expected, Activity::class)) {
271+
$candidates[] = self::durableTypeForClass($expected);
272+
}
273+
274+
if ($field === 'child_workflow_type' && is_subclass_of($expected, Workflow::class)) {
275+
$candidates[] = self::durableTypeForClass($expected);
276+
}
277+
278+
return array_values(array_unique(array_filter(
279+
$candidates,
280+
static fn (mixed $value): bool => is_string($value) && $value !== '',
281+
)));
282+
}
283+
284+
private static function durableTypeForClass(string $class): ?string
285+
{
286+
try {
287+
return TypeRegistry::for($class);
288+
} catch (\Throwable) {
289+
return null;
290+
}
291+
}
292+
248293
/**
249294
* @param array<string, string|null> $expectedDetails
250295
*/
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Unit\V2;
6+
7+
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
8+
use PHPUnit\Framework\TestCase;
9+
use Tests\Fixtures\V2\TestChildGreetingWorkflow;
10+
use Tests\Fixtures\V2\TestMixedEntryActivity;
11+
use Workflow\V2\Enums\HistoryEventType;
12+
use Workflow\V2\Exceptions\HistoryEventShapeMismatchException;
13+
use Workflow\V2\Models\WorkflowHistoryEvent;
14+
use Workflow\V2\Models\WorkflowRun;
15+
use Workflow\V2\Support\WorkflowStepHistory;
16+
17+
final class WorkflowStepHistoryTest extends TestCase
18+
{
19+
public function testActivityTypeDetailAcceptsCanonicalTypeAliasForYieldedClass(): void
20+
{
21+
$run = $this->runWithHistoryEvents([
22+
$this->historyEvent(HistoryEventType::ActivityScheduled, [
23+
'sequence' => 1,
24+
'activity_type' => 'test-mixed-entry-activity',
25+
'activity_class' => TestMixedEntryActivity::class,
26+
]),
27+
]);
28+
29+
WorkflowStepHistory::assertCompatible($run, 1, WorkflowStepHistory::ACTIVITY, [
30+
'activity_type' => TestMixedEntryActivity::class,
31+
]);
32+
33+
$this->addToAssertionCount(1);
34+
}
35+
36+
public function testActivityTypeDetailRejectsMutatedTypeEvenWhenClassFallbackMatches(): void
37+
{
38+
$run = $this->runWithHistoryEvents([
39+
$this->historyEvent(HistoryEventType::ActivityScheduled, [
40+
'sequence' => 1,
41+
'activity_type' => 'changed-activity-type',
42+
'activity_class' => TestMixedEntryActivity::class,
43+
]),
44+
]);
45+
46+
$this->expectException(HistoryEventShapeMismatchException::class);
47+
$this->expectExceptionMessage('Recorded activity_type [changed-activity-type]');
48+
49+
WorkflowStepHistory::assertCompatible($run, 1, WorkflowStepHistory::ACTIVITY, [
50+
'activity_type' => TestMixedEntryActivity::class,
51+
]);
52+
}
53+
54+
public function testChildWorkflowTypeDetailAcceptsCanonicalTypeAliasForYieldedClass(): void
55+
{
56+
$run = $this->runWithHistoryEvents([
57+
$this->historyEvent(HistoryEventType::ChildWorkflowScheduled, [
58+
'sequence' => 2,
59+
'child_workflow_type' => 'test-child-greeting-workflow',
60+
'child_workflow_class' => TestChildGreetingWorkflow::class,
61+
]),
62+
]);
63+
64+
WorkflowStepHistory::assertCompatible($run, 2, WorkflowStepHistory::CHILD_WORKFLOW, [
65+
'child_workflow_type' => TestChildGreetingWorkflow::class,
66+
]);
67+
68+
$this->addToAssertionCount(1);
69+
}
70+
71+
/**
72+
* @param list<WorkflowHistoryEvent> $events
73+
*/
74+
private function runWithHistoryEvents(array $events): WorkflowRun
75+
{
76+
$run = new WorkflowRun();
77+
$run->setRelation('historyEvents', new EloquentCollection($events));
78+
79+
return $run;
80+
}
81+
82+
/**
83+
* @param array<string, mixed> $payload
84+
*/
85+
private function historyEvent(HistoryEventType $type, array $payload): WorkflowHistoryEvent
86+
{
87+
$event = new WorkflowHistoryEvent();
88+
$event->forceFill([
89+
'sequence' => $payload['sequence'] ?? 1,
90+
'event_type' => $type->value,
91+
'payload' => $payload,
92+
]);
93+
94+
return $event;
95+
}
96+
}

0 commit comments

Comments
 (0)