diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 578bb292..68747902 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -57,7 +57,7 @@ jobs: run: vendor/bin/ecs check - name: Run static analysis via PHPStan - run: vendor/bin/phpstan --xdebug analyse src tests + run: vendor/bin/phpstan analyse src tests - name: Create databases run: | diff --git a/.gitignore b/.gitignore index 50e6f7f4..271eb9a2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ composer.lock vendor coverage .env +.phpunit.cache .phpunit.result.cache .php_cs.cache .php-cs-fixer.cache diff --git a/src/Traits/Timers.php b/src/Traits/Timers.php index 88fa010a..2b2a324e 100644 --- a/src/Traits/Timers.php +++ b/src/Traits/Timers.php @@ -5,7 +5,6 @@ namespace Workflow\Traits; use Carbon\CarbonInterval; -use Illuminate\Database\QueryException; use React\Promise\Deferred; use React\Promise\PromiseInterface; use function React\Promise\resolve; @@ -64,7 +63,7 @@ public static function timer($seconds): PromiseInterface 'class' => Signal::class, 'result' => Serializer::serialize(true), ]); - } catch (QueryException $exception) { + } catch (\Illuminate\Database\UniqueConstraintViolationException $exception) { // already logged } } diff --git a/src/WorkflowStub.php b/src/WorkflowStub.php index 54e7b095..9e61c5ef 100644 --- a/src/WorkflowStub.php +++ b/src/WorkflowStub.php @@ -4,7 +4,6 @@ namespace Workflow; -use Illuminate\Database\QueryException; use Illuminate\Support\Arr; use Illuminate\Support\Carbon; use Illuminate\Support\Traits\Macroable; @@ -220,15 +219,11 @@ public function startAsChild(StoredWorkflow $parentWorkflow, int $index, $now, . public function fail($exception): void { - try { - $this->storedWorkflow->exceptions() - ->create([ - 'class' => $this->storedWorkflow->class, - 'exception' => Serializer::serialize($exception), - ]); - } catch (QueryException) { - // already logged - } + $this->storedWorkflow->exceptions() + ->create([ + 'class' => $this->storedWorkflow->class, + 'exception' => Serializer::serialize($exception), + ]); $this->storedWorkflow->status->transitionTo(WorkflowFailedStatus::class); @@ -267,7 +262,7 @@ public function next($index, $now, $class, $result): void 'class' => $class, 'result' => Serializer::serialize($result), ]); - } catch (QueryException) { + } catch (\Illuminate\Database\UniqueConstraintViolationException $exception) { // already logged } diff --git a/tests/TestCase.php b/tests/TestCase.php index 901d0e9e..804f923f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -25,11 +25,7 @@ public static function setUpBeforeClass(): void } for ($i = 0; $i < self::NUMBER_OF_WORKERS; $i++) { - self::$workers[$i] = new Process([ - 'php', - __DIR__ . '/../vendor/orchestra/testbench-core/laravel/artisan', - 'queue:work', - ]); + self::$workers[$i] = new Process(['php', __DIR__ . '/../vendor/bin/testbench', 'queue:work']); self::$workers[$i]->start(); } } diff --git a/tests/Unit/Listeners/MonitorActivityCompletedTest.php b/tests/Unit/Listeners/MonitorActivityCompletedTest.php index ee2a4b6a..0e09da20 100644 --- a/tests/Unit/Listeners/MonitorActivityCompletedTest.php +++ b/tests/Unit/Listeners/MonitorActivityCompletedTest.php @@ -15,6 +15,10 @@ final class MonitorActivityCompletedTest extends TestCase { public function testHandle(): void { + $this->app->make('cache') + ->store() + ->clear(); + config([ 'workflows.monitor_url' => 'http://test', ]); diff --git a/tests/Unit/Listeners/MonitorActivityFailedTest.php b/tests/Unit/Listeners/MonitorActivityFailedTest.php index 78b1dd92..994ad6dd 100644 --- a/tests/Unit/Listeners/MonitorActivityFailedTest.php +++ b/tests/Unit/Listeners/MonitorActivityFailedTest.php @@ -15,6 +15,10 @@ final class MonitorActivityFailedTest extends TestCase { public function testHandle(): void { + $this->app->make('cache') + ->store() + ->clear(); + config([ 'workflows.monitor_url' => 'http://test', ]); diff --git a/tests/Unit/Listeners/MonitorActivityStartedTest.php b/tests/Unit/Listeners/MonitorActivityStartedTest.php index f11213f6..6c443076 100644 --- a/tests/Unit/Listeners/MonitorActivityStartedTest.php +++ b/tests/Unit/Listeners/MonitorActivityStartedTest.php @@ -15,6 +15,10 @@ final class MonitorActivityStartedTest extends TestCase { public function testHandle(): void { + $this->app->make('cache') + ->store() + ->clear(); + config([ 'workflows.monitor_url' => 'http://test', ]); diff --git a/tests/Unit/Listeners/MonitorWorkflowCompletedTest.php b/tests/Unit/Listeners/MonitorWorkflowCompletedTest.php index c17baee7..3e1159d2 100644 --- a/tests/Unit/Listeners/MonitorWorkflowCompletedTest.php +++ b/tests/Unit/Listeners/MonitorWorkflowCompletedTest.php @@ -14,6 +14,10 @@ final class MonitorWorkflowCompletedTest extends TestCase { public function testHandle(): void { + $this->app->make('cache') + ->store() + ->clear(); + config([ 'workflows.monitor_url' => 'http://test', ]); diff --git a/tests/Unit/Listeners/MonitorWorkflowFailedTest.php b/tests/Unit/Listeners/MonitorWorkflowFailedTest.php index ff076088..ffd5e5a8 100644 --- a/tests/Unit/Listeners/MonitorWorkflowFailedTest.php +++ b/tests/Unit/Listeners/MonitorWorkflowFailedTest.php @@ -14,6 +14,10 @@ final class MonitorWorkflowFailedTest extends TestCase { public function testHandle(): void { + $this->app->make('cache') + ->store() + ->clear(); + config([ 'workflows.monitor_url' => 'http://test', ]); diff --git a/tests/Unit/Listeners/MonitorWorkflowStartedTest.php b/tests/Unit/Listeners/MonitorWorkflowStartedTest.php index f57f1e3c..6c2a24ed 100644 --- a/tests/Unit/Listeners/MonitorWorkflowStartedTest.php +++ b/tests/Unit/Listeners/MonitorWorkflowStartedTest.php @@ -14,6 +14,10 @@ final class MonitorWorkflowStartedTest extends TestCase { public function testHandle(): void { + $this->app->make('cache') + ->store() + ->clear(); + config([ 'workflows.monitor_url' => 'http://test', ]); diff --git a/tests/Unit/Middleware/WithoutOverlappingMiddlewareTest.php b/tests/Unit/Middleware/WithoutOverlappingMiddlewareTest.php index 10b17541..c0502a2b 100644 --- a/tests/Unit/Middleware/WithoutOverlappingMiddlewareTest.php +++ b/tests/Unit/Middleware/WithoutOverlappingMiddlewareTest.php @@ -15,6 +15,10 @@ final class WithoutOverlappingMiddlewareTest extends TestCase { public function testMiddleware(): void { + $this->app->make('cache') + ->store() + ->clear(); + $middleware = new WithoutOverlappingMiddleware(1, WithoutOverlappingMiddleware::WORKFLOW); $this->assertSame($middleware->getLockKey(), 'laravel-workflow-overlap:1'); $this->assertSame($middleware->getWorkflowSemaphoreKey(), 'laravel-workflow-overlap:1:workflow'); @@ -28,6 +32,10 @@ public function testMiddleware(): void public function testAllowsOnlyOneWorkflowInstance(): void { + $this->app->make('cache') + ->store() + ->clear(); + $workflow1 = $this->mock(TestWorkflow::class); $middleware1 = new WithoutOverlappingMiddleware(1, WithoutOverlappingMiddleware::WORKFLOW); @@ -65,6 +73,10 @@ public function testAllowsOnlyOneWorkflowInstance(): void public function testAllowsMultipleActivityInstances(): void { + $this->app->make('cache') + ->store() + ->clear(); + $activity1 = $this->mock(TestActivity::class); $middleware1 = new WithoutOverlappingMiddleware(1, WithoutOverlappingMiddleware::ACTIVITY); diff --git a/tests/Unit/Traits/TimersTest.php b/tests/Unit/Traits/TimersTest.php index cc038930..cb92d778 100644 --- a/tests/Unit/Traits/TimersTest.php +++ b/tests/Unit/Traits/TimersTest.php @@ -4,6 +4,10 @@ namespace Tests\Unit\Traits; +use Exception; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\UniqueConstraintViolationException; +use Mockery; use Tests\Fixtures\TestWorkflow; use Tests\TestCase; use Workflow\Models\StoredWorkflow; @@ -138,4 +142,56 @@ public function testLoadsStoredResult(): void ]); $this->assertSame(true, Serializer::unserialize($workflow->logs()->firstWhere('index', 0)->result)); } + + public function testHandlesDuplicateLogInsertionProperly(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->timers() + ->create([ + 'index' => 0, + 'stop_at' => now(), + ]); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => now(), + 'class' => Signal::class, + 'result' => Serializer::serialize(true), + ]); + + $mockLogs = Mockery::mock(HasMany::class) + ->shouldReceive('whereIndex') + ->once() + ->andReturnSelf() + ->shouldReceive('first') + ->once() + ->andReturn(null) + ->shouldReceive('create') + ->andThrow(new UniqueConstraintViolationException('', '', [], new Exception())) + ->getMock(); + + $mockStoredWorkflow = Mockery::spy($storedWorkflow); + + $mockStoredWorkflow->shouldReceive('logs') + ->andReturnUsing(static function () use ($mockLogs) { + return $mockLogs; + }); + + WorkflowStub::setContext([ + 'storedWorkflow' => $mockStoredWorkflow, + 'index' => 0, + 'now' => now(), + 'replaying' => false, + ]); + + WorkflowStub::timer('1 minute') + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + Mockery::close(); + + $this->assertSame(true, $result); + } } diff --git a/tests/Unit/WorkflowStubTest.php b/tests/Unit/WorkflowStubTest.php index 0d030984..20bd90ab 100644 --- a/tests/Unit/WorkflowStubTest.php +++ b/tests/Unit/WorkflowStubTest.php @@ -6,6 +6,7 @@ use Exception; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Queue; use Tests\Fixtures\TestAwaitWorkflow; use Tests\Fixtures\TestBadConnectionWorkflow; use Tests\Fixtures\TestWorkflow; @@ -14,6 +15,7 @@ use Workflow\Serializers\Serializer; use Workflow\Signal; use Workflow\States\WorkflowCompletedStatus; +use Workflow\States\WorkflowCreatedStatus; use Workflow\States\WorkflowPendingStatus; use Workflow\WorkflowStub; @@ -207,4 +209,38 @@ public function testConnection(): void $this->assertSame('redis', WorkflowStub::connection()); $this->assertSame('default', WorkflowStub::queue()); } + + public function testHandlesDuplicateLogInsertionProperly(): void + { + Queue::fake(); + + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->update([ + 'arguments' => Serializer::serialize([]), + 'status' => WorkflowCreatedStatus::$name, + ]); + + $storedWorkflow->timers() + ->create([ + 'index' => 0, + 'stop_at' => now(), + ]); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => now(), + 'class' => Signal::class, + 'result' => Serializer::serialize(true), + ]); + + $workflow = $storedWorkflow->toWorkflow(); + + $workflow->next(0, now(), Signal::class, true); + + $this->assertSame(WorkflowPendingStatus::class, $workflow->status()); + $this->assertSame(1, $workflow->logs()->count()); + + Queue::assertPushed(TestWorkflow::class, 1); + } }