Commit 6710904

mo <mo@mokhan.ca>
2019-05-31 19:06:57
adapt the tree hash with a node object
1 parent a9225e5
Changed files (3)
app
models
spec
app/models/scim/visitor.rb
@@ -1,6 +1,46 @@
 # frozen_string_literal: true
 
 module Scim
+  class Node
+    def initialize(node)
+      @node = node
+    end
+
+    def operator
+      @node[:operator].to_sym
+    end
+
+    def attribute
+      @node[:attribute].to_s
+    end
+
+    def value
+      @node[:value].to_s[1..-2]
+    end
+
+    def accept(visitor)
+      visitor.visit(self)
+    end
+
+    def left
+      self.class.new(self[:left])
+    end
+
+    def right
+      self.class.new(self[:right])
+    end
+
+    def self.parse(query)
+      new(Scim::Kit::V2::Filter.new.parse(query))
+    end
+
+    private
+
+    def [](key)
+      @node[key]
+    end
+  end
+
   class Visitor
     def initialize(clazz, mapper = {})
       @clazz = clazz
@@ -8,11 +48,11 @@ module Scim
     end
 
     def visit(node)
-      case node[:operator].to_sym
+      case node.operator
       when :and
-        visit(node[:left]).merge(visit(node[:right]))
+        visit(node.left).merge(visit(node.right))
       when :or
-        visit(node[:left]).or(visit(node[:right]))
+        visit(node.left).or(visit(node.right))
       when :eq
         @clazz.where(attr_for(node) => value_from(node))
       when :ne
@@ -45,7 +85,7 @@ module Scim
     private
 
     def value_from(node)
-      node[:value].to_s[1..-2]
+      node.value
     end
 
     def cast_value_from(node)
@@ -62,8 +102,7 @@ module Scim
     end
 
     def attr_for(node)
-      attribute = node[:attribute].to_s
-      @mapper[attribute] || attribute
+      @mapper[node.attribute] || node.attribute
     end
   end
 end
spec/models/scim/visitor_spec.rb
@@ -0,0 +1,105 @@
+require 'rails_helper'
+
+RSpec.describe ::Scim::Visitor do
+  subject { described_class.new(User, SCIM::User::ATTRIBUTES) }
+
+  describe "#visit" do
+    let!(:users) { create_list(:user, 10) }
+    let(:random_user) { users.sample }
+    let(:parser) { Scim::Kit::V2::Filter.new }
+
+    def tree_for(filter)
+      parser.parse(filter)
+    end
+
+    specify do
+      results = Scim::Node.parse("userName pr").accept(subject)
+      expect(results.to_sql).to eql(User.where.not(email: nil).to_sql)
+      expect(results).to match_array(users)
+    end
+
+    pending do
+      results = Scim::Node.parse("userName pr and not (userName eq \"#{random_user.email}\")").accept(subject)
+      expect(results).to match_array(users - [random_user])
+    end
+
+    specify do
+      results = Scim::Node.parse("userName eq \"#{random_user.email}\"").accept(subject)
+      expect(results).to match_array([random_user])
+    end
+
+    specify do
+      results = Scim::Node.parse("userName ne \"#{random_user.email}\"").accept(subject)
+      expect(results.pluck(:email)).not_to include(random_user.email)
+    end
+
+    specify do
+      results = Scim::Node.parse("userName co \"#{random_user.email[1..-2]}\"").accept(subject)
+      expect(results).to match_array([random_user])
+    end
+
+    specify do
+      results = Scim::Node.parse("userName sw \"#{random_user.email[0..3]}\"").accept(subject)
+      expect(results).to match_array([random_user])
+    end
+
+    specify do
+      results = Scim::Node.parse("userName ew \"#{random_user.email[-8..-1]}\"").accept(subject)
+      expect(results).to match_array([random_user])
+    end
+
+    specify do
+      freeze_time
+      random_user.update!(updated_at: 10.minutes.from_now)
+
+      results = Scim::Node.parse("meta.lastModified gt \"#{Time.now.iso8601}\"").accept(subject)
+      expect(results).to match_array([random_user])
+    end
+
+    specify do
+      freeze_time
+      random_user.update!(updated_at: 10.minutes.from_now)
+
+      results = Scim::Node.parse("meta.lastModified ge \"#{random_user.updated_at.iso8601}\"").accept(subject)
+      expect(results).to match_array([random_user])
+    end
+
+    specify do
+      freeze_time
+      random_user.update!(updated_at: 10.minutes.from_now)
+
+      results = Scim::Node.parse("meta.lastModified lt \"#{Time.now.iso8601}\"").accept(subject)
+      expect(results).to match_array(users - [random_user])
+    end
+
+    specify do
+      freeze_time
+      random_user.update!(updated_at: 10.minutes.ago)
+
+      results = Scim::Node.parse("meta.lastModified le \"#{random_user.updated_at.iso8601}\"").accept(subject)
+      expect(results).to match_array([random_user])
+    end
+
+    context "when searching for condition a OR condition b" do
+      let(:first_user) { users.sample }
+      let(:second_user) { users.sample }
+      let(:results) { Scim::Node.parse(%(userName eq "#{first_user.email}" or userName eq "#{second_user.email}")).accept(subject) }
+
+      specify { expect(results.pluck(:email)).to match_array([first_user.email, second_user.email]) }
+    end
+
+    context "when searching for condition a AND condition b" do
+      let(:first_user) { users.sample }
+      let(:second_user) { users.sample }
+      let(:results) { Scim::Node.parse(%(meta.lastModified gt "#{10.minutes.from_now.iso8601}" and meta.lastModified lt "#{15.minutes.from_now.iso8601}")).accept(subject) }
+
+      before do
+        freeze_time
+        first_user.update!(updated_at: 11.minutes.from_now)
+        second_user.update!(updated_at: 12.minutes.from_now)
+      end
+
+      specify { expect(results).to match_array([first_user, second_user]) }
+    end
+  end
+end
spec/models/user_spec.rb
@@ -28,11 +28,6 @@ RSpec.describe User do
       expect(results).to match_array(users)
     end
 
-    pending do
-      results = subject.scim_filter_for(tree_for("userName pr and not (userName eq \"#{random_user.email}\")"))
-      expect(results).to match_array(users - [random_user])
-    end
-
     specify do
       results = subject.scim_filter_for(tree_for("userName eq \"#{random_user.email}\""))
       expect(results).to match_array([random_user])