├── .circleci
├── config.yml
├── gemspecs
│ ├── compatible
│ └── latest
├── linter_configs
│ ├── .bundler-audit.yml
│ ├── .commitspell.yml
│ ├── .cspell.yml
│ ├── .fasterer.yml
│ ├── .lefthook.yml
│ ├── .markdownlint.yml
│ ├── .rubocop.yml
│ └── .yamllint.yml
└── scripts
│ ├── changeloglint.sh
│ ├── commitspell.sh
│ ├── release.sh
│ └── set_publisher_credentials.sh
├── .codeclimate.yml
├── .github
├── BRANCH_NAMING_CONVENTION.md
├── DEVELOPMENT_ENVIRONMENT_GUIDE.md
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ ├── issue_report.md
│ └── question.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .reek.yml
├── .rspec
├── .ruby-gemset
├── .ruby-version
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
├── console
└── setup
├── healthcheck_endpoint.gemspec
├── lib
├── healthcheck_endpoint.rb
└── healthcheck_endpoint
│ ├── configuration.rb
│ ├── core.rb
│ ├── error
│ └── configuration
│ │ ├── argument_type.rb
│ │ ├── enpoint_pattern.rb
│ │ ├── http_status_failure.rb
│ │ ├── http_status_success.rb
│ │ ├── not_callable_service.rb
│ │ ├── not_configured.rb
│ │ └── unknown_service.rb
│ ├── rack_middleware.rb
│ ├── resolver.rb
│ └── version.rb
└── spec
├── healthcheck_endpoint
├── configuration_spec.rb
├── error
│ └── configuration
│ │ ├── argument_type_spec.rb
│ │ ├── http_status_failure_spec.rb
│ │ ├── http_status_success_spec.rb
│ │ ├── not_callable_service_spec.rb
│ │ ├── not_configured_spec.rb
│ │ └── unknown_service_spec.rb
├── rack_middleware_spec.rb
├── resolver_spec.rb
├── rspec_helper
│ ├── configuration_spec.rb
│ └── context_generator_spec.rb
└── version_spec.rb
├── healthcheck_endpoint_spec.rb
├── spec_helper.rb
└── support
├── config
├── bundler.rb
├── ffaker.rb
├── json_matchers.rb
├── pry.rb
└── simplecov.rb
├── helpers
├── configuration.rb
└── context_generator.rb
└── schemas
└── jsonapi_response.json
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | version: 2.1
4 |
5 | defaults: &defaults
6 | working_directory: ~/healthcheck-endpoint
7 | docker:
8 | - image: cimg/ruby:<< parameters.ruby-version >>
9 |
10 | orbs:
11 | ruby: circleci/ruby@2.3.0
12 |
13 | references:
14 | bundle_install: &bundle_install
15 | run:
16 | name: Installing gems
17 | command: |
18 | bundle config set --local path '~/vendor/bundle'
19 | bundle install
20 |
21 | install_linters: &install_linters
22 | run:
23 | name: Installing bunch of linters
24 | command: |
25 | curl -1sLf 'https://dl.cloudsmith.io/public/evilmartians/lefthook/setup.deb.sh' | sudo -E bash
26 | sudo apt-get update -y
27 | sudo apt-get install -y lefthook shellcheck yamllint
28 | npm install --prefix='~/.local' --global --save-dev git+https://github.com/streetsidesoftware/cspell-cli markdownlint-cli
29 | cp .circleci/linter_configs/.fasterer.yml .fasterer.yml
30 | cp .circleci/linter_configs/.lefthook.yml lefthook.yml
31 |
32 | install_codeclimate_reporter: &install_codeclimate_reporter
33 | run:
34 | name: Installing CodeClimate test reporter
35 | command: |
36 | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
37 | chmod +x ./cc-test-reporter
38 |
39 | use_latest_bundler: &use_latest_bundler
40 | run:
41 | name: Using latest bundler
42 | command: gem install bundler
43 |
44 | use_latest_gemspec: &use_latest_gemspec
45 | run:
46 | name: Using latest gemspec
47 | command: cp .circleci/gemspecs/latest healthcheck_endpoint.gemspec
48 |
49 | use_compatible_gemspec: &use_compatible_gemspec
50 | run:
51 | name: Using compatible gemspec
52 | command: cp .circleci/gemspecs/compatible healthcheck_endpoint.gemspec
53 |
54 | jobs:
55 | linters-ruby:
56 | parameters:
57 | ruby-version:
58 | type: string
59 |
60 | <<: *defaults
61 |
62 | steps:
63 | - checkout
64 |
65 | - <<: *use_latest_bundler
66 | - <<: *use_latest_gemspec
67 | - <<: *bundle_install
68 | - <<: *install_linters
69 |
70 | - run:
71 | name: Running commit linters
72 | command: lefthook run commit-linters
73 |
74 | - run:
75 | name: Running code style linters
76 | command: lefthook run code-style-linters
77 |
78 | - run:
79 | name: Running code performance linters
80 | command: lefthook run code-performance-linters
81 |
82 | - run:
83 | name: Running code vulnerability linters
84 | command: lefthook run code-vulnerability-linters
85 |
86 | - run:
87 | name: Running code documentation linters
88 | command: lefthook run code-documentation-linters
89 |
90 | - run:
91 | name: Running release linters
92 | command: lefthook run release-linters
93 |
94 | tests-ruby:
95 | parameters:
96 | ruby-version:
97 | type: string
98 |
99 | <<: *defaults
100 |
101 | steps:
102 | - checkout
103 |
104 | - <<: *use_latest_bundler
105 | - <<: *use_latest_gemspec
106 | - <<: *bundle_install
107 | - <<: *install_codeclimate_reporter
108 |
109 | - run:
110 | name: Running RSpec
111 | command: |
112 | ./cc-test-reporter before-build
113 | bundle exec rspec
114 |
115 | - run:
116 | name: Creating CodeClimate test coverage report
117 | command: |
118 | ./cc-test-reporter format-coverage -t simplecov -o "coverage/codeclimate.$CIRCLE_NODE_INDEX.json"
119 |
120 | - store_artifacts:
121 | name: Saving Simplecov coverage artifacts
122 | path: ~/healthcheck-endpoint/coverage
123 | destination: coverage
124 |
125 | - deploy:
126 | name: Uploading CodeClimate test coverage report
127 | command: |
128 | ./cc-test-reporter sum-coverage --output - --parts $CIRCLE_NODE_TOTAL coverage/codeclimate.*.json | ./cc-test-reporter upload-coverage --debug --input -
129 |
130 | compatibility-ruby:
131 | parameters:
132 | ruby-version:
133 | type: string
134 |
135 | <<: *defaults
136 |
137 | steps:
138 | - checkout
139 |
140 | - <<: *use_compatible_gemspec
141 |
142 | - ruby/install-deps:
143 | bundler-version: "2.3.26"
144 | with-cache: false
145 | path: '~/vendor/custom_bundle'
146 |
147 | - run:
148 | name: Running compatibility tests
149 | command: bundle exec rspec
150 |
151 | rubygems-deps-ruby:
152 | parameters:
153 | ruby-version:
154 | type: string
155 |
156 | <<: *defaults
157 |
158 | steps:
159 | - checkout
160 |
161 | - run:
162 | name: Building rubygems dependencies from default gemspec on minimal Ruby version
163 | command: bundle install
164 |
165 | releasing-gem-from-ruby:
166 | parameters:
167 | ruby-version:
168 | type: string
169 |
170 | <<: *defaults
171 |
172 | steps:
173 | - checkout
174 |
175 | - add_ssh_keys:
176 | fingerprints:
177 | - "SHA256:eJhlVtu2gws5rDavHcqZ5GJF/aS8kCctMprdC+Twlns"
178 |
179 | - run:
180 | name: Publishing new release
181 | command: ./.circleci/scripts/release.sh
182 |
183 | workflows:
184 | build_test_deploy:
185 | jobs:
186 | - linters-ruby:
187 | matrix:
188 | parameters:
189 | ruby-version: ["3.3-node"]
190 | - tests-ruby:
191 | matrix:
192 | parameters:
193 | ruby-version: ["3.3"]
194 | - compatibility-ruby:
195 | matrix:
196 | parameters:
197 | ruby-version: ["2.5", "2.6", "2.7", "3.0", "3.1", "3.2"]
198 | - rubygems-deps-ruby:
199 | matrix:
200 | parameters:
201 | ruby-version: ["2.5"]
202 | - releasing-gem-from-ruby:
203 | requires:
204 | - linters-ruby
205 | - tests-ruby
206 | - compatibility-ruby
207 | - rubygems-deps-ruby
208 | matrix:
209 | parameters:
210 | ruby-version: ["2.5"]
211 | filters:
212 | branches:
213 | only: master
214 |
--------------------------------------------------------------------------------
/.circleci/gemspecs/compatible:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'lib/healthcheck_endpoint/version'
4 |
5 | Gem::Specification.new do |spec|
6 | spec.name = 'healthcheck_endpoint'
7 | spec.version = HealthcheckEndpoint::VERSION
8 | spec.authors = ['Vladislav Trotsenko']
9 | spec.email = %w[admin@bestweb.com.ua]
10 | spec.summary = %(Simple configurable application healthcheck rack middleware)
11 | spec.description = %(Simple configurable application healthcheck rack middleware.)
12 | spec.homepage = 'https://github.com/obstools/healthcheck-endpoint'
13 | spec.license = 'MIT'
14 |
15 | spec.required_ruby_version = '>= 2.5.0'
16 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17 | spec.require_paths = %w[lib]
18 |
19 | spec.add_runtime_dependency 'rack', '>= 2.0.1'
20 |
21 | spec.add_development_dependency 'ffaker'
22 | spec.add_development_dependency 'json_matchers'
23 | spec.add_development_dependency 'rake'
24 | spec.add_development_dependency 'rspec'
25 | end
26 |
--------------------------------------------------------------------------------
/.circleci/gemspecs/latest:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'lib/healthcheck_endpoint/version'
4 |
5 | Gem::Specification.new do |spec|
6 | spec.name = 'healthcheck_endpoint'
7 | spec.version = HealthcheckEndpoint::VERSION
8 | spec.authors = ['Vladislav Trotsenko']
9 | spec.email = %w[admin@bestweb.com.ua]
10 | spec.summary = %(Simple configurable application healthcheck rack middleware)
11 | spec.description = %(Simple configurable application healthcheck rack middleware.)
12 | spec.homepage = 'https://github.com/obstools/healthcheck-endpoint'
13 | spec.license = 'MIT'
14 |
15 | spec.required_ruby_version = '>= 2.5.0'
16 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17 | spec.require_paths = %w[lib]
18 |
19 | spec.add_runtime_dependency 'rack', '>= 2.0.1'
20 |
21 | spec.add_development_dependency 'bundler-audit', '~> 0.9.2'
22 | spec.add_development_dependency 'fasterer', '~> 0.11.0'
23 | spec.add_development_dependency 'ffaker', '~> 2.23'
24 | spec.add_development_dependency 'json_matchers', '~> 0.11.1'
25 | spec.add_development_dependency 'pry-byebug', '~> 3.10', '>= 3.10.1'
26 | spec.add_development_dependency 'rake', '~> 13.2', '>= 13.2.1'
27 | spec.add_development_dependency 'reek', '~> 6.3'
28 | spec.add_development_dependency 'rspec', '~> 3.13'
29 | spec.add_development_dependency 'rubocop', '~> 1.67'
30 | spec.add_development_dependency 'rubocop-performance', '~> 1.22', '>= 1.22.1'
31 | spec.add_development_dependency 'rubocop-rspec', '~> 3.1'
32 | spec.add_development_dependency 'simplecov', '~> 0.22.0'
33 | end
34 |
--------------------------------------------------------------------------------
/.circleci/linter_configs/.bundler-audit.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | ignore:
4 | - EXA-MPLE-XXXX
5 |
--------------------------------------------------------------------------------
/.circleci/linter_configs/.commitspell.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | enableGlobDot: true
4 |
5 | patterns:
6 | - name: GithubUser
7 | pattern: /\[@.+\]/gmx
8 |
9 | languageSettings:
10 | - languageId: markdown
11 | ignoreRegExpList:
12 | - Email
13 | - GithubUser
14 |
15 | words:
16 | - Vladislav
17 | - Trotsenko
18 | - bagage
19 | - bagages
20 | - bestwebua
21 | - codebases
22 | - codeclimate
23 | - commitspell
24 | - creds
25 | - gemspecs
26 | - healthcheck
27 | - lefthook
28 | - markdownlint
29 | - rubocop
30 | - simplecov
31 | - stdlib
32 | - obstools
33 | - yamlint
34 |
--------------------------------------------------------------------------------
/.circleci/linter_configs/.cspell.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | enableGlobDot: true
4 |
5 | patterns:
6 | - name: GithubUser
7 | pattern: /\[@.+\]/gmx
8 | - name: MarkdownCode
9 | pattern: /`{1,3}.+`{1,3}/gmx
10 | - name: MarkdownCodeBlock
11 | pattern: /^\s*```[\s\S]*?^\s*```/gmx
12 |
13 | languageSettings:
14 | - languageId: markdown
15 | ignoreRegExpList:
16 | - Email
17 | - GithubUser
18 | - MarkdownCode
19 | - MarkdownCodeBlock
20 |
21 | words:
22 | - Vladislav
23 | - Trotsenko
24 | - bestwebua
25 | - codebases
26 | - commiting
27 | - gemspecs
28 | - hanami
29 | - healthcheck
30 | - healthchecks
31 | - obstools
32 | - roda
33 |
--------------------------------------------------------------------------------
/.circleci/linter_configs/.fasterer.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | exclude_paths:
4 | - '.circleci/**/*.rb'
5 |
--------------------------------------------------------------------------------
/.circleci/linter_configs/.lefthook.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | no_tty: true
4 | skip_output:
5 | - meta
6 |
7 | commit-linters:
8 | commands:
9 | commitspell:
10 | run: .circleci/scripts/commitspell.sh -c '.circleci/linter_configs/.commitspell.yml'
11 |
12 | code-style-linters:
13 | commands:
14 | reek:
15 | run: bundle exec reek
16 | rubocop:
17 | run: bundle exec rubocop -c '.circleci/linter_configs/.rubocop.yml'
18 | shellcheck:
19 | glob: '*.{sh}'
20 | run: shellcheck --norc {all_files}
21 | yamllint:
22 | run: yamllint -c '.circleci/linter_configs/.yamllint.yml' .
23 |
24 | code-performance-linters:
25 | commands:
26 | fasterer:
27 | run: bundle exec fasterer
28 |
29 | code-vulnerability-linters:
30 | commands:
31 | bundle-audit:
32 | run: bundle exec bundle-audit check -c '.circleci/linter_configs/.bundler-audit.yml' --update
33 |
34 | code-documentation-linters:
35 | commands:
36 | cspell:
37 | run: cspell-cli lint -c '.circleci/linter_configs/.cspell.yml' '**/*.{txt,md}'
38 | markdownlint:
39 | run: markdownlint -c '.circleci/linter_configs/.markdownlint.yml' '**/*.md'
40 |
41 | release-linters:
42 | commands:
43 | changeloglint:
44 | run: .circleci/scripts/changeloglint.sh
45 |
--------------------------------------------------------------------------------
/.circleci/linter_configs/.markdownlint.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | default: true
4 |
5 | MD013:
6 | line_length: 500
7 |
8 | MD024:
9 | siblings_only: true
10 |
--------------------------------------------------------------------------------
/.circleci/linter_configs/.rubocop.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | require:
4 | - rubocop-rspec
5 | - rubocop-performance
6 |
7 | AllCops:
8 | DisplayCopNames: true
9 | DisplayStyleGuide: true
10 | TargetRubyVersion: 2.5
11 | SuggestExtensions: false
12 | NewCops: enable
13 |
14 | # Metrics ---------------------------------------------------------------------
15 |
16 | Metrics/ClassLength:
17 | Max: 150
18 |
19 | Metrics/MethodLength:
20 | Max: 15
21 |
22 | Metrics/BlockLength:
23 | Enabled: false
24 |
25 | Metrics/CyclomaticComplexity:
26 | Enabled: false
27 |
28 | Metrics/PerceivedComplexity:
29 | Enabled: false
30 |
31 | # Naming ----------------------------------------------------------------------
32 |
33 | Naming/VariableNumber:
34 | Enabled: false
35 |
36 | Naming/RescuedExceptionsVariableName:
37 | Enabled: false
38 |
39 | Naming/InclusiveLanguage:
40 | Enabled: false
41 |
42 | # Style -----------------------------------------------------------------------
43 |
44 | Style/Documentation:
45 | Enabled: false
46 |
47 | Style/DoubleNegation:
48 | Enabled: false
49 |
50 | Style/EmptyCaseCondition:
51 | Enabled: false
52 |
53 | Style/ParallelAssignment:
54 | Enabled: false
55 |
56 | Style/RescueStandardError:
57 | Enabled: false
58 |
59 | Style/RedundantConstantBase:
60 | Enabled: false
61 |
62 | # Layout ----------------------------------------------------------------------
63 |
64 | Layout/LineLength:
65 | Max: 150
66 |
67 | Layout/ClassStructure:
68 | Enabled: true
69 | Categories:
70 | module_inclusion:
71 | - include
72 | - prepend
73 | - extend
74 | ExpectedOrder:
75 | - module_inclusion
76 | - constants
77 | - public_class_methods
78 | - initializer
79 | - public_methods
80 | - protected_methods
81 | - private_methods
82 |
83 | Layout/EmptyLineAfterGuardClause:
84 | Enabled: false
85 |
86 | # Lint ------------------------------------------------------------------------
87 |
88 | Lint/NoReturnInBeginEndBlocks:
89 | Enabled: false
90 |
91 | # Gemspec ---------------------------------------------------------------------
92 |
93 | Gemspec/RequireMFA:
94 | Enabled: false
95 |
96 | Gemspec/DevelopmentDependencies:
97 | Enabled: false
98 |
99 | Gemspec/AddRuntimeDependency:
100 | Enabled: false
101 |
102 | # Performance -----------------------------------------------------------------
103 |
104 | Performance/MethodObjectAsBlock:
105 | Enabled: false
106 |
107 | # RSpec -----------------------------------------------------------------------
108 |
109 | RSpec/ExampleLength:
110 | Enabled: false
111 |
112 | RSpec/NestedGroups:
113 | Enabled: false
114 |
115 | RSpec/MultipleExpectations:
116 | Enabled: false
117 |
118 | RSpec/MessageChain:
119 | Enabled: false
120 |
121 | RSpec/ContextWording:
122 | Enabled: false
123 |
124 | RSpec/AnyInstance:
125 | Enabled: false
126 |
127 | RSpec/MessageSpies:
128 | Enabled: false
129 |
130 | RSpec/MultipleDescribes:
131 | Enabled: false
132 |
133 | RSpec/MultipleMemoizedHelpers:
134 | Enabled: false
135 |
136 | RSpec/StubbedMock:
137 | Enabled: false
138 |
139 | RSpec/VerifiedDoubleReference:
140 | Enabled: false
141 |
142 | RSpec/StringAsInstanceDoubleConstant:
143 | Enabled: false
144 |
--------------------------------------------------------------------------------
/.circleci/linter_configs/.yamllint.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | extends: default
4 |
5 | rules:
6 | line-length:
7 | max: 200
8 |
--------------------------------------------------------------------------------
/.circleci/scripts/changeloglint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | changelog=$(if [ "$1" = "" ]; then echo "CHANGELOG.md"; else echo "$1"; fi)
5 |
6 | get_current_gem_version() {
7 | ruby -r rubygems -e "puts Gem::Specification::load('$(ls -- *.gemspec)').version"
8 | }
9 |
10 | latest_changelog_tag() {
11 | grep -Po "(?<=\#\# \[)[0-9]+\.[0-9]+\.[0-9]+?(?=\])" "$changelog" | head -n 1
12 | }
13 |
14 | current_gem_version="$(get_current_gem_version)"
15 |
16 | if [ "$current_gem_version" = "$(latest_changelog_tag)" ]
17 | then
18 | echo "SUCCESS: Current gem version ($current_gem_version) has been found on the top of project changelog."
19 | else
20 | echo "FAILURE: Following to \"Keep a Changelog\" convention current gem version ($current_gem_version) must be mentioned on the top of project changelog."
21 | exit 1
22 | fi
23 |
--------------------------------------------------------------------------------
/.circleci/scripts/commitspell.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | configuration=$(if [ "$2" = "" ]; then echo "$2"; else echo " $1 $2"; fi)
5 | latest_commit=$(git rev-parse HEAD)
6 |
7 | spellcheck_info() {
8 | echo "Checking the spelling of the latest commit ($latest_commit) message..."
9 | }
10 |
11 | compose_cspell_command() {
12 | echo "cspell-cli lint stdin$configuration"
13 | }
14 |
15 | cspell="$(compose_cspell_command)"
16 |
17 | spellcheck_latest_commit() {
18 | git log -1 --pretty=%B | $cspell
19 | }
20 |
21 | spellcheck_info
22 | spellcheck_latest_commit
23 |
--------------------------------------------------------------------------------
/.circleci/scripts/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | GH_CLI_RELEASES_URL="https://github.com/cli/cli/releases"
5 | FILE_NAME="gh"
6 | BUILD_ARCHITECTURE="linux_amd64.deb"
7 | DELIMETER="_"
8 | PACKAGE_FILE="$FILE_NAME$DELIMETER$BUILD_ARCHITECTURE"
9 |
10 | gh_cli_latest_release() {
11 | curl -sL -o /dev/null -w '%{url_effective}' "$GH_CLI_RELEASES_URL/latest" | rev | cut -f 1 -d '/'| rev
12 | }
13 |
14 | download_gh_cli() {
15 | test -z "$VERSION" && VERSION="$(gh_cli_latest_release)"
16 | test -z "$VERSION" && {
17 | echo "Unable to get GitHub CLI release." >&2
18 | exit 1
19 | }
20 | curl -s -L -o "$PACKAGE_FILE" "$GH_CLI_RELEASES_URL/download/$VERSION/$FILE_NAME$DELIMETER$(printf '%s' "$VERSION" | cut -c 2-100)$DELIMETER$BUILD_ARCHITECTURE"
21 | }
22 |
23 | install_gh_cli() {
24 | sudo dpkg -i "$PACKAGE_FILE"
25 | rm "$PACKAGE_FILE"
26 | }
27 |
28 | get_release_candidate_version() {
29 | ruby -r rubygems -e "puts Gem::Specification::load('$(ls -- *.gemspec)').version"
30 | }
31 |
32 | release_candidate_tag="v$(get_release_candidate_version)"
33 |
34 | is_an_existing_github_release() {
35 | git fetch origin "refs/tags/$release_candidate_tag" >/dev/null 2>&1
36 | }
37 |
38 | release_to_rubygems() {
39 | echo "Setting RubyGems publisher credentials..."
40 | ./.circleci/scripts/set_publisher_credentials.sh
41 | echo "Preparation for release..."
42 | git config --global user.email "${PUBLISHER_EMAIL}"
43 | git config --global user.name "${PUBLISHER_NAME}"
44 | git stash
45 | gem install yard gem-ctags
46 | bundle install
47 | echo "Publishing new gem release to RubyGems..."
48 | rake release
49 | }
50 |
51 | release_to_github() {
52 | echo "Downloading and installing latest gh cli..."
53 | download_gh_cli
54 | install_gh_cli
55 | echo "Publishing new release notes to GitHub..."
56 | gh release create "$release_candidate_tag" --generate-notes
57 | }
58 |
59 | update_develop_branch() {
60 | echo "Updating develop branch with new release tag..."
61 | git checkout develop
62 | git merge "$release_candidate_tag" --ff --no-edit
63 | git push origin develop
64 | }
65 |
66 | if is_an_existing_github_release
67 | then echo "Tag $release_candidate_tag already exists on GitHub. Skipping releasing flow..."
68 | else release_to_rubygems; release_to_github; update_develop_branch
69 | fi
70 |
--------------------------------------------------------------------------------
/.circleci/scripts/set_publisher_credentials.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 | set +x
4 | mkdir -p ~/.gem
5 |
6 | cat << EOF > ~/.gem/credentials
7 | ---
8 | :rubygems_api_key: ${RUBYGEMS_API_KEY}
9 | EOF
10 |
11 | chmod 0600 ~/.gem/credentials
12 | set -x
13 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | checks:
4 | argument-count:
5 | enabled: false
6 | method-complexity:
7 | enabled: false
8 |
9 | plugins:
10 | rubocop:
11 | enabled: true
12 | channel: rubocop-1-67
13 | config:
14 | file: .circleci/linter_configs/.rubocop.yml
15 |
16 | reek:
17 | enabled: true
18 |
--------------------------------------------------------------------------------
/.github/BRANCH_NAMING_CONVENTION.md:
--------------------------------------------------------------------------------
1 | # Branch naming convention
2 |
3 | ## Branch naming
4 |
5 | > Please note for new pull requests create new branches from current `develop` branch only.
6 |
7 | Branch name should include type of your contribution and context. Please follow next pattern for naming your branches:
8 |
9 | ```bash
10 | feature/add-some-feature
11 | technical/some-technical-improvements
12 | bugfix/fix-some-bug-name
13 | ```
14 |
15 | ## Before PR actions
16 |
17 | ### Squash commits
18 |
19 | Please squash all branch commits into the one before opening your PR from your fork. It's simple to do with the git:
20 |
21 | ```bash
22 | git rebase -i [hash your first commit of your branch]~1
23 | git rebase -i 6467fe36232401fa740af067cfd8ac9ec932fed2~1 # example
24 | ```
25 |
26 | ### Add commit description
27 |
28 | Please complete your commit description following next pattern:
29 |
30 | ```code
31 | Technical/Add info files # should be the same name as your branch name
32 |
33 | * Added license, changelog, contributing, code of conduct docs
34 | * Added GitHub templates
35 | * Updated project license link
36 | ```
37 |
--------------------------------------------------------------------------------
/.github/DEVELOPMENT_ENVIRONMENT_GUIDE.md:
--------------------------------------------------------------------------------
1 | # Development environment guide
2 |
3 | ## Preparing
4 |
5 | Clone `healthcheck-endpoint` repository:
6 |
7 | ```bash
8 | git clone https://github.com/obstools/healthcheck-endpoint.git
9 | cd ruby-gem
10 | ```
11 |
12 | Configure latest Ruby environment:
13 |
14 | ```bash
15 | echo 'ruby-3.2.0' > .ruby-version
16 | cp .circleci/gemspec_latest healthcheck_endpoint.gemspec
17 | ```
18 |
19 | ## Commiting
20 |
21 | Commit your changes excluding `.ruby-version`, `healthcheck_endpoint.gemspec`
22 |
23 | ```bash
24 | git add . ':!.ruby-version' ':!healthcheck_endpoint.gemspec'
25 | git commit -m 'Your new awesome healthcheck_endpoint feature'
26 | ```
27 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | github: [bestwebua]
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG] Your bug report title here"
5 | labels: bug
6 | assignees: bestwebua
7 |
8 | ---
9 |
10 |
11 |
12 | ### New bug checklist
13 |
14 | - [ ] I have updated `healthcheck_endpoint` to the latest version
15 | - [ ] I have read the [Contribution Guidelines](https://github.com/obstools/healthcheck-endpoint/blob/master/CONTRIBUTING.md)
16 | - [ ] I have read the [documentation](https://github.com/obstools/healthcheck-endpoint/blob/master/README.md)
17 | - [ ] I have searched for [existing GitHub issues](https://github.com/obstools/healthcheck-endpoint/issues)
18 |
19 |
20 |
21 | ### Bug description
22 |
23 |
24 | ##### Complete output when running `healthcheck_endpoint`, including the stack trace and command used
25 |
26 |
27 | [INSERT OUTPUT HERE]
28 |
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea
4 | title: "[FEATURE] Your feature request title here"
5 | labels: enhancement
6 | assignees: bestwebua
7 |
8 | ---
9 |
10 |
11 |
12 | ### New feature request checklist
13 |
14 | - [ ] I have updated `healthcheck_endpoint` to the latest version
15 | - [ ] I have read the [Contribution Guidelines](https://github.com/obstools/healthcheck-endpoint/blob/master/CONTRIBUTING.md)
16 | - [ ] I have read the [documentation](https://github.com/obstools/healthcheck-endpoint/blob/master/README.md)
17 | - [ ] I have searched for [existing GitHub issues](https://github.com/obstools/healthcheck-endpoint/issues)
18 |
19 |
20 |
21 | ### Feature description
22 |
23 |
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Issue report
3 | about: Create a report to help us improve
4 | title: "[ISSUE] Your issue report title here"
5 | labels: ''
6 | assignees: bestwebua
7 |
8 | ---
9 |
10 |
11 |
12 | ### New issue checklist
13 |
14 | - [ ] I have updated `healthcheck_endpoint` to the latest version
15 | - [ ] I have read the [Contribution Guidelines](https://github.com/obstools/healthcheck-endpoint/blob/master/CONTRIBUTING.md)
16 | - [ ] I have read the [documentation](https://github.com/obstools/healthcheck-endpoint/blob/master/README.md)
17 | - [ ] I have searched for [existing GitHub issues](https://github.com/obstools/healthcheck-endpoint/issues)
18 |
19 |
20 |
21 | ### Issue description
22 |
23 |
24 | ##### Complete output when running `healthcheck_endpoint`, including the stack trace and command used
25 |
26 |
27 |
28 | ```code
29 | [INSERT OUTPUT HERE]
30 | ```
31 |
32 |
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question
3 | about: Ask your question to team
4 | title: "[QUESTION] Your question title here"
5 | labels: question
6 | assignees: bestwebua
7 |
8 | ---
9 |
10 |
11 |
12 | ### New question checklist
13 |
14 | - [ ] I have read the [Contribution Guidelines](https://github.com/obstools/healthcheck-endpoint/blob/master/CONTRIBUTING.md)
15 | - [ ] I have read the [documentation](https://github.com/obstools/healthcheck-endpoint/blob/master/README.md)
16 | - [ ] I have searched for [existing GitHub issues](https://github.com/obstools/healthcheck-endpoint/issues)
17 |
18 |
19 |
20 | ### Question
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # PR Details
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Description
9 |
10 |
11 |
12 | ## Related Issue
13 |
14 |
15 |
16 |
17 |
18 |
19 | ## Motivation and Context
20 |
21 |
22 |
23 | ## How Has This Been Tested
24 |
25 |
26 |
27 |
28 |
29 | ## Types of changes
30 |
31 |
32 |
33 | - [ ] Docs change / refactoring / dependency upgrade
34 | - [ ] Bug fix (non-breaking change which fixes an issue)
35 | - [ ] New feature (non-breaking change which adds functionality)
36 | - [ ] Breaking change (fix or feature that would cause existing functionality to change)
37 |
38 | ## Checklist
39 |
40 |
41 |
42 |
43 | - [ ] My code follows the code style of this project
44 | - [ ] My change requires a change to the documentation
45 | - [ ] I have updated the documentation accordingly
46 | - [ ] I have read the [**CONTRIBUTING** document](https://github.com/obstools/healthcheck-endpoint/blob/master/CONTRIBUTING.md)
47 | - [ ] I have added tests to cover my changes
48 | - [ ] I have run `bundle exec rspec` from the root directory to see all new and existing tests pass
49 | - [ ] I have run `rubocop` and `reek` to ensure the code style is valid
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /_yardoc/
4 | /coverage/
5 | /doc/
6 | /pkg/
7 | /spec/reports/
8 | /tmp/
9 | .rspec_status
10 | .DS_Store
11 | Gemfile.lock
12 |
--------------------------------------------------------------------------------
/.reek.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | detectors:
4 | IrresponsibleModule:
5 | enabled: false
6 |
7 | ControlParameter:
8 | exclude:
9 | - HealthcheckEndpoint::Configuration#raise_unless
10 | - HealthcheckEndpoint::Configuration#validator_argument_type
11 |
12 | LongParameterList:
13 | exclude:
14 | - HealthcheckEndpoint::Configuration#raise_unless
15 |
16 | ManualDispatch:
17 | exclude:
18 | - HealthcheckEndpoint::Configuration#validator_services_callable
19 |
20 | TooManyConstants:
21 | exclude:
22 | - HealthcheckEndpoint::Configuration
23 |
24 | TooManyStatements:
25 | exclude:
26 | - HealthcheckEndpoint::Configuration#validate_attribute
27 |
28 | UtilityFunction:
29 | exclude:
30 | - HealthcheckEndpoint::Configuration#build_configuration_settings
31 | - HealthcheckEndpoint::Configuration#validator_argument_type
32 | - HealthcheckEndpoint::Configuration#validator_endpoint
33 | - HealthcheckEndpoint::Configuration#validator_http_status_failure
34 | - HealthcheckEndpoint::Configuration#validator_http_status_success
35 | - HealthcheckEndpoint::Configuration#validator_services_callable
36 | - HealthcheckEndpoint::Resolver#configuration
37 |
38 | exclude_paths:
39 | - spec/support/helpers
40 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --require spec_helper
2 | --format documentation
3 |
--------------------------------------------------------------------------------
/.ruby-gemset:
--------------------------------------------------------------------------------
1 | healthcheck_endpoint
2 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | ruby-2.5.0
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | 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).
4 |
5 | ## [1.0.0] - 2024-10-25
6 |
7 | ### Updated
8 |
9 | - Updated gem name to `healthcheck_endpoint`, namespace to `HealthcheckEndpoint`
10 | - Updated gem runtime/development dependencies
11 | - Updated gem documentation
12 |
13 | ## [0.3.0] - 2024-04-15
14 |
15 | ### Added
16 |
17 | - Added ability to show in the response current probe type
18 |
19 | ```json
20 | {
21 | "data": {
22 | "id": "a09efd18-e09f-4207-9a43-b4bf89f76b47",
23 | "type": "application-startup-healthcheck",
24 | "attributes": {
25 | "postgres": true,
26 | "redis": true,
27 | "rabbit": true
28 | }
29 | }
30 | }
31 | ```
32 |
33 | ## [0.2.0] - 2024-03-28
34 |
35 | ### Added
36 |
37 | - Added ability to use configuration with default settings without block passing
38 |
39 | ```ruby
40 | HealthcheckEndpoint.configure # It will create configuration instance with default settings
41 | ```
42 |
43 | ### Updated
44 |
45 | - Updated `HealthcheckEndpoint.configure`, tests
46 | - Updated gem documentation
47 |
48 | ## [0.1.0] - 2024-03-26
49 |
50 | ### Added
51 |
52 | - First release of `healthcheck_endpoint`.
53 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention
26 | or advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in
32 | a professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at . All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: http://contributor-covenant.org
74 | [version]: http://contributor-covenant.org/version/1/4/
75 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to `healthcheck_endpoint`
2 |
3 | Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved.
4 |
5 | Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue or assessing patches and features.
6 |
7 | ## Using the issue tracker
8 |
9 | The issue tracker is the preferred channel for [issue/bug reports](#issuebug-reports), [feature requests](#feature-requests), [questions](#questions) and submitting [pull requests](#pull-requests).
10 |
11 | ## Issue/bug reports
12 |
13 | A bug is a _demonstrable problem_ that is caused by the code in the repository. Good bug reports are extremely helpful - thank you!
14 |
15 | Guidelines for issue/bug reports:
16 |
17 | 1. **Use the GitHub issue search** — check if the issue has already been reported
18 | 2. **Check if the issue has been fixed** — try to reproduce it using the latest `master` or `develop` branch in the repository
19 | 3. `healthcheck_endpoint` [issue template](.github/ISSUE_TEMPLATE/issue_report.md)/[bug template](.github/ISSUE_TEMPLATE/bug_report.md)
20 |
21 | 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.
22 |
23 | ## Feature requests
24 |
25 | Feature requests are welcome. But take a moment to find out whether your idea fits with the scope and aims of the project. It's up to _you_ to make a strong case to convince the project's developers of the merits of this feature. Please provide as much detail and context as possible.
26 |
27 | ## Questions
28 |
29 | We're always open to a new conversations. So if you have any questions just ask us.
30 |
31 | ## Pull requests
32 |
33 | Good pull requests - patches, improvements, new features - are a fantastic help. They should remain focused in scope and avoid containing unrelated commits.
34 |
35 | **Please ask first** before embarking on any significant pull request (e.g. implementing features, refactoring code, porting to a different language), otherwise you risk spending a lot of time working on something that the project's developers might not want to merge into the project.
36 |
37 | Please adhere to the coding conventions used throughout a project (indentation, accurate comments, etc.) and any other requirements (such as test coverage). Not all features proposed will be added but we are open to having a conversation about a feature you are championing.
38 |
39 | Guidelines for pull requests:
40 |
41 | 1. `healthcheck_endpoint` [pull request template](.github/PULL_REQUEST_TEMPLATE.md)
42 | 2. Fork the repo, checkout to `develop` branch
43 | 3. Run the tests. This is to make sure your starting point works
44 | 4. Read our [branch naming convention](.github/BRANCH_NAMING_CONVENTION.md)
45 | 5. Create a new branch
46 | 6. Read our [setup development environment guide](.github/DEVELOPMENT_ENVIRONMENT_GUIDE.md)
47 | 7. Make your changes. Please note that your PR should include tests for the new codebase!
48 | 8. Push to your fork and submit a pull request to `develop` branch
49 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 | git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
5 | gemspec
6 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2024 Vladislav Trotsenko
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 
2 |
3 | [](https://codeclimate.com/github/obstools/healthcheck-endpoint/maintainability)
4 | [](https://codeclimate.com/github/obstools/healthcheck-endpoint/test_coverage)
5 | [](https://circleci.com/gh/obstools/healthcheck-endpoint/tree/master)
6 | [](https://badge.fury.io/rb/healthcheck_endpoint)
7 | [](https://rubygems.org/gems/healthcheck_endpoint)
8 | [](LICENSE.txt)
9 | [](CODE_OF_CONDUCT.md)
10 |
11 | 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.
12 |
13 | ## Table of Contents
14 |
15 | - [Features](#features)
16 | - [Requirements](#requirements)
17 | - [Installation](#installation)
18 | - [Configuring](#configuring)
19 | - [Usage](#usage)
20 | - [Integration](#integration)
21 | - [Rack](#rack)
22 | - [Roda](#roda)
23 | - [Hanami](#hanami)
24 | - [Rails](#rails)
25 | - [Healthcheck endpoint response](#healthcheck-endpoint-response)
26 | - [Contributing](#contributing)
27 | - [License](#license)
28 | - [Code of Conduct](#code-of-conduct)
29 | - [Credits](#credits)
30 | - [Versioning](#versioning)
31 | - [Changelog](CHANGELOG.md)
32 |
33 | ## Features
34 |
35 | - Built-in default configuration
36 | - Configurable services for startup/liveness/readiness probes
37 | - Configurable root endpoints namespace
38 | - Configurable startup/liveness/readiness probes endpoints
39 | - Configurable successful/failure response statuses
40 |
41 | ## Requirements
42 |
43 | Ruby MRI 2.5.0+
44 |
45 | ## Installation
46 |
47 | Add this line to your application's Gemfile:
48 |
49 | ```ruby
50 | gem 'healthcheck_endpoint'
51 | ```
52 |
53 | And then execute:
54 |
55 | ```bash
56 | bundle
57 | ```
58 |
59 | Or install it yourself as:
60 |
61 | ```bash
62 | gem install healthcheck_endpoint
63 | ```
64 |
65 | ## Configuring
66 |
67 | To start working with this gem, you must configure it first as in the example below:
68 |
69 | ```ruby
70 | # config/initializers/healthcheck_endpoint.rb
71 |
72 | require 'healthcheck_endpoint'
73 |
74 | HealthcheckEndpoint.configure do |config|
75 | # Optional parameter. The list of services that can be triggered
76 | # during running probes. Each value of this hash should be callable
77 | # and return boolean.
78 | # It is equal to empty hash by default.
79 | config.services = {
80 | postgres: -> { true },
81 | redis: -> { true },
82 | rabbit: -> { false }
83 | }
84 |
85 | # Optional parameter. The list of services that will be checked
86 | # during running startup probe. As array items must be used an
87 | # existing keys, defined in config.services.
88 | # It is equal to empty array by default.
89 | config.services_startup = %i[postgres]
90 |
91 | # Optional parameter. The list of services that will be checked
92 | # during running liveness probe. As array items must be used an
93 | # existing keys, defined in config.services.
94 | # It is equal to empty array by default.
95 | config.services_liveness = %i[redis]
96 |
97 | # Optional parameter. The list of services that will be checked
98 | # during running liveness probe. As array items must be used an
99 | # existing keys, defined in config.services.
100 | # It is equal to empty array by default.
101 | config.services_readiness = %i[postgres redis rabbit]
102 |
103 | # Optional parameter. The name of middleware's root
104 | # endpoints namespace. Use '/' if you want to use root
105 | # namespace. It is equal to /healthcheck by default.
106 | config.endpoints_namespace = '/application-healthcheck'
107 |
108 | # Optional parameter. The startup endpoint path.
109 | # It is equal to /startup by default.
110 | config.endpoint_startup = '/startup-probe'
111 |
112 | # Optional parameter. The liveness endpoint path.
113 | # It is equal to /liveness by default.
114 | config.endpoint_liveness = '/liveness-probe'
115 |
116 | # Optional parameter. The readiness endpoint path.
117 | # It is equal to /readiness by default.
118 | config.endpoint_readiness = '/readiness-probe'
119 |
120 | # Optional parameter. The HTTP successful status
121 | # for startup probe. It is equal to 200 by default.
122 | config.endpoint_startup_status_success = 201
123 |
124 | # Optional parameter. The HTTP successful status
125 | # for liveness probe. It is equal to 200 by default.
126 | config.endpoint_liveness_status_success = 202
127 |
128 | # Optional parameter. The HTTP successful status
129 | # for readiness probe. It is equal to 200 by default.
130 | config.endpoint_readiness_status_success = 203
131 |
132 | # Optional parameter. The HTTP failure status
133 | # for startup probe. It is equal to 500 by default.
134 | config.endpoint_startup_status_failure = 501
135 |
136 | # Optional parameter. The HTTP failure status
137 | # for liveness probe. It is equal to 500 by default.
138 | config.endpoint_liveness_status_failure = 502
139 |
140 | # Optional parameter. The HTTP failure status
141 | # for readiness probe. It is equal to 500 by default.
142 | config.endpoint_readiness_status_failure = 503
143 | end
144 | ```
145 |
146 | ## Usage
147 |
148 | ### Integration
149 |
150 | Please note, to start using this middleware you should configure `HealthcheckEndpoint` before and then you should to add `HealthcheckEndpoint::RackMiddleware` on the top of middlewares list.
151 |
152 | #### Rack
153 |
154 | ```ruby
155 | require 'healthcheck_endpoint'
156 |
157 | # Configuring HealthcheckEndpoint with default settings
158 | HealthcheckEndpoint.configure
159 |
160 | Rack::Builder.app do
161 | use HealthcheckEndpoint::RackMiddleware
162 | run YourApplication
163 | end
164 | ```
165 |
166 | #### Roda
167 |
168 | ```ruby
169 | require 'healthcheck_endpoint'
170 |
171 | # Configuring HealthcheckEndpoint with default settings
172 | HealthcheckEndpoint.configure
173 |
174 | class YourApplication < Roda
175 | use HealthcheckEndpoint::RackMiddleware
176 | end
177 | ```
178 |
179 | #### Hanami
180 |
181 | ```ruby
182 | # config/initializers/healthcheck_endpoint.rb
183 |
184 | require 'healthcheck_endpoint'
185 |
186 | # Configuring HealthcheckEndpoint with default settings
187 | HealthcheckEndpoint.configure
188 | ```
189 |
190 | ```ruby
191 | # config/environment.rb
192 |
193 | Hanami.configure do
194 | middleware.use HealthcheckEndpoint::RackMiddleware
195 | end
196 | ```
197 |
198 | #### Rails
199 |
200 | For Rails 7+, you can use the built-in healthcheck feature provided by Rails itself. See the [official Rails documentation on healthchecks](https://edgeapi.rubyonrails.org/classes/Rails/HealthController.html) for details. However, if you need more advanced healthcheck functionality or want to maintain consistency across different frameworks, you can still use this gem with Rails as follows:
201 |
202 | ```ruby
203 | # config/initializers/healthcheck_endpoint.rb
204 |
205 | require 'healthcheck_endpoint'
206 |
207 | # Configuring HealthcheckEndpoint with default settings
208 | HealthcheckEndpoint.configure
209 | ```
210 |
211 | ```ruby
212 | # config/application.rb
213 |
214 | class Application < Rails::Application
215 | config.middleware.use HealthcheckEndpoint::RackMiddleware
216 | end
217 | ```
218 |
219 | ### Healthcheck endpoint response
220 |
221 | Each healthcheck endpoint returns proper HTTP status and body. Determining the response status is based on the general result of service checks (when all are successful the status is successful, at least one failure - the status is failure). The response body represented as JSON document with a structure like in the example below:
222 |
223 | ```json
224 | {
225 | "data": {
226 | "id": "a09efd18-e09f-4207-9a43-b4bf89f76b47",
227 | "type": "application-startup-healthcheck",
228 | "attributes": {
229 | "postgres": true,
230 | "redis": true,
231 | "rabbit": true
232 | }
233 | }
234 | }
235 | ```
236 |
237 | ## Contributing
238 |
239 | 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/obstools/healthcheck-endpoint/issues). Be sure to follow Contributor Code of Conduct below and our [Contributing Guidelines](CONTRIBUTING.md).
240 |
241 | ## License
242 |
243 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
244 |
245 | ## Code of Conduct
246 |
247 | Everyone interacting in the `healthcheck_endpoint` project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
248 |
249 | ## Credits
250 |
251 | - [The Contributors](https://github.com/obstools/healthcheck-endpoint/graphs/contributors) for code and awesome suggestions
252 | - [The Stargazers](https://github.com/obstools/healthcheck-endpoint/stargazers) for showing their support
253 |
254 | ## Versioning
255 |
256 | `healthcheck_endpoint` uses [Semantic Versioning 2.0.0](https://semver.org)
257 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'bundler/gem_tasks'
4 | require 'rspec/core/rake_task'
5 |
6 | RSpec::Core::RakeTask.new(:spec)
7 |
8 | task default: :spec
9 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require 'bundler/setup'
5 | require 'healthcheck_endpoint'
6 |
7 | # You can add fixtures and/or initialization code here to make experimenting
8 | # with your gem easier. You can also use a different console, if you like.
9 |
10 | # (If you use this, don't forget to add pry to your Gemfile!)
11 | # require "pry"
12 | # Pry.start
13 |
14 | require 'irb'
15 | IRB.start(__FILE__)
16 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 |
6 | bundle install
7 |
8 | # Do any other automated setup that you need to do here
9 |
--------------------------------------------------------------------------------
/healthcheck_endpoint.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'lib/healthcheck_endpoint/version'
4 |
5 | Gem::Specification.new do |spec|
6 | spec.name = 'healthcheck_endpoint'
7 | spec.version = HealthcheckEndpoint::VERSION
8 | spec.authors = ['Vladislav Trotsenko']
9 | spec.email = %w[admin@bestweb.com.ua]
10 | spec.summary = %(Simple configurable application healthcheck rack middleware)
11 | spec.description = %(Simple configurable application healthcheck rack middleware.)
12 | spec.homepage = 'https://github.com/obstools/healthcheck-endpoint'
13 | spec.license = 'MIT'
14 |
15 | current_ruby_version = ::Gem::Version.new(::RUBY_VERSION)
16 | ffaker_version = current_ruby_version >= ::Gem::Version.new('3.0.0') ? '~> 2.23' : '~> 2.21'
17 |
18 | spec.required_ruby_version = '>= 2.5.0'
19 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20 | spec.require_paths = %w[lib]
21 |
22 | spec.add_runtime_dependency 'rack', '>= 2.0.1'
23 |
24 | spec.add_development_dependency 'ffaker', ffaker_version
25 | spec.add_development_dependency 'json_matchers', '~> 0.11.1'
26 | spec.add_development_dependency 'rake', '~> 13.2', '>= 13.2.1'
27 | spec.add_development_dependency 'rspec', '~> 3.13'
28 | end
29 |
--------------------------------------------------------------------------------
/lib/healthcheck_endpoint.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'healthcheck_endpoint/core'
4 |
5 | module HealthcheckEndpoint
6 | class << self
7 | def configuration(&block)
8 | @configuration ||= begin
9 | return unless block
10 |
11 | HealthcheckEndpoint::Configuration.new(&block)
12 | end
13 | end
14 |
15 | def configure(&block)
16 | return configuration {} unless block # rubocop:disable Lint/EmptyBlock
17 |
18 | configuration(&block)
19 | end
20 |
21 | def reset_configuration!
22 | @configuration = nil
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/healthcheck_endpoint/configuration.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module HealthcheckEndpoint
4 | class Configuration
5 | ATTRIBUTES = %i[
6 | services
7 | services_startup
8 | services_liveness
9 | services_readiness
10 | endpoints_namespace
11 | endpoint_startup
12 | endpoint_liveness
13 | endpoint_readiness
14 | endpoint_startup_status_success
15 | endpoint_liveness_status_success
16 | endpoint_readiness_status_success
17 | endpoint_startup_status_failure
18 | endpoint_liveness_status_failure
19 | endpoint_readiness_status_failure
20 | ].freeze
21 | ENDPOINTS_NAMESPACE = '/healthcheck'
22 | ENDPOINT_STARTUP = '/startup'
23 | ENDPOINT_LIVENESS = '/liveness'
24 | ENDPOINT_READINESS = '/readiness'
25 | DEFAULT_HTTP_STATUS_SUCCESS = 200
26 | DEFAULT_HTTP_STATUS_FAILURE = 500
27 | AVILABLE_HTTP_STATUSES_SUCCESS = (DEFAULT_HTTP_STATUS_SUCCESS..226).freeze
28 | AVILABLE_HTTP_STATUSES_FAILURE = (DEFAULT_HTTP_STATUS_FAILURE..511).freeze
29 |
30 | Settings = ::Struct.new(*HealthcheckEndpoint::Configuration::ATTRIBUTES, keyword_init: true) do
31 | def update(&block)
32 | return self unless block
33 |
34 | tap(&block)
35 | end
36 | end
37 |
38 | attr_reader(*HealthcheckEndpoint::Configuration::ATTRIBUTES)
39 |
40 | def initialize(&block)
41 | configuration_settings = build_configuration_settings(&block)
42 | HealthcheckEndpoint::Configuration::ATTRIBUTES.each do |attribute|
43 | public_send(:"#{attribute}=", configuration_settings.public_send(attribute))
44 | end
45 | end
46 |
47 | HealthcheckEndpoint::Configuration::ATTRIBUTES.each do |attribute|
48 | define_method(:"#{attribute}=") do |argument|
49 | validate_attribute(__method__, attribute, argument)
50 | instance_variable_set(:"@#{attribute}", argument)
51 | end
52 | end
53 |
54 | private
55 |
56 | def build_configuration_settings(&block) # rubocop:disable Metrics/MethodLength
57 | HealthcheckEndpoint::Configuration::Settings.new(
58 | services: {},
59 | services_startup: [],
60 | services_liveness: [],
61 | services_readiness: [],
62 | endpoints_namespace: HealthcheckEndpoint::Configuration::ENDPOINTS_NAMESPACE,
63 | endpoint_startup: HealthcheckEndpoint::Configuration::ENDPOINT_STARTUP,
64 | endpoint_liveness: HealthcheckEndpoint::Configuration::ENDPOINT_LIVENESS,
65 | endpoint_readiness: HealthcheckEndpoint::Configuration::ENDPOINT_READINESS,
66 | endpoint_startup_status_success: HealthcheckEndpoint::Configuration::DEFAULT_HTTP_STATUS_SUCCESS,
67 | endpoint_liveness_status_success: HealthcheckEndpoint::Configuration::DEFAULT_HTTP_STATUS_SUCCESS,
68 | endpoint_readiness_status_success: HealthcheckEndpoint::Configuration::DEFAULT_HTTP_STATUS_SUCCESS,
69 | endpoint_startup_status_failure: HealthcheckEndpoint::Configuration::DEFAULT_HTTP_STATUS_FAILURE,
70 | endpoint_liveness_status_failure: HealthcheckEndpoint::Configuration::DEFAULT_HTTP_STATUS_FAILURE,
71 | endpoint_readiness_status_failure: HealthcheckEndpoint::Configuration::DEFAULT_HTTP_STATUS_FAILURE
72 | ).update(&block)
73 | end
74 |
75 | def validate_attribute(method, attribute, value) # rubocop:disable Metrics/AbcSize
76 | raise_unless(HealthcheckEndpoint::Error::Configuration::ArgumentType, method, *validator_argument_type(attribute, value))
77 | case attribute
78 | when HealthcheckEndpoint::Configuration::ATTRIBUTES[0]
79 | raise_unless(HealthcheckEndpoint::Error::Configuration::NotCallableService, method, *validator_services_callable(value))
80 | when *HealthcheckEndpoint::Configuration::ATTRIBUTES[1..3]
81 | raise_unless(HealthcheckEndpoint::Error::Configuration::UnknownService, method, *validator_services_conformity(value))
82 | when *HealthcheckEndpoint::Configuration::ATTRIBUTES[4..7]
83 | raise_unless(HealthcheckEndpoint::Error::Configuration::EnpointPattern, method, *validator_endpoint(value))
84 | when *HealthcheckEndpoint::Configuration::ATTRIBUTES[8..10]
85 | raise_unless(HealthcheckEndpoint::Error::Configuration::HttpStatusSuccess, method, *validator_http_status_success(value))
86 | when *HealthcheckEndpoint::Configuration::ATTRIBUTES[11..13]
87 | raise_unless(HealthcheckEndpoint::Error::Configuration::HttpStatusFailure, method, *validator_http_status_failure(value))
88 | end
89 | end
90 |
91 | def validator_argument_type(method_name, argument)
92 | [
93 | argument,
94 | argument.is_a?(
95 | case method_name
96 | when :services then ::Hash
97 | when *HealthcheckEndpoint::Configuration::ATTRIBUTES[1..3] then ::Array
98 | when *HealthcheckEndpoint::Configuration::ATTRIBUTES[4..7] then ::String
99 | when *HealthcheckEndpoint::Configuration::ATTRIBUTES[8..13] then ::Integer
100 | end
101 | )
102 | ]
103 | end
104 |
105 | def validator_endpoint(argument)
106 | [argument, argument[%r{\A/.*\z}]]
107 | end
108 |
109 | def validator_http_status_success(argument)
110 | [argument, HealthcheckEndpoint::Configuration::AVILABLE_HTTP_STATUSES_SUCCESS.include?(argument)]
111 | end
112 |
113 | def validator_http_status_failure(argument)
114 | [argument, HealthcheckEndpoint::Configuration::AVILABLE_HTTP_STATUSES_FAILURE.include?(argument)]
115 | end
116 |
117 | def validator_services_callable(services_to_check, target_service = nil)
118 | result = services_to_check.all? do |service_name, service_context|
119 | target_service = service_name
120 | service_context.respond_to?(:call)
121 | end
122 |
123 | [target_service, result]
124 | end
125 |
126 | def validator_services_conformity(services_to_check, target_service = nil)
127 | result = services_to_check.all? do |service_name|
128 | target_service = service_name
129 | services.key?(service_name)
130 | end
131 |
132 | [target_service, result]
133 | end
134 |
135 | def raise_unless(exception_class, argument_name, argument_context, condition)
136 | raise exception_class.new(argument_context, argument_name) unless condition
137 | end
138 | end
139 | end
140 |
--------------------------------------------------------------------------------
/lib/healthcheck_endpoint/core.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module HealthcheckEndpoint
4 | module Error
5 | module Configuration
6 | require_relative 'error/configuration/argument_type'
7 | require_relative 'error/configuration/unknown_service'
8 | require_relative 'error/configuration/not_callable_service'
9 | require_relative 'error/configuration/enpoint_pattern'
10 | require_relative 'error/configuration/http_status_success'
11 | require_relative 'error/configuration/http_status_failure'
12 | require_relative 'error/configuration/not_configured'
13 | end
14 | end
15 |
16 | require_relative 'version'
17 | require_relative 'configuration'
18 | require_relative 'resolver'
19 | require_relative 'rack_middleware'
20 | end
21 |
--------------------------------------------------------------------------------
/lib/healthcheck_endpoint/error/configuration/argument_type.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module HealthcheckEndpoint
4 | module Error
5 | module Configuration
6 | ArgumentType = ::Class.new(::ArgumentError) do
7 | def initialize(arg_value, arg_name)
8 | super("#{arg_value} is not a valid #{arg_name}")
9 | end
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/healthcheck_endpoint/error/configuration/enpoint_pattern.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module HealthcheckEndpoint
4 | module Error
5 | module Configuration
6 | EnpointPattern = ::Class.new(::ArgumentError) do
7 | def initialize(arg_value, arg_name)
8 | super("#{arg_value} does not match a valid enpoint pattern for #{arg_name}")
9 | end
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/healthcheck_endpoint/error/configuration/http_status_failure.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module HealthcheckEndpoint
4 | module Error
5 | module Configuration
6 | HttpStatusFailure = ::Class.new(::ArgumentError) do
7 | def initialize(arg_value, arg_name)
8 | super("Status #{arg_value} is wrong HTTP failure status for #{arg_name}. It should be in the range 500-511")
9 | end
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/healthcheck_endpoint/error/configuration/http_status_success.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module HealthcheckEndpoint
4 | module Error
5 | module Configuration
6 | HttpStatusSuccess = ::Class.new(::ArgumentError) do
7 | def initialize(arg_value, arg_name)
8 | super("Status #{arg_value} is wrong HTTP successful status for #{arg_name}. It should be in the range 200-226")
9 | end
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/healthcheck_endpoint/error/configuration/not_callable_service.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module HealthcheckEndpoint
4 | module Error
5 | module Configuration
6 | NotCallableService = ::Class.new(::ArgumentError) do
7 | def initialize(service_name, services_setter)
8 | super("Service #{service_name} is not callable. All values for #{services_setter} should be a callable objects")
9 | end
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/healthcheck_endpoint/error/configuration/not_configured.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module HealthcheckEndpoint
4 | module Error
5 | module Configuration
6 | NotConfigured = ::Class.new(::RuntimeError) do
7 | def initialize
8 | super('The configuration is empty. Please use HealthcheckEndpoint.configure before')
9 | end
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/healthcheck_endpoint/error/configuration/unknown_service.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module HealthcheckEndpoint
4 | module Error
5 | module Configuration
6 | UnknownService = ::Class.new(::ArgumentError) do
7 | def initialize(service_name, services_setter)
8 | super("Unknown #{service_name} service name for #{services_setter}. You should define it in config.services firstly")
9 | end
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/healthcheck_endpoint/rack_middleware.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module HealthcheckEndpoint
4 | class RackMiddleware
5 | def initialize(
6 | app,
7 | resolver = HealthcheckEndpoint::Resolver,
8 | counfigured = !!HealthcheckEndpoint.configuration
9 | )
10 | @app = app
11 | @resolver = resolver
12 | @counfigured = counfigured
13 | end
14 |
15 | def call(env)
16 | raise HealthcheckEndpoint::Error::Configuration::NotConfigured unless counfigured
17 |
18 | resolver.call(env) || app.call(env)
19 | end
20 |
21 | private
22 |
23 | attr_reader :app, :resolver, :counfigured
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/healthcheck_endpoint/resolver.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module HealthcheckEndpoint
4 | class Resolver
5 | require 'rack'
6 | require 'securerandom'
7 | require 'json'
8 |
9 | PROBE_ENDPOINTS = %i[endpoint_startup endpoint_liveness endpoint_readiness].freeze
10 | CONTENT_TYPE = { 'Content-Type' => 'application/json' }.freeze
11 | ROOT_NAMESPACE = '/'
12 |
13 | def self.call(rack_env)
14 | new(rack_env).call
15 | end
16 |
17 | def initialize(rack_env)
18 | @request = ::Rack::Request.new(rack_env)
19 | end
20 |
21 | def call
22 | return unless probe_name
23 |
24 | [response_status, HealthcheckEndpoint::Resolver::CONTENT_TYPE, [response_jsonapi]]
25 | end
26 |
27 | private
28 |
29 | attr_reader :request
30 |
31 | def configuration
32 | HealthcheckEndpoint.configuration
33 | end
34 |
35 | def root_namespace
36 | @root_namespace ||= configuration.endpoints_namespace
37 | end
38 |
39 | def root_namespace?
40 | root_namespace.eql?(HealthcheckEndpoint::Resolver::ROOT_NAMESPACE)
41 | end
42 |
43 | HealthcheckEndpoint::Resolver::PROBE_ENDPOINTS.each do |method|
44 | define_method(method) do
45 | target_endpoint = configuration.public_send(method)
46 | root_namespace? ? target_endpoint : "#{root_namespace}#{target_endpoint}"
47 | end
48 | end
49 |
50 | def probe_name
51 | @probe_name ||=
52 | case request.path
53 | when endpoint_startup then :startup
54 | when endpoint_liveness then :liveness
55 | when endpoint_readiness then :readiness
56 | end
57 | end
58 |
59 | def probe_result
60 | @probe_result ||=
61 | configuration.public_send(:"services_#{probe_name}").each_with_object({}) do |service_name, response|
62 | response[service_name] = configuration.services[service_name].call
63 | end
64 | end
65 |
66 | def response_status
67 | configuration.public_send(
68 | probe_result.values.all? ? :"endpoint_#{probe_name}_status_success" : :"endpoint_#{probe_name}_status_failure"
69 | )
70 | end
71 |
72 | def response_jsonapi
73 | {
74 | data: {
75 | id: ::SecureRandom.uuid,
76 | type: "application-#{probe_name}-healthcheck", # TODO: it would be great to be able to configure this
77 | attributes: probe_result
78 | }
79 | }.to_json
80 | end
81 | end
82 | end
83 |
--------------------------------------------------------------------------------
/lib/healthcheck_endpoint/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module HealthcheckEndpoint
4 | VERSION = '1.0.0'
5 | end
6 |
--------------------------------------------------------------------------------
/spec/healthcheck_endpoint/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe HealthcheckEndpoint::Configuration do
4 | describe 'defined constants' do
5 | it { expect(described_class).to be_const_defined(:ATTRIBUTES) }
6 | it { expect(described_class).to be_const_defined(:ENDPOINTS_NAMESPACE) }
7 | it { expect(described_class).to be_const_defined(:ENDPOINT_STARTUP) }
8 | it { expect(described_class).to be_const_defined(:ENDPOINT_LIVENESS) }
9 | it { expect(described_class).to be_const_defined(:ENDPOINT_READINESS) }
10 | it { expect(described_class).to be_const_defined(:DEFAULT_HTTP_STATUS_SUCCESS) }
11 | it { expect(described_class).to be_const_defined(:DEFAULT_HTTP_STATUS_FAILURE) }
12 | it { expect(described_class).to be_const_defined(:AVILABLE_HTTP_STATUSES_SUCCESS) }
13 | it { expect(described_class).to be_const_defined(:AVILABLE_HTTP_STATUSES_FAILURE) }
14 | end
15 |
16 | describe HealthcheckEndpoint::Configuration::Settings do
17 | describe '#update' do
18 | subject(:updated_settings) { described_class.new.update(&block) }
19 |
20 | context 'when configuration block has been passed' do
21 | let(:block) { ->(config) { config.services = 42 } }
22 |
23 | it 'updates structure' do
24 | expect(updated_settings).to be_an_instance_of(described_class)
25 | expect(updated_settings.services).to eq(42)
26 | end
27 | end
28 |
29 | context 'when configuration block has not been passed' do
30 | let(:block) { nil }
31 |
32 | it 'does not update structure' do
33 | expect(updated_settings).to be_an_instance_of(described_class)
34 | expect(updated_settings.services).to be_nil
35 | end
36 | end
37 | end
38 | end
39 |
40 | describe '.new' do
41 | subject(:configuration) do
42 | create_configuration(
43 | services: services,
44 | services_startup: services_startup,
45 | services_liveness: services_liveness,
46 | services_readiness: services_readiness,
47 | endpoints_namespace: endpoints_namespace,
48 | endpoint_startup: endpoint_startup,
49 | endpoint_liveness: endpoint_liveness,
50 | endpoint_readiness: endpoint_readiness,
51 | endpoint_startup_status_success: endpoint_startup_status_success,
52 | endpoint_liveness_status_success: endpoint_liveness_status_success,
53 | endpoint_readiness_status_success: endpoint_readiness_status_success,
54 | endpoint_startup_status_failure: endpoint_startup_status_failure,
55 | endpoint_liveness_status_failure: endpoint_liveness_status_failure,
56 | endpoint_readiness_status_failure: endpoint_readiness_status_failure
57 | )
58 | end
59 |
60 | context 'when valid configuration' do
61 | let(:service_name) { create_service(random_service_name) }
62 | let(:services) { create_service(service_name) }
63 | let(:services_startup) { [service_name] }
64 | let(:services_liveness) { [service_name] }
65 | let(:services_readiness) { [service_name] }
66 | let(:endpoints_namespace) { random_endpoint }
67 | let(:endpoint_startup) { random_endpoint }
68 | let(:endpoint_liveness) { random_endpoint }
69 | let(:endpoint_readiness) { random_endpoint }
70 | let(:endpoint_startup_status_success) { random_http_status(successful: true) }
71 | let(:endpoint_liveness_status_success) { random_http_status(successful: true) }
72 | let(:endpoint_readiness_status_success) { random_http_status(successful: true) }
73 | let(:endpoint_startup_status_failure) { random_http_status(successful: false) }
74 | let(:endpoint_liveness_status_failure) { random_http_status(successful: false) }
75 | let(:endpoint_readiness_status_failure) { random_http_status(successful: false) }
76 |
77 | it 'creates configuration instance' do
78 | expect(configuration.services).to eq(services)
79 | expect(configuration.services_startup).to eq(services_startup)
80 | expect(configuration.services_liveness).to eq(services_liveness)
81 | expect(configuration.services_readiness).to eq(services_readiness)
82 | expect(configuration.endpoints_namespace).to eq(endpoints_namespace)
83 | expect(configuration.endpoint_startup).to eq(endpoint_startup)
84 | expect(configuration.endpoint_liveness).to eq(endpoint_liveness)
85 | expect(configuration.endpoint_readiness).to eq(endpoint_readiness)
86 | expect(configuration.endpoint_startup_status_success).to eq(endpoint_startup_status_success)
87 | expect(configuration.endpoint_liveness_status_success).to eq(endpoint_liveness_status_success)
88 | expect(configuration.endpoint_readiness_status_success).to eq(endpoint_readiness_status_success)
89 | expect(configuration.endpoint_startup_status_failure).to eq(endpoint_startup_status_failure)
90 | expect(configuration.endpoint_liveness_status_failure).to eq(endpoint_liveness_status_failure)
91 | expect(configuration.endpoint_readiness_status_failure).to eq(endpoint_readiness_status_failure)
92 | expect(configuration).to be_an_instance_of(described_class)
93 | end
94 | end
95 |
96 | context 'when invalid configuration' do
97 | shared_examples 'raies argument error' do
98 | it 'raies argument error' do
99 | expect { configuration }.to raise_error(
100 | target_exception_class,
101 | expected_error_message
102 | )
103 | end
104 | end
105 |
106 | let(:invalid_argument) { 42 }
107 |
108 | context 'when services= setter is invalid' do
109 | context 'when argument has wrong type' do
110 | subject(:configuration) do
111 | create_configuration(services: invalid_argument)
112 | end
113 |
114 | let(:target_exception_class) { HealthcheckEndpoint::Error::Configuration::ArgumentType }
115 | let(:expected_error_message) { "#{invalid_argument} is not a valid services=" }
116 |
117 | include_examples 'raies argument error'
118 | end
119 |
120 | context 'when argument nested value is not a callable object' do
121 | subject(:configuration) do
122 | create_configuration(services: { service_name => invalid_argument })
123 | end
124 |
125 | let(:service_name) { random_service_name }
126 | let(:target_exception_class) { HealthcheckEndpoint::Error::Configuration::NotCallableService }
127 | let(:expected_error_message) do
128 | "Service #{service_name} is not callable. All values for services= should be a callable objects"
129 | end
130 |
131 | include_examples 'raies argument error'
132 | end
133 | end
134 |
135 | context 'when services_startup= setter is invalid' do
136 | context 'when argument has wrong type' do
137 | subject(:configuration) do
138 | create_configuration(services_startup: invalid_argument)
139 | end
140 |
141 | let(:target_exception_class) { HealthcheckEndpoint::Error::Configuration::ArgumentType }
142 | let(:expected_error_message) { "#{invalid_argument} is not a valid services_startup=" }
143 |
144 | include_examples 'raies argument error'
145 | end
146 |
147 | context 'when argument nested value is not a defined service' do
148 | subject(:configuration) do
149 | create_configuration(services_startup: [invalid_argument])
150 | end
151 |
152 | let(:target_exception_class) { HealthcheckEndpoint::Error::Configuration::UnknownService }
153 | let(:expected_error_message) do
154 | "Unknown #{invalid_argument} service name for services_startup=. You should define it in config.services firstly"
155 | end
156 |
157 | include_examples 'raies argument error'
158 | end
159 | end
160 |
161 | context 'when services_liveness= setter is invalid' do
162 | context 'when argument has wrong type' do
163 | subject(:configuration) do
164 | create_configuration(services_liveness: invalid_argument)
165 | end
166 |
167 | let(:target_exception_class) { HealthcheckEndpoint::Error::Configuration::ArgumentType }
168 | let(:expected_error_message) { "#{invalid_argument} is not a valid services_liveness=" }
169 |
170 | include_examples 'raies argument error'
171 | end
172 |
173 | context 'when argument nested value is not a defined service' do
174 | subject(:configuration) do
175 | create_configuration(services_liveness: [invalid_argument])
176 | end
177 |
178 | let(:target_exception_class) { HealthcheckEndpoint::Error::Configuration::UnknownService }
179 | let(:expected_error_message) do
180 | "Unknown #{invalid_argument} service name for services_liveness=. You should define it in config.services firstly"
181 | end
182 |
183 | include_examples 'raies argument error'
184 | end
185 | end
186 |
187 | context 'when services_readiness= setter is invalid' do
188 | context 'when argument has wrong type' do
189 | subject(:configuration) do
190 | create_configuration(services_readiness: invalid_argument)
191 | end
192 |
193 | let(:target_exception_class) { HealthcheckEndpoint::Error::Configuration::ArgumentType }
194 | let(:expected_error_message) { "#{invalid_argument} is not a valid services_readiness=" }
195 |
196 | include_examples 'raies argument error'
197 | end
198 |
199 | context 'when argument nested value is not a defined service' do
200 | subject(:configuration) do
201 | create_configuration(services_readiness: [invalid_argument])
202 | end
203 |
204 | let(:target_exception_class) { HealthcheckEndpoint::Error::Configuration::UnknownService }
205 | let(:expected_error_message) do
206 | "Unknown #{invalid_argument} service name for services_readiness=. You should define it in config.services firstly"
207 | end
208 |
209 | include_examples 'raies argument error'
210 | end
211 | end
212 |
213 | HealthcheckEndpoint::Configuration::ATTRIBUTES[4..7].each do |endpoint_setter|
214 | context "when #{endpoint_setter}= setter is invalid" do
215 | context 'when argument has wrong type' do
216 | subject(:configuration) do
217 | create_configuration(endpoint_setter => invalid_argument)
218 | end
219 |
220 | let(:target_exception_class) { HealthcheckEndpoint::Error::Configuration::ArgumentType }
221 | let(:expected_error_message) { "#{invalid_argument} is not a valid #{endpoint_setter}=" }
222 |
223 | include_examples 'raies argument error'
224 | end
225 |
226 | context 'when argument is not match to enpoint pattern' do
227 | subject(:configuration) do
228 | create_configuration(endpoint_setter => endpoint)
229 | end
230 |
231 | let(:endpoint) { invalid_argument.to_s }
232 | let(:target_exception_class) { HealthcheckEndpoint::Error::Configuration::EnpointPattern }
233 | let(:expected_error_message) do
234 | "#{endpoint} does not match a valid enpoint pattern for #{endpoint_setter}="
235 | end
236 |
237 | include_examples 'raies argument error'
238 | end
239 | end
240 | end
241 |
242 | HealthcheckEndpoint::Configuration::ATTRIBUTES[8..10].each do |endpoint_setter|
243 | context "when #{endpoint_setter}= setter is invalid" do
244 | context 'when argument has wrong type' do
245 | subject(:configuration) do
246 | create_configuration(endpoint_setter => invalid_argument.to_s)
247 | end
248 |
249 | let(:target_exception_class) { HealthcheckEndpoint::Error::Configuration::ArgumentType }
250 | let(:expected_error_message) { "#{invalid_argument} is not a valid #{endpoint_setter}=" }
251 |
252 | include_examples 'raies argument error'
253 | end
254 |
255 | context 'when argument is not match to enpoint pattern' do
256 | subject(:configuration) do
257 | create_configuration(endpoint_setter => invalid_argument)
258 | end
259 |
260 | let(:target_exception_class) { HealthcheckEndpoint::Error::Configuration::HttpStatusSuccess }
261 | let(:expected_error_message) do
262 | "Status #{invalid_argument} is wrong HTTP successful status for #{endpoint_setter}=. It should be in the range 200-226"
263 | end
264 |
265 | include_examples 'raies argument error'
266 | end
267 | end
268 | end
269 |
270 | HealthcheckEndpoint::Configuration::ATTRIBUTES[11..13].each do |endpoint_setter|
271 | context "when #{endpoint_setter}= setter is invalid" do
272 | context 'when argument has wrong type' do
273 | subject(:configuration) do
274 | create_configuration(endpoint_setter => invalid_argument.to_s)
275 | end
276 |
277 | let(:target_exception_class) { HealthcheckEndpoint::Error::Configuration::ArgumentType }
278 | let(:expected_error_message) { "#{invalid_argument} is not a valid #{endpoint_setter}=" }
279 |
280 | include_examples 'raies argument error'
281 | end
282 |
283 | context 'when argument is not match to enpoint pattern' do
284 | subject(:configuration) do
285 | create_configuration(endpoint_setter => invalid_argument)
286 | end
287 |
288 | let(:target_exception_class) { HealthcheckEndpoint::Error::Configuration::HttpStatusFailure }
289 | let(:expected_error_message) do
290 | "Status #{invalid_argument} is wrong HTTP failure status for #{endpoint_setter}=. It should be in the range 500-511"
291 | end
292 |
293 | include_examples 'raies argument error'
294 | end
295 | end
296 | end
297 | end
298 | end
299 | end
300 |
--------------------------------------------------------------------------------
/spec/healthcheck_endpoint/error/configuration/argument_type_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe HealthcheckEndpoint::Error::Configuration::ArgumentType do
4 | subject(:error_instance) { described_class.new(arg_value, arg_name) }
5 |
6 | let(:arg_value) { random_message }
7 | let(:arg_name) { :some_arg_name }
8 |
9 | it { expect(described_class).to be < ::ArgumentError }
10 | it { expect(error_instance).to be_an_instance_of(described_class) }
11 | it { expect(error_instance.to_s).to eq("#{arg_value} is not a valid #{arg_name}") }
12 | end
13 |
--------------------------------------------------------------------------------
/spec/healthcheck_endpoint/error/configuration/http_status_failure_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe HealthcheckEndpoint::Error::Configuration::HttpStatusFailure do
4 | subject(:error_instance) { described_class.new(arg_value, arg_name) }
5 |
6 | let(:arg_value) { random_http_status(successful: false) }
7 | let(:arg_name) { :some_arg_name }
8 |
9 | it { expect(described_class).to be < ::ArgumentError }
10 | it { expect(error_instance).to be_an_instance_of(described_class) }
11 |
12 | it 'returns exception message context' do
13 | expect(error_instance.to_s).to eq(
14 | "Status #{arg_value} is wrong HTTP failure status for #{arg_name}. It should be in the range 500-511"
15 | )
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/healthcheck_endpoint/error/configuration/http_status_success_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe HealthcheckEndpoint::Error::Configuration::HttpStatusSuccess do
4 | subject(:error_instance) { described_class.new(arg_value, arg_name) }
5 |
6 | let(:arg_value) { random_http_status(successful: false) }
7 | let(:arg_name) { :some_arg_name }
8 |
9 | it { expect(described_class).to be < ::ArgumentError }
10 | it { expect(error_instance).to be_an_instance_of(described_class) }
11 |
12 | it 'returns exception message context' do
13 | expect(error_instance.to_s).to eq(
14 | "Status #{arg_value} is wrong HTTP successful status for #{arg_name}. It should be in the range 200-226"
15 | )
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/healthcheck_endpoint/error/configuration/not_callable_service_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe HealthcheckEndpoint::Error::Configuration::NotCallableService do
4 | subject(:error_instance) { described_class.new(service_name, services_setter) }
5 |
6 | let(:service_name) { random_service_name }
7 | let(:services_setter) { :some_services_setter= }
8 |
9 | it { expect(described_class).to be < ::ArgumentError }
10 | it { expect(error_instance).to be_an_instance_of(described_class) }
11 |
12 | it 'returns exception message context' do
13 | expect(error_instance.to_s).to eq(
14 | "Service #{service_name} is not callable. All values for #{services_setter} should be a callable objects"
15 | )
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/healthcheck_endpoint/error/configuration/not_configured_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe HealthcheckEndpoint::Error::Configuration::NotConfigured do
4 | subject(:error_instance) { described_class.new }
5 |
6 | it { expect(described_class).to be < ::RuntimeError }
7 | it { expect(error_instance).to be_an_instance_of(described_class) }
8 |
9 | it 'returns exception message context' do
10 | expect(error_instance.to_s).to eq(
11 | 'The configuration is empty. Please use HealthcheckEndpoint.configure before'
12 | )
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/spec/healthcheck_endpoint/error/configuration/unknown_service_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe HealthcheckEndpoint::Error::Configuration::UnknownService do
4 | subject(:error_instance) { described_class.new(service_name, services_setter) }
5 |
6 | let(:service_name) { random_service_name }
7 | let(:services_setter) { :some_services_setter= }
8 |
9 | it { expect(described_class).to be < ::ArgumentError }
10 | it { expect(error_instance).to be_an_instance_of(described_class) }
11 |
12 | it 'returns exception message context' do
13 | expect(error_instance.to_s).to eq(
14 | "Unknown #{service_name} service name for #{services_setter}. You should define it in config.services firstly"
15 | )
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/healthcheck_endpoint/rack_middleware_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe HealthcheckEndpoint::RackMiddleware do
4 | subject(:middleware) { described_class.new(app, resolver).call(env) }
5 |
6 | let(:app_context) { 'some app rack response' }
7 | let(:app) { instance_double('RackApplication', call: app_context) }
8 | let(:resolver_context) { true }
9 | let(:resolver) { class_double('Resolver', call: resolver_context) }
10 | let(:env) { {} }
11 |
12 | context 'when HealthcheckEndpoint is configured' do
13 | before { init_configuration }
14 |
15 | context 'when resolver matches the route' do
16 | it 'returns rack response' do
17 | expect(resolver).to receive(:call).with(env)
18 | expect(middleware).to eq(resolver_context)
19 | end
20 | end
21 |
22 | context 'when resolver does not match the route' do
23 | let(:resolver_context) { nil }
24 |
25 | it 'returns rack response' do
26 | expect(resolver).to receive(:call).with(env)
27 | expect(app).to receive(:call).with(env)
28 | expect(middleware).to eq(app_context)
29 | end
30 | end
31 | end
32 |
33 | context 'when HealthcheckEndpoint is not configured' do
34 | it 'raises NotConfigured exception' do
35 | expect { middleware }.to raise_error(
36 | HealthcheckEndpoint::Error::Configuration::NotConfigured,
37 | 'The configuration is empty. Please use HealthcheckEndpoint.configure before'
38 | )
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/spec/healthcheck_endpoint/resolver_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe HealthcheckEndpoint::Resolver do
4 | describe 'defined constants' do
5 | it { expect(described_class).to be_const_defined(:PROBE_ENDPOINTS) }
6 | it { expect(described_class).to be_const_defined(:CONTENT_TYPE) }
7 | it { expect(described_class).to be_const_defined(:ROOT_NAMESPACE) }
8 | end
9 |
10 | describe '.call' do
11 | subject(:resolver) do
12 | described_class.call(
13 | Rack::MockRequest.env_for.merge('PATH_INFO' => healthcheck_endpoint)
14 | )
15 | end
16 |
17 | let(:service_status_first) { true }
18 | let(:service_name_second) { random_service_name }
19 | let(:service_name_first) { random_service_name }
20 |
21 | context 'when unknown endpoint' do
22 | let(:healthcheck_endpoint) { '/unknown_endpoint' }
23 |
24 | before { init_configuration }
25 |
26 | it { is_expected.to be_nil }
27 | end
28 |
29 | %i[startup liveness readiness].each do |probe_name|
30 | context "when #{probe_name} root endpoint" do
31 | let(:service_status_second) { true }
32 | let(:healthcheck_endpoint) { current_configuration.public_send(:"endpoint_#{probe_name}") }
33 |
34 | before do
35 | init_configuration(
36 | services: { service_name_first => -> { service_status_first } },
37 | "services_#{probe_name}": [service_name_first],
38 | endpoints_namespace: '/'
39 | )
40 | end
41 |
42 | it 'returns rack middleware response' do
43 | response_status, _content_type, response_body = resolver
44 |
45 | expect(response_status).to eq(
46 | current_configuration.public_send(:"endpoint_#{probe_name}_status_success")
47 | )
48 | expect(response_body.first).to include("application-#{probe_name}-healthcheck")
49 | end
50 | end
51 |
52 | context "when #{probe_name} endpoint" do
53 | [true, false].each do |status| # rubocop:disable Performance/CollectionLiteralInLoop
54 | context "when successful status is #{status}" do
55 | let(:service_status_second) { status }
56 | let(:healthcheck_endpoint) do
57 | endpoint = current_configuration.public_send(:"endpoint_#{probe_name}")
58 | "#{current_configuration.endpoints_namespace}#{endpoint}"
59 | end
60 |
61 | before do
62 | init_configuration(
63 | services: {
64 | service_name_first => -> { service_status_first },
65 | service_name_second => -> { service_status_second }
66 | },
67 | "services_#{probe_name}": [
68 | service_name_first,
69 | service_name_second
70 | ]
71 | )
72 | end
73 |
74 | it 'returns rack middleware response' do
75 | response_status, content_type, response = resolver
76 | response_body, target_status = response.first, "endpoint_#{probe_name}_status_#{status ? 'success' : 'failure'}"
77 | expected_probe_status = current_configuration.public_send(target_status)
78 |
79 | expect(response_status).to eq(expected_probe_status)
80 | expect(content_type).to eq('Content-Type' => 'application/json')
81 | expect(::JSON.parse(response_body).dig('data', 'attributes')).to eq(
82 | service_name_first => service_status_first,
83 | service_name_second => service_status_second
84 | )
85 | expect(response_body).to match_json_schema('jsonapi_response')
86 | end
87 | end
88 | end
89 | end
90 | end
91 | end
92 | end
93 |
--------------------------------------------------------------------------------
/spec/healthcheck_endpoint/rspec_helper/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe HealthcheckEndpoint::RspecHelper::Configuration, type: :helper do
4 | describe '#configuration_block' do
5 | let(:configuration_params) { { param_one: 1, param_two: 2 } }
6 | let(:configuration_instance) { ::Struct.new(*configuration_params.keys).new }
7 |
8 | before { configuration_block(**configuration_params).call(configuration_instance) }
9 |
10 | it { expect(configuration_block).to be_an_instance_of(::Proc) }
11 |
12 | it 'sets configuration instance attributes' do
13 | configuration_params.each do |attribute, value|
14 | expect(configuration_instance.public_send(attribute)).to eq(value)
15 | end
16 | end
17 | end
18 |
19 | describe '#create_configuration' do
20 | subject(:configuration_builder) do
21 | create_configuration(
22 | services: services,
23 | services_startup: %i[a]
24 | )
25 | end
26 |
27 | let(:services) { { a: -> {} } }
28 | let(:services_startup) { %i[a] }
29 |
30 | it 'returns configuration instance with defined params' do
31 | expect(configuration_builder).to be_an_instance_of(HealthcheckEndpoint::Configuration)
32 | expect(configuration_builder.services).to eq(services)
33 | expect(configuration_builder.services_startup).to eq(services_startup)
34 | end
35 | end
36 |
37 | describe '#init_configuration' do
38 | subject(:configuration_initializer) { init_configuration(endpoints_namespace: endpoints_namespace) }
39 |
40 | let(:endpoints_namespace) { '/some_endpoint' }
41 |
42 | it 'initializes configuration instance with random and predefined params' do
43 | expect(configuration_initializer).to be_an_instance_of(HealthcheckEndpoint::Configuration)
44 | expect(configuration_initializer.endpoints_namespace).to eq(endpoints_namespace)
45 | end
46 | end
47 |
48 | describe '#current_configuration' do
49 | subject(:current_configuration_instance) { current_configuration }
50 |
51 | it do
52 | expect(HealthcheckEndpoint).to receive(:configuration)
53 | current_configuration_instance
54 | end
55 | end
56 |
57 | describe '#reset_configuration' do
58 | subject(:reset_current_configuration) { reset_configuration }
59 |
60 | it do
61 | expect(HealthcheckEndpoint).to receive(:reset_configuration!)
62 | reset_current_configuration
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/spec/healthcheck_endpoint/rspec_helper/context_generator_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe HealthcheckEndpoint::RspecHelper::ContextGenerator, type: :helper do
4 | describe '#random_message' do
5 | it 'returns random message' do
6 | expect(::FFaker::Lorem).to receive(:sentence).and_call_original
7 | expect(random_message).to be_an_instance_of(::String)
8 | end
9 | end
10 |
11 | describe '#random_service_name' do
12 | it 'returns random service name' do
13 | expect(::FFaker::InternetSE).to receive(:login_user_name).and_call_original
14 | expect(random_service_name).to be_an_instance_of(::String)
15 | end
16 | end
17 |
18 | describe '#random_endpoint' do
19 | it 'returns random endpoint' do
20 | expect(::FFaker::InternetSE).to receive(:login_user_name).and_call_original
21 | expect(random_endpoint).to match(%r{\A/.*\z})
22 | end
23 | end
24 |
25 | describe '#random_http_status' do
26 | context 'when successful status' do
27 | it 'returns random successful http status' do
28 | expect(random_http_status(successful: true)).to be_between(200, 226)
29 | end
30 | end
31 |
32 | context 'when failure status' do
33 | it 'returns random failure http status' do
34 | expect(random_http_status(successful: false)).to be_between(500, 511)
35 | end
36 | end
37 | end
38 |
39 | describe '#create_service' do
40 | subject(:builded_service) { create_service(service_name, successful: service_type) }
41 |
42 | let(:service_name) { random_service_name }
43 |
44 | shared_examples 'returns hash with service' do
45 | it 'returns hash with service' do
46 | expect(builded_service.transform_values(&:call)).to eq(service_name => service_type)
47 | end
48 | end
49 |
50 | context 'when successful service' do
51 | let(:service_type) { true }
52 |
53 | include_examples 'returns hash with service'
54 | end
55 |
56 | context 'when failed service' do
57 | let(:service_type) { false }
58 |
59 | include_examples 'returns hash with service'
60 | end
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/spec/healthcheck_endpoint/version_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe HealthcheckEndpoint::VERSION do
4 | it { is_expected.not_to be_nil }
5 | end
6 |
--------------------------------------------------------------------------------
/spec/healthcheck_endpoint_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe HealthcheckEndpoint do
4 | let(:service_name) { create_service(random_service_name) }
5 | let(:services) { create_service(service_name) }
6 | let(:services_startup) { [service_name] }
7 | let(:services_liveness) { [service_name] }
8 | let(:services_readiness) { [service_name] }
9 | let(:endpoints_namespace) { random_endpoint }
10 | let(:endpoint_startup) { random_endpoint }
11 | let(:endpoint_liveness) { random_endpoint }
12 | let(:endpoint_readiness) { random_endpoint }
13 | let(:endpoint_startup_status_success) { random_http_status(successful: true) }
14 | let(:endpoint_liveness_status_success) { random_http_status(successful: true) }
15 | let(:endpoint_readiness_status_success) { random_http_status(successful: true) }
16 | let(:endpoint_startup_status_failure) { random_http_status(successful: false) }
17 | let(:endpoint_liveness_status_failure) { random_http_status(successful: false) }
18 | let(:endpoint_readiness_status_failure) { random_http_status(successful: false) }
19 |
20 | describe '.configure' do
21 | subject(:configuration) { described_class.configure(&config_block) }
22 |
23 | let(:config_block) { nil }
24 |
25 | context 'without block' do
26 | it 'sets default attributes into configuration instance' do
27 | expect { configuration }
28 | .to change(described_class, :configuration)
29 | .from(nil)
30 | .to(be_instance_of(HealthcheckEndpoint::Configuration))
31 | expect(configuration).to be_an_instance_of(HealthcheckEndpoint::Configuration)
32 | expect(configuration.services).to eq({})
33 | expect(configuration.services_startup).to eq([])
34 | expect(configuration.services_liveness).to eq([])
35 | expect(configuration.services_readiness).to eq([])
36 | expect(configuration.endpoints_namespace).to eq(HealthcheckEndpoint::Configuration::ENDPOINTS_NAMESPACE)
37 | expect(configuration.endpoint_startup).to eq(HealthcheckEndpoint::Configuration::ENDPOINT_STARTUP)
38 | expect(configuration.endpoint_liveness).to eq(HealthcheckEndpoint::Configuration::ENDPOINT_LIVENESS)
39 | expect(configuration.endpoint_readiness).to eq(HealthcheckEndpoint::Configuration::ENDPOINT_READINESS)
40 | expect(configuration.endpoint_startup_status_success).to eq(HealthcheckEndpoint::Configuration::DEFAULT_HTTP_STATUS_SUCCESS)
41 | expect(configuration.endpoint_liveness_status_success).to eq(HealthcheckEndpoint::Configuration::DEFAULT_HTTP_STATUS_SUCCESS)
42 | expect(configuration.endpoint_readiness_status_success).to eq(HealthcheckEndpoint::Configuration::DEFAULT_HTTP_STATUS_SUCCESS)
43 | expect(configuration.endpoint_startup_status_failure).to eq(HealthcheckEndpoint::Configuration::DEFAULT_HTTP_STATUS_FAILURE)
44 | expect(configuration.endpoint_liveness_status_failure).to eq(HealthcheckEndpoint::Configuration::DEFAULT_HTTP_STATUS_FAILURE)
45 | expect(configuration.endpoint_readiness_status_failure).to eq(HealthcheckEndpoint::Configuration::DEFAULT_HTTP_STATUS_FAILURE)
46 | end
47 | end
48 |
49 | context 'with block' do
50 | let(:config_block) do
51 | configuration_block(
52 | services: services,
53 | services_startup: services_startup,
54 | services_liveness: services_liveness,
55 | services_readiness: services_readiness,
56 | endpoints_namespace: endpoints_namespace,
57 | endpoint_startup: endpoint_startup,
58 | endpoint_liveness: endpoint_liveness,
59 | endpoint_readiness: endpoint_readiness,
60 | endpoint_startup_status_success: endpoint_startup_status_success,
61 | endpoint_liveness_status_success: endpoint_liveness_status_success,
62 | endpoint_readiness_status_success: endpoint_readiness_status_success,
63 | endpoint_startup_status_failure: endpoint_startup_status_failure,
64 | endpoint_liveness_status_failure: endpoint_liveness_status_failure,
65 | endpoint_readiness_status_failure: endpoint_readiness_status_failure
66 | )
67 | end
68 |
69 | it 'sets attributes into configuration instance' do
70 | expect { configuration }
71 | .to change(described_class, :configuration)
72 | .from(nil)
73 | .to(be_instance_of(HealthcheckEndpoint::Configuration))
74 | expect(configuration).to be_an_instance_of(HealthcheckEndpoint::Configuration)
75 | expect(configuration.services).to eq(services)
76 | expect(configuration.services_startup).to eq(services_startup)
77 | expect(configuration.services_liveness).to eq(services_liveness)
78 | expect(configuration.services_readiness).to eq(services_readiness)
79 | expect(configuration.endpoints_namespace).to eq(endpoints_namespace)
80 | expect(configuration.endpoint_startup).to eq(endpoint_startup)
81 | expect(configuration.endpoint_liveness).to eq(endpoint_liveness)
82 | expect(configuration.endpoint_readiness).to eq(endpoint_readiness)
83 | expect(configuration.endpoint_startup_status_success).to eq(endpoint_startup_status_success)
84 | expect(configuration.endpoint_liveness_status_success).to eq(endpoint_liveness_status_success)
85 | expect(configuration.endpoint_readiness_status_success).to eq(endpoint_readiness_status_success)
86 | expect(configuration.endpoint_startup_status_failure).to eq(endpoint_startup_status_failure)
87 | expect(configuration.endpoint_liveness_status_failure).to eq(endpoint_liveness_status_failure)
88 | expect(configuration.endpoint_readiness_status_failure).to eq(endpoint_readiness_status_failure)
89 | end
90 | end
91 | end
92 |
93 | describe '.reset_configuration!' do
94 | before do
95 | described_class.configure(
96 | &configuration_block(
97 | services: services,
98 | services_startup: services_startup,
99 | services_liveness: services_liveness,
100 | services_readiness: services_readiness,
101 | endpoints_namespace: endpoints_namespace,
102 | endpoint_startup: endpoint_startup,
103 | endpoint_liveness: endpoint_liveness,
104 | endpoint_readiness: endpoint_readiness,
105 | endpoint_startup_status_success: endpoint_startup_status_success,
106 | endpoint_liveness_status_success: endpoint_liveness_status_success,
107 | endpoint_readiness_status_success: endpoint_readiness_status_success,
108 | endpoint_startup_status_failure: endpoint_startup_status_failure,
109 | endpoint_liveness_status_failure: endpoint_liveness_status_failure,
110 | endpoint_readiness_status_failure: endpoint_readiness_status_failure
111 | )
112 | )
113 | end
114 |
115 | it do
116 | expect { described_class.reset_configuration! }
117 | .to change(described_class, :configuration)
118 | .from(be_instance_of(HealthcheckEndpoint::Configuration)).to(nil)
119 | end
120 | end
121 |
122 | describe '.configuration' do
123 | subject(:configuration) { described_class.configuration }
124 |
125 | before do
126 | described_class.configure(&configuration_block(
127 | services: services,
128 | services_startup: services_startup,
129 | services_liveness: services_liveness,
130 | services_readiness: services_readiness,
131 | endpoints_namespace: endpoints_namespace,
132 | endpoint_startup: endpoint_startup,
133 | endpoint_liveness: endpoint_liveness,
134 | endpoint_readiness: endpoint_readiness,
135 | endpoint_startup_status_success: endpoint_startup_status_success,
136 | endpoint_liveness_status_success: endpoint_liveness_status_success,
137 | endpoint_readiness_status_success: endpoint_readiness_status_success,
138 | endpoint_startup_status_failure: endpoint_startup_status_failure,
139 | endpoint_liveness_status_failure: endpoint_liveness_status_failure,
140 | endpoint_readiness_status_failure: endpoint_readiness_status_failure
141 | ))
142 | end
143 |
144 | it 'returns configuration instance' do
145 | expect(configuration).to be_instance_of(HealthcheckEndpoint::Configuration)
146 | expect(configuration.services).to eq(services)
147 | expect(configuration.services_startup).to eq(services_startup)
148 | expect(configuration.services_liveness).to eq(services_liveness)
149 | expect(configuration.services_readiness).to eq(services_readiness)
150 | expect(configuration.endpoints_namespace).to eq(endpoints_namespace)
151 | expect(configuration.endpoint_startup).to eq(endpoint_startup)
152 | expect(configuration.endpoint_liveness).to eq(endpoint_liveness)
153 | expect(configuration.endpoint_readiness).to eq(endpoint_readiness)
154 | expect(configuration.endpoint_startup_status_success).to eq(endpoint_startup_status_success)
155 | expect(configuration.endpoint_liveness_status_success).to eq(endpoint_liveness_status_success)
156 | expect(configuration.endpoint_readiness_status_success).to eq(endpoint_readiness_status_success)
157 | expect(configuration.endpoint_startup_status_failure).to eq(endpoint_startup_status_failure)
158 | expect(configuration.endpoint_liveness_status_failure).to eq(endpoint_liveness_status_failure)
159 | expect(configuration.endpoint_readiness_status_failure).to eq(endpoint_readiness_status_failure)
160 | end
161 | end
162 | end
163 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | rspec_custom = ::File.join(::File.dirname(__FILE__), 'support/**/*.rb')
4 | ::Dir[::File.expand_path(rspec_custom)].sort.each { |file| require file unless file[/\A.+_spec\.rb\z/] }
5 |
6 | require_relative '../lib/healthcheck_endpoint'
7 |
8 | RSpec.configure do |config|
9 | config.expect_with(:rspec) do |expectations|
10 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true
11 | expectations.syntax = :expect
12 | end
13 |
14 | config.mock_with(:rspec) do |mocks|
15 | mocks.verify_partial_doubles = true
16 | end
17 |
18 | config.example_status_persistence_file_path = '.rspec_status'
19 | config.disable_monkey_patching!
20 | config.order = :random
21 | config.before { reset_configuration }
22 |
23 | config.include HealthcheckEndpoint::RspecHelper::ContextGenerator
24 | config.include HealthcheckEndpoint::RspecHelper::Configuration
25 |
26 | ::Kernel.srand(config.seed)
27 | end
28 |
--------------------------------------------------------------------------------
/spec/support/config/bundler.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'bundler/setup'
4 |
--------------------------------------------------------------------------------
/spec/support/config/ffaker.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'ffaker'
4 |
--------------------------------------------------------------------------------
/spec/support/config/json_matchers.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'json_matchers/rspec'
4 |
5 | JsonMatchers.schema_root = 'spec/support/schemas'
6 |
--------------------------------------------------------------------------------
/spec/support/config/pry.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'pry' if ::RUBY_VERSION[/\A3\.3.+\z/]
4 |
--------------------------------------------------------------------------------
/spec/support/config/simplecov.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | if ::RUBY_VERSION[/\A3\.3.+\z/]
4 | require 'simplecov'
5 |
6 | SimpleCov.minimum_coverage(100)
7 | SimpleCov.start { add_filter(%r{\A/spec/}) }
8 | end
9 |
--------------------------------------------------------------------------------
/spec/support/helpers/configuration.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module HealthcheckEndpoint
4 | module RspecHelper
5 | module Configuration
6 | def configuration_block(**configuration_settings)
7 | lambda do |config|
8 | configuration_settings.each do |attribute, value|
9 | config.public_send(:"#{attribute}=", value)
10 | end
11 | end
12 | end
13 |
14 | def create_configuration(**configuration_settings)
15 | HealthcheckEndpoint::Configuration.new(&configuration_block(**configuration_settings))
16 | end
17 |
18 | def init_configuration(**args)
19 | HealthcheckEndpoint.configure(
20 | &configuration_block(
21 | **args
22 | )
23 | )
24 | end
25 |
26 | def current_configuration
27 | HealthcheckEndpoint.configuration
28 | end
29 |
30 | def reset_configuration
31 | HealthcheckEndpoint.reset_configuration!
32 | end
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/spec/support/helpers/context_generator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module HealthcheckEndpoint
4 | module RspecHelper
5 | module ContextGenerator
6 | def random_message
7 | ::FFaker::Lorem.sentence
8 | end
9 |
10 | def random_service_name
11 | ::FFaker::InternetSE.login_user_name
12 | end
13 |
14 | def random_endpoint
15 | "/#{random_service_name}"
16 | end
17 |
18 | def random_http_status(successful:)
19 | (successful ? 200..226 : 500..511).to_a.sample
20 | end
21 |
22 | def create_service(service_name, successful: true)
23 | { service_name => -> { successful } }
24 | end
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/spec/support/schemas/jsonapi_response.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json-schema.org/draft/2019-09/schema",
3 | "$id": "http://example.com/example.json",
4 | "title": "Root Schema",
5 | "type": "object",
6 | "default": {},
7 | "required": [
8 | "data"
9 | ],
10 | "properties": {
11 | "data": {
12 | "title": "The data Schema",
13 | "type": "object",
14 | "default": {},
15 | "required": [
16 | "id",
17 | "type",
18 | "attributes"
19 | ],
20 | "properties": {
21 | "id": {
22 | "title": "The id Schema",
23 | "type": "string",
24 | "default": "",
25 | "examples": [
26 | "ea266931-feb6-4b19-9308-ec29f18f1c02"
27 | ]
28 | },
29 | "type": {
30 | "title": "The type Schema",
31 | "type": "string",
32 | "default": "",
33 | "examples": [
34 | "application-healthcheck"
35 | ]
36 | },
37 | "attributes": {
38 | "title": "The attributes Schema",
39 | "type": "object",
40 | "default": {},
41 | "properties": {
42 | "yettacoletta": {
43 | "title": "The yettacoletta Schema",
44 | "type": "boolean",
45 | "default": false,
46 | "examples": [
47 | true
48 | ]
49 | },
50 | "rubenwikstrm": {
51 | "title": "The rubenwikstrm Schema",
52 | "type": "boolean",
53 | "default": false,
54 | "examples": [
55 | false
56 | ]
57 | }
58 | },
59 | "examples": [{
60 | "yettacoletta": true,
61 | "rubenwikstrm": false
62 | }]
63 | }
64 | },
65 | "examples": [{
66 | "id": "ea266931-feb6-4b19-9308-ec29f18f1c02",
67 | "type": "application-healthcheck",
68 | "attributes": {
69 | "yettacoletta": true,
70 | "rubenwikstrm": false
71 | }
72 | }]
73 | }
74 | },
75 | "examples": [{
76 | "data": {
77 | "id": "ea266931-feb6-4b19-9308-ec29f18f1c02",
78 | "type": "application-healthcheck",
79 | "attributes": {
80 | "yettacoletta": true,
81 | "rubenwikstrm": false
82 | }
83 | }
84 | }]
85 | }
86 |
--------------------------------------------------------------------------------