From 2c5d269efcd843428a32196c2c3588262cd23ffb Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Wed, 29 May 2024 10:14:53 +1000 Subject: [PATCH] add tests --- src/Drivers/DatabaseDriver.php | 2 +- tests/Feature/DatabaseDriverTest.php | 90 ++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/src/Drivers/DatabaseDriver.php b/src/Drivers/DatabaseDriver.php index 0404c86..764c181 100644 --- a/src/Drivers/DatabaseDriver.php +++ b/src/Drivers/DatabaseDriver.php @@ -216,7 +216,7 @@ public function get($feature, $scope): mixed $this->insert($feature, $scope, $value); } catch (UniqueConstraintViolationException $e) { if ($this->retryDepth === 1) { - throw new RuntimeException('Unable to insert feature value from the database.', previous: $e); + throw new RuntimeException('Unable to insert feature value into the database.', previous: $e); } $this->retryDepth++; diff --git a/tests/Feature/DatabaseDriverTest.php b/tests/Feature/DatabaseDriverTest.php index 9de3f33..3cf8b7e 100644 --- a/tests/Feature/DatabaseDriverTest.php +++ b/tests/Feature/DatabaseDriverTest.php @@ -2,12 +2,16 @@ namespace Tests\Feature; +use Exception; use Illuminate\Database\Eloquent\Relations\Relation; +use Illuminate\Database\Events\QueryExecuted; +use Illuminate\Database\UniqueConstraintViolationException; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Event; +use Illuminate\Support\Str; use InvalidArgumentException; use Laravel\Pennant\Contracts\FeatureScopeable; use Laravel\Pennant\Events\AllFeaturesPurged; @@ -19,6 +23,7 @@ use Laravel\Pennant\Events\FeatureUpdatedForAllScopes; use Laravel\Pennant\Events\UnknownFeatureResolved; use Laravel\Pennant\Feature; +use RuntimeException; use Tests\TestCase; use Workbench\App\Models\User; use Workbench\Database\Factories\UserFactory; @@ -1293,6 +1298,91 @@ public function test_it_can_list_stored_features() $this->assertSame(Feature::stored(), ['bar', 'baz']); } + + public function test_it_retries_3_times_and_then_fails() + { + Feature::define('foo', fn () => true); + Feature::define('bar', fn () => true); + $insertAttempts = 0; + DB::listen(function (QueryExecuted $event) use (&$insertAttempts) { + if (Str::startsWith($event->sql, 'insert into "features"')) { + $insertAttempts++; + DB::table('features')->delete(); + throw new UniqueConstraintViolationException($event->connectionName, $event->sql, $event->bindings, new RuntimeException()); + } + }); + + + try { + Feature::for('tim')->loadMissing(['foo', 'bar']); + $this->fail('Should have failed.'); + } catch (Exception $e) { + $this->assertInstanceOf(RuntimeException::class, $e); + $this->assertSame('Unable to insert feature values into the database.', $e->getMessage()); + $this->assertInstanceOf(UniqueConstraintViolationException::class, $e->getPrevious()); + } + + $this->assertSame(3, $insertAttempts); + } + + public function test_it_only_retries_on_conflicts() + { + Feature::define('foo', fn () => true); + Feature::define('bar', fn () => true); + $insertAttempts = 0; + DB::listen(function (QueryExecuted $event) use (&$insertAttempts) { + if (Str::startsWith($event->sql, 'insert into "features"')) { + $insertAttempts++; + throw new UniqueConstraintViolationException($event->connectionName, $event->sql, $event->bindings, new RuntimeException()); + } + }); + + Feature::for('tim')->loadMissing(['foo', 'bar']); + + $this->assertSame(1, $insertAttempts); + } + + public function test_it_retries_2_times_and_then_fails_for_individual_queries() + { + Feature::define('foo', fn () => true); + Feature::define('bar', fn () => true); + $insertAttempts = 0; + DB::listen(function (QueryExecuted $event) use (&$insertAttempts) { + if (Str::startsWith($event->sql, 'insert into "features"')) { + $insertAttempts++; + DB::table('features')->delete(); + throw new UniqueConstraintViolationException($event->connectionName, $event->sql, $event->bindings, new RuntimeException()); + } + }); + + try { + Feature::driver('database')->get('foo', 'tim'); + $this->fail('Should have failed.'); + } catch (Exception $e) { + $this->assertInstanceOf(RuntimeException::class, $e); + $this->assertSame('Unable to insert feature value into the database.', $e->getMessage()); + $this->assertInstanceOf(UniqueConstraintViolationException::class, $e->getPrevious()); + } + + $this->assertSame(2, $insertAttempts); + } + + public function test_it_only_retries_on_conflicts_for_individual_queries() + { + Feature::define('foo', fn () => true); + Feature::define('bar', fn () => true); + $insertAttempts = 0; + DB::listen(function (QueryExecuted $event) use (&$insertAttempts) { + if (Str::startsWith($event->sql, 'insert into "features"')) { + $insertAttempts++; + throw new UniqueConstraintViolationException($event->connectionName, $event->sql, $event->bindings, new RuntimeException()); + } + }); + + Feature::driver('database')->get('foo', 'tim'); + + $this->assertSame(1, $insertAttempts); + } } class UnregisteredFeature