Comparing changes

v0.5.0 v0.5.1
6 commits 22 files changed

Commits

274429b Update CHANGELOG mo khan 2020-05-20 17:32:10
0ee86d6 Convert to GitHub Actions mo khan 2020-05-20 16:46:29
d20fcc6 Do some spring cleaning mo khan 2020-05-20 16:43:22
5a355fc Update bundler and rubocop mo khan 2020-05-20 16:13:09
4373ce3 Specify Accept and Content-Type headers mo khan 2020-05-20 16:05:38
.github/workflows/ci.yml
@@ -0,0 +1,36 @@
+name: ci
+on: [push]
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        ruby: [ '2.5', '2.6', '2.7' ]
+    name: RSpec Ruby ${{ matrix.ruby }}
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          lfs: true
+          submodules: recursive
+      - uses: actions/setup-ruby@v1
+        with:
+          ruby-version: ${{ matrix.ruby }}
+      - name: setup
+        run: ./bin/setup
+      - name: test
+        run: ./bin/test
+  lint:
+    runs-on: ubuntu-latest
+    name: Lint
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          lfs: true
+          submodules: recursive
+      - uses: actions/setup-ruby@v1
+        with:
+          ruby-version: 2.7
+      - name: setup
+        run: ./bin/setup
+      - name: lint
+        run: ./bin/lint
bin/cibuild
@@ -1,22 +1,21 @@
 #!/bin/sh
 
-# script/cibuild: Setup environment for CI to run tests. This is primarily
-#                 designed to run on the continuous integration server.
-
 set -e
 
-cd "$(dirname "$0")/.."
-
-echo ["$(date "+%H:%M:%S")"] "==> Started at…"
+[ -z "$DEBUG" ] || set -x
 
-# GC customizations
+cd "$(dirname "$0")/.."
 export RUBY_GC_MALLOC_LIMIT=79000000
 export RUBY_GC_HEAP_INIT_SLOTS=800000
 export RUBY_HEAP_FREE_MIN=100000
 export RUBY_HEAP_SLOTS_INCREMENT=400000
 export RUBY_HEAP_SLOTS_GROWTH_FACTOR=1
 
-ruby -v
-gem install bundler --conservative
+echo ["$(date "+%H:%M:%S")"] "==> Running setup…"
+bin/setup
+
+echo ["$(date "+%H:%M:%S")"] "==> Running tests…"
 bin/test
+
+echo ["$(date "+%H:%M:%S")"] "==> Running linters…"
 bin/lint
bin/lint
@@ -2,10 +2,8 @@
 
 set -e
 
-[ -z "$DEBUG" ] || set -x
+cd "$(dirname "$0")/.."
 
-echo [$(date "+%H:%M:%S")] "==> Running setup…"
-bin/setup
+export RUBYOPT='-W0'
 
-echo [$(date "+%H:%M:%S")] "==> Running linters…"
 bundle exec rake lint
bin/setup
@@ -1,6 +1,7 @@
 #!/usr/bin/env bash
 set -euo pipefail
 IFS=$'\n\t'
-set -vx
 
+ruby -v
+gem install bundler --conservative -v '~> 2.0'
 bundle check || bundle install --jobs "$(sysctl -n hw.ncpu || nproc)"
bin/shipit
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+bin/cibuild
+bundle exec rake release
bin/test
@@ -1,21 +1,9 @@
-#!/bin/bash
-
-# script/test: Run test suite for application. Optionally pass in a path to an
-#              individual test file to run a single test.
-
+#!/bin/sh
 
 set -e
 
 cd "$(dirname "$0")/.."
 
-[ -z "$DEBUG" ] || set -x
-
-echo ["$(date "+%H:%M:%S")"] "==> Running setup…"
-bin/setup
+export RUBYOPT='-W0'
 
