Commit cc71f96
Changed files (6)
lib
scim
spec
scim
lib/scim/kit/cli/app.rb
@@ -47,31 +47,33 @@ module Scim
def fetch_discovery
discovery = Discovery.new(client)
result = discovery.fetch
- return reporter.report(result) unless result.ok? && options[:validate]
+ return reporter.report(result) unless result.ok? && settings.validate?
reporter.report_validation(result, discovery.errors_for(result.body))
end
def fetch_list(resource_type)
- endpoint = resolve_endpoint(resource_type)
- result = client.fetch(endpoint, query: settings.list_query)
- validate_and_report(result, resource_type) do |schema|
+ entry = resource_type_entry(resource_type)
+ result = client.fetch(
+ endpoint_for(entry), query: settings.list_query
+ )
+ validate_and_report(result, entry) do |schema|
SchemaRegistry.list_response_with_items(schema)
end
end
def fetch_resource(resource_type, id)
- endpoint = resolve_endpoint(resource_type)
- path = "#{endpoint}/#{URI.encode_uri_component(id)}"
- result = client.fetch(
- path, query: { 'attributes' => options[:attributes] }
- )
- validate_and_report(result, resource_type)
+ entry = resource_type_entry(resource_type)
+ path = "#{endpoint_for(entry)}/#{URI.encode_uri_component(id)}"
+ result = client.fetch(path, query: settings.resource_query)
+ validate_and_report(result, entry)
end
- def resolve_endpoint(resource_type)
- endpoint = resource_type_entry(resource_type)[:endpoint]
- raise MissingEndpoint, resource_type if endpoint.to_s.empty?
+ def endpoint_for(entry)
+ endpoint = entry[:endpoint]
+ if endpoint.to_s.empty?
+ raise MissingEndpoint, entry[:name] || entry[:id]
+ end
endpoint.delete_prefix('/')
end
@@ -88,11 +90,12 @@ module Scim
@resource_schema_resolver ||= ResourceSchemaResolver.new(client)
end
- def validate_and_report(result, resource_type, &transform)
- return reporter.report(result) unless result.ok? && options[:validate]
+ def validate_and_report(result, entry, &transform)
+ return reporter.report(result) unless result.ok? && settings.validate?
- entry = resource_type_entry(resource_type)
- errors = validation.errors_for(entry, result.body, &transform)
+ errors = validation.errors_for(
+ entry, result.body, sparse: settings.sparse?, &transform
+ )
return reporter.report_unvalidated(result) unless errors
reporter.report_validation(result, errors)
@@ -100,7 +103,7 @@ module Scim
def validation
@validation ||= ResourceValidation.new(
- resource_schema_resolver, reporter, sparse: !options[:attributes].nil?
+ resource_schema_resolver, reporter
)
end
lib/scim/kit/cli/resource_validation.rb
@@ -4,22 +4,21 @@ module Scim
module Kit
module Cli
class ResourceValidation
- def initialize(resolver, reporter, sparse: false)
+ def initialize(resolver, reporter)
@resolver = resolver
@reporter = reporter
- @sparse = sparse
end
- def errors_for(entry, body, &transform)
+ def errors_for(entry, body, sparse: false, &transform)
schema = schema_for(entry)
return unless schema
- Validator.errors_for(prepare(schema, &transform), body)
+ Validator.errors_for(prepare(schema, sparse, &transform), body)
end
private
- attr_reader :resolver, :reporter, :sparse
+ attr_reader :resolver, :reporter
def schema_for(entry)
schema = resolver.schema_for(entry)
@@ -27,7 +26,7 @@ module Scim
schema
end
- def prepare(schema)
+ def prepare(schema, sparse)
schema = SparseSchema.relax(schema) if sparse
block_given? ? yield(schema) : schema
end
lib/scim/kit/cli/settings.rb
@@ -30,6 +30,18 @@ module Scim
QUERY.transform_values { |name| options[name] }
end
+ def resource_query
+ list_query.slice('attributes')
+ end
+
+ def validate?
+ options[:validate]
+ end
+
+ def sparse?
+ !options[:attributes].nil?
+ end
+
private
attr_reader :options, :env
lib/scim/kit/cli.rb
@@ -11,8 +11,8 @@ require 'scim/kit/cli/resource_schema_resolver'
require 'scim/kit/cli/resource_type_resolver'
require 'scim/kit/cli/resource_validation'
require 'scim/kit/cli/schema_registry'
-require 'scim/kit/cli/settings'
require 'scim/kit/cli/scim_schema_converter'
+require 'scim/kit/cli/settings'
require 'scim/kit/cli/sparse_schema'
require 'scim/kit/cli/validator'
spec/scim/kit/cli/discovery_spec.rb
@@ -52,10 +52,10 @@ RSpec.describe Scim::Kit::Cli::Discovery do
describe '#errors_for' do
it 'is empty when every document conforms' do
- documents = subject.fetch.body
- documents[:service_provider_configuration] = valid_config
- documents[:schemas] = valid_list
- documents[:resource_types] = valid_list
+ documents = {
+ service_provider_configuration: valid_config,
+ schemas: valid_list, resource_types: valid_list
+ }
expect(subject.errors_for(documents)).to eql({})
end
spec/scim/kit/cli/resource_validation_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+RSpec.describe Scim::Kit::Cli::ResourceValidation do
+ subject { described_class.new(resolver, reporter) }
+
+ let(:reporter) { Scim::Kit::Cli::Reporter.new(Thor::Shell::Basic.new) }
+ let(:entry) { { name: 'User', schema: core_urn } }
+ let(:core_urn) { 'urn:ietf:params:scim:schemas:core:2.0:User' }
+ let(:schema) do
+ {
+ 'type' => 'object',
+ 'properties' => { 'userName' => { 'type' => 'string' } },
+ 'required' => %w[schemas id userName]
+ }
+ end
+ let(:resolver) do
+ instance_double(
+ Scim::Kit::Cli::ResourceSchemaResolver,
+ schema_for: schema, undeclared_extensions: []
+ )
+ end
+ let(:resource) { { schemas: [core_urn], id: '1', userName: 'mo' } }
+
+ describe '#errors_for' do
+ it 'is empty for a conforming resource' do
+ expect(subject.errors_for(entry, resource)).to eql([])
+ end
+
+ it 'reports a schema violation' do
+ expect(subject.errors_for(entry, resource.merge(userName: 42)))
+ .to include(/userName.*is not of type: string/)
+ end
+
+ it 'applies the transform block to the schema' do
+ errors = subject.errors_for(entry, { totalResults: 1 }) do |resource_schema|
+ Scim::Kit::Cli::SchemaRegistry.list_response_with_items(resource_schema)
+ end
+
+ expect(errors).to include(/missing required keys.*schemas/)
+ end
+
+ context 'when sparse is set' do
+ it 'does not demand attributes the server was not asked for' do
+ errors = subject.errors_for(
+ entry, { schemas: [core_urn], id: '1' }, sparse: true
+ )
+
+ expect(errors).to eql([])
+ end
+
+ it 'still demands the always-returned attributes' do
+ expect(subject.errors_for(entry, { userName: 'mo' }, sparse: true))
+ .to include(/missing required keys/)
+ end
+ end
+
+ context 'when the schema cannot be resolved' do
+ let(:resolver) do
+ instance_double(
+ Scim::Kit::Cli::ResourceSchemaResolver, schema_for: nil
+ )
+ end
+
+ it 'returns nil so the caller can flag it as unvalidated' do
+ allow($stderr).to receive(:print)
+
+ expect(subject.errors_for(entry, resource)).to be_nil
+ end
+
+ it 'warns naming the resource type' do
+ expect { subject.errors_for(entry, resource) }
+ .to output(/no schema found for resource type "User"/).to_stderr
+ end
+ end
+
+ context 'when the resource type declares an undeclared extension' do
+ let(:resolver) do
+ instance_double(
+ Scim::Kit::Cli::ResourceSchemaResolver,
+ schema_for: schema, undeclared_extensions: ['urn:vendor:2.0:Thing']
+ )
+ end
+
+ it 'warns naming the extension urn' do
+ expect { subject.errors_for(entry, resource) }
+ .to output(/urn:vendor:2.0:Thing/).to_stderr
+ end
+
+ it 'still validates the resource' do
+ allow($stderr).to receive(:print)
+
+ expect(subject.errors_for(entry, resource)).to eql([])
+ end
+ end
+ end
+end