|
33 | 33 | use Workflow\V2\Models\WorkflowSignal; |
34 | 34 | use Workflow\V2\Models\WorkflowTask; |
35 | 35 | use Workflow\V2\Models\WorkflowTimer; |
| 36 | +use Workflow\V2\Models\WorkflowUpdate; |
36 | 37 | use Workflow\V2\Support\ActivitySnapshot; |
37 | 38 | use Workflow\V2\Support\HistoryExport; |
38 | 39 | use Workflow\V2\Support\RunLineageProjector; |
@@ -329,6 +330,7 @@ public function testItBuildsVersionedReplayBundleFromTypedHistoryAndProjections( |
329 | 330 | $this->assertSame($signal->id, $bundle['signals'][0]['id']); |
330 | 331 | $this->assertSame('approved-by', $bundle['signals'][0]['name']); |
331 | 332 | $this->assertSame('applied', $bundle['signals'][0]['status']); |
| 333 | + $this->assertSame(config('workflows.serializer'), $bundle['signals'][0]['payload_codec']); |
332 | 334 | $this->assertSame($task->id, $bundle['tasks'][0]['id']); |
333 | 335 | $this->assertSame($activity->id, $bundle['activities'][0]['id']); |
334 | 336 | $this->assertSame($activity->id, $bundle['activities'][0]['idempotency_key']); |
@@ -1486,6 +1488,105 @@ public function testItEmbedsAvroWrapperSchemaWhenBundleContainsAvroPayloads(): v |
1486 | 1488 | $this->assertSame('01', $bundle['codec_schemas']['avro']['typed_prefix_hex']); |
1487 | 1489 | } |
1488 | 1490 |
|
| 1491 | + public function testItStampsRowLocalPayloadCodecOnSignalAndUpdateExportRows(): void |
| 1492 | + { |
| 1493 | + if (! class_exists(\Apache\Avro\Schema\AvroSchema::class)) { |
| 1494 | + $this->markTestSkipped('apache/avro package is not installed in this environment.'); |
| 1495 | + } |
| 1496 | + |
| 1497 | + config()->set('workflows.serializer', 'json'); |
| 1498 | + $run = $this->createMinimalCompletedRun('history-export-mixed-codec'); |
| 1499 | + $run->forceFill(['payload_codec' => 'json'])->save(); |
| 1500 | + |
| 1501 | + $instance = $run->instance; |
| 1502 | + |
| 1503 | + $signalCommand = WorkflowCommand::record($instance, $run, [ |
| 1504 | + 'command_type' => CommandType::Signal->value, |
| 1505 | + 'target_scope' => 'instance', |
| 1506 | + 'payload_codec' => config('workflows.serializer'), |
| 1507 | + 'payload' => Serializer::serialize([ |
| 1508 | + 'name' => 'approved-by', |
| 1509 | + 'arguments' => ['Taylor'], |
| 1510 | + ]), |
| 1511 | + 'source' => 'webhook', |
| 1512 | + 'status' => CommandStatus::Accepted->value, |
| 1513 | + 'outcome' => CommandOutcome::SignalReceived->value, |
| 1514 | + 'accepted_at' => now()->subMinute(), |
| 1515 | + 'applied_at' => now()->subMinute(), |
| 1516 | + ]); |
| 1517 | + |
| 1518 | + $signal = WorkflowSignal::query()->create([ |
| 1519 | + 'workflow_command_id' => $signalCommand->id, |
| 1520 | + 'workflow_instance_id' => $instance->id, |
| 1521 | + 'workflow_run_id' => $run->id, |
| 1522 | + 'target_scope' => 'instance', |
| 1523 | + 'resolved_workflow_run_id' => $run->id, |
| 1524 | + 'signal_name' => 'approved-by', |
| 1525 | + 'signal_wait_id' => 'signal-wait-avro', |
| 1526 | + 'status' => 'applied', |
| 1527 | + 'outcome' => 'signal_received', |
| 1528 | + 'command_sequence' => $signalCommand->command_sequence, |
| 1529 | + 'workflow_sequence' => 1, |
| 1530 | + 'payload_codec' => 'avro', |
| 1531 | + 'arguments' => 'avro-encoded-signal-blob', |
| 1532 | + 'received_at' => now()->subMinute(), |
| 1533 | + 'applied_at' => now()->subMinute(), |
| 1534 | + 'closed_at' => now()->subMinute(), |
| 1535 | + ]); |
| 1536 | + |
| 1537 | + $updateCommand = WorkflowCommand::record($instance, $run, [ |
| 1538 | + 'command_type' => CommandType::Update->value, |
| 1539 | + 'target_scope' => 'instance', |
| 1540 | + 'payload_codec' => config('workflows.serializer'), |
| 1541 | + 'payload' => Serializer::serialize([ |
| 1542 | + 'name' => 'mark-approved', |
| 1543 | + 'arguments' => [true, 'api'], |
| 1544 | + ]), |
| 1545 | + 'source' => 'api', |
| 1546 | + 'status' => CommandStatus::Accepted->value, |
| 1547 | + 'outcome' => CommandOutcome::UpdateCompleted->value, |
| 1548 | + 'accepted_at' => now()->subMinute(), |
| 1549 | + 'applied_at' => now()->subMinute(), |
| 1550 | + ]); |
| 1551 | + |
| 1552 | + $update = WorkflowUpdate::query()->create([ |
| 1553 | + 'workflow_command_id' => $updateCommand->id, |
| 1554 | + 'workflow_instance_id' => $instance->id, |
| 1555 | + 'workflow_run_id' => $run->id, |
| 1556 | + 'command_sequence' => $updateCommand->command_sequence, |
| 1557 | + 'workflow_sequence' => 1, |
| 1558 | + 'update_name' => 'mark-approved', |
| 1559 | + 'status' => 'completed', |
| 1560 | + 'outcome' => 'update_completed', |
| 1561 | + 'payload_codec' => 'avro', |
| 1562 | + 'arguments' => 'avro-encoded-update-args', |
| 1563 | + 'result' => 'avro-encoded-update-result', |
| 1564 | + 'accepted_at' => now()->subMinute(), |
| 1565 | + 'applied_at' => now()->subMinute(), |
| 1566 | + 'closed_at' => now()->subMinute(), |
| 1567 | + ]); |
| 1568 | + |
| 1569 | + $bundle = HistoryExport::forRun($run->refresh(), Carbon::parse('2026-04-17 14:00:00')); |
| 1570 | + |
| 1571 | + $signalRow = collect($bundle['signals'])->firstWhere('id', $signal->id); |
| 1572 | + $this->assertIsArray($signalRow); |
| 1573 | + $this->assertSame('avro', $signalRow['payload_codec']); |
| 1574 | + $this->assertSame('avro-encoded-signal-blob', $signalRow['arguments']); |
| 1575 | + |
| 1576 | + $updateRow = collect($bundle['updates'])->firstWhere('id', $update->id); |
| 1577 | + $this->assertIsArray($updateRow); |
| 1578 | + $this->assertSame('avro', $updateRow['payload_codec']); |
| 1579 | + $this->assertSame('mark-approved', $updateRow['name']); |
| 1580 | + $this->assertSame('avro-encoded-update-args', $updateRow['arguments']); |
| 1581 | + $this->assertSame('avro-encoded-update-result', $updateRow['result']); |
| 1582 | + |
| 1583 | + // The run itself is JSON-coded, but the Avro signal/update rows must |
| 1584 | + // trigger codec_schemas.avro so an offline consumer has the wrapper |
| 1585 | + // schema needed to decode those blobs. |
| 1586 | + $this->assertArrayHasKey('avro', $bundle['codec_schemas']); |
| 1587 | + $this->assertSame('00', $bundle['codec_schemas']['avro']['wrapper_prefix_hex']); |
| 1588 | + } |
| 1589 | + |
1489 | 1590 | public function testItOmitsAvroSchemasWhenBundleHasNoAvroPayloads(): void |
1490 | 1591 | { |
1491 | 1592 | config()->set('workflows.serializer', 'json'); |
|
0 commit comments