require 'spec_helper' sample_script = <<DELOREAN A: a =? 123.0 b = a * 3 B: A c = a + b d =? e = c / a f = e * d C: p0 =? a = 456.0 + p0 D: in =? "no input" out = in DELOREAN sample_script3 = <<eof A: a = 2 p =? c = a * 2 pc = p + c lc = [pc, pc] C: A p =? 3 B: A p =? 5 eof sample_script4 = <<eof import M3 A: M3::A p =? 10 c = a * 2 d = pc - 1 e =? f =? g = e * 5 + f h = f + 1 i =? ptest = p * 10 ii = i result = [{"a": p, "b": 456}, {"a": 789, "b": p}] eof sample_script5 = <<eof A: f =? res = if f == "Apple" then 0 else if f == "Banana" then 1 else if f == "Orange" then 2 else 9 result = [{"a": "str", "b": 456}, {"a": 789, "b": "str"}] result2 = [{"a": "str", "b": 456}, {"a": 789, "b": "str"}] eof sample_script6 = <<eof A: b =? res = b + 1 eof sample_script7 = <<eof A: b =? res = b eof sample_script8 = <<eof A: b =? res = 123 eof sample_script9 = <<eof A: b =? res = b + 1 result = [{"a": 1, "b": res}, {"a": 789, "b": res}] eof sample_script10 = <<eof A: opt1 =? optn =? opttf =? opttrue =? optfalse =? req1 =? req2 =? req3 =? optif = if opttf == true then opttrue else if opttf == false then optfalse else nil v1 = if req1 == 'no opts' then req2 else if req1 == "opt1" then opt1 else if req2 != 'no opts' then optn else if req3 == "opttf" then optif else 'req3' eof script3_schema = <<eof A: pc = { "properties : { "p" : { "type" : "integer" }, } } eof script4_schema = <<eof A: d = { "properties" : { "p" : { "type" : "integer" }, } } d_ = { "type" : "integer" } ii = {} ii_ = { "type" : "integer" } g = { "properties" : { "e" : { "type" : "integer" }, "f" : { "type" : "integer" }, } } g_ = { "type" : "integer" } lc = { "properties" : { "p" : { "type" : "integer" }, } } result = { "properties" : { "p" : { "type" : "integer" }, } } result_ = { "type": "array", "minItems": 1, "items": { "type": "object", "properties": { "a": { "type" : "integer" }, "b": { "type" : "integer" } } } } eof script5_schema = <<eof A: res = { "properties" : { "f" : { "pg_enum" : "FruitsEnum" }, } } result = { "properties" : { "f" : { "pg_enum" : "FruitsEnum" }, } } result_ = { "type": "array", "minItems": 1, "items": { "type": "object", "properties": { "a": { "type" : "integer" }, "b": { "type" : "string" } } } } result2 = { "properties" : { "f" : { "pg_enum" : "FruitsEnum" }, } } result2_ = { "type": "array", "minItems": 1, "items": { "type": "object", "properties": { "a": { "type" : "integer" }, "b": { "type" : "string" } } } } eof script6_schema = <<eof A: res = { "properties" : { "b" : { "type" : "float" }, } } eof script7_schema = <<eof A: res = { "properties" : { "b" : { "pg_enum" : "NonExistantEnum" }, } } eof script8_schema = <<eof A: res = { "properties" : { "b" : { "pg_enum" : "Gemini::MiDurationType" }, } } eof script9_schema = <<eof A: res = { "properties" : { "b" : { "type" : "number" }, } } result = { "properties" : { "b" : { "type" : "number" }, } } result_ = { "type": "array", "minItems": 1, "items": { "type": "object", "properties": { "a": { "type" : "integer" }, "b": { "type" : "integer" }, "c": { "type" : "string" } }, "required" : ["a", "b", "c"] } } eof script10_schema = <<eof A: properties = { "opt1" : { "type" : "string" }, "opttf" : { "type" : "boolean" }, "opttrue" : { "type" : "string" }, "optfalse" : { "type" : "string" }, "optdisallow" : { "type" : "string" }, "req1" : { "pg_enum" : "CondEnum" }, "req2" : { "pg_enum" : "CondEnum" } } req1_is_opt1 = Marty::SchemaHelper.enum_is('req1', ['opt1']) req2_is_not_no_opts = Marty::SchemaHelper.not( Marty::SchemaHelper.enum_is('req2', ['no opts'])) req3_is_opttf = Marty::SchemaHelper.enum_is('req3', ['opttf']) opttf_is_true = Marty::SchemaHelper.bool_is('opttf', true) opttf_is_false = Marty::SchemaHelper.bool_is('opttf', false) # opt1 is required if req1 == 'opt1' opt1_check = Marty::SchemaHelper.required_if(['opt1'], req1_is_opt1) # optn is required if req2 != 'no opts' optn_check = Marty::SchemaHelper.required_if(['optn'], req2_is_not_no_opts) # opttf is required if req3 == 'opttf' opttf_check = Marty::SchemaHelper.required_if(['opttf'], req3_is_opttf) # opttrue is required if opttf is true opttrue_check = Marty::SchemaHelper.required_if(['opttrue'], opttf_is_true) # optfalse is required if opttf is false optfalse_check = Marty::SchemaHelper.required_if(['optfalse'], opttf_is_false) # optdisallow is not allowed if opttf is false optdisallow_check = Marty::SchemaHelper.disallow_if_conds(['optdisallow'], opttf_is_false) # opttf is optional (contingent on req3) so eval of opttrue_check # and optfalse_check is dependent upon opttf existing opttruefalse_check = Marty::SchemaHelper.dep_check('opttf', opttrue_check, optfalse_check, optdisallow_check) dip_check = Marty::SchemaHelper.disallow_if_present('opttf', 'opt3', 'opt4') dinp_check = Marty::SchemaHelper.disallow_if_not_present('opttf', 'opt5', 'opt6') v1 = { "properties": properties, "required": ["req1", "req2", "req3"], "allOf": [ opt1_check, optn_check, opttf_check, opttruefalse_check, dip_check, dinp_check ] } eof describe Marty::RpcController do before(:each) { @routes = Marty::Engine.routes # HACKY: 'params' param is special to the Rails controller test helper (at # least as of 4.2). Setting this avoids test framework code that relies on # params being a hash. @request.env['PATH_INFO'] = "/marty/rpc/evaluate.json" } before(:each) { @p0 = Marty::Posting.do_create("BASE", Date.today, 'a comment') @t1 = Marty::Script.load_script_bodies({ "M1" => sample_script, "M2" => sample_script.gsub(/a/, "aa").gsub(/b/, "bb"), "M3" => sample_script3, "M4" => sample_script4, "M5" => sample_script5, "M6" => sample_script6, "M7" => sample_script7, "M8" => sample_script8, "M9" => sample_script9, "M10" => sample_script10, "M3Schemas" => script3_schema, "M4Schemas" => script4_schema, "M5Schemas" => script5_schema, "M6Schemas" => script6_schema, "M7Schemas" => script7_schema, "M8Schemas" => script8_schema, "M9Schemas" => script9_schema, "M10Schemas" => script10_schema, }, Date.today + 1.minute) @p1 = Marty::Posting.do_create("BASE", Date.today + 2.minute, 'a comment') @t2 = Marty::Script.load_script_bodies({ "M1" => sample_script.gsub(/A/, "AA")+' e =? "hello"', }, Date.today + 3.minute) @p2 = Marty::Posting.do_create("BASE", Date.today + 4.minute, 'a comment') @data = [["some data",7,[1,2,3],{foo: "bar", baz: "quz"},5,"string"], ["some more data",[1,2,3],5,{foo: "bar", baz: "quz"},5,"string"]] @data_json = @data.to_json } after(:each) do Marty::Log.delete_all end let(:t1) { @t1 } let(:t2) { @t2 } let(:p0) { @p0 } let(:p1) { @p1 } let(:p2) { @p2 } it "should be able to post" do post 'evaluate', { format: :json, script: "M1", node: "B", attrs: "e", tag: t1.name, params: { a: 333, d: 5}.to_json, } expect(response.body).to eq(4.to_json) end it "should be able to post background job" do Delayed::Worker.delay_jobs = false post 'evaluate', { format: :json, script: "M1", node: "B", attrs: "e", tag: t1.name, params: { a: 333, d: 5}.to_json, background: true, } res = ActiveSupport::JSON.decode response.body expect(res).to include('job_id') job_id = res['job_id'] promise = Marty::Promise.find_by_id(job_id) expect(promise.result).to eq({"e"=>4}) Delayed::Worker.delay_jobs = true end it "should be able to post background job with non-array attr" do Delayed::Worker.delay_jobs = false post 'evaluate', { format: :json, script: "M1", node: "B", attrs: "e", tag: t1.name, params: { a: 333, d: 5}.to_json, background: true, } res = ActiveSupport::JSON.decode response.body expect(res).to include('job_id') job_id = res['job_id'] promise = Marty::Promise.find_by_id(job_id) expect(promise.result).to eq({"e"=>4}) Delayed::Worker.delay_jobs = true end it "should be able to post with complex data" do post 'evaluate', { format: :json, script: "M1", node: "D", attrs: "out", tag: t1.name, params: {in: @data}.to_json } expect(response.body).to eq(@data_json) end # content-type: application/json structures the request a little differently # so we also test that it "should be able to post (JSON) with complex data" do @request.env['CONTENT_TYPE'] = 'application/json' @request.env['ACCEPT'] = 'application/json' post 'evaluate', { format: :json, script: "M1", node: "D", attrs:"out", tag: t1.name, params: {in: @data}.to_json } expect(response.body).to eq(@data_json) end it "should be able to run scripts" do get 'evaluate', { format: :json, script: "M1", node: "A", attrs: "a", tag: t1.name, } # puts 'Z'*40, request.inspect expect(response.body).to eq(123.0.to_json) get 'evaluate', { format: :json, script: "M1", node: "A", attrs: "a", params: {"a" => 4.5}.to_json, tag: t1.name, } expect(response.body).to eq(4.5.to_json) get 'evaluate', { format: :json, script: "M1", node: "B", attrs: "a", params: {"a" => 4.5}.to_json, tag: t1.name, } expect(response.body).to eq(4.5.to_json) get 'evaluate', { format: :json, script: "M1", tag: "DEV", node: "AA", attrs: "a", params: {"a" => 3.3}.to_json, } expect(response.body).to eq(3.3.to_json) end it "should be able to use posting name for tags" do get 'evaluate', { format: :json, script: "M1", node: "A", attrs: "a", tag: p0.name, } expect(response.body["error"]).to_not be_nil get 'evaluate', { format: :json, script: "M1", node: "A", attrs: "a", params: {"a" => 4.5}.to_json, tag: p1.name, } expect(response.body).to eq(4.5.to_json) get 'evaluate', { format: :json, script: "M1", node: "B", attrs: "a", params: {"a" => 4.5}.to_json, tag: p2.name, } expect(response.body).to eq(4.5.to_json) get 'evaluate', { format: :json, script: "M1", tag: "NOW", node: "AA", attrs: "a", params: {"a" => 3.3}.to_json, } expect(response.body).to eq(3.3.to_json) end it "should be able to run scripts 2" do get 'evaluate', { format: :json, script: "M3", node: "C", attrs: "pc", } # puts 'Z'*40, request.inspect expect(response.body).to eq(7.to_json) get 'evaluate', { format: :json, script: "M3", node: "B", attrs: "pc", } # puts 'Z'*40, request.inspect expect(response.body).to eq(9.to_json) get 'evaluate', { format: :json, script: "M3", node: "A", attrs: "pc", } # puts 'Z'*40, request.inspect expect(response.body).to match(/"error":"undefined parameter p"/) end it "should be able to handle imports" do get 'evaluate', { format: :json, script: "M4", node: "A", attrs: "a", } # puts 'Z'*40, request.inspect expect(response.body).to eq(2.to_json) end it "should support CSV" do get 'evaluate', { format: :csv, script: "M4", node: "A", attrs: "a", } # puts 'Z'*40, request.inspect expect(response.body).to eq("2\r\n") end it "should support CSV (2)" do get 'evaluate', { format: :csv, script: "M4", node: "A", attrs: "result", } # puts 'Z'*40, request.inspect expect(response.body).to eq("a,b\r\n10,456\r\n789,10\r\n") end it "returns an error message on missing schema script (csv)" do Marty::ApiConfig.create!(script: "M1", node: "A", attr: nil, logged: false, input_validated: true) attr = "b" params = {"a" => 5}.to_json get 'evaluate', { format: :csv, script: "M1", node: "A", attrs: attr, params: params } expect = "Schema error for M1/A attrs=b: Schema not defined\r\n" expect(response.body).to eq("error,#{expect}") end it "returns an error message on missing schema script (json)" do Marty::ApiConfig.create!(script: "M1", node: "A", attr: nil, logged: false, input_validated: true) attr = "b" params = {"a" => 5}.to_json get 'evaluate', { format: :json, script: "M1", node: "A", attrs: attr, params: params } expect = "Schema error for M1/A attrs=b: Schema not defined" res = JSON.parse(response.body) expect(res.keys.size).to eq(1) expect(res.keys[0]).to eq("error") expect(res.values[0]).to eq(expect) end it "returns an error message on missing attributes in schema script" do Marty::ApiConfig.create!(script: "M4", node: "A", attr: nil, logged: false, input_validated: true) attr = "h" params = {"f" => 5}.to_json get 'evaluate', { format: :csv, script: "M4", node: "A", attrs: attr, params: params } expect = "Schema error for M4/A attrs=h: Problem with schema" expect(response.body).to include("error,#{expect}") end it "returns an error message on invalid schema" do Marty::ApiConfig.create!(script: "M3", node: "A", attr: nil, logged: false, input_validated: true) attr = "pc" params = {"p" => 5}.to_json get 'evaluate', { format: :csv, script: "M3", node: "A", attrs: attr, params: params } expect = "Schema error for M3/A attrs=pc: Problem with schema: "\ "syntax error M3Schemas:2\r\n" expect(response.body).to eq("error,#{expect}") end it "returns a validation error when validating a single attribute" do Marty::ApiConfig.create!(script: "M4", node: "A", attr: nil, logged: false, input_validated: true) attr = "d" params = {"p" => "132"}.to_json get 'evaluate', { format: :csv, script: "M4", node: "A", attrs: attr, params: params } expect = '[""The property \'#/p\' of type string did not '\ 'match the following type: integer' expect(response.body).to include(expect) end context "output_validation" do it "validates output" do Marty::ApiConfig.create!(script: "M4", node: "A", attr: nil, logged: false, input_validated: true, output_validated: true, strict_validate: true) attr = "ii" params = {"p" => 132, "e" => 55, "f"=>16, "i"=>"string"}.to_json get 'evaluate', { format: :json, script: "M4", node: "A", attrs: attr, params: params } res = JSON.parse(response.body) errpart = "of type string did not match the following type: integer" expect(res['error']).to include(errpart) logs = Marty::Log.all expect(logs.count).to eq(1) expect(logs[0].details["error"][0]).to include(errpart) end it "validates output (bad type, with strict errors)" do Marty::ApiConfig.create!(script: "M5", node: "A", attr: nil, logged: false, input_validated: true, output_validated: true, strict_validate: true) attr = "result" params = {"f" => "Banana"}.to_json get 'evaluate', { format: :json, script: "M5", node: "A", attrs: attr, params: params } res = JSON.parse(response.body) expect(res).to include("error") expect1 = "The property '#/0/b' of type integer did not match the "\ "following type: string" expect2 = "The property '#/0/a' of type string did not match the "\ "following type: integer" expect(res["error"]).to include(expect1) expect(res["error"]).to include(expect2) logs = Marty::Log.all expect(logs.count).to eq(1) expect(logs[0].message).to eq("API M5:A.result") expect(logs[0].details["error"].join).to include(expect1) expect(logs[0].details["error"].join).to include(expect2) expect(logs[0].details["data"]).to eq([{"a"=>"str", "b"=>456}, {"a"=>789, "b"=>"str"}]) end it "validates output (bad type, with non strict errors)" do Marty::ApiConfig.create!(script: "M5", node: "A", attr: "result2", logged: false, input_validated: true, output_validated: true, strict_validate: false) attr = "result2" params = {"f" => "Banana"}.to_json get 'evaluate', { format: :json, script: "M5", node: "A", attrs: attr, params: params } expect1 = "The property '#/0/b' of type integer did not match the "\ "following type: string" expect2 = "The property '#/0/a' of type string did not match the "\ "following type: integer" logs = Marty::Log.all expect(logs.count).to eq(1) expect(logs[0].message).to eq("API M5:A.result2") expect(logs[0].details["error"].join).to include(expect1) expect(logs[0].details["error"].join).to include(expect2) expect(logs[0].details["data"]).to eq([{"a"=>"str", "b"=>456}, {"a"=>789, "b"=>"str"}]) end it "validates output (missing item)" do Marty::ApiConfig.create!(script: "M9", node: "A", attr: nil, logged: false, input_validated: true, output_validated: true, strict_validate: true) attr = "result" params = {"b" => 122}.to_json get 'evaluate', { format: :json, script: "M9", node: "A", attrs: attr, params: params } res = JSON.parse(response.body) expect(res).to include("error") expect1 = "The property '#/0' did not contain a required property of 'c'" expect2 = "The property '#/1' did not contain a required property of 'c'" expect(res["error"]).to include(expect1) expect(res["error"]).to include(expect2) logs = Marty::Log.all expect(logs.count).to eq(1) expect(logs[0].message).to eq("API M9:A.result") expect(logs[0].details["error"].join).to include(expect1) expect(logs[0].details["error"].join).to include(expect2) expect(logs[0].details["data"]).to eq([{"a"=>1, "b"=>123}, {"a"=>789, "b"=>123}]) end end it "validates schema" do Marty::ApiConfig.create!(script: "M4", node: "A", attr: nil, logged: false, input_validated: true) attr = "lc" params = {"p" => 5}.to_json get 'evaluate', { format: :csv, script: "M4", node: "A", attrs: attr, params: params } expect(response.body).to eq("9\r\n9\r\n") end it "catches JSON::Validator exceptions" do Marty::ApiConfig.create!(script: "M6", node: "A", attr: nil, logged: false, input_validated: true) attr = "res" params = {"b" => 5.22}.to_json get 'evaluate', { format: :json, script: "M6", node: "A", attrs: attr, params: params } expect = 'res: The property \'#/properties/b/type\' of type string '\ 'did not match one or more of the required schemas' res = JSON.parse(response.body) expect(res.keys.size).to eq(1) expect(res.keys[0]).to eq("error") expect(res.values[0]).to eq(expect) end class FruitsEnum VALUES=Set['Apple', 'Banana', 'Orange'] end class CondEnum VALUES=Set['no opts','opt1','opt2','opttf'] end it "validates schema with a pg_enum (Positive)" do Marty::ApiConfig.create!(script: "M5", node: "A", attr: nil, logged: false, input_validated: true) attr = "res" params = {"f" => "Banana"}.to_json get 'evaluate', { format: :csv, script: "M5", node: "A", attrs: attr, params: params } expect(response.body).to eq("1\r\n") end it "validates schema with a pg_enum (Negative)" do Marty::ApiConfig.create!(script: "M5", node: "A", attr: nil, logged: false, input_validated: true) attr = "res" params = {"f" => "Beans"}.to_json get 'evaluate', { format: :csv, script: "M5", node: "A", attrs: attr, params: params } expect = "property '#/f' value 'Beans' not contained in FruitsEnum" expect(response.body).to include(expect) end it "validates schema with a non-existant enum" do Marty::ApiConfig.create!(script: "M7", node: "A", attr: nil, logged: false, input_validated: true) attr = "res" params = {"b" => "MemberOfANonExistantEnum"}.to_json get 'evaluate', { format: :json, script: "M7", node: "A", attrs: attr, params: params } expect = "property '#/b': 'NonExistantEnum' is not a pg_enum" res = JSON.parse(response.body) expect(res.keys.size).to eq(1) expect(res.keys[0]).to eq("error") expect(res.values[0]).to include(expect) end it "validates pgenum with capitalization issues" do Marty::ApiConfig.create!(script: "M8", node: "A", attr: nil, logged: false, input_validated: true) skip "pending until a solution is found that handles "\ "autoload issues involving constantize" attr = "res" params = {"b" => "Annual"}.to_json get 'evaluate', { format: :json, script: "M8", node: "A", attrs: attr, params: params } end it "should log good req" do Marty::ApiConfig.create!(script: "M3", node: "A", attr: nil, logged: true) attr = "lc" params = {"p" => 5} get 'evaluate', { format: :csv, script: "M3", node: "A", attrs: attr, params: params.to_json } expect(response.body).to eq("9\r\n9\r\n") log = Marty::Log.order(id: :desc).first expect(log.details['script']).to eq("M3") expect(log.details['node']).to eq("A") expect(log.details['attrs']).to eq(attr) expect(log.details['input']).to eq(params) expect(log.details['output']).to eq([9, 9]) expect(log.details['remote_ip']).to eq("0.0.0.0") expect(log.details['error']).to eq(nil) end it "should log good req [background]" do Marty::ApiConfig.create!(script: "M3", node: "A", attr: nil, logged: true) attr = "lc" params = {"p" => 5} get 'evaluate', { format: :csv, script: "M3", node: "A", attrs: attr, params: params.to_json, background: true } expect(response.body).to match(/job_id,/) log = Marty::Log.order(id: :desc).first end it "should not log if it should not log" do get 'evaluate', { format: :json, script: "M1", node: "A", attrs: "a", tag: t1.name, } expect(Marty::Log.count).to eq(0) end it "should handle atom attribute" do Marty::ApiConfig.create!(script: "M3", node: "A", attr: nil, logged: true) params = {"p" => 5} get 'evaluate', { format: :csv, script: "M3", node: "A", attrs: "lc", params: params.to_json } expect(response.body).to eq("9\r\n9\r\n") log = Marty::Log.order(id: :desc).first expect(log.details['script']).to eq("M3") expect(log.details['node']).to eq("A") expect(log.details['attrs']).to eq("lc") expect(log.details['input']).to eq(params) expect(log.details['output']).to eq([9, 9]) expect(log.details['remote_ip']).to eq("0.0.0.0") expect(log.details['error']).to eq(nil) end it "should support api authorization - api_key not required" do api = Marty::ApiAuth.new api.app_name = 'TestApp' api.script_name = 'M2' api.save! get 'evaluate', { format: :json, script: "M3", node: "C", attrs: "pc", } expect(response.body).to eq(7.to_json) end it "should support api authorization - api_key required but missing" do api = Marty::ApiAuth.new api.app_name = 'TestApp' api.script_name = 'M3' api.save! get 'evaluate', { format: :json, script: "M3", node: "C", attrs: "pc", } expect(response.body).to match(/"error":"Permission denied"/) end it "should support api authorization - api_key required and supplied" do api = Marty::ApiAuth.new api.app_name = 'TestApp' api.script_name = 'M3' api.save! apic = Marty::ApiConfig.create!(script: 'M3', logged: true) attr = "pc" get 'evaluate', { format: :json, script: "M3", node: "C", attrs: attr, api_key: api.api_key, } expect(response.body).to eq(7.to_json) log = Marty::Log.order(id: :desc).first expect(log.details['script']).to eq("M3") expect(log.details['node']).to eq("C") expect(log.details['attrs']).to eq(attr) expect(log.details['output']).to eq(7) expect(log.details['remote_ip']).to eq("0.0.0.0") expect(log.details['auth_name']).to eq("TestApp") end it "should support api authorization - api_key required but incorrect" do api = Marty::ApiAuth.new api.app_name = 'TestApp' api.script_name = 'M3' api.save! get 'evaluate', { format: :json, script: "M3", node: "C", attrs: "pc", api_key: api.api_key + 'x', } expect(response.body).to match(/"error":"Permission denied"/) end context "conditional validation" do before(:all) do Marty::ApiConfig.create!(script: "M10", node: "A", attr: nil, logged: false, input_validated: true, output_validated: false, strict_validate: false) end def do_call(req1, req2, req3, optionals={}) attr = "v1" params = optionals.merge({"req1" => req1, "req2"=> req2, "req3"=> req3}).to_json # to see what the schema helpers generated: # engine = Marty::ScriptSet.new(nil).get_engine("M10Schemas") # x=engine.evaluate("A", "v1", {}) # binding.pry get 'evaluate', { format: :json, script: "M10", node: "A", attrs: attr, params: params } end it "does conditional" do aggregate_failures "conditionals" do [ # first group has all required fields [['opt1', 'no opts', 'no opts', opt1: 'hi mom'], "hi mom"], [['no opts', 'no opts', 'no opts', opt1: 'hi mom'], "no opts"], [['opt2', 'opt2', 'no opts', optn: 'foo'], 'foo'], [['opt2', 'no opts', 'opt2'], 'req3'], [['opt2', 'no opts', 'opttf', opttf: true, opttrue: 'bar'], 'bar'], [['opt2', 'no opts', 'opttf', opttf: false, optfalse: 'baz'], 'baz'], # second group is missing fields or has other errors [['opt1', 'no opts', 'no opts'], "did not contain a required property of 'opt1'"], [['opt2', 'opt2', 'no opts',], "did not contain a required property of 'optn'"], [['opt2', 'no opts', 'opttf'], "did not contain a required property of 'opttf'"], [['opt2', 'no opts', 'opttf', opttf: true], "did not contain a required property of 'opttrue'"], [['opt2', 'no opts', 'opttf', opttf: false], "did not contain a required property of 'optfalse'"], [['opt2', 'no opts', 'opttf', opttf: false, optfalse: "val", optdisallow: "hi mom"], "disallowed parameter 'optdisallow' of type string was received"], [['opt2', 'no opts', 'opttf', opttf: false, optfalse: "val", opt3: "hi"], "disallowed parameter 'opt3' of type string was received"], [['opt2', 'no opts', 'opttf', opttf: true, opttrue: "val", opt4: "mom"], "disallowed parameter 'opt4' of type string was received"], [['opt2', 'no opts', 'xyz', opt5: "hi"], "disallowed parameter 'opt5' of type string was received"], ].each do |a, exp| do_call(*a) res = JSON.parse(response.body) expect(res.is_a?(Hash) ? res['error'] : res).to include(exp) end end end end context "error handling" do it 'returns bad attrs if attr is not a string' do get :evaluate, format: :json, attrs: 0 expect(response.body).to match(/"error":"Malformed attrs"/) end it 'returns malformed attrs for improperly formatted json' do get :evaluate, format: :json, attrs: "{" expect(response.body).to match(/"error":"Malformed attrs"/) end it 'returns malformed attrs if attr is not an array of strings' do get :evaluate, format: :json, attrs: "{}" expect(response.body).to match(/"error":"Malformed attrs"/) get :evaluate, format: :json, attrs: "[0]" expect(response.body).to match(/"error":"Malformed attrs"/) end it 'returns bad params if params is not a string' do get :evaluate, format: :json, attrs: "e", params: 0 expect(response.body).to match(/"error":"Bad params"/) end it 'returns malformed params for improperly formatted json' do get :evaluate, format: :json, attrs: "e", params: "{" expect(response.body).to match(/"error":"Malformed params"/) end it 'returns malformed params if params is not a hash' do get :evaluate, format: :json, attrs: "e", params: "[0]" expect(response.body).to match(/"error":"Malformed params"/) end it 'returns engine/tag lookup error if script not found' do get :evaluate, format: :json, script: 'M1', attrs: "e", tag: 'invalid' expect(response.body).to match(/"error":"Can't get engine:/) get :evaluate, format: :json, script: 'Invalid', attrs: "e", tag: t1.name expect(response.body).to match(/"error":"Can't get engine:/) end it 'returns the script runtime error (no node specified)' do get :evaluate, format: :json, script: 'M1', attrs: "e", tag: t1.name expect(response.body).to match(/"error":"bad node/) end end end