smarts_api 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ *.rbc
2
+ *.sassc
3
+ .sass-cache
4
+ capybara-*.html
5
+ .rspec
6
+ /.bundle
7
+ /vendor/bundle
8
+ /log/*
9
+ /tmp/*
10
+ /db/*.sqlite3
11
+ /public/system/*
12
+ /coverage/
13
+ /spec/tmp/*
14
+ **.orig
15
+ rerun.txt
16
+ pickle-email-*.html
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in smarts_api.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,46 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ smarts_api (0.0.1)
5
+ active_support
6
+ typhoeus
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ active_support (3.0.0)
12
+ activesupport (= 3.0.0)
13
+ activesupport (3.0.0)
14
+ addressable (2.3.5)
15
+ crack (0.4.1)
16
+ safe_yaml (~> 0.9.0)
17
+ diff-lcs (1.2.4)
18
+ ethon (0.6.1)
19
+ ffi (>= 1.3.0)
20
+ mime-types (~> 1.18)
21
+ ffi (1.9.0)
22
+ mime-types (1.25)
23
+ rspec (2.14.1)
24
+ rspec-core (~> 2.14.0)
25
+ rspec-expectations (~> 2.14.0)
26
+ rspec-mocks (~> 2.14.0)
27
+ rspec-core (2.14.5)
28
+ rspec-expectations (2.14.2)
29
+ diff-lcs (>= 1.1.3, < 2.0)
30
+ rspec-mocks (2.14.3)
31
+ safe_yaml (0.9.5)
32
+ timecop (0.6.3)
33
+ typhoeus (0.6.5)
34
+ ethon (~> 0.6.1)
35
+ webmock (1.13.0)
36
+ addressable (>= 2.2.7)
37
+ crack (>= 0.3.2)
38
+
39
+ PLATFORMS
40
+ ruby
41
+
42
+ DEPENDENCIES
43
+ rspec (~> 2.14.1)
44
+ smarts_api!
45
+ timecop
46
+ webmock
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Steve Mitchell
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,7 @@
1
+ smarts_api
2
+ ==========
3
+
4
+ Ruby API for Sparkling Logic SMARTS
5
+
6
+
7
+ This project and it's creators, contributors, fans, and haters are in no way associated with Sparkling Logic, the innovator and creator of SMARTS.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,3 @@
1
+ class SmartsApi::Error < StandardError
2
+
3
+ end
@@ -0,0 +1,45 @@
1
+ class SmartsApi::ConnectMessage < SmartsApi::Message
2
+
3
+ def send
4
+ logger.info "Connecting to #{uri}" if logger.respond_to?(:info)
5
+ response = Typhoeus::Request.post(uri,
6
+ :method => method,
7
+ :headers => {:Accept => "text/json"},
8
+ :body => make_form(request_params)
9
+ )
10
+ raise SmartsApi::Error, "Service connection failed. Recieved empty reply" if response.nil? || response.body.blank?
11
+ reply = JSON.parse(response.body)
12
+
13
+ raise SmartsApi::Error, "Connection failed. Received malformed response." unless reply["Header"] && reply["Header"]["SessionId"]
14
+
15
+ session = reply["Header"]["SessionId"]
16
+ raise SmartsApi::Error, "Connection failed. Did not receive session ID" if session == "00000000-0000-0000-0000-000000000000"
17
+
18
+ return session
19
+ end
20
+
21
+ def request_document
22
+ {:OperationId =>1 , :Header => {:DeploymentId => project_id}}.to_json
23
+ end
24
+
25
+ def request_params
26
+ params = {
27
+ :appId => app_id,
28
+ :pwd => pwd,
29
+ :reqData => request_document,
30
+ :reqTime => self.timestamp,
31
+ :userId => user_id,
32
+ :workspaceId => workspace_id
33
+ }
34
+ signature = {
35
+ :sign => sign_request(params)
36
+ }
37
+ return params.merge(signature)
38
+ end
39
+
40
+ def uri
41
+ "#{base_uri}connect"
42
+ end
43
+
44
+ end
45
+
@@ -0,0 +1,39 @@
1
+ class SmartsApi::DisconnectMessage < SmartsApi::Message
2
+
3
+
4
+ def send(session)
5
+ body = make_form(request_params(session))
6
+ logger.info "Disconnecting" if logger.respond_to?(:info)
7
+ response = Typhoeus::Request.post(uri,
8
+ :method => method,
9
+ :headers => {:Accept => "text/json"},
10
+ :body => body
11
+ )
12
+ raise SmartsApi::Error, "Service connection failed. Recieved empty reply" if response.nil? || response.body.blank?
13
+ reply = JSON.parse(response.body)
14
+
15
+ raise SmartsApi::Error, "Connection failed. Received malformed response." unless reply["Header"] && reply["Header"]["SessionId"]
16
+
17
+ session = reply["Header"]["SessionId"]
18
+ raise SmartsApi::Error, "Connection failed. Did not receive session ID" if session == "00000000-0000-0000-0000-000000000000"
19
+ return session
20
+ end
21
+
22
+ def request_params(session)
23
+ params = {
24
+ :appId => app_id,
25
+ :reqTime => timestamp,
26
+ :session => session
27
+ }
28
+ signature = {
29
+ :sign => sign_request(params)
30
+ }
31
+ return params.merge(signature)
32
+ end
33
+
34
+ def uri
35
+ "#{base_uri}disconnect"
36
+ end
37
+
38
+
39
+ end
@@ -0,0 +1,51 @@
1
+ class SmartsApi::EvaluateMessage < SmartsApi::Message
2
+
3
+ def send(session, member, decision)
4
+ body = make_form(request_params(session, member, decision))
5
+ logger.info "Evaluating" if logger.respond_to?(:info)
6
+ response = Typhoeus::Request.post(uri,
7
+ :method => method,
8
+ :headers => {:Accept => "text/json"},
9
+ :body => body
10
+ )
11
+ raise SmartsApi::Error, "Rules Evaluation failed failed. Recieved empty reply" if response.nil? || response.body.blank?
12
+ reply = JSON.parse(response.body)
13
+
14
+ raise SmartsApi::Error, "Rules Evaluation failed failed. Received malformed response." unless reply["Header"] && ["Body"]
15
+
16
+ body = reply["Body"]
17
+
18
+ if body.blank?
19
+ logger.info "Rules Engine Evaluation failed. \n\n #{body} \n\n #{response.body}" if logger.respond_to?(:info)
20
+
21
+ raise SmartsApi::Error, "Rules Evaluation failed. Returned JSON is blank."
22
+ end
23
+
24
+ logger.info "Updating issues" if logger.respond_to?(:info)
25
+ member.process_smarts_response body
26
+ return body
27
+ end
28
+
29
+ def request_params(session, member, decision)
30
+ params = {
31
+ :appId => app_id,
32
+ :reqData => request_document(session, member, decision),
33
+ :reqTime => timestamp,
34
+ :session => session
35
+ }
36
+ signature = {
37
+ :sign => sign_request(params)
38
+ }
39
+ return params.merge(signature)
40
+ end
41
+
42
+ def request_document(session, member, decision)
43
+ doc = {:OperationId =>1 , :Header => {:SessionId => session, :DecisionId => decision}, :Body => {:Documents => []}}
44
+ doc[:Body][:Documents] = member.smarts_document
45
+ return doc.to_json
46
+ end
47
+
48
+ def uri
49
+ "#{base_uri}evaluate"
50
+ end
51
+ end
@@ -0,0 +1,81 @@
1
+ require 'typhoeus'
2
+ require 'json'
3
+ require 'active_support/core_ext/class'
4
+ require 'active_support/core_ext/array'
5
+ include ERB::Util
6
+ require 'openssl'
7
+ require 'base64'
8
+
9
+ class SmartsApi::Message
10
+ attr_reader :logger
11
+ cattr_accessor :user_id, :pwd, :app_id, :workspace_id, :access_key, :base_uri, :project_id
12
+
13
+
14
+ def initialize(logger=nil)
15
+ @logger = logger
16
+ end
17
+
18
+ def method
19
+ :post
20
+ end
21
+
22
+ def uri
23
+ base_uri
24
+ end
25
+
26
+ def sign_request(params)
27
+ #Below you will find the reverse-engineered ruby implementation of Sparkling Logic's security signing algorithm.
28
+ #this was translated from a javascript library on their documentation site, then
29
+
30
+ #The params object include the following parameters, in this order: AppId, pwd, reqData, reqTime, userID, and workspaceID.
31
+ #naming of the parameters is significant, and case-sensitive.
32
+
33
+ #combine the parameters into a long encoded string (again, order of elements is critical)
34
+ encoded_parts = []
35
+ http = URI.parse(uri)
36
+ encoded_parts[0] = method.to_s.upcase
37
+ encoded_parts[1] = http.host
38
+ encoded_parts[2] = http.request_uri
39
+ encoded_parts[3] = encode_hash(params)
40
+
41
+ encoded = encoded_parts.join("\n") #Join parts with newline character
42
+
43
+ #5 Digest the encode string with SHA256, using the pre-shared AccessKey as the digest key.
44
+ digest = OpenSSL::Digest::Digest.new('sha256')
45
+ hash = OpenSSL::HMAC.hexdigest(digest, access_key, encoded)
46
+
47
+ #6 OpenSSL::Digest correctly translates the Hash to a string of bytes. Sparkling Logic's algorithm ...unexpectedle converts the hex value to the corresponding ascii code.
48
+ #So we need to iterate each pair in our byte string and convert to ascii. this is a little ugly, but necessary
49
+ #7 AND THEN we encode that string to base64. Not sure why, exactly...
50
+ signature = Base64.encode64(hex_string_to_ascii(hash))
51
+ #8 re-URL_encode the string and remove line-endings. Again, for absolutely no logical reason. I think the javascript version did this automatically.
52
+ signature = url_encode(signature)
53
+ signature = signature.gsub("%0A", "" )
54
+ return signature
55
+
56
+ end
57
+
58
+
59
+ def hex_string_to_ascii hex_str
60
+ #we need to iterate our hex-string and convert each pair to a hex-number. Then evaluate that as ascii code and replace with the corresponding ascii character.
61
+ ascii_str = ''
62
+ hex_str.split('').in_groups_of(2){|c| ascii_str << (c[0]+c[1]).hex.chr }
63
+ ascii_str
64
+ end
65
+
66
+ def timestamp
67
+ Time.now.utc.iso8601.to_s # => "2012-06-21T18:15:09Z"
68
+ end
69
+
70
+ def encode_hash(hash)
71
+ new = []
72
+ hash.each do |k,v|
73
+ new << k.to_s+"="+url_encode(v)
74
+ end
75
+ return new.join("&")
76
+ end
77
+
78
+ def make_form(request_params)
79
+ request_params.map{|k,v| "#{k}=#{v}"}.join("&")
80
+ end
81
+ end
@@ -0,0 +1,5 @@
1
+ module SmartsApi
2
+ module Version # :nodoc:
3
+ STRING = '0.0.1'
4
+ end
5
+ end
data/lib/smarts_api.rb ADDED
@@ -0,0 +1,17 @@
1
+
2
+ module SmartsApi
3
+ Dir[File.expand_path('../../lib/smarts_api/*.rb', __FILE__)].each {|f| require f}
4
+ Dir[File.expand_path('../../lib/smarts_api/*/*.rb', __FILE__)].each {|f| require f}
5
+
6
+ def self.evaluate(decision, obj, logger = nil)
7
+ raise SmartsApi::Error.new("Object to be evaluated must define a method 'smarts_document'") unless obj.respond_to?(:smarts_document)
8
+ logger.info "processing request for #{obj}" if logger.respond_to?(:info)
9
+
10
+ session = SmartsApi::ConnectMessage.new(logger).send
11
+ response = SmartsApi::EvaluateMessage.new(logger).
12
+ send(session, obj, decision)
13
+
14
+ #SmartsApi::DisconnectMessage.new(logger).disconnect(session)
15
+ end
16
+
17
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/smarts_api/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Steve Mitchell"]
6
+ gem.email = ["theSteveMitchell@gmail.com"]
7
+ gem.description = "smarts_api-#{SmartsApi::Version::STRING}"
8
+ gem.summary = "api gem for Sparkling Logic's SMARTS business logic platform"
9
+ gem.homepage = "http://github.com/theSteveMitchell/smarts_api"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "smarts_api"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = SmartsApi::Version::STRING
17
+
18
+ gem.add_dependency('typhoeus')
19
+ gem.add_dependency('activesupport')
20
+
21
+ gem.add_development_dependency('rspec', '~> 2.14.1')
22
+ gem.add_development_dependency('webmock')
23
+ gem.add_development_dependency('timecop')
24
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe SmartsApi::ConnectMessage do
4
+ before (:all) do
5
+ SmartsApi::Message.base_uri = "http://smarts.dev.thismashine.com/"
6
+ SmartsApi::Message.access_key = "sshhhh...Secret!"
7
+ end
8
+
9
+ it 'should return session ID if successful' do
10
+
11
+ stub_http_request(:post, "#{SmartsApi::Message.base_uri}connect")
12
+ .to_return(:status => 200, :body => "{\"Header\":{\"SessionId\":\"487d2c44-43fe-44d3-988f-ea462af03169\"},\"Body\":null,\"ErrorInfo\":null,\"Metrics\":null,\"Success\":true,\"OperationException\":null}")
13
+
14
+
15
+ SmartsApi::ConnectMessage.new().send.should == "487d2c44-43fe-44d3-988f-ea462af03169"
16
+
17
+ end
18
+
19
+ it 'should throw an error if the connection returns an error' do
20
+
21
+ stub_http_request(:post, "#{SmartsApi::Message.base_uri}connect")
22
+ .to_return(:status => 200, :body => "{\"OperationId\":0,\"Header\":{\"SessionId\":\"00000000-0000-0000-0000-000000000000\",\"TransactionTime\":\"2012-06-22T21:02:16.642625Z\",\"Workspace\":null,\"DeploymentId\":null,\"DecisionId\":null},\"Body\":null,\"ErrorInfo\":{\"ErrorCode\":\"ServerException\",\"ErrorMessage\":\"Exception during connection\",\"Details\":[\"Invalid API access\"]},\"Metrics\":null,\"Success\":false,\"OperationException\":{\"IsRestException\":true,\"ErrorType\":\"DocApiAccessDeniedException\",\"CompleteStackTrace\":\"Type: DocApiAccessDeniedException\\r\\nMessage: Invalid API access\\r\\nStack Trace:\\r\\n at Splog.Rest.Base.DocRestHttpHandler.VerifyHmacSignature(String method, IEnumerable`1 keys, String signature, String[] paramaters)\\r\\n at Splog.Rest.Decisions.DocRestDecisionService.Connect(String appId, String reqTime, String userId, String pwd, String workspaceId, String reqData, String sign)\\r\\n\\r\\n\",\"ExtraInfo\":null,\"Message\":\"[DecisionServer] Exception connecting to the decision server for session 72950db0-9639-477b-b824-b82ad5122b56\",\"Data\":{}}}")
23
+
24
+ expect{SmartsApi::ConnectMessage.new().send}.to raise_error(SmartsApi::Error)
25
+
26
+ end
27
+
28
+ it 'should throw an error if the returned sessionID is empty' do
29
+
30
+ stub_http_request(:post, "#{SmartsApi::Message.base_uri}connect")
31
+ .to_return(:status => 200, :body => "{\"OperationId\":0,\"Header\":{\"SessionId\":\"00000000-0000-0000-0000-000000000000\",\"TransactionTime\":\"2012-06-22T21:02:16.642625Z\",\"Workspace\":null,\"DeploymentId\":null,\"DecisionId\":null},\"Body\":null,\"ErrorInfo\":{\"ErrorCode\":\"ServerException\",\"ErrorMessage\":\"Exception during connection\",\"Details\":[\"Invalid API access\"]},\"Metrics\":null,\"Success\":false,\"OperationException\":{\"IsRestException\":true,\"ErrorType\":\"DocApiAccessDeniedException\",\"CompleteStackTrace\":\"Type: DocApiAccessDeniedException\\r\\nMessage: Invalid API access\\r\\nStack Trace:\\r\\n at Splog.Rest.Base.DocRestHttpHandler.VerifyHmacSignature(String method, IEnumerable`1 keys, String signature, String[] paramaters)\\r\\n at Splog.Rest.Decisions.DocRestDecisionService.Connect(String appId, String reqTime, String userId, String pwd, String workspaceId, String reqData, String sign)\\r\\n\\r\\n\",\"ExtraInfo\":null,\"Message\":\"[DecisionServer] Exception connecting to the decision server for session 72950db0-9639-477b-b824-b82ad5122b56\",\"Data\":{}}}")
32
+
33
+ expect{SmartsApi::ConnectMessage.new().send}.to raise_error(SmartsApi::Error)
34
+
35
+ end
36
+
37
+ it 'should throw an error if no body is returned' do
38
+
39
+ stub_http_request(:post, "#{SmartsApi::Message.base_uri}connect")
40
+ .to_return(:status => 200)
41
+ expect{SmartsApi::ConnectMessage.new().send}.to raise_error(SmartsApi::Error)
42
+
43
+ end
44
+
45
+ it 'should throw an error if status code is bad' do
46
+
47
+ stub_http_request(:post, "#{SmartsApi::Message.base_uri}connect")
48
+ .to_return(:status => 500)
49
+ expect{SmartsApi::ConnectMessage.new().send}.to raise_error(SmartsApi::Error)
50
+
51
+ end
52
+
53
+
54
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe SmartsApi::DisconnectMessage do
5
+ it 'should return session ID if successful' do
6
+
7
+ stub_http_request(:post, "#{SmartsApi::Message.base_uri}disconnect")
8
+ .to_return(:status => 200, :body => "{\"Header\":{\"SessionId\":\"487d2c44-43fe-44d3-988f-ea462af03169\"},\"Body\":null,\"ErrorInfo\":null,\"Metrics\":null,\"Success\":true,\"OperationException\":null}")
9
+
10
+
11
+ SmartsApi::DisconnectMessage.new().send("487d2c44-43fe-44d3-988f-ea462af03169").should == "487d2c44-43fe-44d3-988f-ea462af03169"
12
+
13
+ end
14
+
15
+ it 'should throw an error if the connection returns an error' do
16
+
17
+ stub_http_request(:post, "#{SmartsApi::Message.base_uri}disconnect")
18
+ .to_return(:status => 200, :body => "{\"OperationId\":0,\"Header\":{\"SessionId\":\"00000000-0000-0000-0000-000000000000\",\"TransactionTime\":\"2012-06-22T21:02:16.642625Z\",\"Workspace\":null,\"DeploymentId\":null,\"DecisionId\":null},\"Body\":null,\"ErrorInfo\":{\"ErrorCode\":\"ServerException\",\"ErrorMessage\":\"Exception during connection\",\"Details\":[\"Invalid API access\"]},\"Metrics\":null,\"Success\":false,\"OperationException\":{\"IsRestException\":true,\"ErrorType\":\"DocApiAccessDeniedException\",\"CompleteStackTrace\":\"Type: DocApiAccessDeniedException\\r\\nMessage: Invalid API access\\r\\nStack Trace:\\r\\n at Splog.Rest.Base.DocRestHttpHandler.VerifyHmacSignature(String method, IEnumerable`1 keys, String signature, String[] paramaters)\\r\\n at Splog.Rest.Decisions.DocRestDecisionService.Connect(String appId, String reqTime, String userId, String pwd, String workspaceId, String reqData, String sign)\\r\\n\\r\\n\",\"ExtraInfo\":null,\"Message\":\"[DecisionServer] Exception connecting to the decision server for session 72950db0-9639-477b-b824-b82ad5122b56\",\"Data\":{}}}")
19
+
20
+ expect{SmartsApi::DisconnectMessage.new().send("487d2c44-43fe-44d3-988f-ea462af03169")}.to raise_error(SmartsApi::Error)
21
+
22
+ end
23
+
24
+ it 'should throw an error if the returned sessionID is empty returns an error' do
25
+
26
+ stub_http_request(:post, "#{SmartsApi::Message.base_uri}disconnect")
27
+ .to_return(:status => 200, :body => "{\"OperationId\":0,\"Header\":{\"SessionId\":\"00000000-0000-0000-0000-000000000000\",\"TransactionTime\":\"2012-06-22T21:02:16.642625Z\",\"Workspace\":null,\"DeploymentId\":null,\"DecisionId\":null},\"Body\":null,\"ErrorInfo\":{\"ErrorCode\":\"ServerException\",\"ErrorMessage\":\"Exception during connection\",\"Details\":[\"Invalid API access\"]},\"Metrics\":null,\"Success\":false,\"OperationException\":{\"IsRestException\":true,\"ErrorType\":\"DocApiAccessDeniedException\",\"CompleteStackTrace\":\"Type: DocApiAccessDeniedException\\r\\nMessage: Invalid API access\\r\\nStack Trace:\\r\\n at Splog.Rest.Base.DocRestHttpHandler.VerifyHmacSignature(String method, IEnumerable`1 keys, String signature, String[] paramaters)\\r\\n at Splog.Rest.Decisions.DocRestDecisionService.Connect(String appId, String reqTime, String userId, String pwd, String workspaceId, String reqData, String sign)\\r\\n\\r\\n\",\"ExtraInfo\":null,\"Message\":\"[DecisionServer] Exception connecting to the decision server for session 72950db0-9639-477b-b824-b82ad5122b56\",\"Data\":{}}}")
28
+
29
+ expect{SmartsApi::DisconnectMessage.new().send("487d2c44-43fe-44d3-988f-ea462af03169")}.to raise_error(SmartsApi::Error)
30
+
31
+ end
32
+
33
+ it 'should throw an error if no body is returned' do
34
+
35
+ stub_http_request(:post, "#{SmartsApi::Message.base_uri}disconnect")
36
+ .to_return(:status => 200)
37
+ expect{SmartsApi::DisconnectMessage.new().send("487d2c44-43fe-44d3-988f-ea462af03169")}.to raise_error(SmartsApi::Error)
38
+
39
+ end
40
+
41
+ it 'should throw an error if status code is bad' do
42
+ stub_http_request(:post, "#{SmartsApi::Message.base_uri}disconnect")
43
+ .to_return(:status => 500)
44
+ expect{SmartsApi::DisconnectMessage.new().send("487d2c44-43fe-44d3-988f-ea462af03169")}.to raise_error(SmartsApi::Error)
45
+
46
+ end
47
+
48
+
49
+ end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ describe SmartsApi::EvaluateMessage do
4
+
5
+ before (:all) do
6
+ SmartsApi::Message.base_uri = "http://smarts.dev.thismashine.com/"
7
+ SmartsApi::Message.access_key = "sshhhh...Secret!"
8
+ end
9
+
10
+ let(:eval_class) {
11
+ Class.new() do
12
+
13
+ def smarts_document
14
+ "123"
15
+ end
16
+
17
+ def process_smarts_response(body)
18
+
19
+ end
20
+ end
21
+ }
22
+
23
+ let(:eval_object) {
24
+ eval_class.new()
25
+ }
26
+
27
+ it 'should return session ID if successful' do
28
+
29
+ body = "{\"OperationId\":1,\"Header\":{\"SessionId\":\"e3e2b012-e9b6-45e7-a96a-a009ebf0a07a\"},\"Body\":{\"Documents\":[{\"identification\":{\"actor_group\":\"root\",\"actor_id\":3,\"gender\":\"Male\",\"birth_date\":\"1984-12-08T06:00:00Z\"},\"historical_properties\":[{\"weight\":\"150\"}]}]},\"ErrorInfo\":null,\"Metrics\":null,\"Success\":true,\"OperationException\":null}"
30
+
31
+ stub_http_request(:post, "#{SmartsApi::Message.base_uri}evaluate")
32
+ .to_return(:status => 200, :body => body)
33
+ SmartsApi::EvaluateMessage.new().send("487d2c44-43fe-44d3-988f-ea462af03169", eval_object, "Issues Analysis Decision").should == JSON.parse(body)["Body"]
34
+
35
+ end
36
+
37
+ it 'should throw an error if the connection returns an error' do
38
+ stub_http_request(:post, "#{SmartsApi::Message.base_uri}evaluate")
39
+ .to_return(:status => 200, :body => "{\"OperationId\":0,\"Header\":{\"SessionId\":\"00000000-0000-0000-0000-000000000000\",\"TransactionTime\":\"2012-06-22T21:02:16.642625Z\",\"Workspace\":null,\"DeploymentId\":null,\"DecisionId\":null},\"Body\":null,\"ErrorInfo\":{\"ErrorCode\":\"ServerException\",\"ErrorMessage\":\"Exception during connection\",\"Details\":[\"Invalid API access\"]},\"Metrics\":null,\"Success\":false,\"OperationException\":{\"IsRestException\":true,\"ErrorType\":\"DocApiAccessDeniedException\",\"CompleteStackTrace\":\"Type: DocApiAccessDeniedException\\r\\nMessage: Invalid API access\\r\\nStack Trace:\\r\\n at Splog.Rest.Base.DocRestHttpHandler.VerifyHmacSignature(String method, IEnumerable`1 keys, String signature, String[] paramaters)\\r\\n at Splog.Rest.Decisions.DocRestDecisionService.Connect(String appId, String reqTime, String userId, String pwd, String workspaceId, String reqData, String sign)\\r\\n\\r\\n\",\"ExtraInfo\":null,\"Message\":\"[DecisionServer] Exception connecting to the decision server for session 72950db0-9639-477b-b824-b82ad5122b56\",\"Data\":{}}}")
40
+ expect{SmartsApi::EvaluateMessage.new().send("487d2c44-43fe-44d3-988f-ea462af03169",eval_object, "Issues Analysis Decision")}.to raise_error(SmartsApi::Error)
41
+
42
+ end
43
+
44
+ it 'should throw an error if the returned sessionID is empty returns an error' do
45
+
46
+ stub_http_request(:post, "#{SmartsApi::Message.base_uri}evaluate")
47
+ .to_return(:status => 200, :body => "{\"OperationId\":0,\"Header\":{\"SessionId\":\"00000000-0000-0000-0000-000000000000\",\"TransactionTime\":\"2012-06-22T21:02:16.642625Z\",\"Workspace\":null,\"DeploymentId\":null,\"DecisionId\":null},\"Body\":null,\"ErrorInfo\":{\"ErrorCode\":\"ServerException\",\"ErrorMessage\":\"Exception during connection\",\"Details\":[\"Invalid API access\"]},\"Metrics\":null,\"Success\":false,\"OperationException\":{\"IsRestException\":true,\"ErrorType\":\"DocApiAccessDeniedException\",\"CompleteStackTrace\":\"Type: DocApiAccessDeniedException\\r\\nMessage: Invalid API access\\r\\nStack Trace:\\r\\n at Splog.Rest.Base.DocRestHttpHandler.VerifyHmacSignature(String method, IEnumerable`1 keys, String signature, String[] paramaters)\\r\\n at Splog.Rest.Decisions.DocRestDecisionService.Connect(String appId, String reqTime, String userId, String pwd, String workspaceId, String reqData, String sign)\\r\\n\\r\\n\",\"ExtraInfo\":null,\"Message\":\"[DecisionServer] Exception connecting to the decision server for session 72950db0-9639-477b-b824-b82ad5122b56\",\"Data\":{}}}")
48
+ expect{SmartsApi::EvaluateMessage.new().send("487d2c44-43fe-44d3-988f-ea462af03169",eval_object, "Issues Analysis Decision")}.to raise_error(SmartsApi::Error)
49
+
50
+ end
51
+
52
+ it 'should throw an error if no body is returned' do
53
+
54
+ stub_http_request(:post, "#{SmartsApi::Message.base_uri}evaluate")
55
+ .to_return(:status => 200)
56
+ expect{SmartsApi::EvaluateMessage.new().send("487d2c44-43fe-44d3-988f-ea462af03169",eval_object, "Issues Analysis Decision")}.to raise_error(SmartsApi::Error)
57
+
58
+ end
59
+
60
+ it 'should throw an error if status code is bad' do
61
+
62
+ stub_http_request(:post, "#{SmartsApi::Message.base_uri}evaluate")
63
+ .to_return(:status => 500)
64
+ expect{SmartsApi::EvaluateMessage.new().send("487d2c44-43fe-44d3-988f-ea462af03169",eval_object, "Issues Analysis Decision")}.to raise_error(SmartsApi::Error)
65
+
66
+ end
67
+
68
+ it 'should call the document method on the member object' do
69
+ eval_object.should_receive(:smarts_document)
70
+ body = "{\"OperationId\":1,\"Header\":{\"SessionId\":\"e3e2b012-e9b6-45e7-a96a-a009ebf0a07a\"},\"Body\":{\"Documents\":[{\"identification\":{\"actor_group\":\"root\",\"actor_id\":3,\"gender\":\"Male\",\"birth_date\":\"1984-12-08T06:00:00Z\"},\"historical_properties\":[{\"weight\":\"150\"}]}]},\"ErrorInfo\":null,\"Metrics\":null,\"Success\":true,\"OperationException\":null}"
71
+
72
+ stub_http_request(:post, "#{SmartsApi::Message.base_uri}evaluate")
73
+ .to_return(:status => 200, :body => body)
74
+ SmartsApi::EvaluateMessage.new().send("487d2c44-43fe-44d3-988f-ea462af03169", eval_object, "Issues Analysis Decision").should == JSON.parse(body)["Body"]
75
+ end
76
+
77
+ it "should pass the returned message body to the evaluated object for processing" do
78
+
79
+ body = "{\"OperationId\":1,\"Header\":{\"SessionId\":\"e3e2b012-e9b6-45e7-a96a-a009ebf0a07a\"},\"Body\":{\"Documents\":[{\"identification\":{\"actor_group\":\"root\",\"actor_id\":3,\"gender\":\"Male\",\"birth_date\":\"1984-12-08T06:00:00Z\"},\"historical_properties\":[{\"weight\":\"150\"}]}]},\"ErrorInfo\":null,\"Metrics\":null,\"Success\":true,\"OperationException\":null}"
80
+ eval_object.should_receive(:process_smarts_response).with(JSON.parse(body)["Body"])
81
+ stub_http_request(:post, "#{SmartsApi::Message.base_uri}evaluate")
82
+ .to_return(:status => 200, :body => body)
83
+ SmartsApi::EvaluateMessage.new().send("487d2c44-43fe-44d3-988f-ea462af03169", eval_object, "Issues Analysis Decision").should == JSON.parse(body)["Body"]
84
+ end
85
+
86
+ end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ describe SmartsApi::Message do
4
+
5
+ before (:all) do
6
+ SmartsApi::Message.base_uri = "http://www.versign.com/request/doSomething.aspc"
7
+ SmartsApi::Message.access_key = "secretKey"
8
+ end
9
+
10
+ describe 'initializer' do
11
+ it "should keep the logger for logging purposes" do
12
+ logger = Logger.new(STDOUT)
13
+ message = SmartsApi::Message.new(logger)
14
+
15
+ message.logger.should == logger
16
+
17
+ end
18
+ end
19
+
20
+ describe 'timestamp' do
21
+ it "should be formatted correctly" do
22
+ Timecop.freeze(Time.utc(2012, 6, 21, 12, 34, 56))
23
+ SmartsApi::Message.new().timestamp.should == "2012-06-21T12:34:56Z"
24
+ Timecop.return
25
+
26
+ end
27
+ end
28
+
29
+ describe 'hash encoding' do
30
+ it 'should convert a hash into a url_encoded string' do
31
+ hash = {:a => "123", :b => "aber=/3sd8:&++\\", :c => "hellpo"}
32
+
33
+ SmartsApi::Message.new().encode_hash(hash).should == "a=123&b=aber%3D%2F3sd8%3A%26%2B%2B%5C&c=hellpo"
34
+
35
+ end
36
+
37
+ end
38
+
39
+ describe 'ascii conversion' do
40
+ it "should convert a hex string to ascii in the way the service expects" do
41
+ #even if it's wrong...'
42
+
43
+ SmartsApi::Message.new().hex_string_to_ascii("3D").should == "="
44
+ SmartsApi::Message.new().hex_string_to_ascii("0B").should == "\v"
45
+ SmartsApi::Message.new().hex_string_to_ascii("52").should == "R"
46
+
47
+ hex = "52410baf20302e9e2c31b5ecea597348aa3df3067c58cf35cb5bd94273dc1de0"
48
+ SmartsApi::Message.new().hex_string_to_ascii(hex)
49
+ .should == "RA\v\xAF 0.\x9E,1\xB5\xEC\xEAYsH\xAA=\xF3\x06|X\xCF5\xCB[\xD9Bs\xDC\x1D\xE0"
50
+ end
51
+
52
+
53
+ end
54
+
55
+ #Notes on signing: The signing algorithm for Sparkling Logic is very brittle and depends on ALL of the
56
+ #values in sparkling_logic.yml, as well as timestamp. The below tests will fail if any of those settings change.
57
+ #so if the signature does not match the expected, and one of those settings did change, we can update the expected sig.
58
+ describe 'signing' do
59
+ it "should create a correct signature when no params supplied" do
60
+ Timecop.freeze(Time.utc(2012, 6, 21, 12, 34, 56))
61
+
62
+ SmartsApi::Message.new().sign_request({})
63
+ .should == "dOqhdTRLwMSUBSO7HZDRTsD63fVVA%2FKnffCn3DuaRnE%3D"
64
+ Timecop.return
65
+ end
66
+
67
+ it "should create a correct signature when params supplied" do
68
+ Timecop.freeze(Time.utc(2012, 6, 21, 12, 34, 56))
69
+ params = {:param1 => "nothing", :param2 => "nothing More"}
70
+
71
+ SmartsApi::Message.new().sign_request(params)
72
+ .should == "EIvlfFrm7FpotCVp6MIt76P6bz%2BUKWbVaO7pAIclyd8%3D"
73
+ Timecop.return
74
+ end
75
+
76
+ end
77
+
78
+
79
+
80
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe SmartsApi do
4
+
5
+ describe "evaluate" do
6
+
7
+ before (:all) do
8
+ SmartsApi::Message.base_uri = "http://smarts.dev.thismashine.com/"
9
+ SmartsApi::Message.access_key = "sshhhh...Secret!"
10
+ end
11
+
12
+ describe "expectations on eval object" do
13
+
14
+ it 'should raise error and not call connect if obj is nil' do
15
+ expect{SmartsApi.evaluate(
16
+ "string", nil
17
+ )}.to raise_error(SmartsApi::Error, "Object to be evaluated must define a method 'smarts_document'")
18
+ end
19
+
20
+ it 'should raise error and not call connect if obj does not define the document method' do
21
+ RegularClass = Class.new() {}
22
+ instance = RegularClass.new
23
+ instance.should_not be_nil
24
+ expect{SmartsApi.evaluate(
25
+ "string", instance
26
+ )}.to raise_error(SmartsApi::Error, "Object to be evaluated must define a method 'smarts_document'")
27
+ end
28
+
29
+ it 'should accept any object that defines the document method' do
30
+ EvalClass= Class.new() do
31
+ def smarts_document
32
+ "EvalClass.Instance 1"
33
+ end
34
+ end
35
+
36
+ instance = EvalClass.new
37
+ instance.should_not be_nil
38
+
39
+ SmartsApi::ConnectMessage.any_instance.should_receive(:send).and_return("session 334")
40
+ SmartsApi::EvaluateMessage.any_instance.should_receive(:send)
41
+ SmartsApi.evaluate("string", instance)
42
+ end
43
+ end
44
+
45
+ describe "specifying decision string" do
46
+ it "should include the decision name and session when sending the evaluate message" do
47
+
48
+ instance = EvalClass.new
49
+ instance.should_not be_nil
50
+ SmartsApi::ConnectMessage.any_instance.should_receive(:send).and_return("session 3339")
51
+ SmartsApi::EvaluateMessage.any_instance.should_receive(:send).with("session 3339", instance, "Chosen_decision")
52
+ SmartsApi.evaluate("Chosen_decision", instance)
53
+ end
54
+ end
55
+ end
56
+
57
+
58
+ end
@@ -0,0 +1,3 @@
1
+ require 'webmock/rspec'
2
+ require 'timecop'
3
+ Dir[File.expand_path('../../lib/smarts_api.rb', __FILE__)].each {|f| require f}
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smarts_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Steve Mitchell
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-09-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: typhoeus
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: activesupport
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 2.14.1
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 2.14.1
62
+ - !ruby/object:Gem::Dependency
63
+ name: webmock
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: timecop
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: smarts_api-0.0.1
95
+ email:
96
+ - theSteveMitchell@gmail.com
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - .gitignore
102
+ - Gemfile
103
+ - Gemfile.lock
104
+ - LICENSE
105
+ - README.md
106
+ - Rakefile
107
+ - lib/smarts_api.rb
108
+ - lib/smarts_api/error.rb
109
+ - lib/smarts_api/message.rb
110
+ - lib/smarts_api/message/connect_message.rb
111
+ - lib/smarts_api/message/disconnect_message.rb
112
+ - lib/smarts_api/message/evaluate_message.rb
113
+ - lib/smarts_api/version.rb
114
+ - smarts_api.gemspec
115
+ - spec/smarts_api/message/connect_message_spec.rb
116
+ - spec/smarts_api/message/disconnect_message_spec.rb
117
+ - spec/smarts_api/message/evaluate_message_spec.rb
118
+ - spec/smarts_api/message_spec.rb
119
+ - spec/smarts_api_spec.rb
120
+ - spec/spec_helper.rb
121
+ homepage: http://github.com/theSteveMitchell/smarts_api
122
+ licenses: []
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ! '>='
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ none: false
135
+ requirements:
136
+ - - ! '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 1.8.24
142
+ signing_key:
143
+ specification_version: 3
144
+ summary: api gem for Sparkling Logic's SMARTS business logic platform
145
+ test_files:
146
+ - spec/smarts_api/message/connect_message_spec.rb
147
+ - spec/smarts_api/message/disconnect_message_spec.rb
148
+ - spec/smarts_api/message/evaluate_message_spec.rb
149
+ - spec/smarts_api/message_spec.rb
150
+ - spec/smarts_api_spec.rb
151
+ - spec/spec_helper.rb