-echo ["$(date "+%H:%M:%S")"] "==> Running tests…"
-if [[ $# -eq 0 ]]; then
-  bundle exec rake spec
-else
-  bundle exec rspec "$1"
-fi
+bundle exec rspec "$@"
lib/scim/kit/v2/templates/attribute.json.jbuilder
@@ -3,7 +3,7 @@
 json.key_format! camelize: :lower
 if _type.complex? && !_type.multi_valued
   json.set! _type.name do
-    dynamic_attributes.values.each do |attribute|
+    dynamic_attributes.each_value do |attribute|
       render attribute, json: json
     end
   end
lib/scim/kit/v2/attribute_type.rb
@@ -108,7 +108,7 @@ module Scim
         def valid_complex?(item)
           return false unless item.is_a?(Hash)
 
-          item.keys.each do |key|
+          item.each_key do |key|
             return false unless type_for(key)&.valid?(item[key])
           end
         end
lib/scim/kit/v2/configuration.rb
@@ -40,7 +40,8 @@ module Scim
         attr_accessor :resource_types
         attr_accessor :schemas
 
-        def initialize
+        def initialize(http: Scim::Kit::Http.new)
+          @http = http
           @resource_types = {}
           @schemas = {}
 
@@ -48,9 +49,11 @@ module Scim
         end
 
         def load_from(base_url)
+          base_url = "#{base_url}/"
           uri = URI.join(base_url, 'ServiceProviderConfig')
-          self.service_provider_configuration =
-            ServiceProviderConfiguration.parse(client.get(uri).body)
+          json = http.get(uri)
+
+          self.service_provider_configuration = ServiceProviderConfiguration.parse(json, json)
 
           load_items(base_url, 'Schemas', Schema, schemas)
           load_items(base_url, 'ResourceTypes', ResourceType, resource_types)
@@ -58,25 +61,15 @@ module Scim
 
         private
 
+        attr_reader :http
+
         def load_items(base_url, path, type, items)
-          response = client.get(URI.join(base_url, path), headers: headers)
-          hashes = JSON.parse(response.body, symbolize_names: true)
+          hashes = http.get(URI.join(base_url, path))
           hashes.each do |hash|
             item = type.from(hash)
             items[item.id] = item
           end
         end
-
-        def client
-          @client ||= Net::Hippie::Client.new
-        end
-
-        def headers
-          {
-            'Accept' => 'application/scim+json',
-            'Content-Type' => 'application/scim+json'
-          }
-        end
       end
     end
   end
lib/scim/kit/http.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Scim
+  module Kit
+    class Http
+      attr_reader :driver, :retries
+
+      def initialize(driver: Http.default_driver, retries: 3)
+        @driver = driver
+        @retries = retries
+      end
+
+      def get(uri)
+        driver.with_retry(retries: retries) do |client|
+          response = client.get(uri)
+          ok?(response) ? JSON.parse(response.body, symbolize_names: true) : {}
+        end
+      rescue *Net::Hippie::CONNECTION_ERRORS => error
+        Scim::Kit.logger.error(error)
+        {}
+      end
+
+      def self.default_driver
+        @default_driver ||= Net::Hippie::Client.new(headers: headers).tap do |http|
+          http.logger = Scim::Kit.logger
+          http.open_timeout = 1
+          http.read_timeout = 5
+          http.follow_redirects = 3
+        end
+      end
+
+      def self.headers
+        {
+          'Accept' => 'application/scim+json',
+          'Content-Type' => 'application/scim+json',
+          'User-Agent' => "scim/kit #{Scim::Kit::VERSION}"
+        }
+      end
+
+      private
+
+      def ok?(response)
+        response.is_a?(Net::HTTPSuccess)
+      end
+    end
+  end
+end
lib/scim/kit/v2.rb
@@ -68,7 +68,7 @@ module Scim
             false
           end
         },
-        reference: ->(x) { x =~ /\A#{URI.regexp(%w[http https])}\z/ },
+        reference: ->(x) { x =~ /\A#{URI::DEFAULT_PARSER.make_regexp(%w[http https])}\z/ },
         string: ->(x) { x.is_a?(String) }
       }.freeze
 
lib/scim/kit/version.rb
@@ -2,6 +2,6 @@
 
 module Scim
   module Kit
-    VERSION = '0.5.0'
+    VERSION = '0.5.1'
   end
 end
lib/scim/kit.rb
@@ -10,6 +10,7 @@ require 'tilt'
 require 'tilt/jbuilder'
 
 require 'scim/kit/dynamic_attributes'
+require 'scim/kit/http'
 require 'scim/kit/templatable'
 require 'scim/kit/template'
 require 'scim/kit/v2'
spec/scim/kit/v2/filter_spec.rb
@@ -35,9 +35,9 @@ RSpec.describe Scim::Kit::V2::Filter do
 
   specify { expect(subject.parse_with_debug('userName eq "jeramy@ziemann.biz"')).to be_truthy }
   specify { expect(subject.parse_with_debug(%((title pr) and (userType eq "Employee")))).to be_truthy }
-  specify { expect(subject.attribute_expression.parse_with_debug(%(title pr and userType eq "Employee"))).not_to be_truthy }
+  specify { expect(subject.attribute_expression).not_to parse(%(title pr and userType eq "Employee")) }
   specify { expect(subject.logical_expression.parse_with_debug(%((title pr) and (userType eq "Employee")))).to be_truthy }
-  specify { expect(subject.value_path.parse_with_debug(%(title pr and userType eq "Employee"))).not_to be_truthy }
+  specify { expect(subject.value_path).not_to parse(%(title pr and userType eq "Employee")) }
 
   [
     'emails[(type eq "work") and (value co "@example.com")]'
spec/scim/kit/v2/resource_spec.rb
@@ -49,7 +49,7 @@ RSpec.describe Scim::Kit::V2::Resource do
     end
   end
 
-  context 'with attribute named "type"' do
+  context 'with attribute named "members"' do
     before do
       schema.add_attribute(name: 'members') do |attribute|
         attribute.mutability = :read_only
.rspec
@@ -1,3 +1,3 @@
---format documentation
+--format progress
 --color
 --require spec_helper
.rubocop.yml
@@ -9,19 +9,49 @@ AllCops:
     - 'vendor/**/*'
   TargetRubyVersion: 2.5
 
+Layout/ArgumentAlignment:
+  EnforcedStyle: with_fixed_indentation
+
+Layout/EndOfLine:
+  EnforcedStyle: lf
+
 Layout/FirstArrayElementIndentation:
   EnforcedStyle: consistent
 
+Layout/LineLength:
+  Exclude:
+    - 'spec/**/*.rb'
+  IgnoredPatterns:
+    - '^#*'
+
+Layout/MultilineMethodCallIndentation:
+  Enabled: true
+  EnforcedStyle: indented
+
+Layout/ParameterAlignment:
+  Enabled: true
+  EnforcedStyle: with_fixed_indentation
+  IndentationWidth: 2
+
+Lint/AmbiguousBlockAssociation:
+  Exclude:
+    - 'spec/**/*.rb'
+
+Lint/RaiseException:
+  Enabled: true
+
+Lint/StructNewOverride:
+  Enabled: true
+
 Metrics/BlockLength:
   Exclude:
     - '*.gemspec'
+    - 'Rakefile'
     - 'spec/**/*.rb'
 
-Metrics/LineLength:
+Metrics/ModuleLength:
   Exclude:
     - 'spec/**/*.rb'
-  IgnoredPatterns:
-    - '^#*'
 
 Naming/FileName:
   Exclude:
@@ -30,6 +60,30 @@ Naming/FileName:
 Naming/RescuedExceptionsVariableName:
   PreferredName: error
 
+Style/Documentation:
+  Enabled: false
+
+Style/HashEachMethods:
+  Enabled: true
+
+Style/HashTransformKeys:
+  Enabled: true
+
+Style/HashTransformValues:
+  Enabled: true
+
+Style/StringLiterals:
+  EnforcedStyle: 'single_quotes'
+
+Style/TrailingCommaInArrayLiteral:
+  Enabled: false
+
+Style/TrailingCommaInHashLiteral:
+  Enabled: false
+
+RSpec/FilePath:
+  Enabled: false
+
 RSpec/NamedSubject:
   Enabled: false
 
.travis.yml
@@ -1,9 +0,0 @@
----
-sudo: false
-language: ruby
-cache: bundler
-rvm:
-  - 2.5.7
-  - 2.6.5
-  - 2.7.0
-before_install: gem install bundler -v 1.17.1
CHANGELOG.md
@@ -1,4 +1,4 @@
-Version 0.5.0
+Version 0.5.1
 
 # Changelog
 All notable changes to this project will be documented in this file.
@@ -7,7 +7,16 @@ 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).
 
 ## [Unreleased]
-- nil
+
+## [0.5.1] - 2020-05-20
+### Fixed
+- Specify `Accept: application/scim+json` header when discovering a SCIM API.
+- Specify `Content-Type: application/scim+json` header when discovering a SCIM API.
+- Specify `User-Agent: scim/kit <version>` header when discovering a SCIM API.
+- Follow HTTP redirects when discovering a SCIM API.
+- Retry 3 times with backoff + jitter when a connection to a SCIM discovery API fails.
+- Specify a 1 second open timeout.
+- Specify a 5 second read timeout.
 
 ## [0.5.0] - 2020-01-21
 ### Added
@@ -44,8 +53,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - \_assign does not coerce values by default.
 - errors are merged together instead of overwritten during attribute validation.
 
-[Unreleased]: https://github.com/mokhan/scim-kit/compare/v0.5.0...HEAD
-[0.4.0]: https://github.com/mokhan/scim-kit/compare/v0.4.0...v0.5.0
+[Unreleased]: https://github.com/mokhan/scim-kit/compare/v0.5.1...HEAD
+[0.5.1]: https://github.com/mokhan/scim-kit/compare/v0.5.0...v0.5.1
+[0.5.0]: https://github.com/mokhan/scim-kit/compare/v0.4.0...v0.5.0
 [0.4.0]: https://github.com/mokhan/scim-kit/compare/v0.3.2...v0.4.0
 [0.3.2]: https://github.com/mokhan/scim-kit/compare/v0.3.1...v0.3.2
 [0.3.1]: https://github.com/mokhan/scim-kit/compare/v0.3.0...v0.3.1
Gemfile.lock
@@ -1,9 +1,9 @@
 PATH
   remote: .
   specs:
-    scim-kit (0.5.0)
+    scim-kit (0.5.1)
       activemodel (>= 5.2.0)
-      net-hippie (~> 0.2)
+      net-hippie (~> 0.3)
       parslet (~> 1.8)
       tilt (~> 2.0)
       tilt-jbuilder (~> 0.7)
@@ -11,62 +11,62 @@ PATH
 GEM
   remote: https://rubygems.org/
   specs:
-    activemodel (6.0.2.1)
-      activesupport (= 6.0.2.1)
-    activesupport (6.0.2.1)
+    activemodel (6.0.3.1)
+      activesupport (= 6.0.3.1)
+    activesupport (6.0.3.1)
       concurrent-ruby (~> 1.0, >= 1.0.2)
       i18n (>= 0.7, < 2)
       minitest (~> 5.1)
       tzinfo (~> 1.1)
-      zeitwerk (~> 2.2)
+      zeitwerk (~> 2.2, >= 2.2.2)
     addressable (2.7.0)
       public_suffix (>= 2.0.2, < 5.0)
     ast (2.4.0)
     bundler-audit (0.6.1)
       bundler (>= 1.2.0, < 3)
       thor (~> 0.18)
-    byebug (11.1.0)
-    concurrent-ruby (1.1.5)
+    byebug (11.1.3)
+    concurrent-ruby (1.1.6)
     crack (0.4.3)
       safe_yaml (~> 1.0.0)
     diff-lcs (1.3)
-    ffaker (2.13.0)
-    hashdiff (1.0.0)
+    ffaker (2.14.0)
+    hashdiff (1.0.1)
     i18n (1.8.2)
       concurrent-ruby (~> 1.0)
-    jaro_winkler (1.5.4)
-    jbuilder (2.9.1)
-      activesupport (>= 4.2.0)
-    minitest (5.14.0)
-    net-hippie (0.3.1)
+    jbuilder (2.10.0)
+      activesupport (>= 5.0.0)
+    minitest (5.14.1)
+    net-hippie (0.3.2)
     parallel (1.19.1)
-    parser (2.7.0.2)
+    parser (2.7.1.2)
       ast (~> 2.4.0)
     parslet (1.8.2)
-    public_suffix (4.0.3)
+    public_suffix (4.0.5)
     rainbow (3.0.0)
     rake (13.0.1)
+    rexml (3.2.4)
     rspec (3.9.0)
       rspec-core (~> 3.9.0)
       rspec-expectations (~> 3.9.0)
       rspec-mocks (~> 3.9.0)
-    rspec-core (3.9.1)
-      rspec-support (~> 3.9.1)
-    rspec-expectations (3.9.0)
+    rspec-core (3.9.2)
+      rspec-support (~> 3.9.3)
+    rspec-expectations (3.9.2)
       diff-lcs (>= 1.2.0, < 2.0)
       rspec-support (~> 3.9.0)
     rspec-mocks (3.9.1)
       diff-lcs (>= 1.2.0, < 2.0)
       rspec-support (~> 3.9.0)
-    rspec-support (3.9.2)
-    rubocop (0.79.0)
-      jaro_winkler (~> 1.5.1)
+    rspec-support (3.9.3)
+    rubocop (0.83.0)
       parallel (~> 1.10)
       parser (>= 2.7.0.1)
       rainbow (>= 2.2.2, < 4.0)
+      rexml
       ruby-progressbar (~> 1.7)
-      unicode-display_width (>= 1.4.0, < 1.7)
-    rubocop-rspec (1.37.1)
+      unicode-display_width (>= 1.4.0, < 2.0)
+    rubocop-rspec (1.39.0)
       rubocop (>= 0.68.1)
     ruby-progressbar (1.10.1)
     safe_yaml (1.0.5)
@@ -76,20 +76,20 @@ GEM
     tilt-jbuilder (0.7.1)
       jbuilder
       tilt (>= 1.3.0, < 3)
-    tzinfo (1.2.6)
+    tzinfo (1.2.7)
       thread_safe (~> 0.1)
-    unicode-display_width (1.6.1)
-    webmock (3.8.0)
+    unicode-display_width (1.7.0)
+    webmock (3.8.3)
       addressable (>= 2.3.6)
       crack (>= 0.3.2)
       hashdiff (>= 0.4.0, < 2.0.0)
-    zeitwerk (2.2.2)
+    zeitwerk (2.3.0)
 
 PLATFORMS
   ruby
 
 DEPENDENCIES
-  bundler (~> 1.17)
+  bundler (~> 2.0)
   bundler-audit (~> 0.6)
   byebug
   ffaker (~> 2.7)
@@ -101,4 +101,4 @@ DEPENDENCIES
   webmock (~> 3.5)
 
 BUNDLED WITH
-   1.17.3
+   2.1.4
README.md
@@ -1,6 +1,6 @@
 # Scim::Kit
 
-[![Build Status](https://travis-ci.org/mokhan/scim-kit.svg?branch=master)](https://travis-ci.org/mokhan/scim-kit)
+[![Build Status](https://github.com/mokhan/scim-kit/workflows/ci/badge.svg)](https://github.com/mokhan/scim-kit/actions)
 [![Code Climate](https://codeclimate.com/github/mokhan/scim-kit.svg)](https://codeclimate.com/github/mokhan/scim-kit)
 [![Gem Version](https://badge.fury.io/rb/scim-kit.svg)](https://rubygems.org/gems/scim-kit)
 
scim-kit.gemspec
@@ -27,15 +27,15 @@ Gem::Specification.new do |spec|
     File.basename(file)
   end
   spec.require_paths = ['lib']
-  spec.required_ruby_version = '>= 2.5.0'
+  spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
   spec.metadata['yard.run'] = 'yri'
 
   spec.add_dependency 'activemodel', '>= 5.2.0'
-  spec.add_dependency 'net-hippie', '~> 0.2'
+  spec.add_dependency 'net-hippie', '~> 0.3'
   spec.add_dependency 'parslet', '~> 1.8'
   spec.add_dependency 'tilt', '~> 2.0'
   spec.add_dependency 'tilt-jbuilder', '~> 0.7'
-  spec.add_development_dependency 'bundler', '~> 1.17'
+  spec.add_development_dependency 'bundler', '~> 2.0'
   spec.add_development_dependency 'bundler-audit', '~> 0.6'
   spec.add_development_dependency 'byebug'
   spec.add_development_dependency 'ffaker', '~> 2.7'