diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 6b02067..8da69a5 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -30,6 +30,11 @@ jobs: echo "::add-matcher::${{ runner.tool_cache }}/php.json" echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + - name: Setup redis + uses: supercharge/redis-github-action@1.2.0 + with: + redis-version: 6 + - name: Install dependencies run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 74d6f49..ed04472 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -13,4 +13,7 @@ ./src + + + diff --git a/tests/CircuitBreakerTest.php b/tests/CircuitBreakerTest.php index 4b45130..f8aecc2 100644 --- a/tests/CircuitBreakerTest.php +++ b/tests/CircuitBreakerTest.php @@ -9,6 +9,8 @@ use Stfn\CircuitBreaker\Exceptions\CircuitForceOpenException; use Stfn\CircuitBreaker\Exceptions\CircuitHalfOpenFailException; use Stfn\CircuitBreaker\Exceptions\CircuitOpenException; +use Stfn\CircuitBreaker\Storage\InMemoryStorage; +use Stfn\CircuitBreaker\Storage\RedisStorage; class CircuitBreakerTest extends TestCase { @@ -29,6 +31,8 @@ public function test_if_it_can_handle_function_success() }); $this->assertEquals($object, $result); + + $this->assertTrue($breaker->isClosed()); } public function test_if_it_will_throw_an_exception_if_circuit_breaker_is_open() @@ -121,6 +125,18 @@ public function test_if_it_will_transit_back_to_open_state_after_first_fail() $this->assertTrue($breaker->isOpen()); } + public function test_if_it_will_transit_to_half_open_state_after_recovery_time() + { + $breaker = CircuitBreaker::for('test')->withOptions(['recovery_time' => 1]); + $breaker->openCircuit(); + + sleep(2); + + $breaker->call(fn () => true); + + $this->assertEquals(CircuitState::HalfOpen, $breaker->getStorage()->getState()); + } + public function test_if_listener_is_called() { $object = new class () extends CircuitBreakerListener { @@ -250,4 +266,15 @@ public function onStateChange(CircuitBreaker $breaker, CircuitState $previousSta $this->assertEquals("closed->open,open->half_open,half_open->closed,closed->force_open,", $object->state); } + + public function test_if_it_can_set_a_new_storage() + { + $breaker = CircuitBreaker::for('test'); + + $this->assertInstanceOf(InMemoryStorage::class, $breaker->getStorage()); + + $breaker->storage(new RedisStorage(new \Redis())); + + $this->assertInstanceOf(RedisStorage::class, $breaker->getStorage()); + } } diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php index 838c5ff..9d58910 100644 --- a/tests/ConfigTest.php +++ b/tests/ConfigTest.php @@ -27,5 +27,7 @@ public function test_if_it_can_set_valid_config() $this->assertEquals($setup['failure_threshold'], $config->failureThreshold); $this->assertEquals($setup['recovery_time'], $config->recoveryTime); $this->assertEquals($setup['sample_duration'], $config->sampleDuration); + + $this->assertEquals($setup, $config->toArray()); } } diff --git a/tests/Storage/RedisStorageTest.php b/tests/Storage/RedisStorageTest.php new file mode 100644 index 0000000..d51625e --- /dev/null +++ b/tests/Storage/RedisStorageTest.php @@ -0,0 +1,108 @@ +getRedisInstance()); + + $storage->init(CircuitBreaker::for('test')); + + $this->assertEquals(CircuitState::Closed, $storage->getState()); + } + + public function test_if_set_state_will_change_value() + { + $storage = new RedisStorage($this->getRedisInstance()); + + $storage->init(CircuitBreaker::for('test')); + + $this->assertEquals(CircuitState::Closed, $storage->getState()); + + $storage->setState(CircuitState::HalfOpen); + + $this->assertEquals(CircuitState::HalfOpen, $storage->getState()); + } + + public function test_if_increment_failure_will_increase_number_of_failures() + { + $storage = new RedisStorage($this->getRedisInstance()); + + $storage->init(CircuitBreaker::for('test')); + + $this->assertEquals(0, $storage->getNumberOfFailures()); + + $storage->incrementFailure(); + + $this->assertEquals(1, $storage->getNumberOfFailures()); + + $storage->incrementFailure(); + + + $this->assertEquals(2, $storage->getNumberOfFailures()); + } + + public function test_if_reset_counter_will_remove_fail_count() + { + $storage = new RedisStorage($this->getRedisInstance()); + $storage->init(CircuitBreaker::for('test')); + + $storage->incrementFailure(); + $storage->incrementFailure(); + $storage->incrementFailure(); + + $this->assertEquals(3, $storage->getNumberOfFailures()); + + $storage->resetCounter(); + + $this->assertEquals(0, $storage->getNumberOfFailures()); + } + + public function test_transition_to_open_state() + { + $storage = new RedisStorage($this->getRedisInstance()); + $storage->init(CircuitBreaker::for('test')); + + $storage->open(); + + $this->assertEquals(CircuitState::Open, $storage->getState()); + $this->assertEquals(0, $storage->getNumberOfFailures()); + $this->assertNotEquals(0, $storage->openedAt()); + } + + public function test_transition_to_closed_state() + { + $storage = new RedisStorage($this->getRedisInstance()); + $storage->init(CircuitBreaker::for('test')); + + $storage->open(); + $storage->close(); + + $this->assertEquals(CircuitState::Closed, $storage->getState()); + $this->assertEquals(0, $storage->openedAt()); + } + + public function getRedisInstance() + { + if (! $this->redis) { + $this->redis = new \Redis(); + $this->redis->connect(getenv("REDIS_HOST")); + } + + return $this->redis; + } + + public function tearDown(): void + { + $this->getRedisInstance()->flushDB(); + } +}