├── .github └── workflows │ └── main.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console ├── release.sh └── setup ├── doc └── logo.png ├── health_bit.gemspec ├── lib ├── health_bit.rb └── health_bit │ ├── check.rb │ ├── check_error.rb │ ├── formatter.rb │ └── version.rb └── spec ├── health_bit ├── check_error_spec.rb ├── check_spec.rb ├── health_bit_spec.rb └── integration_spec.rb └── spec_helper.rb /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | rspec: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | ruby-version: [3.0, 2.7, 2.6] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Ruby ${{ matrix.ruby-version }} 17 | uses: ruby/setup-ruby@v1 18 | with: 19 | ruby-version: ${{ matrix.ruby-version }} 20 | 21 | - name: Cache gems 22 | uses: actions/cache@v1 23 | with: 24 | path: vendor/bundle 25 | key: ${{ runner.os }}-${{ matrix.ruby-version }}-bundler-${{ hashFiles('**/Gemfile.lock') }} 26 | restore-keys: | 27 | ${{ runner.os }}-${{ matrix.ruby-version }}-bundler- 28 | 29 | - name: Install gems 30 | run: | 31 | bundle config path vendor/bundle 32 | bundle install --jobs 4 --retry 3 33 | 34 | - name: Run tests 35 | run: bundle exec rspec 36 | 37 | rubocop: 38 | runs-on: ubuntu-latest 39 | 40 | steps: 41 | - uses: actions/checkout@v2 42 | 43 | - name: Set up Ruby 44 | uses: ruby/setup-ruby@v1 45 | with: 46 | ruby-version: 3.0 47 | 48 | - name: Cache gems 49 | uses: actions/cache@v1 50 | with: 51 | path: vendor/bundle 52 | key: ${{ runner.os }}-bundler-${{ hashFiles('**/Gemfile.lock') }} 53 | restore-keys: | 54 | ${{ runner.os }}-bundler- 55 | 56 | - name: Install gems 57 | run: | 58 | bundle config path vendor/bundle 59 | bundle install --jobs 4 --retry 3 60 | 61 | - name: Run Rubocop 62 | run: bundle exec rubocop 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /pkg/ 6 | /spec/reports/ 7 | /tmp/ 8 | /*.gem 9 | 10 | # rspec failure tracking 11 | .rspec_status 12 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - rubocop-performance 3 | 4 | AllCops: 5 | AutoCorrect: false 6 | Exclude: 7 | - 'click_house.gemspec' 8 | - 'bin/*' 9 | - 'spec/**/*' 10 | - 'vendor/**/*' 11 | TargetRubyVersion: 3 12 | 13 | Bundler/OrderedGems: 14 | Enabled: false 15 | 16 | Gemspec/RequiredRubyVersion: 17 | Enabled: false 18 | 19 | # ============================== Gemspec ====================== 20 | Gemspec/DeprecatedAttributeAssignment: 21 | Enabled: true 22 | Gemspec/RequireMFA: # new in 1.23 23 | Enabled: true 24 | 25 | # ============================== Performance ====================== 26 | Performance/AncestorsInclude: 27 | Enabled: true 28 | Performance/BigDecimalWithNumericArgument: 29 | Enabled: true 30 | Performance/RedundantSortBlock: 31 | Enabled: true 32 | Performance/RedundantStringChars: 33 | Enabled: true 34 | Performance/ReverseFirst: 35 | Enabled: true 36 | Performance/SortReverse: 37 | Enabled: true 38 | Performance/Squeeze: 39 | Enabled: true 40 | Performance/StringInclude: 41 | Enabled: true 42 | Performance/Sum: 43 | Enabled: true 44 | Performance/BlockGivenWithExplicitBlock: 45 | Enabled: true 46 | Performance/CollectionLiteralInLoop: 47 | Enabled: true 48 | Performance/ConstantRegexp: 49 | Enabled: true 50 | Performance/MethodObjectAsBlock: 51 | Enabled: true 52 | Performance/RedundantEqualityComparisonBlock: 53 | Enabled: true 54 | Performance/RedundantSplitRegexpArgument: 55 | Enabled: true 56 | Performance/MapCompact: 57 | Enabled: true 58 | Performance/ConcurrentMonotonicTime: # new in 1.12 59 | Enabled: true 60 | Performance/StringIdentifierArgument: # new in 1.13 61 | Enabled: true 62 | 63 | # ============================== Documentation ====================== 64 | Style/Documentation: 65 | Enabled: false 66 | 67 | # ============================== Metrics ============================ 68 | Metrics/ClassLength: 69 | Max: 180 70 | Metrics/BlockLength: 71 | Enabled: true 72 | Metrics/MethodLength: 73 | Max: 25 74 | Metrics/AbcSize: 75 | Max: 40 76 | 77 | # ============================== Naming ============================= 78 | Naming/PredicateName: 79 | ForbiddenPrefixes: 80 | - is_ 81 | Naming/FileName: 82 | Enabled: true 83 | Exclude: 84 | - 'Gemfile' 85 | Naming/MethodParameterName: 86 | Enabled: false 87 | Naming/AccessorMethodName: 88 | Enabled: false 89 | Naming/InclusiveLanguage: 90 | Enabled: true 91 | Naming/BlockForwarding: # new in 1.24 92 | Enabled: true 93 | 94 | # ============================== Layout ============================= 95 | Layout/HashAlignment: 96 | EnforcedHashRocketStyle: key 97 | EnforcedColonStyle: key 98 | Layout/ParameterAlignment: 99 | EnforcedStyle: with_fixed_indentation 100 | Layout/CaseIndentation: 101 | EnforcedStyle: case 102 | IndentOneStep: false 103 | Layout/MultilineMethodCallIndentation: 104 | Enabled: true 105 | EnforcedStyle: indented 106 | Layout/SpaceBeforeBlockBraces: 107 | EnforcedStyle: space 108 | EnforcedStyleForEmptyBraces: space 109 | Layout/EmptyLines: 110 | Enabled: true 111 | Layout/EmptyLineAfterMagicComment: 112 | Enabled: false 113 | Layout/EmptyLinesAroundBlockBody: 114 | Enabled: true 115 | Layout/EndAlignment: 116 | EnforcedStyleAlignWith: variable 117 | Layout/FirstHashElementIndentation: 118 | EnforcedStyle: consistent 119 | Layout/HeredocIndentation: 120 | Enabled: false 121 | Layout/RescueEnsureAlignment: 122 | Enabled: false 123 | Layout/SpaceAroundMethodCallOperator: 124 | Enabled: true 125 | Layout/LineLength: 126 | Max: 140 127 | Layout/EmptyLinesAroundAttributeAccessor: 128 | Enabled: true 129 | Layout/BeginEndAlignment: 130 | Enabled: true 131 | Layout/SpaceBeforeBrackets: 132 | Enabled: true 133 | Layout/LineEndStringConcatenationIndentation: 134 | Enabled: true 135 | Layout/LineContinuationLeadingSpace: # new in 1.31 136 | Enabled: true 137 | Layout/LineContinuationSpacing: # new in 1.31 138 | Enabled: true 139 | 140 | # ============================== Security ============================== 141 | Security/CompoundHash: # new in 1.28 142 | Enabled: true 143 | Security/IoMethods: # new in 1.22 144 | Enabled: true 145 | 146 | # ============================== Style ============================== 147 | Style/RescueModifier: 148 | Enabled: true 149 | Style/PercentLiteralDelimiters: 150 | PreferredDelimiters: 151 | default: '[]' 152 | '%i': '[]' 153 | '%w': '[]' 154 | Exclude: 155 | - 'config/routes.rb' 156 | Style/StringLiterals: 157 | Enabled: true 158 | Style/AsciiComments: 159 | Enabled: false 160 | Style/Copyright: 161 | Enabled: false 162 | Style/SafeNavigation: 163 | Enabled: false 164 | Style/Lambda: 165 | Enabled: false 166 | Style/Alias: 167 | Enabled: true 168 | EnforcedStyle: prefer_alias_method 169 | Style/ClassAndModuleChildren: 170 | Enabled: true 171 | EnforcedStyle: nested 172 | Style/TrailingCommaInArrayLiteral: 173 | Enabled: true 174 | EnforcedStyleForMultiline: no_comma 175 | Style/RescueStandardError: 176 | Enabled: true 177 | EnforcedStyle: explicit 178 | Style/InverseMethods: 179 | AutoCorrect: false 180 | Enabled: true 181 | Style/IfUnlessModifier: 182 | Enabled: false 183 | Style/SpecialGlobalVars: 184 | Enabled: false 185 | Style/BlockComments: 186 | Enabled: false 187 | Style/GuardClause: 188 | Enabled: false 189 | Style/TrailingCommaInHashLiteral: 190 | Enabled: false 191 | Style/ExponentialNotation: 192 | Enabled: true 193 | Style/HashEachMethods: 194 | Enabled: true 195 | Style/HashTransformKeys: 196 | Enabled: true 197 | Style/HashTransformValues: 198 | Enabled: true 199 | Style/RedundantFetchBlock: 200 | Enabled: true 201 | Style/RedundantRegexpCharacterClass: 202 | Enabled: true 203 | Style/RedundantRegexpEscape: 204 | Enabled: true 205 | Style/SlicingWithRange: 206 | Enabled: true 207 | Style/AccessorGrouping: 208 | Enabled: true 209 | Style/ArrayCoercion: 210 | Enabled: true 211 | Style/BisectedAttrAccessor: 212 | Enabled: true 213 | Style/CaseLikeIf: 214 | Enabled: true 215 | Style/ExplicitBlockArgument: 216 | Enabled: true 217 | Style/GlobalStdStream: 218 | Enabled: true 219 | Style/HashAsLastArrayItem: 220 | Enabled: true 221 | Style/HashLikeCase: 222 | Enabled: true 223 | Style/OptionalBooleanParameter: 224 | Enabled: true 225 | Style/RedundantAssignment: 226 | Enabled: true 227 | Style/RedundantFileExtensionInRequire: 228 | Enabled: true 229 | Style/SingleArgumentDig: 230 | Enabled: true 231 | Style/StringConcatenation: 232 | Enabled: true 233 | Style/CombinableLoops: 234 | Enabled: true 235 | Style/KeywordParametersOrder: 236 | Enabled: true 237 | Style/RedundantSelfAssignment: 238 | Enabled: true 239 | Style/SoleNestedConditional: 240 | Enabled: true 241 | Style/ArgumentsForwarding: 242 | Enabled: true 243 | Style/CollectionCompact: 244 | Enabled: true 245 | Style/DocumentDynamicEvalDefinition: 246 | Enabled: true 247 | Style/EndlessMethod: 248 | Enabled: true 249 | Style/HashExcept: 250 | Enabled: true 251 | Style/IfWithBooleanLiteralBranches: 252 | Enabled: true 253 | Style/NegatedIfElseCondition: 254 | Enabled: true 255 | Style/NilLambda: 256 | Enabled: true 257 | Style/RedundantArgument: 258 | Enabled: true 259 | Style/SwapValues: 260 | Enabled: true 261 | Style/HashConversion: 262 | Enabled: true 263 | Style/StringChars: 264 | Enabled: true 265 | Style/InPatternThen: 266 | Enabled: true 267 | Style/MultilineInPatternThen: 268 | Enabled: true 269 | Style/QuotedSymbols: 270 | Enabled: true 271 | Style/EmptyHeredoc: # new in 1.32 272 | Enabled: true 273 | Style/EnvHome: # new in 1.29 274 | Enabled: true 275 | Style/FetchEnvVar: # new in 1.28 276 | Enabled: true 277 | Style/FileRead: # new in 1.24 278 | Enabled: true 279 | Style/FileWrite: # new in 1.24 280 | Enabled: true 281 | Style/MagicCommentFormat: # new in 1.35 282 | Enabled: true 283 | Style/MapCompactWithConditionalBlock: # new in 1.30 284 | Enabled: true 285 | Style/MapToHash: # new in 1.24 286 | Enabled: true 287 | Style/NestedFileDirname: # new in 1.26 288 | Enabled: true 289 | Style/NumberedParameters: # new in 1.22 290 | Enabled: true 291 | Style/NumberedParametersLimit: # new in 1.22 292 | Enabled: true 293 | Style/ObjectThen: # new in 1.28 294 | Enabled: true 295 | Style/OpenStructUse: # new in 1.23 296 | Enabled: true 297 | Style/OperatorMethodCall: # new in 1.37 298 | Enabled: true 299 | Style/RedundantEach: # new in 1.38 300 | Enabled: true 301 | Style/RedundantInitialize: # new in 1.27 302 | Enabled: true 303 | Style/RedundantSelfAssignmentBranch: # new in 1.19 304 | Enabled: true 305 | Style/RedundantStringEscape: # new in 1.37 306 | Enabled: true 307 | Style/SelectByRegexp: # new in 1.22 308 | Enabled: true 309 | # ============================== Lint ============================== 310 | Lint/DuplicateMethods: 311 | Enabled: false 312 | Lint/AmbiguousOperator: 313 | Enabled: false 314 | Lint/RaiseException: 315 | Enabled: true 316 | Lint/StructNewOverride: 317 | Enabled: true 318 | Lint/DeprecatedOpenSSLConstant: 319 | Enabled: true 320 | Lint/MixedRegexpCaptureTypes: 321 | Enabled: true 322 | Lint/BinaryOperatorWithIdenticalOperands: 323 | Enabled: true 324 | Lint/DuplicateElsifCondition: 325 | Enabled: true 326 | Lint/DuplicateRescueException: 327 | Enabled: true 328 | Lint/EmptyConditionalBody: 329 | Enabled: true 330 | Lint/FloatComparison: 331 | Enabled: true 332 | Lint/MissingSuper: 333 | Enabled: false 334 | Lint/OutOfRangeRegexpRef: 335 | Enabled: true 336 | Lint/SelfAssignment: 337 | Enabled: true 338 | Lint/TopLevelReturnWithArgument: 339 | Enabled: true 340 | Lint/UnreachableLoop: 341 | Enabled: true 342 | Lint/ConstantDefinitionInBlock: 343 | Enabled: true 344 | Lint/DuplicateRequire: 345 | Enabled: true 346 | Lint/EmptyFile: 347 | Enabled: true 348 | Lint/IdentityComparison: 349 | Enabled: true 350 | Lint/TrailingCommaInAttributeDeclaration: 351 | Enabled: true 352 | Lint/UselessMethodDefinition: 353 | Enabled: true 354 | Lint/UselessTimes: 355 | Enabled: true 356 | Lint/AmbiguousAssignment: 357 | Enabled: true 358 | Lint/DeprecatedConstants: 359 | Enabled: true 360 | Lint/DuplicateBranch: 361 | Enabled: true 362 | Lint/DuplicateRegexpCharacterClassElement: 363 | Enabled: true 364 | Lint/EmptyBlock: 365 | Enabled: true 366 | Lint/EmptyClass: 367 | Enabled: true 368 | Lint/LambdaWithoutLiteralBlock: 369 | Enabled: true 370 | Lint/NoReturnInBeginEndBlocks: 371 | Enabled: true 372 | Lint/NumberedParameterAssignment: 373 | Enabled: true 374 | Lint/OrAssignmentToConstant: 375 | Enabled: true 376 | Lint/RedundantDirGlobSort: 377 | Enabled: true 378 | Lint/SymbolConversion: 379 | Enabled: true 380 | Lint/ToEnumArguments: 381 | Enabled: true 382 | Lint/TripleQuotes: 383 | Enabled: true 384 | Lint/UnexpectedBlockArity: 385 | Enabled: true 386 | Lint/UnmodifiedReduceAccumulator: 387 | Enabled: true 388 | Lint/EmptyInPattern: 389 | Enabled: true 390 | Lint/AmbiguousOperatorPrecedence: # new in 1.21 391 | Enabled: true 392 | Lint/AmbiguousRange: # new in 1.19 393 | Enabled: true 394 | Lint/ConstantOverwrittenInRescue: # new in 1.31 395 | Enabled: true 396 | Lint/DuplicateMagicComment: # new in 1.37 397 | Enabled: true 398 | Lint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21 399 | Enabled: true 400 | Lint/NonAtomicFileOperation: # new in 1.31 401 | Enabled: true 402 | Lint/RefinementImportMethods: # new in 1.27 403 | Enabled: true 404 | Lint/RequireRangeParentheses: # new in 1.32 405 | Enabled: true 406 | Lint/RequireRelativeSelfPath: # new in 1.22 407 | Enabled: true 408 | Lint/UselessRuby2Keywords: # new in 1.23 409 | Enabled: true 410 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.9 2 | - [13](https://github.com/shlima/health_bit/pull/13) Pass env to check blocks or #call methods 3 | - [11](https://github.com/shlima/health_bit/issues/11) Added custom formatter 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | # Specify your gem's dependencies in health_bit.gemspec 6 | gemspec 7 | 8 | gem 'rake', '~> 13.0' 9 | gem 'rspec', '~> 3.0' 10 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | health_bit (0.2.0) 5 | rack 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | ast (2.4.2) 11 | coderay (1.1.3) 12 | diff-lcs (1.5.0) 13 | json (2.6.2) 14 | method_source (1.0.0) 15 | parallel (1.22.1) 16 | parser (3.1.2.1) 17 | ast (~> 2.4.1) 18 | pry (0.14.1) 19 | coderay (~> 1.1) 20 | method_source (~> 1.0) 21 | rack (3.0.0) 22 | rainbow (3.1.1) 23 | rake (13.0.6) 24 | regexp_parser (2.6.0) 25 | rexml (3.2.5) 26 | rspec (3.12.0) 27 | rspec-core (~> 3.12.0) 28 | rspec-expectations (~> 3.12.0) 29 | rspec-mocks (~> 3.12.0) 30 | rspec-core (3.12.0) 31 | rspec-support (~> 3.12.0) 32 | rspec-expectations (3.12.0) 33 | diff-lcs (>= 1.2.0, < 2.0) 34 | rspec-support (~> 3.12.0) 35 | rspec-mocks (3.12.0) 36 | diff-lcs (>= 1.2.0, < 2.0) 37 | rspec-support (~> 3.12.0) 38 | rspec-support (3.12.0) 39 | rubocop (1.38.0) 40 | json (~> 2.3) 41 | parallel (~> 1.10) 42 | parser (>= 3.1.2.1) 43 | rainbow (>= 2.2.2, < 4.0) 44 | regexp_parser (>= 1.8, < 3.0) 45 | rexml (>= 3.2.5, < 4.0) 46 | rubocop-ast (>= 1.23.0, < 2.0) 47 | ruby-progressbar (~> 1.7) 48 | unicode-display_width (>= 1.4.0, < 3.0) 49 | rubocop-ast (1.23.0) 50 | parser (>= 3.1.1.0) 51 | rubocop-performance (1.15.0) 52 | rubocop (>= 1.7.0, < 2.0) 53 | rubocop-ast (>= 0.4.0) 54 | ruby-progressbar (1.11.0) 55 | unicode-display_width (2.3.0) 56 | 57 | PLATFORMS 58 | ruby 59 | 60 | DEPENDENCIES 61 | bundler 62 | health_bit! 63 | pry 64 | rake (~> 13.0) 65 | rspec (~> 3.0) 66 | rubocop 67 | rubocop-performance 68 | 69 | BUNDLED WITH 70 | 2.2.15 71 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Aliaksandr Shylau 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 | ![CI](https://github.com/shlima/health_bit/workflows/CI/badge.svg) 2 | [![gem version](https://badge.fury.io/rb/health_bit.svg)](https://rubygems.org/gems/health_bit) 3 | 4 | # HealthBit 5 | 6 | ![](./doc/logo.png?sanitize=true) 7 | 8 | This gem was inspired by the [health_check](https://github.com/ianheggie/health_check), but is simpler and more 9 | extensible and contains up to 95% less code. 10 | 11 | Key differences: 12 | * is a rack application (just a lambda function) 13 | * can be used with rails, sinatra or any other rack application 14 | * can add custom checks 15 | * can add multiple endpoints with independent checks 16 | * can use any rack middleware (such as http basic auth, IP whitelist) 17 | 18 | ## Toc 19 | 20 | * [Installation](#installation) 21 | * [Configuration](#configuration) 22 | * [Add Checks](#add-checks) 23 | * [Add a Route](#add-a-route) 24 | * [Password Protection](#password-protection) 25 | * [Multiple endpoints](#multiple-endpoints) 26 | * [Custom formatter](#custom-formatter) 27 | 28 | ## Check Examples 29 | 30 | * [Database check](#database-check) 31 | * [Redis check](#redis-check) 32 | * [Sidekiq check](#sidekiq-check) 33 | * [Rails cache check](#rails-cache-check) 34 | * [Elasticsearch check](#elasticsearch-check) 35 | * [RabbitMQ check](#rabbitmq-check) 36 | * [HTTP check](#http-check) 37 | * [ClickHouse check](#clickhouse-check) 38 | 39 | ## Installation 40 | 41 | Add this line to your application's Gemfile: 42 | 43 | ```ruby 44 | gem 'health_bit' 45 | ``` 46 | 47 | ## Configuration 48 | 49 | ```ruby 50 | # config/initializers/health_bit.rb 51 | 52 | HealthBit.configure do |c| 53 | # DEFAULT SETTINGS ARE SHOWN BELOW 54 | c.success_text = '%d checks passed 🎉' 55 | c.headers = { 56 | 'Content-Type' => 'text/plain;charset=utf-8', 57 | 'Cache-Control' => 'private,max-age=0,must-revalidate,no-store' 58 | } 59 | c.success_code = 200 60 | c.fail_code = 500 61 | c.show_backtrace = false 62 | c.formatter = HealthBit::Formatter.new 63 | # DEFAULT SETTINGS ARE SHOWN ABOVE 64 | 65 | c.add('Check name') do 66 | # Body check, should returns `true` 67 | true 68 | end 69 | end 70 | ``` 71 | 72 | ## Add Checks 73 | 74 | By default, the **gem does not contain any checks**, **you should add the 75 | necessary checks by yourself**. The check should return `false` or `nil` 76 | to be considered unsuccessful or throw an exception, any other 77 | values are considered satisfactory. 78 | 79 | Example checks: 80 | 81 | ```ruby 82 | ## Successful checks 83 | HealthBit.add('PostgreSQL') do |env| 84 | ApplicationRecord.connection.select_value('SELECT 1') == 1 85 | end 86 | 87 | HealthBit.add('Custom') do |env| 88 | next(false) if 1 != 0 89 | 90 | true 91 | end 92 | 93 | ## Failed checks 94 | HealthBit.add('Database') do |env| 95 | false 96 | end 97 | 98 | HealthBit.add('Docker service') do |env| 99 | raise 'not responding' 100 | end 101 | 102 | # The Check can be added as an object responding to a call 103 | # (to be able to test your check) 104 | class Covid19Check 105 | def self.call(env) 106 | false 107 | end 108 | end 109 | 110 | HealthBit.add('COVID-19 Checker', Covid19Check) 111 | ``` 112 | 113 | ## Add a Route 114 | 115 | Since the gem is a rack application, you must mount it to app's 116 | routes. Below is an example for the Rails. 117 | 118 | ```ruby 119 | # config/routes.rb 120 | 121 | Rails.application.routes.draw do 122 | mount HealthBit.rack => '/health' 123 | end 124 | ``` 125 | 126 | ```bash 127 | curl --verbose http://localhost:3000/health 128 | 129 | < HTTP/1.1 200 OK 130 | < Content-Type: text/plain;charset=utf-8 131 | < Cache-Control: private,max-age=0,must-revalidate,no-store 132 | < X-Request-Id: 59a796b9-29f7-4302-b1ff-5d0b06dd6637 133 | < X-Runtime: 0.006007 134 | < Vary: Origin 135 | < Transfer-Encoding: chunked 136 | 137 | 4 checks passed 🎉 138 | ``` 139 | 140 | ## Password Protection 141 | 142 | Since the gem is a common rack application, you can add any rack 143 | middleware to it. Below is an example with HTTP-auth for the Rails. 144 | 145 | ```ruby 146 | # config/routes.rb 147 | 148 | Rails.application.routes.draw do 149 | HealthBit.rack.use Rack::Auth::Basic do |username, password| 150 | ActiveSupport::SecurityUtils.secure_compare(Digest::SHA256.hexdigest(username), Digest::SHA256.hexdigest('user')) & ActiveSupport::SecurityUtils.secure_compare(Digest::SHA256.hexdigest(password), Digest::SHA256.hexdigest('password')) 151 | end 152 | 153 | mount HealthBit.rack => '/health' 154 | end 155 | ``` 156 | 157 | ## Database check 158 | 159 | ```ruby 160 | HealthBit.add('Database') do |env| 161 | ApplicationRecord.connection.select_value('SELECT 1') == 1 162 | end 163 | ``` 164 | 165 | ## Redis check 166 | 167 | ```ruby 168 | HealthBit.add('Redis') do |env| 169 | Redis.current.ping == 'PONG' 170 | end 171 | ``` 172 | 173 | ## Sidekiq check 174 | 175 | ```ruby 176 | HealthBit.add('Sidekiq') do |env| 177 | Sidekiq.redis(&:ping) == 'PONG' 178 | end 179 | ``` 180 | 181 | ## Rails cache check 182 | 183 | ```ruby 184 | HealthBit.add('Rails cache') do |env| 185 | Rails.cache.write('__health_bit__', '1', expires_in: 1.second) 186 | end 187 | ``` 188 | 189 | ## Elasticsearch check 190 | 191 | ```ruby 192 | HealthBit.add('Elasticsearch') do |env| 193 | Elasticsearch::Client.new.ping 194 | end 195 | ``` 196 | 197 | ## RabbitMQ check 198 | 199 | ```ruby 200 | HealthBit.add('RabbitMQ') do |env| 201 | Bunny::Connection.connect(&:connection) 202 | end 203 | ``` 204 | 205 | ## HTTP check 206 | 207 | ```ruby 208 | HealthBit.add('HTTP check') do |env| 209 | Net::HTTP.new('www.example.com', 80).request_get('/').kind_of?(Net::HTTPSuccess) 210 | end 211 | ``` 212 | 213 | ## ClickHouse check 214 | 215 | ```ruby 216 | HealthBit.add('ClickHouse') do |env| 217 | ClickHouse.connection.ping 218 | end 219 | ``` 220 | 221 | ## Multiple endpoints 222 | 223 | Sometimes you have to add several health check endpoints. Let's say 224 | you have to check the docker container health and the health 225 | of your application as a whole. Below is an example for the Rails. 226 | 227 | ```ruby 228 | # config/initializers/health_bit.rb 229 | 230 | DockerCheck = HealthBit.clone 231 | AppCheck = HealthBit.clone 232 | 233 | DockerCheck.add('Docker Health') do |env| 234 | true 235 | end 236 | 237 | AppCheck.add('App Health') do |env| 238 | ApplicationRecord.connection.select_value("SELECT 't'::boolean") 239 | end 240 | ``` 241 | 242 | ```ruby 243 | # config/routes.rb 244 | 245 | Rails.application.routes.draw do 246 | mount DockerCheck.rack => '/docker' 247 | mount AppCheck.rack => '/app' 248 | end 249 | ``` 250 | 251 | ## Custom formatter 252 | 253 | You can easily [configure](https://github.com/shlima/health_bit/blob/master/lib/health_bit/formatter.rb) custom format of response body, headers and 254 | http statuses. 255 | 256 | ```ruby 257 | class JsonFormatter < HealthBit::Formatter 258 | # @param error HealthBit::CheckError 259 | # @param env Hash 260 | # @param health_bit HealthBit 261 | def format_failure(error, env, health_bit) 262 | { 'status': 'error' }.to_json 263 | end 264 | 265 | # @param error HealthBit::CheckError 266 | # @param env Hash 267 | # @param health_bit HealthBit 268 | def headers_failure(error, env, health_bit) 269 | { 270 | 'Content-Type' => 'application/json' 271 | } 272 | end 273 | end 274 | 275 | HealthBit.configure do |c| 276 | c.formatter = JsonFormatter.new 277 | end 278 | ``` 279 | -------------------------------------------------------------------------------- /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 | 3 | require 'bundler/setup' 4 | require 'health_bit' 5 | require 'pry' 6 | 7 | Pry.start 8 | -------------------------------------------------------------------------------- /bin/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm ./*.gem 4 | gem build health_bit.gemspec 5 | gem push health_bit-* 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /doc/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shlima/health_bit/0ca4d6599c24bce19b1e792ddd47d685940b1246/doc/logo.png -------------------------------------------------------------------------------- /health_bit.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'lib/health_bit/version' 4 | 5 | # rubocop:disable Layout/LineLength 6 | Gem::Specification.new do |spec| 7 | spec.name = 'health_bit' 8 | spec.version = HealthBit::VERSION 9 | spec.authors = ['Aliaksandr Shylau'] 10 | spec.email = %w[alex.shilov.by@gmail.com] 11 | 12 | spec.summary = 'Tiny health check of Rack apps like Rails, Sinatra' 13 | spec.description = 'Tiny health check of Rack apps like Rails, Sinatra for use with uptime checking systems like Kubernetes, Docker or Uptimerobot' 14 | spec.homepage = 'https://github.com/shlima/health_bit' 15 | spec.license = 'MIT' 16 | spec.required_ruby_version = Gem::Requirement.new('>= 2.6.0') 17 | spec.metadata['rubygems_mfa_required'] = 'true' 18 | spec.metadata['homepage_uri'] = spec.homepage 19 | spec.metadata['source_code_uri'] = spec.homepage 20 | spec.metadata['changelog_uri'] = 'https://github.com/shlima/health_bit/releases' 21 | spec.files = Dir.chdir(File.expand_path(__dir__)) do 22 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 23 | end 24 | spec.bindir = 'exe' 25 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 26 | spec.require_paths = %w[lib] 27 | 28 | spec.add_dependency 'rack' 29 | spec.add_development_dependency 'bundler' 30 | spec.add_development_dependency 'pry' 31 | spec.add_development_dependency 'rake' 32 | spec.add_development_dependency 'rspec' 33 | spec.add_development_dependency 'rubocop' 34 | spec.add_development_dependency 'rubocop-performance' 35 | end 36 | # rubocop:enable Layout/LineLength 37 | -------------------------------------------------------------------------------- /lib/health_bit.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rack' 4 | require 'health_bit/version' 5 | 6 | module HealthBit 7 | autoload :Check, 'health_bit/check' 8 | autoload :CheckError, 'health_bit/check_error' 9 | autoload :Formatter, 'health_bit/formatter' 10 | 11 | DEFAULT_SUCCESS_TEXT = '%d checks passed 🎉' 12 | DEFAULT_HEADERS = { 13 | 'Content-Type' => 'text/plain;charset=utf-8', 14 | 'Cache-Control' => 'private,max-age=0,must-revalidate,no-store' 15 | }.freeze 16 | DEFAULT_SUCCESS_CODE = 200 17 | DEFAULT_FAIL_CODE = 500 18 | DEFAULT_FORMATTER = Formatter.new 19 | 20 | extend self # rubocop:disable Style/ModuleFunction 21 | 22 | attr_writer :success_text, :success_code, :fail_code, :headers, :formatter 23 | attr_accessor :show_backtrace 24 | 25 | def success_text 26 | format(@success_text || DEFAULT_SUCCESS_TEXT, count: checks.length) 27 | end 28 | 29 | def success_code 30 | @success_code || DEFAULT_SUCCESS_CODE 31 | end 32 | 33 | def fail_code 34 | @fail_code || DEFAULT_FAIL_CODE 35 | end 36 | 37 | def headers 38 | (@headers || DEFAULT_HEADERS).dup 39 | end 40 | 41 | # @return [Formatter] 42 | def formatter 43 | @formatter || DEFAULT_FORMATTER 44 | end 45 | 46 | def checks 47 | @checks ||= [] 48 | end 49 | 50 | def configure 51 | yield(self) 52 | end 53 | 54 | # @return [self] 55 | def add(name, handler = nil, &block) 56 | raise ArgumentError, <<~MSG if handler && block 57 | Both and were passed to the <#{name}> check 58 | MSG 59 | 60 | raise ArgumentError, <<~MSG unless handler || block 61 | Nor or were passed to the <#{name}> check 62 | MSG 63 | 64 | checks.push(Check.new(name, handler || block)) 65 | 66 | self 67 | end 68 | 69 | # @return [nil, CheckError] 70 | def check(env) 71 | checks.each do |check| 72 | (exception = check.call(env)).nil? ? next : (return exception) 73 | end 74 | 75 | nil 76 | end 77 | 78 | def rack(this = self) 79 | @rack ||= Rack::Builder.new do 80 | run ->(env) do 81 | if (error = this.check(env)) 82 | [ 83 | this.formatter.code_failure(error, env, this), 84 | this.formatter.headers_failure(error, env, this), 85 | [this.formatter.format_failure(error, env, this)] 86 | ] 87 | else 88 | [ 89 | this.formatter.code_success(env, this), 90 | this.formatter.headers_success(env, this), 91 | [this.formatter.format_success(error, env, this)] 92 | ] 93 | end 94 | end 95 | end 96 | end 97 | 98 | def clone 99 | Module.new.tap do |dolly| 100 | dolly.singleton_class.include(HealthBit) 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /lib/health_bit/check.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HealthBit 4 | class Check 5 | attr_reader :name, :handler 6 | 7 | def initialize(name, handler) 8 | @name = name 9 | @handler = handler 10 | end 11 | 12 | # @return [nil] if its ok 13 | # @return [CheckError] if not 14 | def call(env = {}) 15 | arity = handler.is_a?(Proc) ? handler.arity : handler.method(:call).arity 16 | return if arity.abs == 1 ? handler.call(env) : handler.call 17 | 18 | raise('The check has returned a negative value') 19 | rescue Exception => e # rubocop:disable Lint/RescueException 20 | CheckError.new(name, exception: e) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/health_bit/check_error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HealthBit 4 | class CheckError < StandardError 5 | FORMAT_FULL = :full 6 | FORMAT_SHORT = nil 7 | 8 | attr_reader :name, :exception 9 | 10 | def initialize(name, exception: nil) 11 | @name = name 12 | @exception = exception 13 | end 14 | 15 | # @return [String] 16 | def to_s(format = nil) 17 | io = StringIO.new 18 | io.puts "Check <#{name}> failed" 19 | 20 | case format 21 | when FORMAT_FULL 22 | describe_exception(io) 23 | end 24 | 25 | io.string.chomp 26 | end 27 | 28 | private 29 | 30 | def describe_exception(io) 31 | return if exception.nil? 32 | 33 | io.puts exception.inspect 34 | io.puts Array(exception.backtrace).join("\n") 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/health_bit/formatter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rack' 4 | require 'health_bit/version' 5 | 6 | module HealthBit 7 | # rubocop:disable Lint/UnusedMethodArgument 8 | class Formatter 9 | # @param error HealthBit::CheckError 10 | # @param env Hash 11 | # @param health_bit HealthBit 12 | def format_success(error, env, health_bit) 13 | health_bit.success_text 14 | end 15 | 16 | # @param error HealthBit::CheckError 17 | # @param env Hash 18 | # @param health_bit HealthBit 19 | def format_failure(error, env, health_bit) 20 | format = health_bit.show_backtrace ? CheckError::FORMAT_FULL : CheckError::FORMAT_SHORT 21 | error.to_s(format) 22 | end 23 | 24 | # @param env Hash 25 | # @param health_bit HealthBit 26 | def headers_success(env, health_bit) 27 | health_bit.headers 28 | end 29 | 30 | # @param error HealthBit::CheckError 31 | # @param env Hash 32 | # @param health_bit HealthBit 33 | def headers_failure(error, env, health_bit) 34 | health_bit.headers 35 | end 36 | 37 | # @param env Hash 38 | # @param health_bit HealthBit 39 | def code_success(env, health_bit) 40 | health_bit.success_code 41 | end 42 | 43 | # @param error HealthBit::CheckError 44 | # @param env Hash 45 | # @param health_bit HealthBit 46 | def code_failure(error, env, health_bit) 47 | health_bit.fail_code 48 | end 49 | end 50 | # rubocop:enable Lint/UnusedMethodArgument 51 | end 52 | -------------------------------------------------------------------------------- /lib/health_bit/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HealthBit 4 | VERSION = '0.2.0' 5 | end 6 | -------------------------------------------------------------------------------- /spec/health_bit/check_error_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe HealthBit::CheckError do 2 | describe '#to_s' do 3 | subject do 4 | described_class.new('foo', exception: exception) 5 | end 6 | 7 | let(:exception) do 8 | RuntimeError.new 9 | end 10 | 11 | context 'when short format' do 12 | let(:expectation) do 13 | 'Check failed' 14 | end 15 | 16 | it 'works with the empty argv' do 17 | expect(subject.to_s).to eq(expectation) 18 | end 19 | 20 | it 'works with the short argv' do 21 | expect(subject.to_s(described_class::FORMAT_SHORT)).to eq(expectation) 22 | end 23 | end 24 | 25 | context 'when full format' do 26 | it 'works' do 27 | expect(subject.to_s(described_class::FORMAT_FULL)).to include(String(exception.class)) 28 | end 29 | end 30 | 31 | context 'when full format with empty exception' do 32 | let(:exception) do 33 | nil 34 | end 35 | 36 | it 'works' do 37 | expect { subject.to_s(described_class::FORMAT_FULL) }.not_to raise_error 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/health_bit/check_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe HealthBit::Check do 2 | describe '#call' do 3 | context 'when fail' do 4 | let(:subjects) do 5 | [ 6 | described_class.new('foo', -> { nil }), 7 | described_class.new('foo', -> { false }), 8 | described_class.new('foo', -> { raise }), 9 | described_class.new('foo', -> { break(false); true }), 10 | described_class.new('foo', Class.new { def self.call ; false ; end }), 11 | described_class.new('foo', Class.new { def self.call ; nil ; end }), 12 | described_class.new('foo', Class.new { def self.call ; raise ; end }), 13 | ] 14 | end 15 | 16 | it 'returns an error' do 17 | subjects.each do |subject| 18 | expect(subject.call).to be_a(HealthBit::CheckError) 19 | end 20 | end 21 | end 22 | 23 | context 'when success' do 24 | let(:subjects) do 25 | [ 26 | described_class.new('bar', -> { true }), 27 | described_class.new('bar', -> { break(true); false }), 28 | described_class.new('bar', Class.new { def self.call ; true ; end }), 29 | ] 30 | end 31 | 32 | it 'returns an error' do 33 | subjects.each do |subject| 34 | expect(subject.call).to eq(nil) 35 | end 36 | end 37 | end 38 | 39 | context 'env' do 40 | let(:subjects) do 41 | [ 42 | described_class.new('foo', -> (env) { env.call }), 43 | described_class.new('foo', Class.new { def self.call(env) ; env.call ; end }), 44 | described_class.new('foo', Proc.new { |env| env.call }), 45 | ] 46 | end 47 | 48 | 49 | it 'passes env to a handler' do 50 | subjects.each do |subject| 51 | env = double('student') 52 | expect(env).to receive(:call).once 53 | subject.call(env) 54 | end 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/health_bit/health_bit_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe HealthBit do 2 | subject do 3 | described_class.clone 4 | end 5 | 6 | describe '#configure' do 7 | let(:configure) do 8 | this = nil 9 | subject.configure { |o| this = o } 10 | this 11 | end 12 | 13 | it 'yields self' do 14 | expect(configure).to eq(subject) 15 | end 16 | end 17 | 18 | describe '#success_code, #success_code=' do 19 | context 'when default' do 20 | it 'works' do 21 | expect(subject.success_code).to eq(200) 22 | end 23 | end 24 | 25 | context 'when set' do 26 | before do 27 | subject.success_code = 0 28 | end 29 | 30 | it 'works' do 31 | expect(subject.success_code).to eq(0) 32 | end 33 | end 34 | end 35 | 36 | describe '#fail_code, #fail_code=' do 37 | context 'when default' do 38 | it 'works' do 39 | expect(subject.fail_code).to eq(500) 40 | end 41 | end 42 | 43 | context 'when set' do 44 | before do 45 | subject.fail_code = 0 46 | end 47 | 48 | it 'works' do 49 | expect(subject.fail_code).to eq(0) 50 | end 51 | end 52 | end 53 | 54 | describe '#formatter, #formatter=' do 55 | context 'when default' do 56 | it 'works' do 57 | expect(subject.formatter).to be_a(HealthBit::Formatter) 58 | end 59 | end 60 | 61 | context 'when set' do 62 | before do 63 | subject.formatter = 0 64 | end 65 | 66 | it 'works' do 67 | expect(subject.formatter).to eq(0) 68 | end 69 | end 70 | end 71 | 72 | describe '#headers, #headers=' do 73 | context 'when default' do 74 | it 'works' do 75 | expect(subject.headers).to eq({'Content-Type' => 'text/plain;charset=utf-8', 'Cache-Control' => 'private,max-age=0,must-revalidate,no-store' }) 76 | end 77 | end 78 | 79 | context 'when modified' do 80 | it 'returns a clone (for the Grape)' do 81 | expect { subject.headers.clear }.not_to change { subject.headers } 82 | end 83 | end 84 | 85 | context 'when set' do 86 | before do 87 | subject.headers = { foo: :bar } 88 | end 89 | 90 | it 'works' do 91 | expect(subject.headers).to eq(foo: :bar) 92 | end 93 | end 94 | end 95 | 96 | describe '#success_text, #success_text=' do 97 | context 'when default' do 98 | it 'works' do 99 | expect(subject.success_text).to eq('0 checks passed 🎉') 100 | end 101 | end 102 | 103 | context 'when set' do 104 | before do 105 | subject.success_text = 'foo' 106 | end 107 | 108 | it 'works' do 109 | expect(subject.success_text).to eq('foo') 110 | end 111 | end 112 | end 113 | 114 | describe '#add' do 115 | it 'returns self' do 116 | expect(subject.add('1', Class.new)).to eq(subject) 117 | end 118 | 119 | context 'when object' do 120 | before do 121 | subject.add('foo', Class.new) 122 | end 123 | 124 | it 'adds check' do 125 | expect(subject.checks.length).to eq(1) 126 | expect(subject.checks.first).to have_attributes(name: 'foo') 127 | end 128 | end 129 | 130 | context 'when block' do 131 | before do 132 | subject.add('bar') do 133 | 1 134 | end 135 | end 136 | 137 | it 'works' do 138 | expect(subject.checks.length).to eq(1) 139 | expect(subject.checks.first).to have_attributes(name: 'bar') 140 | end 141 | end 142 | 143 | context 'when both handler and block passed' do 144 | let(:add) do 145 | subject.add('bar', Class.new) do 146 | 1 147 | end 148 | end 149 | 150 | it 'errors' do 151 | expect { add }.to raise_error(ArgumentError) 152 | end 153 | end 154 | 155 | context 'when neither handler nor block passed' do 156 | let(:add) do 157 | subject.add('bar') 158 | end 159 | 160 | it 'errors' do 161 | expect { add }.to raise_error(ArgumentError) 162 | end 163 | end 164 | end 165 | 166 | describe '#rack' do 167 | let(:response) do 168 | subject.rack.call({}).to_s 169 | end 170 | 171 | context 'when show_backtrace=true' do 172 | before do 173 | subject.show_backtrace = true 174 | subject.add('foo') { false } 175 | end 176 | 177 | it 'prints backtrace' do 178 | expect(response).to include('health_bit/lib/health_bit') 179 | end 180 | end 181 | 182 | context 'when show_backtrace=false' do 183 | before do 184 | subject.add('foo') { false } 185 | end 186 | 187 | it 'hides backtrace' do 188 | expect(response).not_to include('health_bit/lib/health_bit') 189 | end 190 | end 191 | 192 | context 'when adding middleware' do 193 | it 'works' do 194 | expect { subject.rack.use(Class.new) }.to change { subject.rack.instance_variable_get(:@use).count }.by(1) 195 | end 196 | end 197 | end 198 | 199 | describe '#clone' do 200 | let!(:donor) do 201 | subject.clone 202 | end 203 | 204 | let!(:dolly_1) do 205 | donor.clone 206 | end 207 | 208 | let!(:dolly_2) do 209 | dolly_1.clone 210 | end 211 | 212 | context 'when #checks' do 213 | before do 214 | donor.add('donor', 1) 215 | dolly_1.add('dolly', 1) 216 | end 217 | 218 | it 'works' do 219 | expect(donor.checks.length).to eq(1) 220 | expect(dolly_1.checks.length).to eq(1) 221 | expect(dolly_2.checks).to be_empty 222 | expect(donor.checks.first).to have_attributes(name: 'donor') 223 | expect(dolly_1.checks.first).to have_attributes(name: 'dolly') 224 | end 225 | end 226 | 227 | context 'when attr_accessors (like success_text)' do 228 | before do 229 | donor.success_text = 'donor succeed' 230 | dolly_1.success_text = 'dolly succeed' 231 | end 232 | 233 | it 'works' do 234 | expect(dolly_2.success_text).to eq(described_class.success_text) 235 | expect(dolly_1.success_text).to eq('dolly succeed') 236 | expect(donor.success_text).to eq('donor succeed') 237 | end 238 | end 239 | end 240 | end 241 | -------------------------------------------------------------------------------- /spec/health_bit/integration_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe HealthBit do 2 | let(:app1) do 3 | described_class.clone 4 | end 5 | 6 | let(:app2) do 7 | described_class.clone 8 | end 9 | 10 | let(:app3) do 11 | described_class.clone 12 | end 13 | 14 | before do 15 | # just initialize instance variable 16 | # inside a donor 17 | described_class.checks 18 | end 19 | 20 | before do 21 | app1.headers = { 'app1' => 1 } 22 | app1.success_code = 201 23 | app1.fail_code = 0 24 | app1.add('app1') do 25 | true 26 | end 27 | end 28 | 29 | before do 30 | app2.headers = { 'app2' => 2 } 31 | app2.success_code = 0 32 | app2.fail_code = 503 33 | app2.add('app2') do 34 | false 35 | end 36 | end 37 | 38 | before do 39 | app3.headers = { 'app3' => 1 } 40 | app3.success_code = 200 41 | app3.fail_code = 500 42 | app3.add('app3') do |env| 43 | env["FOO"] == "BAR" 44 | end 45 | end 46 | 47 | context 'when app1' do 48 | it 'passes' do 49 | status, headers, content = app1.rack.call({}) 50 | 51 | expect(status).to eq(201) 52 | expect(headers).to eq('app1' => 1) 53 | expect(content).to contain_exactly('1 checks passed 🎉') 54 | end 55 | end 56 | 57 | context 'when app2' do 58 | it 'passes' do 59 | status, headers, content = app2.rack.call({}) 60 | 61 | expect(status).to eq(503) 62 | expect(headers).to eq('app2' => 2) 63 | expect(content).to contain_exactly("Check failed") 64 | end 65 | end 66 | 67 | context 'when app3' do 68 | it 'passes' do 69 | status, headers, content = app3.rack.call({ 'FOO' => 'BAR' }) 70 | 71 | expect(status).to eq(200) 72 | expect(headers).to eq('app3' => 1) 73 | expect(content).to contain_exactly('1 checks passed 🎉') 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'health_bit' 3 | require 'pry' 4 | 5 | RSpec.configure do |config| 6 | config.example_status_persistence_file_path = '.rspec_status' 7 | config.disable_monkey_patching! 8 | config.filter_run :focus 9 | config.run_all_when_everything_filtered = true 10 | config.order = :random 11 | Kernel.srand config.seed 12 | 13 | config.expect_with :rspec do |c| 14 | c.syntax = :expect 15 | end 16 | end 17 | --------------------------------------------------------------------------------