require "spec_helper" describe "Expressive" do before(:each) do @scope = Expressive::TopLevel.new end describe "all_symbols" do it { Expressive.all_symbols.should =~ %w(+ - * / = set sum $sub get put post >= > < <= and or if date datetime lookup round $round $days_ago $hours_ago $minutes_ago $append $id $head $lookup $reverse $tail $hash $concat $split $sms $hval) } end describe "understands booleans" do it { Expressive.run("true").should eql true } it { Expressive.run("false").should eql false } end describe "understands numbers" do it { Expressive.run("5").should eql 5 } it { Expressive.run("5.5").should eql 5.5 } end describe "understands string literals" do it { Expressive.run('"hello world "').should eql "hello world " } it { Expressive.run('"hello world 2012"').should eql "hello world 2012" } it { Expressive.run('"hello world 2.5"').should eql "hello world 2.5" } it { Expressive.run('"hello world false"').should eql "hello world false" } context "string manipulation" do it {Expressive.run('($concat "hello" " world")').should eql "hello world" } it { @scope["scope_value"] = "world" Expressive.run('($concat "hello " scope_value)', @scope).should eql "hello world" } it {Expressive.run('($split "a,b,c,d,e" ",")').should eql ['a', 'b', 'c', 'd', 'e'] } end end describe "hash manipulation" do context "hash definition" do it { Expressive.run('($hash (test "value"))').should eql ({"test" => "value"})} it { Expressive.run('($hash (test 5))').should eql ({"test" => 5})} it { Expressive.run('($hash (test 5.3))').should eql ({"test" => 5.3})} it { Expressive.run('($hash (test "value") (test2 64))').should eql ({"test" => "value", "test2" => 64})} it { Expressive.run('($hash (test "value") (test2 (date)))').should eql ({"test" => "value", "test2" => Time.parse(Date.today.to_s).utc})} it { Expressive.run('($hash (test "value") (test2 (date)) (key ($hash (within "a_hash"))))').should eql ({"test" => "value", "test2" => Time.parse(Date.today.to_s).utc, "key" => {"within" => "a_hash"}})} end context "hash value" do it { Expressive.run('($hval "test" ($hash (test "value")))').should eql "value"} end end it "understands variables" do @scope["hello"] = "World" Expressive.run("hello", @scope).should eql "World" end describe "under stands arithmetic" do it { Expressive.run("(+ 4 2)").should eql 6.0 } it { Expressive.run("(- 4 2)").should eql 2.0 } it { Expressive.run("(* 4 2)").should eql 8.0 } it { Expressive.run("(/ 4 2)").should eql 2.0 } it { Expressive.run("(sum 1 2 3)").should eql 6.0 } it { Expressive.run("($sub 1 2 3)").should eql -4.0 } it { Expressive.run("(sum )").should eql 0 } it { Expressive.run("(- (sum 1 5 7) 3)").should eql 10.0} it { Expressive.run("(round 0.12345 2)").should eql 0.12} it { Expressive.run("(round 0.45 0)").should eql 0} it { Expressive.run("(round 0.55 0)").should eql 1} end describe "understands comparisson" do it { Expressive.run("(= 4 2)").should eql false } it { Expressive.run("(= 4 4)").should eql true } it { Expressive.run("(> 4 2)").should eql true } it { Expressive.run("(> 2 4)").should eql false } it { Expressive.run("(< 4 2)").should eql false } it { Expressive.run("(< 2 4)").should eql true } it { Expressive.run("(>= 4 4)").should eql true } it { Expressive.run("(<= 4 4)").should eql true } end describe "understands list operations" do it { Expressive.run("($head (1 2 3))").should eql 1 } it { Expressive.run("($tail (1 2 3))").should eql [2, 3] } it { Expressive.run("($reverse (1 2 3))").should eql [3 ,2 ,1] } end describe "understands compound statements" do it { Expressive.run("(= (+ 4 2) 6)").should eql true } it { Expressive.run("(if (and (< 3 9) (> 2 1)), true, false)").should eql true } it { Expressive.run("(if (and (< 10 9) (> 2 1)), true, false)").should eql false } end describe "understands conditional statements" do it { Expressive.run('(if (> 5 4) "greater" "not so greater")').should eql "greater" } it { Expressive.run('(if (= 5.2473 5.3473) true false)').should eql false } it { Expressive.run('(if (= 5.3473 5.3473) true false)').should eql true } it { Expressive.run('(if (= ($round 5.2473 2) ($round 5.3473 2)) true false)').should eql false } it { Expressive.run('(if (= ($round 5.2473 1) ($round 5.3473 1)) true false)').should eql false } it { Expressive.run('(if (= ($round 5.3473 1) ($round 5.3473 1)) true false)').should eql true } context "it understands the need for commas (if you like that kind of thing" do it { Expressive.run('(if (< 5 4), "greater", "not so greater")').should eql "not so greater" } end context "nil values" do it { Expressive.run('(if (> nil_value 4) "greater" "not so greater")', @scope).should eql "not so greater" } it { Expressive.run('(if (> 4 nil_value) "greater" "not so greater")', @scope).should eql "greater" } it { Expressive.run('(if (> 4 nil_value) "greater" "not so greater")', @scope).should eql "greater" } it { Expressive.run('(if (<= nil_value 4) "greater" "not so greater")', @scope).should eql "greater" } it { Expressive.run('(if (>= 4 nil_value) "greater" "not so greater")', @scope).should eql "greater" } end context "multiple if statements" do before do @scope["depth"] = "4mm" @statement = <<-EOH (if (= depth "8mm") 1 (if (= depth "7mm") 0.8 (if (= depth "6mm") 0.65 (if (= depth "5mm") 0.5 (if (= depth "4mm") 0.35 (if (= depth "3mm") 0.2 (if (= depth "2mm") 0.05 0) ) ) ) ) ) ) EOH end it do @scope["depth"] = "8mm" Expressive.run(@statement, @scope).should eql 1 end it do @scope["depth"] = "7mm" Expressive.run(@statement, @scope).should eql 0.8 end it do @scope["depth"] = "6mm" Expressive.run(@statement, @scope).should eql 0.65 end it do @scope["depth"] = "5mm" Expressive.run(@statement, @scope).should eql 0.5 end it do @scope["depth"] = "4mm" Expressive.run(@statement, @scope).should eql 0.35 end it do @scope["depth"] = "3mm" Expressive.run(@statement, @scope).should eql 0.2 end it do @scope["depth"] = "2mm" Expressive.run(@statement, @scope).should eql 0.05 end it do @scope["depth"] = "1.6mm" Expressive.run(@statement, @scope).should eql 0 end end context "multiple if statements with set" do before do @scope["letter_template"] = "" @statement = <<-EOH (set letter_001_sent (if (= letter_template "001-OFFCHA-GENERAL") "Y", letter_001_sent)) (set letter_002_sent (if (= letter_template "002-OFFCHA-NO8PCTINT") "Y", letter_002_sent)) (set letter_003_sent (if (= letter_template "003-OFFCHA-DEBTPURCH") "Y", letter_003_sent)) (set letter_004_sent (if (= letter_template "004-SIGNEDACC-CLIENT") "Y", letter_004_sent)) (set letter_005_sent (if (= letter_template "005-SIGNEDACC-LENDER") "Y", letter_005_sent)) EOH end it "does nothing when letter_template is nil" do @scope["letter_template"] = be_nil Expressive.run(@statement, @scope) @scope["letter_001_sent"].should be_nil @scope["letter_002_sent"].should be_nil @scope["letter_003_sent"].should be_nil @scope["letter_004_sent"].should be_nil @scope["letter_005_sent"].should be_nil end it "does not affect existing values" do @scope["letter_001_sent"] = "Y" @scope["letter_template"] = "002-OFFCHA-NO8PCTINT" Expressive.run(@statement, @scope) @scope["letter_002_sent"].should == "Y" @scope["letter_001_sent"].should == "Y" @scope["letter_003_sent"].should be_nil @scope["letter_004_sent"].should be_nil @scope["letter_005_sent"].should be_nil end it "sets letter_001_sent to Y" do @scope["letter_template"] = "001-OFFCHA-GENERAL" Expressive.run(@statement, @scope) @scope["letter_001_sent"].should == "Y" @scope["letter_002_sent"].should be_nil @scope["letter_003_sent"].should be_nil @scope["letter_004_sent"].should be_nil @scope["letter_005_sent"].should be_nil end it "sets letter_002_sent to Y" do @scope["letter_template"] = "002-OFFCHA-NO8PCTINT" Expressive.run(@statement, @scope) @scope["letter_002_sent"].should == "Y" @scope["letter_001_sent"].should be_nil @scope["letter_003_sent"].should be_nil @scope["letter_004_sent"].should be_nil @scope["letter_005_sent"].should be_nil end it "sets letter_003_sent to Y" do @scope["letter_template"] = "003-OFFCHA-DEBTPURCH" Expressive.run(@statement, @scope) @scope["letter_003_sent"].should == "Y" @scope["letter_001_sent"].should be_nil @scope["letter_002_sent"].should be_nil @scope["letter_004_sent"].should be_nil @scope["letter_005_sent"].should be_nil end it "sets letter_004_sent to Y" do @scope["letter_template"] = "004-SIGNEDACC-CLIENT" Expressive.run(@statement, @scope) @scope["letter_004_sent"].should == "Y" @scope["letter_001_sent"].should be_nil @scope["letter_002_sent"].should be_nil @scope["letter_003_sent"].should be_nil @scope["letter_005_sent"].should be_nil end it "sets letter_005_sent to Y" do @scope["letter_template"] = "005-SIGNEDACC-LENDER" Expressive.run(@statement, @scope) @scope["letter_005_sent"].should == "Y" @scope["letter_001_sent"].should be_nil @scope["letter_002_sent"].should be_nil @scope["letter_003_sent"].should be_nil @scope["letter_004_sent"].should be_nil end end end describe "understands logical statements" do it { Expressive.run('(and true true)').should eql true } it { Expressive.run('(and true false)').should eql false } it { Expressive.run('(or true true)').should eql true } it { Expressive.run('(or true false)').should eql true } it { Expressive.run('(or false false)').should eql false } context "nil values" do it { Expressive.run('(and true nil_value)', @scope).should eql false } it { Expressive.run('(and nil_value true)', @scope).should eql false } it { Expressive.run('(or nil_value true)', @scope).should eql true } it { Expressive.run('(or nil_value false)', @scope).should eql false } end end describe "understands the modification of scope" do it "works" do @scope['total_payment_amount'] = 2.32 @scope['refund_amount'] = 2.32 @scope['payment_reconciled'] = "asdf" Expressive.run('(if (= ($round total_payment_amount 2) ($round refund_amount 2)) true false)', @scope).should eql true Expressive.run('(if (= ($round total_payment_amount 2) ($round refund_amount 2)) true, false)', @scope).should eql true Expressive.run('(= (- ($round total_payment_amount 2) ($round refund_amount 2)) 0)', @scope).should eql true Expressive.run('(set payment_reconciled (if (= ($round total_payment_amount 2) ($round refund_amount 2)) true false))', @scope) @scope['payment_reconciled'].should eql true @scope['total_payment_amount'] = 3 @scope['refund_amount'] = 2.32 @scope['payment_reconciled'] = "asdf" Expressive.run('(if (= ($round total_payment_amount 2) ($round refund_amount 2)) true false)', @scope).should eql false Expressive.run('(if (= ($round total_payment_amount 2) ($round refund_amount 2)) true, false)', @scope).should eql false Expressive.run('(= (- ($round total_payment_amount 2) ($round refund_amount 2)) 0)', @scope).should eql false Expressive.run('(set payment_reconciled (if (= ($round total_payment_amount 2) ($round refund_amount 2)) true false))', @scope) puts @scope['payment_reconciled'] @scope['payment_reconciled'].should eql false end it "using single set commands" do @scope['vsi'] = 'oldvalue' Expressive.run('(set vsi "vsi1234")', @scope).should eql "vsi1234" @scope['vsi'].should eql 'vsi1234' end it "using single set commands with if" do @scope['vsi'] = 'oldvalue' Expressive.run('(set vsi (if (= 1 2) "vsi1234", vsi))', @scope) @scope['vsi'].should eql 'oldvalue' Expressive.run('(set vsi (if (= 1 1) "vsi1234", vsi))', @scope).should eql "vsi1234" @scope['vsi'].should eql 'vsi1234' end it "using multiple set commands" do Expressive.run('(and (set vsi "vsi1234") (set vso "vso1234") (set vsbool true))', @scope).should eql true @scope['vsi'].should eql 'vsi1234' @scope['vso'].should eql 'vso1234' @scope['vsbool'].should be_true end end describe "understands date parsing" do it "will return the current date" do now = Date.today.to_time.utc Timecop.freeze(now) do Expressive.run('(date)').should eql now end end it "will return a date parsed from text" do date = Date.new(2012, 11, 22).to_time.utc Expressive.run('(date(22/11/2012))').should eql date end end describe "understands datetime parsing" do it "will return the current date and time" do Timecop.freeze(DateTime.now) do now = DateTime.now Expressive.run('(datetime)').should eql now end end it "will return a date and time parsed from text" do date = DateTime.parse("22nd November 2012 13:04") Expressive.run('(datetime(22/11/2012 13:04:00))').should eql date end end describe "understands using lookup tables" do before(:each) do @user1 = mock(:user, id:1, login: "user1", display_name: "User 1") @user2 = mock(:user, id:2, login: "user2", display_name: "User 2") @scope.add_lookup_table("users", @user1.login => @user1, @user2.login => @user2) @scope.add_lookup_table("users", "x_current" => @user2) end it "will return a value from a lookup table" do Expressive.run('(lookup users "x_current")', @scope).should eql @user2 Expressive.run('(lookup users "user1")', @scope).should eql @user1 end it "will set multiple values based on results of a lookup" do @owned_by_ext_value = Expressive::ExtendedValue.new(:x_owned_by, @scope) @owned_by_ext_value.setter = Proc.new do |value, scope| scope["owned_by_id"] = value.id scope["owned_by_type"] = value.class.to_s end Expressive.run('(set x_owned_by (lookup users "x_current"))', @scope) @scope["owned_by_id"].should eql @user2.id @scope["owned_by_type"].should eql "RSpec::Mocks::Mock" end end describe "understands adding values to lists" do it "add a single value to a list of values" do Expressive.run('($append participating_teams 1)', @scope) @scope['participating_teams'].should == [1] end it "append to a list values list of values" do @scope['participating_teams'] = [0] Expressive.run('($append participating_teams (1 2 3))', @scope) @scope['participating_teams'].should == [0, 1, 2, 3] end it "add a hash to a list of values" do @scope['documents'] = [] Expressive.run('($append documents ($hash (test "value") (another_test 53.2) (and_a_date (date))))', @scope) @scope['documents'].should == [{"test" => "value", "another_test" => 53.2, "and_a_date" => Time.parse(Date.today.to_s).utc}] end end describe "understands retrieving the id of an object" do it do @scope["an_object"] = mock(:an_object, id: 5) Expressive.run('($id an_object)', @scope).should == 5 end end describe "understands time ranges" do before do @now = Time.now Time.stub!(:now).and_return(@now) end it { Expressive.run("($days_ago 28)").should eql (@now - 28 * 86400) } it { Expressive.run("($hours_ago 7)").should eql (@now - 7 * 3600) } it { Expressive.run("($minutes_ago 12)").should eql (@now - 12 * 60) } end describe "understands using lookup functions" do it "should perform the lookup function (add account and reverse login)" do @scope.add_lookup_function("account_reverse_login", account: "ea") do |options, login| "#{options[:account]}-#{login.reverse}" end Expressive.run('(lookup account_reverse_login "ijonas")', @scope).should eql "ea-sanoji" end it "should support multiple parameters to a lookup function" do @scope.add_lookup_function("related_count") do |options, related_type, query_specs, last| [related_type, query_specs.length, last] end Expressive.run('(lookup related_count "matters" (1 2 3) 10)', @scope).should eql ['matters', 3, 10] end end describe "understands sms statements" do before(:each) do ENV["clickatell_username"] = "user" ENV["clickatell_password"] = "pass" ENV["clickatell_api_id"] = "apiid" @base_url = "http://api.clickatell.com/http/sendmsg?user=user&password=pass&api_id=apiid" end it "should call http with hardcoded text" do end it "should send sms based off values in case" do @scope['mobile_phone_no'] = "447968115250" request = stub_request(:get, @base_url + "&to=447968115250&text=this%20is%20a%20test").to_return(body: {'goodbye' => 'srsly'}) Expressive.run('($sms "447968115250" "this is a test")', @scope) assert_requested(request) @scope['goodbye'].should eql 'srsly' end it "should normalise regular phone numbers" do @scope['mobile_phone_no'] = "07968115250" request = stub_request(:get, @base_url + "&to=447968115250&text=this%20is%20a%20test").to_return(body: {'goodbye' => 'srsly'}) Expressive.run('($sms "447968115250" "this is a test")', @scope) assert_requested(request) @scope['goodbye'].should eql 'srsly' end it "should normalise international phone numbers" do @scope['mobile_phone_no'] = "+447968115250" request = stub_request(:get, @base_url + "&to=447968115250&text=this%20is%20a%20test").to_return(body: {'goodbye' => 'srsly'}) Expressive.run('($sms "447968115250" "this is a test")', @scope) assert_requested(request) @scope['goodbye'].should eql 'srsly' end it "should encode data" do @scope['mobile_phone_no'] = "+447968115250" request = stub_request(:get, @base_url + "&to=447968115250&text=This%20is%20you%27re%20text%20-%20it%20%26%20the%20message%20should%20be%20encoded").to_return(body: {'goodbye' => 'srsly'}) Expressive.run('($sms "447968115250" "This is you\'re text - it & the message should be encoded")', @scope) assert_requested(request) @scope['goodbye'].should eql 'srsly' end end describe "understands web-hook statements" do context "put" do it_should_behave_like "a webhook", :put it "should put the cope parameters if given" do @scope['ohai'] = "world" request = stub_request(:put, "www.example.com").with(body: {'ohai' => 'world'}).to_return(body: {'goodbye' => 'srsly'}) Expressive.run('(put "http://www.example.com" ohai)', @scope) assert_requested(request) @scope['goodbye'].should eql 'srsly' end it "should put all parameters if wild card is given" do @scope['ohai'] = "world" @scope['monty'] = "python" request = stub_request(:put, "www.example.com").with(body: {'monty' => 'python', 'ohai' => 'world'}).to_return(body: {'goodbye' => 'srsly'}) Expressive.run('(put "http://www.example.com" "*")', @scope) assert_requested(request) @scope['goodbye'].should eql 'srsly' end end context "get" do it_should_behave_like "a webhook", :get it "should get with parameters if given" do @scope['ohai'] = "world" request = stub_request(:get, "www.example.com/?ohai=world").to_return(body: {'goodbye' => 'srsly'}) Expressive.run('(get "http://www.example.com" ohai)', @scope) assert_requested(request) @scope['goodbye'].should eql 'srsly' end it "should get with generated url" do @scope['ohai'] = "world" request = stub_request(:get, "www.example.com/?ohai=world").to_return(body: {'goodbye' => 'srsly'}) Expressive.run('(get ($concat "http://www.example.com/?ohai=" ohai))', @scope) assert_requested(request) @scope['goodbye'].should eql 'srsly' end it "should get with all parameters if wild card is given" do @scope['ohai'] = "world" @scope["monty"] = "python" request = stub_request(:get, "www.example.com/?ohai=world&monty=python").to_return(body: {'goodbye' => 'srsly'}) Expressive.run('(get "http://www.example.com" "*")', @scope) assert_requested(request) @scope['goodbye'].should eql 'srsly' end it "should get with headers" do @scope['ohai'] = "world" @scope["monty"] = "python" request = stub_request(:get, "www.example.com/?ohai=world&monty=python") .with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate', 'User-Agent'=>'Ruby', "AUTH-TOKEN" => "123456"}) .to_return(body: {'goodbye' => 'srsly'}) Expressive.run('(get "http://www.example.com" "*" headers "AUTH-TOKEN=123456")', @scope) assert_requested(request) @scope['goodbye'].should eql 'srsly' end end context "post" do it_should_behave_like "a webhook", :post it "should post the cope parameters if given" do @scope['ohai'] = "world" request = stub_request(:post, "www.example.com").with(body: {'ohai' => 'world'}).to_return(body: {'goodbye' => 'srsly'}) Expressive.run('(post "http://www.example.com" ohai)', @scope) assert_requested(request) @scope['goodbye'].should eql 'srsly' end it "should get with all parameters if wild card is given" do @scope['ohai'] = "world" @scope["monty"] = "python" request = stub_request(:post, "www.example.com").with(body: {'monty' => 'python', 'ohai' => 'world'}).to_return(body: {'goodbye' => 'srsly'}) Expressive.run('(post "http://www.example.com" "*")', @scope) assert_requested(request) @scope['goodbye'].should eql 'srsly' end it "should take a hash of values to post" do @scope['ohai'] = "world" @scope["monty"] = "python" request = stub_request(:post, "www.example.com").with(body: {'param1' => 'value1', 'param2' => 'value2', 'param3' => 'value3'}).to_return(body: {'param4' => 'value4'}) Expressive.run('(post "http://www.example.com" ($hash (param1 "value1") (param2 "value2") (param3 "value3")))', @scope) assert_requested(request) @scope['param4'].should eql 'value4' end it "should be able to create a new hash of values to post" do @scope['ohai'] = "world" @scope["monty"] = "python" request = stub_request(:post, "www.example.com").with(body: {'param1' => 'world', 'param2' => 'value2', 'param3' => 'python'}).to_return(body: {'param4' => 'value4'}) Expressive.run('(post "http://www.example.com" ($hash (param1 ohai) (param2 "value2") (param3 monty)))', @scope) assert_requested(request) @scope['param4'].should eql 'value4' end it "should be able to create a nested hash of values to post" do @scope['ohai'] = "world" @scope["monty"] = "python" @scope["nested"] = "conference" request = stub_request(:post, "www.example.com").with(body: {'param1' => 'world', 'param2' => {'level1' => {'level2' => 'conference'}}, 'param3' => 'python'}).to_return(body: {'param4' => 'value4'}) Expressive.run('(post "http://www.example.com" ($hash (param1 ohai) (param2 ($hash (level1 ($hash (level2 nested))))) (param3 monty)))', @scope) assert_requested(request) @scope['param4'].should eql 'value4' end it "should extract data from response of post" do @scope['ohai'] = "world" @scope["monty"] = "python" request = stub_request(:post, "www.example.com").with(body: {'param1' => 'world', 'param2' => 'value2', 'param3' => 'python'}).to_return(body: {'param4' => 'value4'}) Expressive.run('(post "http://www.example.com" ($hash (param1 ohai) (param2 "value2") (param3 monty)))', @scope) assert_requested(request) @scope['param4'].should eql 'value4' end it "should update scope with data extracted from response and no other" do @scope['ohai'] = "world" @scope["monty"] = "python" request = stub_request(:post, "www.example.com").with(body: {'param1' => 'world', 'param2' => 'value2', 'param3' => 'python'}).to_return(body: {'case_id' => '452'}) Expressive.run('(set related_id ($hval "case_id" (post "http://www.example.com" ($hash (param1 ohai) (param2 "value2") (param3 monty)) false)))', @scope) assert_requested(request) @scope.to_hash.keys.should_not include 'case_id' puts @scope.to_hash @scope['related_id'].should eql '452' end end context "conditional post" do it "should post to correct url" do @scope['ohai'] = "world" @scope["monty"] = "python" request = stub_request(:post, "www.example.com").with(body: {'monty' => 'python', 'ohai' => 'world'}).to_return(body: {'goodbye' => 'srsly'}) Expressive.run('(post (if (= ohai "world") "http://www.example.com", "http://www.wrongurl.com") "*")', @scope) assert_requested(request) @scope['goodbye'].should eql 'srsly' end end end end