diff --git a/.circleci/config.yml b/.circleci/config.yml index cb89900..5fc5a4b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2.1 defaults: &defaults - working_directory: ~/ruby-on-strum-gem-name + working_directory: ~/ruby-on-strum-healthcheck docker: - image: cimg/ruby:<< parameters.ruby-version >> @@ -44,12 +44,12 @@ references: use_latest_gemspec: &use_latest_gemspec run: name: Using latest gemspec - command: cp .circleci/gemspecs/latest on_strum-gem_name.gemspec + command: cp .circleci/gemspecs/latest on_strum-healthcheck.gemspec use_compatible_gemspec: &use_compatible_gemspec run: name: Using compatible gemspec - command: cp .circleci/gemspecs/compatible on_strum-gem_name.gemspec + command: cp .circleci/gemspecs/compatible on_strum-healthcheck.gemspec jobs: linters-ruby: @@ -119,7 +119,7 @@ jobs: - store_artifacts: name: Saving Simplecov coverage artifacts - path: ~/ruby-on-strum-gem-name/coverage + path: ~/ruby-on-strum-healthcheck/coverage destination: coverage - deploy: @@ -174,7 +174,7 @@ jobs: - add_ssh_keys: fingerprints: - - "SO:ME:FIN:G:ER:PR:IN:T" + - "SHA256:eJhlVtu2gws5rDavHcqZ5GJF/aS8kCctMprdC+Twlns" - run: name: Publishing new release diff --git a/.circleci/gemspecs/compatible b/.circleci/gemspecs/compatible index c1285a3..5cf6535 100644 --- a/.circleci/gemspecs/compatible +++ b/.circleci/gemspecs/compatible @@ -1,21 +1,25 @@ # frozen_string_literal: true -require_relative 'lib/on_strum/logs/version' +require_relative 'lib/on_strum/healthcheck/version' Gem::Specification.new do |spec| - spec.name = 'on_strum-gem_name' - spec.version = OnStrum::GemName::VERSION + spec.name = 'on_strum-healthcheck' + spec.version = OnStrum::Healthcheck::VERSION spec.authors = ['Vladislav Trotsenko'] spec.email = %w[admin@on-strum.org] - spec.summary = %(on_strum-gem_name) - spec.description = %(on_strum-gem_name description) - spec.homepage = 'https://github.com/on-strum/ruby-on-strum-gem-name' + spec.summary = %(Simple configurable application healthcheck rack middleware) + spec.description = %(Simple configurable application healthcheck rack middleware.) + spec.homepage = 'https://github.com/on-strum/ruby-on-strum-healthcheck' spec.license = 'MIT' spec.required_ruby_version = '>= 2.5.0' spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } spec.require_paths = %w[lib] + spec.add_runtime_dependency 'rack', '>= 2.0.1' + + spec.add_development_dependency 'ffaker' + spec.add_development_dependency 'json_matchers' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec' end diff --git a/.circleci/gemspecs/latest b/.circleci/gemspecs/latest index b9a5c16..061cd2b 100644 --- a/.circleci/gemspecs/latest +++ b/.circleci/gemspecs/latest @@ -1,28 +1,32 @@ # frozen_string_literal: true -require_relative 'lib/on_strum/logs/version' +require_relative 'lib/on_strum/healthcheck/version' Gem::Specification.new do |spec| - spec.name = 'on_strum-gem_name' - spec.version = OnStrum::GemName::VERSION + spec.name = 'on_strum-healthcheck' + spec.version = OnStrum::Healthcheck::VERSION spec.authors = ['Vladislav Trotsenko'] spec.email = %w[admin@on-strum.org] - spec.summary = %(on_strum-gem_name) - spec.description = %(on_strum-gem_name description) - spec.homepage = 'https://github.com/on-strum/ruby-on-strum-gem-name' + spec.summary = %(Simple configurable application healthcheck rack middleware) + spec.description = %(Simple configurable application healthcheck rack middleware.) + spec.homepage = 'https://github.com/on-strum/ruby-on-strum-healthcheck' spec.license = 'MIT' spec.required_ruby_version = '>= 2.5.0' spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } spec.require_paths = %w[lib] + spec.add_runtime_dependency 'rack', '>= 2.0.1' + spec.add_development_dependency 'bundler-audit', '~> 0.9.1' spec.add_development_dependency 'fasterer', '~> 0.11.0' + spec.add_development_dependency 'ffaker', '~> 2.23' + spec.add_development_dependency 'json_matchers', '~> 0.11.1' spec.add_development_dependency 'pry-byebug', '~> 3.10', '>= 3.10.1' spec.add_development_dependency 'rake', '~> 13.1' spec.add_development_dependency 'reek', '~> 6.3' spec.add_development_dependency 'rspec', '~> 3.13' - spec.add_development_dependency 'rubocop', '~> 1.61' + spec.add_development_dependency 'rubocop', '~> 1.62', '>= 1.62.1' spec.add_development_dependency 'rubocop-performance', '~> 1.20', '>= 1.20.2' spec.add_development_dependency 'rubocop-rspec', '~> 2.27', '>= 2.27.1' spec.add_development_dependency 'simplecov', '~> 0.22.0' diff --git a/.circleci/linter_configs/.commitspell.yml b/.circleci/linter_configs/.commitspell.yml index 8c9288c..0f6cd51 100644 --- a/.circleci/linter_configs/.commitspell.yml +++ b/.circleci/linter_configs/.commitspell.yml @@ -19,7 +19,9 @@ words: - codebases - codeclimate - commitspell + - creds - gemspecs + - healthcheck - lefthook - markdownlint - rubocop diff --git a/.circleci/linter_configs/.cspell.yml b/.circleci/linter_configs/.cspell.yml index 9bf8aee..c6264c6 100644 --- a/.circleci/linter_configs/.cspell.yml +++ b/.circleci/linter_configs/.cspell.yml @@ -19,11 +19,13 @@ languageSettings: - MarkdownCodeBlock words: - - Commiting - - Nazarov - Serhiy - - Vladislav - - Trotsenko + - Nazarov + - commiting - codebases - gemspecs - - onStrum + - onstrum + - healthcheck + - healthchecks + - roda + - hanami diff --git a/.codeclimate.yml b/.codeclimate.yml index e0fa878..b75f1ac 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -9,7 +9,7 @@ checks: plugins: rubocop: enabled: true - channel: rubocop-1-61 + channel: rubocop-1-62 config: file: .circleci/linter_configs/.rubocop.yml diff --git a/.github/DEVELOPMENT_ENVIRONMENT_GUIDE.md b/.github/DEVELOPMENT_ENVIRONMENT_GUIDE.md index 41b6ba8..7cb3f52 100644 --- a/.github/DEVELOPMENT_ENVIRONMENT_GUIDE.md +++ b/.github/DEVELOPMENT_ENVIRONMENT_GUIDE.md @@ -2,10 +2,10 @@ ## Preparing -Clone `ruby-on-strum-gem-name` repository: +Clone `ruby-on-strum-healthcheck` repository: ```bash -git clone https://github.com/on-strum/ruby-on-strum-gem-name.git +git clone https://github.com/on-strum/ruby-on-strum-healthcheck.git cd ruby-gem ``` @@ -13,14 +13,14 @@ Configure latest Ruby environment: ```bash echo 'ruby-3.2.0' > .ruby-version -cp .circleci/gemspec_latest on_strum-gem_name.gemspec +cp .circleci/gemspec_latest on_strum-healthcheck.gemspec ``` ## Commiting -Commit your changes excluding `.ruby-version`, `on_strum-gem_name.gemspec` +Commit your changes excluding `.ruby-version`, `on_strum-healthcheck.gemspec` ```bash -git add . ':!.ruby-version' ':!on_strum-gem_name.gemspec' -git commit -m 'Your new awesome on_strum-gem_name feature' +git add . ':!.ruby-version' ':!on_strum-healthcheck.gemspec' +git commit -m 'Your new awesome on_strum-healthcheck feature' ``` diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 5e1f176..f46ee2b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,21 +7,21 @@ assignees: Serhiy-Nazarov --- - + ### New bug checklist -- [ ] I have updated `on_strum-gem_name` to the latest version -- [ ] I have read the [Contribution Guidelines](https://github.com/on-strum/ruby-on-strum-gem-name/blob/master/CONTRIBUTING.md) -- [ ] I have read the [documentation](https://github.com/on-strum/ruby-on-strum-gem-name/blob/master/README.md) -- [ ] I have searched for [existing GitHub issues](https://github.com/on-strum/ruby-on-strum-gem-name/issues) +- [ ] I have updated `on_strum-healthcheck` to the latest version +- [ ] I have read the [Contribution Guidelines](https://github.com/on-strum/ruby-on-strum-healthcheck/blob/master/CONTRIBUTING.md) +- [ ] I have read the [documentation](https://github.com/on-strum/ruby-on-strum-healthcheck/blob/master/README.md) +- [ ] I have searched for [existing GitHub issues](https://github.com/on-strum/ruby-on-strum-healthcheck/issues) ### Bug description -##### Complete output when running `on_strum-gem_name`, including the stack trace and command used +##### Complete output when running `on_strum-healthcheck`, including the stack trace and command used
[INSERT OUTPUT HERE]
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 748eb34..a9b9890 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -7,14 +7,14 @@ assignees: Serhiy-Nazarov --- - + ### New feature request checklist -- [ ] I have updated `on_strum-gem_name` to the latest version -- [ ] I have read the [Contribution Guidelines](https://github.com/on-strum/ruby-on-strum-gem-name/blob/master/CONTRIBUTING.md) -- [ ] I have read the [documentation](https://github.com/on-strum/ruby-on-strum-gem-name/blob/master/README.md) -- [ ] I have searched for [existing GitHub issues](https://github.com/on-strum/ruby-on-strum-gem-name/issues) +- [ ] I have updated `on_strum-healthcheck` to the latest version +- [ ] I have read the [Contribution Guidelines](https://github.com/on-strum/ruby-on-strum-healthcheck/blob/master/CONTRIBUTING.md) +- [ ] I have read the [documentation](https://github.com/on-strum/ruby-on-strum-healthcheck/blob/master/README.md) +- [ ] I have searched for [existing GitHub issues](https://github.com/on-strum/ruby-on-strum-healthcheck/issues) diff --git a/.github/ISSUE_TEMPLATE/issue_report.md b/.github/ISSUE_TEMPLATE/issue_report.md index 324df6f..219e7d6 100644 --- a/.github/ISSUE_TEMPLATE/issue_report.md +++ b/.github/ISSUE_TEMPLATE/issue_report.md @@ -7,21 +7,21 @@ assignees: Serhiy-Nazarov --- - + ### New issue checklist -- [ ] I have updated `on_strum-gem_name` to the latest version -- [ ] I have read the [Contribution Guidelines](https://github.com/on-strum/ruby-on-strum-gem-name/blob/master/CONTRIBUTING.md) -- [ ] I have read the [documentation](https://github.com/on-strum/ruby-on-strum-gem-name/blob/master/README.md) -- [ ] I have searched for [existing GitHub issues](https://github.com/on-strum/ruby-on-strum-gem-name/issues) +- [ ] I have updated `on_strum-healthcheck` to the latest version +- [ ] I have read the [Contribution Guidelines](https://github.com/on-strum/ruby-on-strum-healthcheck/blob/master/CONTRIBUTING.md) +- [ ] I have read the [documentation](https://github.com/on-strum/ruby-on-strum-healthcheck/blob/master/README.md) +- [ ] I have searched for [existing GitHub issues](https://github.com/on-strum/ruby-on-strum-healthcheck/issues) ### Issue description -##### Complete output when running `on_strum-gem_name`, including the stack trace and command used +##### Complete output when running `on_strum-healthcheck`, including the stack trace and command used
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 3a5c3b8..07e5d22 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -7,13 +7,13 @@ assignees: Serhiy-Nazarov --- - + ### New question checklist -- [ ] I have read the [Contribution Guidelines](https://github.com/on-strum/ruby-on-strum-gem-name/blob/master/CONTRIBUTING.md) -- [ ] I have read the [documentation](https://github.com/on-strum/ruby-on-strum-gem-name/blob/master/README.md) -- [ ] I have searched for [existing GitHub issues](https://github.com/on-strum/ruby-on-strum-gem-name/issues) +- [ ] I have read the [Contribution Guidelines](https://github.com/on-strum/ruby-on-strum-healthcheck/blob/master/CONTRIBUTING.md) +- [ ] I have read the [documentation](https://github.com/on-strum/ruby-on-strum-healthcheck/blob/master/README.md) +- [ ] I have searched for [existing GitHub issues](https://github.com/on-strum/ruby-on-strum-healthcheck/issues) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2165d4b..0788a5b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -43,7 +43,7 @@ - [ ] My code follows the code style of this project - [ ] My change requires a change to the documentation - [ ] I have updated the documentation accordingly -- [ ] I have read the [**CONTRIBUTING** document](https://github.com/on-strum/ruby-on-strum-gem-name/blob/master/CONTRIBUTING.md) +- [ ] I have read the [**CONTRIBUTING** document](https://github.com/on-strum/ruby-on-strum-healthcheck/blob/master/CONTRIBUTING.md) - [ ] I have added tests to cover my changes - [ ] I have run `bundle exec rspec` from the root directory to see all new and existing tests pass - [ ] I have run `rubocop` and `reek` to ensure the code style is valid diff --git a/.reek.yml b/.reek.yml index 8207a07..61a6327 100644 --- a/.reek.yml +++ b/.reek.yml @@ -4,5 +4,36 @@ detectors: IrresponsibleModule: enabled: false + ControlParameter: + exclude: + - OnStrum::Healthcheck::Configuration#raise_unless + - OnStrum::Healthcheck::Configuration#validator_argument_type + + LongParameterList: + exclude: + - OnStrum::Healthcheck::Configuration#raise_unless + + ManualDispatch: + exclude: + - OnStrum::Healthcheck::Configuration#validator_services_callable + + TooManyConstants: + exclude: + - OnStrum::Healthcheck::Configuration + + TooManyStatements: + exclude: + - OnStrum::Healthcheck::Configuration#validate_attribute + + UtilityFunction: + exclude: + - OnStrum::Healthcheck::Configuration#build_configuration_settings + - OnStrum::Healthcheck::Configuration#validator_argument_type + - OnStrum::Healthcheck::Configuration#validator_endpoint + - OnStrum::Healthcheck::Configuration#validator_http_status_failure + - OnStrum::Healthcheck::Configuration#validator_http_status_success + - OnStrum::Healthcheck::Configuration#validator_services_callable + - OnStrum::Healthcheck::Resolver#configuration + exclude_paths: - spec/support/helpers diff --git a/.ruby-gemset b/.ruby-gemset index 0aaf669..7ef10a2 100644 --- a/.ruby-gemset +++ b/.ruby-gemset @@ -1 +1 @@ -on_strum-gem_name +on_strum-healthcheck diff --git a/CHANGELOG.md b/CHANGELOG.md index 061a02e..2f8c4c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.1.0] - 2023-xx-xx +## [0.1.0] - 2024-03-26 ### Added -- First release of `on_strum-gem_name`. +- First release of `on_strum-healthcheck`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9bdbec5..e2303f9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to `on_strum-gem_name` +# Contributing to `on_strum-healthcheck` Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved. @@ -16,7 +16,7 @@ Guidelines for issue/bug reports: 1. **Use the GitHub issue search** — check if the issue has already been reported 2. **Check if the issue has been fixed** — try to reproduce it using the latest `master` or `develop` branch in the repository -3. `on_strum-gem_name` [issue template](.github/ISSUE_TEMPLATE/issue_report.md)/[bug template](.github/ISSUE_TEMPLATE/bug_report.md) +3. `on_strum-healthcheck` [issue template](.github/ISSUE_TEMPLATE/issue_report.md)/[bug template](.github/ISSUE_TEMPLATE/bug_report.md) A good bug report shouldn't leave others needing to chase you up for more information. Please try to be as detailed as possible in your report. What is your environment? What steps will reproduce the issue? What would you expect to be the outcome? All these details will help people to fix any potential bugs. @@ -38,7 +38,7 @@ Please adhere to the coding conventions used throughout a project (indentation, Guidelines for pull requests: -1. `on_strum-gem_name` [pull request template](.github/PULL_REQUEST_TEMPLATE.md) +1. `on_strum-healthcheck` [pull request template](.github/PULL_REQUEST_TEMPLATE.md) 2. Fork the repo, checkout to `develop` branch 3. Run the tests. This is to make sure your starting point works 4. Read our [branch naming convention](.github/BRANCH_NAMING_CONVENTION.md) diff --git a/README.md b/README.md index 963f54e..d094531 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,26 @@ -# `on_strum-gem_name` - Short description of this gem - -[![Maintainability](https://api.codeclimate.com/v1/badges/codeclimate_id/maintainability)](https://codeclimate.com/github/on-strum/ruby-on-strum-gem-name/maintainability) -[![Test Coverage](https://api.codeclimate.com/v1/badges/codeclimate_id/test_coverage)](https://codeclimate.com/github/on-strum/ruby-on-strum-gem-name/test_coverage) -[![CircleCI](https://circleci.com/gh/on-strum/ruby-on-strum-gem-name/tree/master.svg?style=svg)](https://circleci.com/gh/on-strum/ruby-on-strum-gem-name/tree/master) -[![Gem Version](https://badge.fury.io/rb/ruby_gem_name.svg)](https://badge.fury.io/rb/ruby_gem_name) -[![Downloads](https://img.shields.io/gem/dt/ruby_gem_name.svg?colorA=004d99&colorB=0073e6)](https://rubygems.org/gems/ruby_gem_name) -[![GitHub](https://img.shields.io/github/license/on-strum/ruby-on-strum-gem-name)](LICENSE.txt) +# ![OnStrum::Healthcheck - Simple configurable application healthcheck rack middleware](https://repository-images.githubusercontent.com/769335579/9094eefe-bfc8-483d-9a65-e406d1eb92c6) + +[![Maintainability](https://api.codeclimate.com/v1/badges/b4dc21883d489d67fbef/maintainability)](https://codeclimate.com/github/on-strum/ruby-on-strum-healthcheck/maintainability) +[![Test Coverage](https://api.codeclimate.com/v1/badges/b4dc21883d489d67fbef/test_coverage)](https://codeclimate.com/github/on-strum/ruby-on-strum-healthcheck/test_coverage) +[![CircleCI](https://circleci.com/gh/on-strum/ruby-on-strum-healthcheck/tree/master.svg?style=svg)](https://circleci.com/gh/on-strum/ruby-on-strum-healthcheck/tree/master) +[![Gem Version](https://badge.fury.io/rb/ruby_healthcheck.svg)](https://badge.fury.io/rb/ruby_healthcheck) +[![Downloads](https://img.shields.io/gem/dt/ruby_healthcheck.svg?colorA=004d99&colorB=0073e6)](https://rubygems.org/gems/ruby_healthcheck) +[![GitHub](https://img.shields.io/github/license/on-strum/ruby-on-strum-healthcheck)](LICENSE.txt) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v1.4%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md) -Full description of this gem here... +Simple configurable application healthcheck rack middleware. This middleware allows you to embed healthcheck endpoints into your rack based application to perform healthcheck probes. Make your application compatible with [Docker](https://docs.docker.com/reference/dockerfile/#healthcheck)/[Kubernetes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-a-liveness-http-request) healthchecks in a seconds. ## Table of Contents - [Features](#features) - [Requirements](#requirements) - [Installation](#installation) +- [Configuring](#configuring) - [Usage](#usage) + - [Rack](#rack) + - [Roda](#roda) + - [Hanami](#hanami) + - [Rails](#rails) - [Contributing](#contributing) - [License](#license) - [Code of Conduct](#code-of-conduct) @@ -25,7 +30,11 @@ Full description of this gem here... ## Features -The list of features here... +- Built-in default configuration +- Configurable services for startup/liveness/readiness probes +- Configurable root endpoints namespace +- Configurable startup/liveness/readiness probes endpoints +- Configurable successful/failure response statuses ## Requirements @@ -36,7 +45,7 @@ Ruby MRI 2.5.0+ Add this line to your application's Gemfile: ```ruby -gem 'on_strum-gem_name' +gem 'on_strum-healthcheck' ``` And then execute: @@ -48,16 +57,154 @@ bundle Or install it yourself as: ```bash -gem install on_strum-gem_name +gem install on_strum-healthcheck +``` + +## Configuring + +To start working with this gem, you must configure it first as in the example below: + +```ruby +# config/initializers/on_strum_healthcheck.rb + +require 'on_strum/healthcheck' + +OnStrum::Healthcheck.configure do |config| + # Optional parameter. The list of services that can be triggered + # during running probes. Each value of this hash should be callable + # and return boolean. + # It is equal to empty hash by default. + config.services = { + postges: -> { true }, + redis: -> { true }, + rabbit: -> { false } + } + + # Optional parameter. The list of services that will be checked + # during running startup probe. As array items must be used an + # existing keys, defined in config.services. + # It is equal to empty array by default. + config.services_startup = %i[postges] + + # Optional parameter. The list of services that will be checked + # during running liveness probe. As array items must be used an + # existing keys, defined in config.services. + # It is equal to empty array by default. + config.services_liveness = %i[redis] + + # Optional parameter. The list of services that will be checked + # during running liveness probe. As array items must be used an + # existing keys, defined in config.services. + # It is equal to empty array by default. + config.services_readiness = %i[postges redis rabbit] + + # Optional parameter. The name of middleware's root + # endpoints namespace. Use '/' if you want to use root + # namespace. It is equal to /healthcheck by default. + config.endpoints_namespace = '/application-healthcheck' + + # Optional parameter. The startup endpoint path. + # It is equal to /startup by default. + config.endpoint_startup = '/startup-probe' + + # Optional parameter. The liveness endpoint path. + # It is equal to /liveness by default. + config.endpoint_liveness = '/liveness-probe' + + # Optional parameter. The readiness endpoint path. + # It is equal to /readiness by default. + config.endpoint_readiness = '/readiness-probe' + + # Optional parameter. The HTTP successful status + # for startup probe. It is equal to 200 by default. + config.endpoint_startup_status_success = 201 + + # Optional parameter. The HTTP successful status + # for liveness probe. It is equal to 200 by default. + config.endpoint_liveness_status_success = 202 + + # Optional parameter. The HTTP successful status + # for readiness probe. It is equal to 200 by default. + config.endpoint_readiness_status_success = 203 + + # Optional parameter. The HTTP failure status + # for startup probe. It is equal to 500 by default. + config.endpoint_startup_status_failure = 501 + + # Optional parameter. The HTTP failure status + # for liveness probe. It is equal to 500 by default. + config.endpoint_liveness_status_failure = 502 + + # Optional parameter. The HTTP failure status + # for readiness probe. It is equal to 500 by default. + config.endpoint_readiness_status_failure = 503 +end ``` ## Usage -Use cases of this gem here... +Please note, to start using this middleware you should configure `OnStrum::Healthcheck` before and then you should to add `OnStrum::Healthcheck::RackMiddleware` on the top of middlewares list. + +### Rack + +```ruby +require 'rack' +require 'on_strum/healthcheck' + +OnStrum::Healthcheck.configure + +RackCascade = Rack::Builder.app do + use OnStrum::Healthcheck::RackMiddleware + run YourApplication +end +``` + +### Roda + +```ruby +require 'roda' +require 'on_strum/healthcheck' + +OnStrum::Healthcheck.configure + +class YourApplication < Roda + use OnStrum::Healthcheck::RackMiddleware +end +``` + +### Hanami + +```ruby +# config/initializers/on_strum_healthcheck.rb + +require 'on_strum/healthcheck' + +OnStrum::Healthcheck.configure + +# config/environment.rb + +Hanami.configure do + middleware.use MyRackMiddleware +end +``` + +### Rails + +```ruby +# config/initializers/on_strum_healthcheck.rb + +require 'on_strum/healthcheck' + +OnStrum::Healthcheck.configure + +# config/application.rb + +config.middleware.use OnStrum::Healthcheck::RackMiddleware +``` ## Contributing -Bug reports and pull requests are welcome on GitHub at . This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. Please check the [open tickets](https://github.com/on-strum/ruby-on-strum-gem-name/issues). Be sure to follow Contributor Code of Conduct below and our [Contributing Guidelines](CONTRIBUTING.md). +Bug reports and pull requests are welcome on GitHub at . This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. Please check the [open tickets](https://github.com/on-strum/ruby-on-strum-healthcheck/issues). Be sure to follow Contributor Code of Conduct below and our [Contributing Guidelines](CONTRIBUTING.md). ## License @@ -65,13 +212,13 @@ The gem is available as open source under the terms of the [MIT License](https:/ ## Code of Conduct -Everyone interacting in the `on_strum-gem_name` project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md). +Everyone interacting in the `on_strum-healthcheck` project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md). ## Credits -- [The Contributors](https://github.com/on-strum/ruby-on-strum-gem-name/graphs/contributors) for code and awesome suggestions -- [The Stargazers](https://github.com/on-strum/ruby-on-strum-gem-name/stargazers) for showing their support +- [The Contributors](https://github.com/on-strum/ruby-on-strum-healthcheck/graphs/contributors) for code and awesome suggestions +- [The Stargazers](https://github.com/on-strum/ruby-on-strum-healthcheck/stargazers) for showing their support ## Versioning -`on_strum-gem_name` uses [Semantic Versioning 2.0.0](https://semver.org) +`on_strum-healthcheck` uses [Semantic Versioning 2.0.0](https://semver.org) diff --git a/bin/console b/bin/console index d1da0ec..e4bc288 100755 --- a/bin/console +++ b/bin/console @@ -2,7 +2,7 @@ # frozen_string_literal: true require 'bundler/setup' -require 'on_strum/logs' +require 'on_strum/healthcheck' # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. diff --git a/lib/on_strum/gem_name.rb b/lib/on_strum/gem_name.rb deleted file mode 100644 index 0eb61c7..0000000 --- a/lib/on_strum/gem_name.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -require_relative 'logs/core' - -# module OnStrum -# module GemName -# end -# end diff --git a/lib/on_strum/gem_name/core.rb b/lib/on_strum/gem_name/core.rb deleted file mode 100644 index e209e56..0000000 --- a/lib/on_strum/gem_name/core.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -module OnStrum - module GemName - require_relative 'version' - end -end diff --git a/lib/on_strum/healthcheck.rb b/lib/on_strum/healthcheck.rb new file mode 100644 index 0000000..9b83bbe --- /dev/null +++ b/lib/on_strum/healthcheck.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require_relative 'healthcheck/core' + +module OnStrum + module Healthcheck + class << self + def configuration(&block) + @configuration ||= begin + return unless block + + OnStrum::Healthcheck::Configuration.new(&block) + end + end + + def configure(&block) + configuration(&block) + end + + def reset_configuration! + @configuration = nil + end + end + end +end diff --git a/lib/on_strum/healthcheck/configuration.rb b/lib/on_strum/healthcheck/configuration.rb new file mode 100644 index 0000000..b0999b3 --- /dev/null +++ b/lib/on_strum/healthcheck/configuration.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +module OnStrum + module Healthcheck + class Configuration + ATTRIBUTES = %i[ + services + services_startup + services_liveness + services_readiness + endpoints_namespace + endpoint_startup + endpoint_liveness + endpoint_readiness + endpoint_startup_status_success + endpoint_liveness_status_success + endpoint_readiness_status_success + endpoint_startup_status_failure + endpoint_liveness_status_failure + endpoint_readiness_status_failure + ].freeze + ENDPOINTS_NAMESPACE = '/healthcheck' + ENDPOINT_STARTUP = '/startup' + ENDPOINT_LIVENESS = '/liveness' + ENDPOINT_READINESS = '/readiness' + DEFAULT_HTTP_STATUS_SUCCESS = 200 + DEFAULT_HTTP_STATUS_FAILURE = 500 + AVILABLE_HTTP_STATUSES_SUCCESS = (DEFAULT_HTTP_STATUS_SUCCESS..226).freeze + AVILABLE_HTTP_STATUSES_FAILURE = (DEFAULT_HTTP_STATUS_FAILURE..511).freeze + + Settings = ::Struct.new(*OnStrum::Healthcheck::Configuration::ATTRIBUTES, keyword_init: true) do + def update(&block) + return self unless block + + tap(&block) + end + end + + attr_reader(*OnStrum::Healthcheck::Configuration::ATTRIBUTES) + + def initialize(&block) + configuration_settings = build_configuration_settings(&block) + OnStrum::Healthcheck::Configuration::ATTRIBUTES.each do |attribute| + public_send(:"#{attribute}=", configuration_settings.public_send(attribute)) + end + end + + OnStrum::Healthcheck::Configuration::ATTRIBUTES.each do |attribute| + define_method(:"#{attribute}=") do |argument| + validate_attribute(__method__, attribute, argument) + instance_variable_set(:"@#{attribute}", argument) + end + end + + private + + def build_configuration_settings(&block) # rubocop:disable Metrics/MethodLength + OnStrum::Healthcheck::Configuration::Settings.new( + services: {}, + services_startup: [], + services_liveness: [], + services_readiness: [], + endpoints_namespace: OnStrum::Healthcheck::Configuration::ENDPOINTS_NAMESPACE, + endpoint_startup: OnStrum::Healthcheck::Configuration::ENDPOINT_STARTUP, + endpoint_liveness: OnStrum::Healthcheck::Configuration::ENDPOINT_LIVENESS, + endpoint_readiness: OnStrum::Healthcheck::Configuration::ENDPOINT_READINESS, + endpoint_startup_status_success: OnStrum::Healthcheck::Configuration::DEFAULT_HTTP_STATUS_SUCCESS, + endpoint_liveness_status_success: OnStrum::Healthcheck::Configuration::DEFAULT_HTTP_STATUS_SUCCESS, + endpoint_readiness_status_success: OnStrum::Healthcheck::Configuration::DEFAULT_HTTP_STATUS_SUCCESS, + endpoint_startup_status_failure: OnStrum::Healthcheck::Configuration::DEFAULT_HTTP_STATUS_FAILURE, + endpoint_liveness_status_failure: OnStrum::Healthcheck::Configuration::DEFAULT_HTTP_STATUS_FAILURE, + endpoint_readiness_status_failure: OnStrum::Healthcheck::Configuration::DEFAULT_HTTP_STATUS_FAILURE + ).update(&block) + end + + def validate_attribute(method, attribute, value) # rubocop:disable Metrics/AbcSize + raise_unless(OnStrum::Healthcheck::Error::Configuration::ArgumentType, method, *validator_argument_type(attribute, value)) + case attribute + when OnStrum::Healthcheck::Configuration::ATTRIBUTES[0] + raise_unless(OnStrum::Healthcheck::Error::Configuration::NotCallableService, method, *validator_services_callable(value)) + when *OnStrum::Healthcheck::Configuration::ATTRIBUTES[1..3] + raise_unless(OnStrum::Healthcheck::Error::Configuration::UnknownService, method, *validator_services_conformity(value)) + when *OnStrum::Healthcheck::Configuration::ATTRIBUTES[4..7] + raise_unless(OnStrum::Healthcheck::Error::Configuration::EnpointPattern, method, *validator_endpoint(value)) + when *OnStrum::Healthcheck::Configuration::ATTRIBUTES[8..10] + raise_unless(OnStrum::Healthcheck::Error::Configuration::HttpStatusSuccess, method, *validator_http_status_success(value)) + when *OnStrum::Healthcheck::Configuration::ATTRIBUTES[11..13] + raise_unless(OnStrum::Healthcheck::Error::Configuration::HttpStatusFailure, method, *validator_http_status_failure(value)) + end + end + + def validator_argument_type(method_name, argument) + [ + argument, + argument.is_a?( + case method_name + when :services then ::Hash + when *OnStrum::Healthcheck::Configuration::ATTRIBUTES[1..3] then ::Array + when *OnStrum::Healthcheck::Configuration::ATTRIBUTES[4..7] then ::String + when *OnStrum::Healthcheck::Configuration::ATTRIBUTES[8..13] then ::Integer + end + ) + ] + end + + def validator_endpoint(argument) + [argument, argument[%r{\A/.*\z}]] + end + + def validator_http_status_success(argument) + [argument, OnStrum::Healthcheck::Configuration::AVILABLE_HTTP_STATUSES_SUCCESS.include?(argument)] + end + + def validator_http_status_failure(argument) + [argument, OnStrum::Healthcheck::Configuration::AVILABLE_HTTP_STATUSES_FAILURE.include?(argument)] + end + + def validator_services_callable(services_to_check, target_service = nil) + result = services_to_check.all? do |service_name, service_context| + target_service = service_name + service_context.respond_to?(:call) + end + + [target_service, result] + end + + def validator_services_conformity(services_to_check, target_service = nil) + result = services_to_check.all? do |service_name| + target_service = service_name + services.key?(service_name) + end + + [target_service, result] + end + + def raise_unless(exception_class, argument_name, argument_context, condition) + raise exception_class.new(argument_context, argument_name) unless condition + end + end + end +end diff --git a/lib/on_strum/healthcheck/core.rb b/lib/on_strum/healthcheck/core.rb new file mode 100644 index 0000000..b447345 --- /dev/null +++ b/lib/on_strum/healthcheck/core.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module OnStrum + module Healthcheck + module Error + module Configuration + require_relative 'error/configuration/argument_type' + require_relative 'error/configuration/unknown_service' + require_relative 'error/configuration/not_callable_service' + require_relative 'error/configuration/enpoint_pattern' + require_relative 'error/configuration/http_status_success' + require_relative 'error/configuration/http_status_failure' + require_relative 'error/configuration/not_configured' + end + end + + require_relative 'version' + require_relative 'configuration' + require_relative 'resolver' + require_relative 'rack_middleware' + end +end diff --git a/lib/on_strum/healthcheck/error/configuration/argument_type.rb b/lib/on_strum/healthcheck/error/configuration/argument_type.rb new file mode 100644 index 0000000..fd43a5f --- /dev/null +++ b/lib/on_strum/healthcheck/error/configuration/argument_type.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module OnStrum + module Healthcheck + module Error + module Configuration + ArgumentType = ::Class.new(::ArgumentError) do + def initialize(arg_value, arg_name) + super("#{arg_value} is not a valid #{arg_name}") + end + end + end + end + end +end diff --git a/lib/on_strum/healthcheck/error/configuration/enpoint_pattern.rb b/lib/on_strum/healthcheck/error/configuration/enpoint_pattern.rb new file mode 100644 index 0000000..bec9039 --- /dev/null +++ b/lib/on_strum/healthcheck/error/configuration/enpoint_pattern.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module OnStrum + module Healthcheck + module Error + module Configuration + EnpointPattern = ::Class.new(::ArgumentError) do + def initialize(arg_value, arg_name) + super("#{arg_value} does not match a valid enpoint pattern for #{arg_name}") + end + end + end + end + end +end diff --git a/lib/on_strum/healthcheck/error/configuration/http_status_failure.rb b/lib/on_strum/healthcheck/error/configuration/http_status_failure.rb new file mode 100644 index 0000000..76bf8c2 --- /dev/null +++ b/lib/on_strum/healthcheck/error/configuration/http_status_failure.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module OnStrum + module Healthcheck + module Error + module Configuration + HttpStatusFailure = ::Class.new(::ArgumentError) do + def initialize(arg_value, arg_name) + super("Status #{arg_value} is wrong HTTP failure status for #{arg_name}. It should be in the range 500-511") + end + end + end + end + end +end diff --git a/lib/on_strum/healthcheck/error/configuration/http_status_success.rb b/lib/on_strum/healthcheck/error/configuration/http_status_success.rb new file mode 100644 index 0000000..eee5fbb --- /dev/null +++ b/lib/on_strum/healthcheck/error/configuration/http_status_success.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module OnStrum + module Healthcheck + module Error + module Configuration + HttpStatusSuccess = ::Class.new(::ArgumentError) do + def initialize(arg_value, arg_name) + super("Status #{arg_value} is wrong HTTP successful status for #{arg_name}. It should be in the range 200-226") + end + end + end + end + end +end diff --git a/lib/on_strum/healthcheck/error/configuration/not_callable_service.rb b/lib/on_strum/healthcheck/error/configuration/not_callable_service.rb new file mode 100644 index 0000000..a48c547 --- /dev/null +++ b/lib/on_strum/healthcheck/error/configuration/not_callable_service.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module OnStrum + module Healthcheck + module Error + module Configuration + NotCallableService = ::Class.new(::ArgumentError) do + def initialize(service_name, services_setter) + super("Service #{service_name} is not callable. All values for #{services_setter} should be a callable objects") + end + end + end + end + end +end diff --git a/lib/on_strum/healthcheck/error/configuration/not_configured.rb b/lib/on_strum/healthcheck/error/configuration/not_configured.rb new file mode 100644 index 0000000..83745e3 --- /dev/null +++ b/lib/on_strum/healthcheck/error/configuration/not_configured.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module OnStrum + module Healthcheck + module Error + module Configuration + NotConfigured = ::Class.new(::RuntimeError) do + def initialize + super('The configuration is empty. Please use OnStrum::Healthcheck.configure before') + end + end + end + end + end +end diff --git a/lib/on_strum/healthcheck/error/configuration/unknown_service.rb b/lib/on_strum/healthcheck/error/configuration/unknown_service.rb new file mode 100644 index 0000000..d5c1df7 --- /dev/null +++ b/lib/on_strum/healthcheck/error/configuration/unknown_service.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module OnStrum + module Healthcheck + module Error + module Configuration + UnknownService = ::Class.new(::ArgumentError) do + def initialize(service_name, services_setter) + super("Unknown #{service_name} service name for #{services_setter}. You should define it in config.services firstly") + end + end + end + end + end +end diff --git a/lib/on_strum/healthcheck/rack_middleware.rb b/lib/on_strum/healthcheck/rack_middleware.rb new file mode 100644 index 0000000..53a0edb --- /dev/null +++ b/lib/on_strum/healthcheck/rack_middleware.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module OnStrum + module Healthcheck + class RackMiddleware + def initialize( + app, + resolver = OnStrum::Healthcheck::Resolver, + counfigured = !!OnStrum::Healthcheck.configuration, + * + ) + @app = app + @resolver = resolver + @counfigured = counfigured + end + + def call(env) + raise OnStrum::Healthcheck::Error::Configuration::NotConfigured unless counfigured + + resolver.call(env) || app.call(env) + end + + private + + attr_reader :app, :resolver, :counfigured + end + end +end diff --git a/lib/on_strum/healthcheck/resolver.rb b/lib/on_strum/healthcheck/resolver.rb new file mode 100644 index 0000000..de8ce31 --- /dev/null +++ b/lib/on_strum/healthcheck/resolver.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +module OnStrum + module Healthcheck + class Resolver + require 'rack' + require 'securerandom' + require 'json' + + PROBE_ENDPOINTS = %i[endpoint_startup endpoint_liveness endpoint_readiness].freeze + CONTENT_TYPE = { 'Content-Type' => 'application/json' }.freeze + JSONAPI_RESPONSE_TYPE = 'application-healthcheck' + ROOT_NAMESPACE = '/' + + def self.call(rack_env) + new(rack_env).call + end + + def initialize(rack_env) + @request = ::Rack::Request.new(rack_env) + end + + def call + return unless probe_name + + [response_status, OnStrum::Healthcheck::Resolver::CONTENT_TYPE, [response_jsonapi]] + end + + private + + attr_reader :request + + def configuration + OnStrum::Healthcheck.configuration + end + + def root_namespace + @root_namespace ||= configuration.endpoints_namespace + end + + def root_namespace? + root_namespace.eql?(OnStrum::Healthcheck::Resolver::ROOT_NAMESPACE) + end + + OnStrum::Healthcheck::Resolver::PROBE_ENDPOINTS.each do |method| + define_method(method) do + target_endpoint = configuration.public_send(method) + root_namespace? ? target_endpoint : "#{root_namespace}#{target_endpoint}" + end + end + + def probe_name + @probe_name ||= + case request.path + when endpoint_startup then :startup + when endpoint_liveness then :liveness + when endpoint_readiness then :readiness + end + end + + def probe_result + @probe_result ||= + configuration.public_send(:"services_#{probe_name}").each_with_object({}) do |service_name, response| + response[service_name] = configuration.services[service_name].call + end + end + + def response_status + configuration.public_send( + probe_result.values.all? ? :"endpoint_#{probe_name}_status_success" : :"endpoint_#{probe_name}_status_failure" + ) + end + + def response_jsonapi + { + data: { + id: ::SecureRandom.uuid, + type: OnStrum::Healthcheck::Resolver::JSONAPI_RESPONSE_TYPE, + attributes: probe_result + } + }.to_json + end + end + end +end diff --git a/lib/on_strum/gem_name/version.rb b/lib/on_strum/healthcheck/version.rb similarity index 78% rename from lib/on_strum/gem_name/version.rb rename to lib/on_strum/healthcheck/version.rb index 9846461..b3d3c1d 100644 --- a/lib/on_strum/gem_name/version.rb +++ b/lib/on_strum/healthcheck/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module OnStrum - module GemName + module Healthcheck VERSION = '0.1.0' end end diff --git a/on_strum-healthcheck.gemspec b/on_strum-healthcheck.gemspec new file mode 100644 index 0000000..4b544d2 --- /dev/null +++ b/on_strum-healthcheck.gemspec @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require_relative 'lib/on_strum/healthcheck/version' + +Gem::Specification.new do |spec| + spec.name = 'on_strum-healthcheck' + spec.version = OnStrum::Healthcheck::VERSION + spec.authors = ['Vladislav Trotsenko'] + spec.email = %w[admin@on-strum.org] + spec.summary = %(Simple configurable application healthcheck rack middleware) + spec.description = %(Simple configurable application healthcheck rack middleware.) + spec.homepage = 'https://github.com/on-strum/ruby-on-strum-healthcheck' + spec.license = 'MIT' + + current_ruby_version = ::Gem::Version.new(::RUBY_VERSION) + ffaker_version = current_ruby_version >= ::Gem::Version.new('3.0.0') ? '~> 2.23' : '~> 2.21' + + spec.required_ruby_version = '>= 2.5.0' + spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + spec.require_paths = %w[lib] + + spec.add_runtime_dependency 'rack', '>= 2.0.1' + + spec.add_development_dependency 'ffaker', ffaker_version + spec.add_development_dependency 'json_matchers', '~> 0.11.1' + spec.add_development_dependency 'rake', '~> 13.1' + spec.add_development_dependency 'rspec', '~> 3.13' +end diff --git a/on_strum-logs.gemspec b/on_strum-logs.gemspec deleted file mode 100644 index 5f462cb..0000000 --- a/on_strum-logs.gemspec +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require_relative 'lib/on_strum/gem_name/version' - -Gem::Specification.new do |spec| - spec.name = 'on_strum-gem_name' - spec.version = OnStrum::GemName::VERSION - spec.authors = ['Vladislav Trotsenko'] - spec.email = %w[admin@on-strum.org] - spec.summary = %(on_strum-gem_name) - spec.description = %(on_strum-gem_name description) - spec.homepage = 'https://github.com/on-strum/ruby-on-strum-gem-name' - spec.license = 'MIT' - - spec.required_ruby_version = '>= 2.5.0' - spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - spec.require_paths = %w[lib] - - spec.add_development_dependency 'rake', '~> 13.1' - spec.add_development_dependency 'rspec', '~> 3.13' -end diff --git a/spec/on_strum/gem_name_spec.rb b/spec/on_strum/gem_name_spec.rb deleted file mode 100644 index 4160167..0000000 --- a/spec/on_strum/gem_name_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe OnStrum::GemName do -end diff --git a/spec/on_strum/healthcheck/configuration_spec.rb b/spec/on_strum/healthcheck/configuration_spec.rb new file mode 100644 index 0000000..0d1b647 --- /dev/null +++ b/spec/on_strum/healthcheck/configuration_spec.rb @@ -0,0 +1,299 @@ +# frozen_string_literal: true + +RSpec.describe OnStrum::Healthcheck::Configuration do + describe 'defined constants' do + it { expect(described_class).to be_const_defined(:ATTRIBUTES) } + it { expect(described_class).to be_const_defined(:ENDPOINTS_NAMESPACE) } + it { expect(described_class).to be_const_defined(:ENDPOINT_STARTUP) } + it { expect(described_class).to be_const_defined(:ENDPOINT_LIVENESS) } + it { expect(described_class).to be_const_defined(:ENDPOINT_READINESS) } + it { expect(described_class).to be_const_defined(:DEFAULT_HTTP_STATUS_SUCCESS) } + it { expect(described_class).to be_const_defined(:DEFAULT_HTTP_STATUS_FAILURE) } + it { expect(described_class).to be_const_defined(:AVILABLE_HTTP_STATUSES_SUCCESS) } + it { expect(described_class).to be_const_defined(:AVILABLE_HTTP_STATUSES_FAILURE) } + end + + describe OnStrum::Healthcheck::Configuration::Settings do + describe '#update' do + subject(:updated_settings) { described_class.new.update(&block) } + + context 'when configuration block has been passed' do + let(:block) { ->(config) { config.services = 42 } } + + it 'updates structure' do + expect(updated_settings).to be_an_instance_of(described_class) + expect(updated_settings.services).to eq(42) + end + end + + context 'when configuration block has not been passed' do + let(:block) { nil } + + it 'does not update structure' do + expect(updated_settings).to be_an_instance_of(described_class) + expect(updated_settings.services).to be_nil + end + end + end + end + + describe '.new' do + subject(:configuration) do + create_configuration( + services: services, + services_startup: services_startup, + services_liveness: services_liveness, + services_readiness: services_readiness, + endpoints_namespace: endpoints_namespace, + endpoint_startup: endpoint_startup, + endpoint_liveness: endpoint_liveness, + endpoint_readiness: endpoint_readiness, + endpoint_startup_status_success: endpoint_startup_status_success, + endpoint_liveness_status_success: endpoint_liveness_status_success, + endpoint_readiness_status_success: endpoint_readiness_status_success, + endpoint_startup_status_failure: endpoint_startup_status_failure, + endpoint_liveness_status_failure: endpoint_liveness_status_failure, + endpoint_readiness_status_failure: endpoint_readiness_status_failure + ) + end + + context 'when valid configuration' do + let(:service_name) { create_service(random_service_name) } + let(:services) { create_service(service_name) } + let(:services_startup) { [service_name] } + let(:services_liveness) { [service_name] } + let(:services_readiness) { [service_name] } + let(:endpoints_namespace) { random_endpoint } + let(:endpoint_startup) { random_endpoint } + let(:endpoint_liveness) { random_endpoint } + let(:endpoint_readiness) { random_endpoint } + let(:endpoint_startup_status_success) { random_http_status(successful: true) } + let(:endpoint_liveness_status_success) { random_http_status(successful: true) } + let(:endpoint_readiness_status_success) { random_http_status(successful: true) } + let(:endpoint_startup_status_failure) { random_http_status(successful: false) } + let(:endpoint_liveness_status_failure) { random_http_status(successful: false) } + let(:endpoint_readiness_status_failure) { random_http_status(successful: false) } + + it 'creates configuration instance' do + expect(configuration.services).to eq(services) + expect(configuration.services_startup).to eq(services_startup) + expect(configuration.services_liveness).to eq(services_liveness) + expect(configuration.services_readiness).to eq(services_readiness) + expect(configuration.endpoints_namespace).to eq(endpoints_namespace) + expect(configuration.endpoint_startup).to eq(endpoint_startup) + expect(configuration.endpoint_liveness).to eq(endpoint_liveness) + expect(configuration.endpoint_readiness).to eq(endpoint_readiness) + expect(configuration.endpoint_startup_status_success).to eq(endpoint_startup_status_success) + expect(configuration.endpoint_liveness_status_success).to eq(endpoint_liveness_status_success) + expect(configuration.endpoint_readiness_status_success).to eq(endpoint_readiness_status_success) + expect(configuration.endpoint_startup_status_failure).to eq(endpoint_startup_status_failure) + expect(configuration.endpoint_liveness_status_failure).to eq(endpoint_liveness_status_failure) + expect(configuration.endpoint_readiness_status_failure).to eq(endpoint_readiness_status_failure) + expect(configuration).to be_an_instance_of(described_class) + end + end + + context 'when invalid configuration' do + shared_examples 'raies argument error' do + it 'raies argument error' do + expect { configuration }.to raise_error( + target_exception_class, + expected_error_message + ) + end + end + + let(:invalid_argument) { 42 } + + context 'when services= setter is invalid' do + context 'when argument has wrong type' do + subject(:configuration) do + create_configuration(services: invalid_argument) + end + + let(:target_exception_class) { OnStrum::Healthcheck::Error::Configuration::ArgumentType } + let(:expected_error_message) { "#{invalid_argument} is not a valid services=" } + + include_examples 'raies argument error' + end + + context 'when argument nested value is not a callable object' do + subject(:configuration) do + create_configuration(services: { service_name => invalid_argument }) + end + + let(:service_name) { random_service_name } + let(:target_exception_class) { OnStrum::Healthcheck::Error::Configuration::NotCallableService } + let(:expected_error_message) do + "Service #{service_name} is not callable. All values for services= should be a callable objects" + end + + include_examples 'raies argument error' + end + end + + context 'when services_startup= setter is invalid' do + context 'when argument has wrong type' do + subject(:configuration) do + create_configuration(services_startup: invalid_argument) + end + + let(:target_exception_class) { OnStrum::Healthcheck::Error::Configuration::ArgumentType } + let(:expected_error_message) { "#{invalid_argument} is not a valid services_startup=" } + + include_examples 'raies argument error' + end + + context 'when argument nested value is not a defined service' do + subject(:configuration) do + create_configuration(services_startup: [invalid_argument]) + end + + let(:target_exception_class) { OnStrum::Healthcheck::Error::Configuration::UnknownService } + let(:expected_error_message) do + "Unknown #{invalid_argument} service name for services_startup=. You should define it in config.services firstly" + end + + include_examples 'raies argument error' + end + end + + context 'when services_liveness= setter is invalid' do + context 'when argument has wrong type' do + subject(:configuration) do + create_configuration(services_liveness: invalid_argument) + end + + let(:target_exception_class) { OnStrum::Healthcheck::Error::Configuration::ArgumentType } + let(:expected_error_message) { "#{invalid_argument} is not a valid services_liveness=" } + + include_examples 'raies argument error' + end + + context 'when argument nested value is not a defined service' do + subject(:configuration) do + create_configuration(services_liveness: [invalid_argument]) + end + + let(:target_exception_class) { OnStrum::Healthcheck::Error::Configuration::UnknownService } + let(:expected_error_message) do + "Unknown #{invalid_argument} service name for services_liveness=. You should define it in config.services firstly" + end + + include_examples 'raies argument error' + end + end + + context 'when services_readiness= setter is invalid' do + context 'when argument has wrong type' do + subject(:configuration) do + create_configuration(services_readiness: invalid_argument) + end + + let(:target_exception_class) { OnStrum::Healthcheck::Error::Configuration::ArgumentType } + let(:expected_error_message) { "#{invalid_argument} is not a valid services_readiness=" } + + include_examples 'raies argument error' + end + + context 'when argument nested value is not a defined service' do + subject(:configuration) do + create_configuration(services_readiness: [invalid_argument]) + end + + let(:target_exception_class) { OnStrum::Healthcheck::Error::Configuration::UnknownService } + let(:expected_error_message) do + "Unknown #{invalid_argument} service name for services_readiness=. You should define it in config.services firstly" + end + + include_examples 'raies argument error' + end + end + + OnStrum::Healthcheck::Configuration::ATTRIBUTES[4..7].each do |endpoint_setter| + context "when #{endpoint_setter}= setter is invalid" do + context 'when argument has wrong type' do + subject(:configuration) do + create_configuration(endpoint_setter => invalid_argument) + end + + let(:target_exception_class) { OnStrum::Healthcheck::Error::Configuration::ArgumentType } + let(:expected_error_message) { "#{invalid_argument} is not a valid #{endpoint_setter}=" } + + include_examples 'raies argument error' + end + + context 'when argument is not match to enpoint pattern' do + subject(:configuration) do + create_configuration(endpoint_setter => endpoint) + end + + let(:endpoint) { invalid_argument.to_s } + let(:target_exception_class) { OnStrum::Healthcheck::Error::Configuration::EnpointPattern } + let(:expected_error_message) do + "#{endpoint} does not match a valid enpoint pattern for #{endpoint_setter}=" + end + + include_examples 'raies argument error' + end + end + end + + OnStrum::Healthcheck::Configuration::ATTRIBUTES[8..10].each do |endpoint_setter| + context "when #{endpoint_setter}= setter is invalid" do + context 'when argument has wrong type' do + subject(:configuration) do + create_configuration(endpoint_setter => invalid_argument.to_s) + end + + let(:target_exception_class) { OnStrum::Healthcheck::Error::Configuration::ArgumentType } + let(:expected_error_message) { "#{invalid_argument} is not a valid #{endpoint_setter}=" } + + include_examples 'raies argument error' + end + + context 'when argument is not match to enpoint pattern' do + subject(:configuration) do + create_configuration(endpoint_setter => invalid_argument) + end + + let(:target_exception_class) { OnStrum::Healthcheck::Error::Configuration::HttpStatusSuccess } + let(:expected_error_message) do + "Status #{invalid_argument} is wrong HTTP successful status for #{endpoint_setter}=. It should be in the range 200-226" + end + + include_examples 'raies argument error' + end + end + end + + OnStrum::Healthcheck::Configuration::ATTRIBUTES[11..13].each do |endpoint_setter| + context "when #{endpoint_setter}= setter is invalid" do + context 'when argument has wrong type' do + subject(:configuration) do + create_configuration(endpoint_setter => invalid_argument.to_s) + end + + let(:target_exception_class) { OnStrum::Healthcheck::Error::Configuration::ArgumentType } + let(:expected_error_message) { "#{invalid_argument} is not a valid #{endpoint_setter}=" } + + include_examples 'raies argument error' + end + + context 'when argument is not match to enpoint pattern' do + subject(:configuration) do + create_configuration(endpoint_setter => invalid_argument) + end + + let(:target_exception_class) { OnStrum::Healthcheck::Error::Configuration::HttpStatusFailure } + let(:expected_error_message) do + "Status #{invalid_argument} is wrong HTTP failure status for #{endpoint_setter}=. It should be in the range 500-511" + end + + include_examples 'raies argument error' + end + end + end + end + end +end diff --git a/spec/on_strum/healthcheck/error/configuration/argument_type_spec.rb b/spec/on_strum/healthcheck/error/configuration/argument_type_spec.rb new file mode 100644 index 0000000..b58175c --- /dev/null +++ b/spec/on_strum/healthcheck/error/configuration/argument_type_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +RSpec.describe OnStrum::Healthcheck::Error::Configuration::ArgumentType do + subject(:error_instance) { described_class.new(arg_value, arg_name) } + + let(:arg_value) { random_message } + let(:arg_name) { :some_arg_name } + + it { expect(described_class).to be < ::ArgumentError } + it { expect(error_instance).to be_an_instance_of(described_class) } + it { expect(error_instance.to_s).to eq("#{arg_value} is not a valid #{arg_name}") } +end diff --git a/spec/on_strum/healthcheck/error/configuration/http_status_failure_spec.rb b/spec/on_strum/healthcheck/error/configuration/http_status_failure_spec.rb new file mode 100644 index 0000000..8a7ee29 --- /dev/null +++ b/spec/on_strum/healthcheck/error/configuration/http_status_failure_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +RSpec.describe OnStrum::Healthcheck::Error::Configuration::HttpStatusFailure do + subject(:error_instance) { described_class.new(arg_value, arg_name) } + + let(:arg_value) { random_http_status(successful: false) } + let(:arg_name) { :some_arg_name } + + it { expect(described_class).to be < ::ArgumentError } + it { expect(error_instance).to be_an_instance_of(described_class) } + + it 'returns exception message context' do + expect(error_instance.to_s).to eq( + "Status #{arg_value} is wrong HTTP failure status for #{arg_name}. It should be in the range 500-511" + ) + end +end diff --git a/spec/on_strum/healthcheck/error/configuration/http_status_success_spec.rb b/spec/on_strum/healthcheck/error/configuration/http_status_success_spec.rb new file mode 100644 index 0000000..58602e7 --- /dev/null +++ b/spec/on_strum/healthcheck/error/configuration/http_status_success_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +RSpec.describe OnStrum::Healthcheck::Error::Configuration::HttpStatusSuccess do + subject(:error_instance) { described_class.new(arg_value, arg_name) } + + let(:arg_value) { random_http_status(successful: false) } + let(:arg_name) { :some_arg_name } + + it { expect(described_class).to be < ::ArgumentError } + it { expect(error_instance).to be_an_instance_of(described_class) } + + it 'returns exception message context' do + expect(error_instance.to_s).to eq( + "Status #{arg_value} is wrong HTTP successful status for #{arg_name}. It should be in the range 200-226" + ) + end +end diff --git a/spec/on_strum/healthcheck/error/configuration/not_callable_service_spec.rb b/spec/on_strum/healthcheck/error/configuration/not_callable_service_spec.rb new file mode 100644 index 0000000..0117ba8 --- /dev/null +++ b/spec/on_strum/healthcheck/error/configuration/not_callable_service_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +RSpec.describe OnStrum::Healthcheck::Error::Configuration::NotCallableService do + subject(:error_instance) { described_class.new(service_name, services_setter) } + + let(:service_name) { random_service_name } + let(:services_setter) { :some_services_setter= } + + it { expect(described_class).to be < ::ArgumentError } + it { expect(error_instance).to be_an_instance_of(described_class) } + + it 'returns exception message context' do + expect(error_instance.to_s).to eq( + "Service #{service_name} is not callable. All values for #{services_setter} should be a callable objects" + ) + end +end diff --git a/spec/on_strum/healthcheck/error/configuration/not_configured_spec.rb b/spec/on_strum/healthcheck/error/configuration/not_configured_spec.rb new file mode 100644 index 0000000..7143d9e --- /dev/null +++ b/spec/on_strum/healthcheck/error/configuration/not_configured_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +RSpec.describe OnStrum::Healthcheck::Error::Configuration::NotConfigured do + subject(:error_instance) { described_class.new } + + it { expect(described_class).to be < ::RuntimeError } + it { expect(error_instance).to be_an_instance_of(described_class) } + + it 'returns exception message context' do + expect(error_instance.to_s).to eq( + 'The configuration is empty. Please use OnStrum::Healthcheck.configure before' + ) + end +end diff --git a/spec/on_strum/healthcheck/error/configuration/unknown_service_spec.rb b/spec/on_strum/healthcheck/error/configuration/unknown_service_spec.rb new file mode 100644 index 0000000..78fa9ed --- /dev/null +++ b/spec/on_strum/healthcheck/error/configuration/unknown_service_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +RSpec.describe OnStrum::Healthcheck::Error::Configuration::UnknownService do + subject(:error_instance) { described_class.new(service_name, services_setter) } + + let(:service_name) { random_service_name } + let(:services_setter) { :some_services_setter= } + + it { expect(described_class).to be < ::ArgumentError } + it { expect(error_instance).to be_an_instance_of(described_class) } + + it 'returns exception message context' do + expect(error_instance.to_s).to eq( + "Unknown #{service_name} service name for #{services_setter}. You should define it in config.services firstly" + ) + end +end diff --git a/spec/on_strum/healthcheck/rack_middleware_spec.rb b/spec/on_strum/healthcheck/rack_middleware_spec.rb new file mode 100644 index 0000000..2502e49 --- /dev/null +++ b/spec/on_strum/healthcheck/rack_middleware_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +RSpec.describe OnStrum::Healthcheck::RackMiddleware do + subject(:middleware) { described_class.new(app, resolver).call(env) } + + let(:app_context) { 'some app rack response' } + let(:app) { instance_double('RackApplication', call: app_context) } + let(:resolver_context) { true } + let(:resolver) { class_double('Resolver', call: resolver_context) } + let(:env) { {} } + + context 'when OnStrum::Healthcheck is configured' do + before { init_configuration } + + context 'when resolver matches the route' do + it 'returns rack response' do + expect(resolver).to receive(:call).with(env) + expect(middleware).to eq(resolver_context) + end + end + + context 'when resolver does not match the route' do + let(:resolver_context) { nil } + + it 'returns rack response' do + expect(resolver).to receive(:call).with(env) + expect(app).to receive(:call).with(env) + expect(middleware).to eq(app_context) + end + end + end + + context 'when OnStrum::Healthcheck is not configured' do + it 'raises NotConfigured exception' do + expect { middleware }.to raise_error( + OnStrum::Healthcheck::Error::Configuration::NotConfigured, + 'The configuration is empty. Please use OnStrum::Healthcheck.configure before' + ) + end + end +end diff --git a/spec/on_strum/healthcheck/resolver_spec.rb b/spec/on_strum/healthcheck/resolver_spec.rb new file mode 100644 index 0000000..3cfee05 --- /dev/null +++ b/spec/on_strum/healthcheck/resolver_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +RSpec.describe OnStrum::Healthcheck::Resolver do + describe 'defined constants' do + it { expect(described_class).to be_const_defined(:PROBE_ENDPOINTS) } + it { expect(described_class).to be_const_defined(:CONTENT_TYPE) } + it { expect(described_class).to be_const_defined(:JSONAPI_RESPONSE_TYPE) } + it { expect(described_class).to be_const_defined(:ROOT_NAMESPACE) } + end + + describe '.call' do + subject(:resolver) do + described_class.call( + Rack::MockRequest.env_for.merge('PATH_INFO' => healthcheck_enpoint) + ) + end + + let(:service_status_first) { true } + let(:service_name_second) { random_service_name } + let(:service_name_first) { random_service_name } + + context 'when unknown endpoint' do + let(:healthcheck_enpoint) { '/unknown_endpoint' } + + before { init_configuration } + + it { is_expected.to be_nil } + end + + %i[startup liveness readiness].each do |probe_name| + context "when #{probe_name} root endpoint" do + let(:service_status_second) { true } + let(:healthcheck_enpoint) { current_configuration.public_send(:"endpoint_#{probe_name}") } + + before do + init_configuration( + services: { service_name_first => -> { service_status_first } }, + "services_#{probe_name}": [service_name_first], + endpoints_namespace: '/' + ) + end + + it 'returns rack middleware response' do + response_status, _content_type, _response = resolver + + expect(response_status).to eq( + current_configuration.public_send(:"endpoint_#{probe_name}_status_success") + ) + end + end + + context "when #{probe_name} endpoint" do + [true, false].each do |status| # rubocop:disable Performance/CollectionLiteralInLoop + context "when successful status is #{status}" do + let(:service_status_second) { status } + let(:healthcheck_enpoint) do + endpoint = current_configuration.public_send(:"endpoint_#{probe_name}") + "#{current_configuration.endpoints_namespace}#{endpoint}" + end + + before do + init_configuration( + services: { + service_name_first => -> { service_status_first }, + service_name_second => -> { service_status_second } + }, + "services_#{probe_name}": [ + service_name_first, + service_name_second + ] + ) + end + + it 'returns rack middleware response' do + response_status, content_type, response = resolver + response_body, target_status = response.first, "endpoint_#{probe_name}_status_#{status ? 'success' : 'failure'}" + expected_probe_status = current_configuration.public_send(target_status) + + expect(response_status).to eq(expected_probe_status) + expect(content_type).to eq('Content-Type' => 'application/json') + expect(::JSON.parse(response_body).dig('data', 'attributes')).to eq( + service_name_first => service_status_first, + service_name_second => service_status_second + ) + expect(response_body).to match_json_schema('jsonapi_response') + end + end + end + end + end + end +end diff --git a/spec/on_strum/healthcheck/rspec_helper/configuration_spec.rb b/spec/on_strum/healthcheck/rspec_helper/configuration_spec.rb new file mode 100644 index 0000000..c274e1d --- /dev/null +++ b/spec/on_strum/healthcheck/rspec_helper/configuration_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +RSpec.describe OnStrum::Healthcheck::RspecHelper::Configuration, type: :helper do + describe '#configuration_block' do + let(:configuration_params) { { param_one: 1, param_two: 2 } } + let(:configuration_instance) { ::Struct.new(*configuration_params.keys).new } + + before { configuration_block(**configuration_params).call(configuration_instance) } + + it { expect(configuration_block).to be_an_instance_of(::Proc) } + + it 'sets configuration instance attributes' do + configuration_params.each do |attribute, value| + expect(configuration_instance.public_send(attribute)).to eq(value) + end + end + end + + describe '#create_configuration' do + subject(:configuration_builder) do + create_configuration( + services: services, + services_startup: %i[a] + ) + end + + let(:services) { { a: -> {} } } + let(:services_startup) { %i[a] } + + it 'returns configuration instance with defined params' do + expect(configuration_builder).to be_an_instance_of(OnStrum::Healthcheck::Configuration) + expect(configuration_builder.services).to eq(services) + expect(configuration_builder.services_startup).to eq(services_startup) + end + end + + describe '#init_configuration' do + subject(:configuration_initializer) { init_configuration(endpoints_namespace: endpoints_namespace) } + + let(:endpoints_namespace) { '/some_endpoint' } + + it 'initializes configuration instance with random and predefined params' do + expect(configuration_initializer).to be_an_instance_of(OnStrum::Healthcheck::Configuration) + expect(configuration_initializer.endpoints_namespace).to eq(endpoints_namespace) + end + end + + describe '#current_configuration' do + subject(:current_configuration_instance) { current_configuration } + + it do + expect(OnStrum::Healthcheck).to receive(:configuration) + current_configuration_instance + end + end + + describe '#reset_configuration' do + subject(:reset_current_configuration) { reset_configuration } + + it do + expect(OnStrum::Healthcheck).to receive(:reset_configuration!) + reset_current_configuration + end + end +end diff --git a/spec/on_strum/healthcheck/rspec_helper/context_generator_spec.rb b/spec/on_strum/healthcheck/rspec_helper/context_generator_spec.rb new file mode 100644 index 0000000..421a836 --- /dev/null +++ b/spec/on_strum/healthcheck/rspec_helper/context_generator_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +RSpec.describe OnStrum::Healthcheck::RspecHelper::ContextGenerator, type: :helper do + describe '#random_message' do + it 'returns random message' do + expect(::FFaker::Lorem).to receive(:sentence).and_call_original + expect(random_message).to be_an_instance_of(::String) + end + end + + describe '#random_service_name' do + it 'returns random service name' do + expect(::FFaker::InternetSE).to receive(:login_user_name).and_call_original + expect(random_service_name).to be_an_instance_of(::String) + end + end + + describe '#random_endpoint' do + it 'returns random endpoint' do + expect(::FFaker::InternetSE).to receive(:login_user_name).and_call_original + expect(random_endpoint).to match(%r{\A/.*\z}) + end + end + + describe '#random_http_status' do + context 'when successful status' do + it 'returns random successful http status' do + expect(random_http_status(successful: true)).to be_between(200, 226) + end + end + + context 'when failure status' do + it 'returns random failure http status' do + expect(random_http_status(successful: false)).to be_between(500, 511) + end + end + end + + describe '#create_service' do + subject(:builded_service) { create_service(service_name, successful: service_type) } + + let(:service_name) { random_service_name } + + shared_examples 'returns hash with service' do + it 'returns hash with service' do + expect(builded_service.transform_values(&:call)).to eq(service_name => service_type) + end + end + + context 'when successful service' do + let(:service_type) { true } + + include_examples 'returns hash with service' + end + + context 'when failed service' do + let(:service_type) { false } + + include_examples 'returns hash with service' + end + end +end diff --git a/spec/on_strum/gem_name/version_spec.rb b/spec/on_strum/healthcheck/version_spec.rb similarity index 59% rename from spec/on_strum/gem_name/version_spec.rb rename to spec/on_strum/healthcheck/version_spec.rb index 6bc40e1..33a7002 100644 --- a/spec/on_strum/gem_name/version_spec.rb +++ b/spec/on_strum/healthcheck/version_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -RSpec.describe OnStrum::GemName::VERSION do +RSpec.describe OnStrum::Healthcheck::VERSION do it { is_expected.not_to be_nil } end diff --git a/spec/on_strum/healthcheck_spec.rb b/spec/on_strum/healthcheck_spec.rb new file mode 100644 index 0000000..78e5bf2 --- /dev/null +++ b/spec/on_strum/healthcheck_spec.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +RSpec.describe OnStrum::Healthcheck do + let(:service_name) { create_service(random_service_name) } + let(:services) { create_service(service_name) } + let(:services_startup) { [service_name] } + let(:services_liveness) { [service_name] } + let(:services_readiness) { [service_name] } + let(:endpoints_namespace) { random_endpoint } + let(:endpoint_startup) { random_endpoint } + let(:endpoint_liveness) { random_endpoint } + let(:endpoint_readiness) { random_endpoint } + let(:endpoint_startup_status_success) { random_http_status(successful: true) } + let(:endpoint_liveness_status_success) { random_http_status(successful: true) } + let(:endpoint_readiness_status_success) { random_http_status(successful: true) } + let(:endpoint_startup_status_failure) { random_http_status(successful: false) } + let(:endpoint_liveness_status_failure) { random_http_status(successful: false) } + let(:endpoint_readiness_status_failure) { random_http_status(successful: false) } + + describe '.configure' do + subject(:configuration) { described_class.configure(&config_block) } + + let(:config_block) { nil } + + context 'without block' do + it { expect(configuration).to be_nil } + it { expect { configuration }.not_to change(described_class, :configuration) } + end + + context 'with block' do + let(:config_block) do + configuration_block( + services: services, + services_startup: services_startup, + services_liveness: services_liveness, + services_readiness: services_readiness, + endpoints_namespace: endpoints_namespace, + endpoint_startup: endpoint_startup, + endpoint_liveness: endpoint_liveness, + endpoint_readiness: endpoint_readiness, + endpoint_startup_status_success: endpoint_startup_status_success, + endpoint_liveness_status_success: endpoint_liveness_status_success, + endpoint_readiness_status_success: endpoint_readiness_status_success, + endpoint_startup_status_failure: endpoint_startup_status_failure, + endpoint_liveness_status_failure: endpoint_liveness_status_failure, + endpoint_readiness_status_failure: endpoint_readiness_status_failure + ) + end + + it 'sets attributes into configuration instance' do + expect { configuration } + .to change(described_class, :configuration) + .from(nil) + .to(be_instance_of(OnStrum::Healthcheck::Configuration)) + expect(configuration).to be_an_instance_of(OnStrum::Healthcheck::Configuration) + expect(configuration.services).to eq(services) + expect(configuration.services_startup).to eq(services_startup) + expect(configuration.services_liveness).to eq(services_liveness) + expect(configuration.services_readiness).to eq(services_readiness) + expect(configuration.endpoints_namespace).to eq(endpoints_namespace) + expect(configuration.endpoint_startup).to eq(endpoint_startup) + expect(configuration.endpoint_liveness).to eq(endpoint_liveness) + expect(configuration.endpoint_readiness).to eq(endpoint_readiness) + expect(configuration.endpoint_startup_status_success).to eq(endpoint_startup_status_success) + expect(configuration.endpoint_liveness_status_success).to eq(endpoint_liveness_status_success) + expect(configuration.endpoint_readiness_status_success).to eq(endpoint_readiness_status_success) + expect(configuration.endpoint_startup_status_failure).to eq(endpoint_startup_status_failure) + expect(configuration.endpoint_liveness_status_failure).to eq(endpoint_liveness_status_failure) + expect(configuration.endpoint_readiness_status_failure).to eq(endpoint_readiness_status_failure) + end + end + end + + describe '.reset_configuration!' do + before do + described_class.configure( + &configuration_block( + services: services, + services_startup: services_startup, + services_liveness: services_liveness, + services_readiness: services_readiness, + endpoints_namespace: endpoints_namespace, + endpoint_startup: endpoint_startup, + endpoint_liveness: endpoint_liveness, + endpoint_readiness: endpoint_readiness, + endpoint_startup_status_success: endpoint_startup_status_success, + endpoint_liveness_status_success: endpoint_liveness_status_success, + endpoint_readiness_status_success: endpoint_readiness_status_success, + endpoint_startup_status_failure: endpoint_startup_status_failure, + endpoint_liveness_status_failure: endpoint_liveness_status_failure, + endpoint_readiness_status_failure: endpoint_readiness_status_failure + ) + ) + end + + it do + expect { described_class.reset_configuration! } + .to change(described_class, :configuration) + .from(be_instance_of(OnStrum::Healthcheck::Configuration)).to(nil) + end + end + + describe '.configuration' do + subject(:configuration) { described_class.configuration } + + before do + described_class.configure(&configuration_block( + services: services, + services_startup: services_startup, + services_liveness: services_liveness, + services_readiness: services_readiness, + endpoints_namespace: endpoints_namespace, + endpoint_startup: endpoint_startup, + endpoint_liveness: endpoint_liveness, + endpoint_readiness: endpoint_readiness, + endpoint_startup_status_success: endpoint_startup_status_success, + endpoint_liveness_status_success: endpoint_liveness_status_success, + endpoint_readiness_status_success: endpoint_readiness_status_success, + endpoint_startup_status_failure: endpoint_startup_status_failure, + endpoint_liveness_status_failure: endpoint_liveness_status_failure, + endpoint_readiness_status_failure: endpoint_readiness_status_failure + )) + end + + it 'returns configuration instance' do + expect(configuration).to be_instance_of(OnStrum::Healthcheck::Configuration) + expect(configuration.services).to eq(services) + expect(configuration.services_startup).to eq(services_startup) + expect(configuration.services_liveness).to eq(services_liveness) + expect(configuration.services_readiness).to eq(services_readiness) + expect(configuration.endpoints_namespace).to eq(endpoints_namespace) + expect(configuration.endpoint_startup).to eq(endpoint_startup) + expect(configuration.endpoint_liveness).to eq(endpoint_liveness) + expect(configuration.endpoint_readiness).to eq(endpoint_readiness) + expect(configuration.endpoint_startup_status_success).to eq(endpoint_startup_status_success) + expect(configuration.endpoint_liveness_status_success).to eq(endpoint_liveness_status_success) + expect(configuration.endpoint_readiness_status_success).to eq(endpoint_readiness_status_success) + expect(configuration.endpoint_startup_status_failure).to eq(endpoint_startup_status_failure) + expect(configuration.endpoint_liveness_status_failure).to eq(endpoint_liveness_status_failure) + expect(configuration.endpoint_readiness_status_failure).to eq(endpoint_readiness_status_failure) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6e03a18..f8fc31d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,7 +3,7 @@ rspec_custom = ::File.join(::File.dirname(__FILE__), 'support/**/*.rb') ::Dir[::File.expand_path(rspec_custom)].sort.each { |file| require file unless file[/\A.+_spec\.rb\z/] } -require_relative '../lib/on_strum/gem_name' +require_relative '../lib/on_strum/healthcheck' RSpec.configure do |config| config.expect_with(:rspec) do |expectations| @@ -18,6 +18,10 @@ config.example_status_persistence_file_path = '.rspec_status' config.disable_monkey_patching! config.order = :random + config.before { reset_configuration } + + config.include OnStrum::Healthcheck::RspecHelper::ContextGenerator + config.include OnStrum::Healthcheck::RspecHelper::Configuration ::Kernel.srand(config.seed) end diff --git a/spec/support/config/json_matchers.rb b/spec/support/config/json_matchers.rb new file mode 100644 index 0000000..7838017 --- /dev/null +++ b/spec/support/config/json_matchers.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require 'json_matchers/rspec' + +JsonMatchers.schema_root = 'spec/support/schemas' diff --git a/spec/support/helpers/configuration.rb b/spec/support/helpers/configuration.rb new file mode 100644 index 0000000..dc7b31b --- /dev/null +++ b/spec/support/helpers/configuration.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module OnStrum + module Healthcheck + module RspecHelper + module Configuration + def configuration_block(**configuration_settings) + lambda do |config| + configuration_settings.each do |attribute, value| + config.public_send(:"#{attribute}=", value) + end + end + end + + def create_configuration(**configuration_settings) + OnStrum::Healthcheck::Configuration.new(&configuration_block(**configuration_settings)) + end + + def init_configuration(**args) + OnStrum::Healthcheck.configure( + &configuration_block( + **args + ) + ) + end + + def current_configuration + OnStrum::Healthcheck.configuration + end + + def reset_configuration + OnStrum::Healthcheck.reset_configuration! + end + end + end + end +end diff --git a/spec/support/helpers/context_generator.rb b/spec/support/helpers/context_generator.rb new file mode 100644 index 0000000..2412e45 --- /dev/null +++ b/spec/support/helpers/context_generator.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module OnStrum + module Healthcheck + module RspecHelper + module ContextGenerator + def random_message + ::FFaker::Lorem.sentence + end + + def random_service_name + ::FFaker::InternetSE.login_user_name + end + + def random_endpoint + "/#{random_service_name}" + end + + def random_http_status(successful:) + (successful ? 200..226 : 500..511).to_a.sample + end + + def create_service(service_name, successful: true) + { service_name => -> { successful } } + end + end + end + end +end diff --git a/spec/support/schemas/jsonapi_response.json b/spec/support/schemas/jsonapi_response.json new file mode 100644 index 0000000..e9d922a --- /dev/null +++ b/spec/support/schemas/jsonapi_response.json @@ -0,0 +1,85 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://example.com/example.json", + "title": "Root Schema", + "type": "object", + "default": {}, + "required": [ + "data" + ], + "properties": { + "data": { + "title": "The data Schema", + "type": "object", + "default": {}, + "required": [ + "id", + "type", + "attributes" + ], + "properties": { + "id": { + "title": "The id Schema", + "type": "string", + "default": "", + "examples": [ + "ea266931-feb6-4b19-9308-ec29f18f1c02" + ] + }, + "type": { + "title": "The type Schema", + "type": "string", + "default": "", + "examples": [ + "application-healthcheck" + ] + }, + "attributes": { + "title": "The attributes Schema", + "type": "object", + "default": {}, + "properties": { + "yettacoletta": { + "title": "The yettacoletta Schema", + "type": "boolean", + "default": false, + "examples": [ + true + ] + }, + "rubenwikstrm": { + "title": "The rubenwikstrm Schema", + "type": "boolean", + "default": false, + "examples": [ + false + ] + } + }, + "examples": [{ + "yettacoletta": true, + "rubenwikstrm": false + }] + } + }, + "examples": [{ + "id": "ea266931-feb6-4b19-9308-ec29f18f1c02", + "type": "application-healthcheck", + "attributes": { + "yettacoletta": true, + "rubenwikstrm": false + } + }] + } + }, + "examples": [{ + "data": { + "id": "ea266931-feb6-4b19-9308-ec29f18f1c02", + "type": "application-healthcheck", + "attributes": { + "yettacoletta": true, + "rubenwikstrm": false + } + } + }] +}