geti 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +78 -0
- data/Guardfile +4 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +22 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/config/test_credentials.yml.example +2 -0
- data/doc/.DS_Store +0 -0
- data/doc/Application Gateway Doc.pdf +0 -0
- data/doc/Auth Gateway Doc.pdf +0 -0
- data/doc/Auth Gateway Implementation Guide.pdf +0 -0
- data/doc/implementation_notes.md +29 -0
- data/doc/readme.txt +28 -0
- data/lib/geti.rb +7 -0
- data/lib/geti/app_client.rb +277 -0
- data/lib/geti/auth_client.rb +133 -0
- data/lib/geti/client.rb +42 -0
- data/lib/geti/response.rb +24 -0
- data/lib/geti/terminal_settings.rb +7 -0
- data/spec/geti_app_client_spec.rb +166 -0
- data/spec/geti_auth_client_spec.rb +28 -0
- data/spec/helper.rb +37 -0
- data/spec/remote/geti_app_client_spec.rb +50 -0
- data/spec/remote/geti_auth_client_spec.rb +99 -0
- metadata +193 -0
@@ -0,0 +1,133 @@
|
|
1
|
+
class Geti::AuthClient < Geti::Client
|
2
|
+
def initialize(auth, terminal_opts, env='test')
|
3
|
+
super
|
4
|
+
|
5
|
+
@sec_code = terminal_opts[:sec_code]
|
6
|
+
@verify_check = terminal_opts[:verify].include? :check
|
7
|
+
@verify_id = terminal_opts[:verify].include? :identity
|
8
|
+
@dl_required = terminal_opts[:verify].include? :dl
|
9
|
+
end
|
10
|
+
|
11
|
+
# Loads terminal settings for the configured terminal. Returns a
|
12
|
+
# TerminalSettings object that can be used to confirm requested
|
13
|
+
# verification features, terminal ID, and XSD/XML templates.
|
14
|
+
def get_terminal_settings
|
15
|
+
Geti::TerminalSettings.new(soap_request("GetCertificationTerminalSettings"))
|
16
|
+
end
|
17
|
+
|
18
|
+
# Used to verify that an XML request is valid for use on the
|
19
|
+
# terminal. The returned Result will have a validation response
|
20
|
+
# but no authorization.
|
21
|
+
# NOTE: CERTIFICATION SERVER ONLY
|
22
|
+
def validate(opts)
|
23
|
+
response = soap_request("AuthGatewayCertification") do |xml|
|
24
|
+
data_packet(xml, opts)
|
25
|
+
end
|
26
|
+
Geti::Response.new(response)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Creates an authorization for funds transfer. Returns a Result
|
30
|
+
# with both validation and (if valid) authorization responses.
|
31
|
+
def process(opts)
|
32
|
+
response = soap_request("ProcessSingleCertificationCheck") do |xml|
|
33
|
+
data_packet(xml, opts)
|
34
|
+
end
|
35
|
+
Geti::Response.new(response)
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def data_packet(xml, opts)
|
40
|
+
xml.AUTH_GATEWAY do # has an optional REQUEST_ID attribute for later lookups
|
41
|
+
xml.TRANSACTION do
|
42
|
+
xml.TRANSACTION_ID
|
43
|
+
xml.MERCHANT do
|
44
|
+
xml.TERMINAL_ID terminal_id
|
45
|
+
end
|
46
|
+
xml.PACKET do
|
47
|
+
xml.IDENTIFIER identifier(opts[:type])
|
48
|
+
xml.ACCOUNT do
|
49
|
+
xml.ROUTING_NUMBER opts[:routing_number]
|
50
|
+
xml.ACCOUNT_NUMBER opts[:account_number]
|
51
|
+
xml.ACCOUNT_TYPE opts[:account_type]
|
52
|
+
end
|
53
|
+
xml.CONSUMER do
|
54
|
+
xml.FIRST_NAME opts[:first_name]
|
55
|
+
xml.LAST_NAME opts[:last_name]
|
56
|
+
xml.ADDRESS1
|
57
|
+
xml.ADDRESS2
|
58
|
+
xml.CITY
|
59
|
+
xml.STATE
|
60
|
+
xml.ZIP
|
61
|
+
xml.PHONE_NUMBER
|
62
|
+
xml.DL_STATE
|
63
|
+
xml.DL_NUMBER
|
64
|
+
xml.COURTESY_CARD_ID
|
65
|
+
if @verify_id
|
66
|
+
xml.IDENTITY do
|
67
|
+
xml.SSN4
|
68
|
+
xml.DOB_YEAR
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
xml.CHECK do
|
73
|
+
xml.CHECK_AMOUNT("%.2d" % ((opts[:amount]||0)/100.0))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def identifier(name)
|
81
|
+
{ :authorize => 'A',
|
82
|
+
:void => 'V',
|
83
|
+
:override => 'O',
|
84
|
+
:payroll => 'P',
|
85
|
+
:recurring => 'R'
|
86
|
+
}[name]
|
87
|
+
end
|
88
|
+
|
89
|
+
def service_address
|
90
|
+
"https://demo.eftchecks.com/webservices/AuthGateway.asmx?WSDL"
|
91
|
+
end
|
92
|
+
|
93
|
+
def soap_header
|
94
|
+
{ "AuthGatewayHeader" => {
|
95
|
+
"UserName" => @user,
|
96
|
+
"Password" => @pass,
|
97
|
+
"TerminalID" => terminal_id.to_s
|
98
|
+
},
|
99
|
+
:attributes! => { 'AuthGatewayHeader' => {'xmlns'=>"http://tempuri.org/GETI.eMagnus.WebServices/AuthGateway"}}
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
def terminal_id
|
104
|
+
base = {
|
105
|
+
# Guaranteed
|
106
|
+
'PPD' => 1010, # Debit-only
|
107
|
+
'POP' => 1110,
|
108
|
+
'TEL' => 1210,
|
109
|
+
'C21' => 1610,
|
110
|
+
'CCD' => 1710, # Debit only
|
111
|
+
'PPD_cr' => 1810, # Debit and Credit
|
112
|
+
'CCD_cr' => 1910, # Debit and Credit
|
113
|
+
# Non-Guaranteed
|
114
|
+
'PPD_ng' => 2010,
|
115
|
+
'TEL_ng' => 2210,
|
116
|
+
'WEB' => 2310, # Web is always non-guaranteed
|
117
|
+
'CCD_ng' => 2710,
|
118
|
+
'PPD_ng_cr' => 2810,
|
119
|
+
'CCD_ng_cr' => 2910
|
120
|
+
}[@sec_code]
|
121
|
+
offset = {
|
122
|
+
[false, false, false] => 0,
|
123
|
+
[true, false, false] => 1,
|
124
|
+
[false, true, true ] => 2,
|
125
|
+
[true, true, true ] => 3,
|
126
|
+
[false, true, false] => 4,
|
127
|
+
[true, true, false] => 5,
|
128
|
+
[false, false, true ] => 6,
|
129
|
+
[true, false, true ] => 7
|
130
|
+
}[[@dl_required, @verify_check, @verify_id]]
|
131
|
+
base + offset
|
132
|
+
end
|
133
|
+
end
|
data/lib/geti/client.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'savon'
|
2
|
+
require 'httpi'
|
3
|
+
|
4
|
+
class Geti::Client
|
5
|
+
def initialize(auth, terminal_opts, env='test')
|
6
|
+
@user = auth[:user]
|
7
|
+
@pass = auth[:pass]
|
8
|
+
@env = env
|
9
|
+
end
|
10
|
+
|
11
|
+
def soap_client
|
12
|
+
@soap_client ||= Savon.client(service_address)
|
13
|
+
end
|
14
|
+
|
15
|
+
def soap_request(operation, op_key=nil)
|
16
|
+
operation.sub!('Certification','') unless certification?
|
17
|
+
response = soap_client.request operation do
|
18
|
+
http.headers.delete('SOAPAction')
|
19
|
+
config.soap_header = soap_header
|
20
|
+
xml = Builder::XmlMarkup.new
|
21
|
+
xml.instruct!
|
22
|
+
yield xml if block_given?
|
23
|
+
content = xml.target!
|
24
|
+
soap.body = {"DataPacket" => content}
|
25
|
+
end
|
26
|
+
|
27
|
+
op_key ||= operation.gsub(/(.)([A-Z])/, '\1_\2').downcase
|
28
|
+
operation.sub!('_certification','') unless certification?
|
29
|
+
response_key = (op_key+'_response').to_sym
|
30
|
+
result_key = (op_key+'_result').to_sym
|
31
|
+
|
32
|
+
xml_parser.parse(response.body[response_key][result_key])
|
33
|
+
end
|
34
|
+
|
35
|
+
def certification?
|
36
|
+
@env != 'production'
|
37
|
+
end
|
38
|
+
|
39
|
+
def xml_parser
|
40
|
+
@xml_parser or Nori
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
class Geti::Response
|
4
|
+
attr_reader :validation, :authorization, :exception
|
5
|
+
|
6
|
+
def initialize(response)
|
7
|
+
@validation = OpenStruct.new(response[:response][:validation_message])
|
8
|
+
@authorization = OpenStruct.new(response[:response][:authorization_message])
|
9
|
+
@exception = OpenStruct.new(response[:response][:exception])
|
10
|
+
end
|
11
|
+
|
12
|
+
def errors
|
13
|
+
err = []
|
14
|
+
Array(@validation.validation_error).each do |e|
|
15
|
+
err << e[:message]
|
16
|
+
end
|
17
|
+
err << @exception.message
|
18
|
+
err.compact
|
19
|
+
end
|
20
|
+
|
21
|
+
def success?
|
22
|
+
validation.result == "Passed"
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe Geti::AppClient do
|
4
|
+
def mock_soap!(client, parsed_response, operation, op_key=nil)
|
5
|
+
op_key ||= operation.gsub(/(.)([A-Z])/, '\1_\2').downcase
|
6
|
+
response_key = (op_key+'_response').to_sym
|
7
|
+
result_key = (op_key+'_result').to_sym
|
8
|
+
|
9
|
+
data = OpenStruct.new(:body => {response_key => {result_key => :encoded_xml}})
|
10
|
+
|
11
|
+
client.soap_client.should_receive(:request).with(operation).and_return(data)
|
12
|
+
client.xml_parser.should_receive(:parse).with(:encoded_xml).and_return(parsed_response)
|
13
|
+
end
|
14
|
+
|
15
|
+
def request_payload
|
16
|
+
{
|
17
|
+
:id => 123456,
|
18
|
+
:name => "Cogsley's Cogs",
|
19
|
+
:industry => "Metal_Fabricators",
|
20
|
+
:address => "123 Main St",
|
21
|
+
:city => "Vancouver",
|
22
|
+
:state => "WA",
|
23
|
+
:zip => "10120",
|
24
|
+
:phone => "5555551234",
|
25
|
+
:business_type => "Corporation",
|
26
|
+
:days_in_business => 404,
|
27
|
+
|
28
|
+
:contact_name => 'George Jetson',
|
29
|
+
:physical_address => "123 Main St",
|
30
|
+
:physical_city => "Vancouver",
|
31
|
+
:physical_state => "WA",
|
32
|
+
:physical_zip => "10120",
|
33
|
+
:physical_phone => "5555551234",
|
34
|
+
|
35
|
+
:principal_first_name => "Carl",
|
36
|
+
:principal_last_name => "Cogsley",
|
37
|
+
:principal_title => 'President',
|
38
|
+
:principal_address => "123 Main St",
|
39
|
+
:principal_city => "Vancouver",
|
40
|
+
:principal_state => "WA",
|
41
|
+
:principal_zip => "10120",
|
42
|
+
:principal_dob => "1965-04-28",
|
43
|
+
:principal_ssn => '111222123',
|
44
|
+
|
45
|
+
:average_amount => "4000",
|
46
|
+
:max_amount => "7600",
|
47
|
+
|
48
|
+
:taxpayer_name => "Carl Cogsley",
|
49
|
+
:taxpayer_id => "123456789",
|
50
|
+
|
51
|
+
:routing_number => "490000018",
|
52
|
+
:account_number => "123456789",
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def success_response
|
57
|
+
{:response=>{
|
58
|
+
:status=>"Approved",
|
59
|
+
:message=>"1 merchant(s) created.\n0 merchant(s) not created due to errors.\n\n-----------------------\nMerchants Created:\nCogsley's Cogs (ISO ID: 9999, CrossRef: 123456, Status: AppApprovedandActivated)\n\n",
|
60
|
+
:app_data=>{
|
61
|
+
:merchant=>{
|
62
|
+
:@name=>"Cogsley's Cogs",
|
63
|
+
:@active=>"1",
|
64
|
+
:@type=>"Merchant",
|
65
|
+
:@cross_ref_id=>"123456",
|
66
|
+
:@id=>"20",
|
67
|
+
:location=>{
|
68
|
+
:terminal=>{
|
69
|
+
:@manual_entry=>"N",
|
70
|
+
:@name=>"Lipman Nurit 3000-01 (111163) ",
|
71
|
+
:@active=>"1",
|
72
|
+
:@type=>"Terminal",
|
73
|
+
:@cross_ref_id=>"41680",
|
74
|
+
:@id=>"111163",
|
75
|
+
:@mid=>"101-111163-606"},
|
76
|
+
:@name=>"Cogsley's Cogs ",
|
77
|
+
:@active=>"1",
|
78
|
+
:@ach_name=>"COGSLEYSCOGS",
|
79
|
+
:@type=>"Location",
|
80
|
+
:@cross_ref_id=>"123456",
|
81
|
+
:@id=>"31"},
|
82
|
+
:poc1=>{
|
83
|
+
:@password=>"UGPRDGIX",
|
84
|
+
:@user_name=>"CCogsley",
|
85
|
+
:@last_name=>"Cogsley",
|
86
|
+
:@first_name=>"Carl"}}},
|
87
|
+
:"@xmlns:xsd"=>"http://www.w3.org/2001/XMLSchema",
|
88
|
+
:"@xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
|
89
|
+
:validation_message=>{:result=>"Passed", :schema_file_path=>nil}}}
|
90
|
+
end
|
91
|
+
|
92
|
+
def error_response
|
93
|
+
{:response=>{
|
94
|
+
:"@xmlns:xsd"=>"http://www.w3.org/2001/XMLSchema",
|
95
|
+
:"@xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
|
96
|
+
:validation_message=>
|
97
|
+
{:result=>"Failed",
|
98
|
+
:schema_file_path=>
|
99
|
+
"http://demo.eftchecks.com/WebServices/schemas/app/NewMerchApp_ACH.xsd",
|
100
|
+
:validation_error=>
|
101
|
+
[{:@line_number=>"1",
|
102
|
+
:severity=>"Error",
|
103
|
+
:message=>"The 'pocAddress1' attribute is not declared.",
|
104
|
+
:@line_position=>"1193"},
|
105
|
+
{:@line_number=>"1",
|
106
|
+
:severity=>"Error",
|
107
|
+
:message=>"The required attribute 'pocAddress' is missing.",
|
108
|
+
:@line_position=>"1020"}]}}}
|
109
|
+
end
|
110
|
+
|
111
|
+
def repeat_response
|
112
|
+
{:response=>{
|
113
|
+
:status=>"Pending",
|
114
|
+
:message=>
|
115
|
+
"1 merchant(s) created.\n0 merchant(s) not created due to errors.\n\n-----------------------\nMerchants Created:\nCogsley's Cogs (ISO ID: 9999, CrossRef: 123456, Status: PendingInput)\n\n",
|
116
|
+
:validation_message=>{:result=>"Passed", :schema_file_path=>nil},
|
117
|
+
:"@xmlns:xsd"=>"http://www.w3.org/2001/XMLSchema",
|
118
|
+
:"@xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
|
119
|
+
:app_data=>{:merchant=>{:@id=>"21"}}}}
|
120
|
+
end
|
121
|
+
|
122
|
+
describe '#board_merchant_ach' do
|
123
|
+
it 'calls BoardCertificationMerchant_ACH' do
|
124
|
+
client = Geti::AppClient.new(test_credentials, {})
|
125
|
+
mock_soap!(client, success_response, "BoardCertificationMerchant_ACH", "board_certification_merchant_ach")
|
126
|
+
client.board_merchant_ach(request_payload)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'calls BoardMerchant_ACH in production' do
|
130
|
+
client = Geti::AppClient.new(test_credentials, {}, 'production')
|
131
|
+
mock_soap!(client, success_response, "BoardMerchant_ACH", "board_certification_merchant_ach")
|
132
|
+
client.board_merchant_ach(request_payload)
|
133
|
+
end
|
134
|
+
|
135
|
+
describe 'response on success' do
|
136
|
+
let(:client) {
|
137
|
+
Geti::AppClient.new(test_credentials, {}).tap{|c|
|
138
|
+
mock_soap!(c, response, "BoardCertificationMerchant_ACH", "board_certification_merchant_ach")
|
139
|
+
}
|
140
|
+
}
|
141
|
+
subject { client.board_merchant_ach(request_payload) }
|
142
|
+
|
143
|
+
describe 'on success' do
|
144
|
+
let(:response) { success_response }
|
145
|
+
its([:success]) { should be_true }
|
146
|
+
its([:status]) { should eq("Approved") }
|
147
|
+
|
148
|
+
it 'normalizes (nested) keys' do
|
149
|
+
subject[:app_data][:merchant][:id].should eq("20")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe 'on repeat' do
|
154
|
+
let(:response) { repeat_response }
|
155
|
+
its([:success]) { should be_true }
|
156
|
+
its([:status]) { should eq("Pending") }
|
157
|
+
end
|
158
|
+
|
159
|
+
describe 'on error' do
|
160
|
+
let(:response) { error_response }
|
161
|
+
its([:success]) { should be_false }
|
162
|
+
its([:status]) { should be_nil }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe Geti::AuthClient do
|
4
|
+
def mock_soap!(client, parsed_response, operation, op_key=nil)
|
5
|
+
op_key ||= operation.gsub(/(.)([A-Z])/, '\1_\2').downcase
|
6
|
+
response_key = (op_key+'_response').to_sym
|
7
|
+
result_key = (op_key+'_result').to_sym
|
8
|
+
|
9
|
+
data = OpenStruct.new(:body => {response_key => {result_key => :encoded_xml}})
|
10
|
+
|
11
|
+
client.soap_client.should_receive(:request).with(operation).and_return(data)
|
12
|
+
client.xml_parser.should_receive(:parse).with(:encoded_xml).and_return(parsed_response)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#get_terminal_settings' do
|
16
|
+
it 'calls GetCertificationTerminalSettings' do
|
17
|
+
client = Geti::AuthClient.new(test_credentials, {:sec_code => 'WEB', :verify => []})
|
18
|
+
mock_soap!(client, {}, "GetCertificationTerminalSettings")
|
19
|
+
client.get_terminal_settings
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'calls GetTerminalSettings in production' do
|
23
|
+
client = Geti::AuthClient.new(test_credentials, {:sec_code => 'WEB', :verify => []}, 'production')
|
24
|
+
mock_soap!(client, {}, "GetTerminalSettings")
|
25
|
+
client.get_terminal_settings
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development, :test)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'rspec'
|
12
|
+
require 'rspec/mocks'
|
13
|
+
require 'ostruct'
|
14
|
+
class OpenStruct
|
15
|
+
def inspect
|
16
|
+
"<OpenStruct \"#{@table.inspect}\">"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
21
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
22
|
+
|
23
|
+
require 'pp'
|
24
|
+
require 'geti'
|
25
|
+
|
26
|
+
|
27
|
+
Savon.configure do |config|
|
28
|
+
config.log = HTTPI.log = false unless ENV['SOAP_DEBUG']
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_credentials
|
32
|
+
YAML.load(File.read('config/test_credentials.yml'))
|
33
|
+
end
|
34
|
+
|
35
|
+
def xit(*args, &block)
|
36
|
+
# noop, disabled spec.
|
37
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe Geti::AppClient do
|
4
|
+
describe '#board_merchant_ach' do
|
5
|
+
it 'has a successful response' do
|
6
|
+
t = Time.now.to_i
|
7
|
+
client = Geti::AppClient.new(test_credentials, {})
|
8
|
+
response = client.board_merchant_ach({
|
9
|
+
:id => t,
|
10
|
+
:name => "Cogsley's Cogs %d" % t,
|
11
|
+
:industry => "Metal_Fabricators",
|
12
|
+
:address => "123 Main St",
|
13
|
+
:city => "Vancouver",
|
14
|
+
:state => "WA",
|
15
|
+
:zip => "10120",
|
16
|
+
:phone => "5555551234",
|
17
|
+
:business_type => "Corporation",
|
18
|
+
:days_in_business => 404,
|
19
|
+
|
20
|
+
:contact_name => 'George Jetson',
|
21
|
+
:physical_address => "123 Main St",
|
22
|
+
:physical_city => "Vancouver",
|
23
|
+
:physical_state => "WA",
|
24
|
+
:physical_zip => "10120",
|
25
|
+
:physical_phone => "5555551234",
|
26
|
+
|
27
|
+
:principal_first_name => "Carl",
|
28
|
+
:principal_last_name => "Cogsley",
|
29
|
+
:principal_title => 'President',
|
30
|
+
:principal_address => "123 Main St",
|
31
|
+
:principal_city => "Vancouver",
|
32
|
+
:principal_state => "WA",
|
33
|
+
:principal_zip => "10120",
|
34
|
+
:principal_dob => "1965-04-28",
|
35
|
+
:principal_ssn => '111222123',
|
36
|
+
|
37
|
+
:average_amount => "4000",
|
38
|
+
:max_amount => "7600",
|
39
|
+
|
40
|
+
:taxpayer_name => "Carl Cogsley",
|
41
|
+
:taxpayer_id => "123456789",
|
42
|
+
|
43
|
+
:routing_number => "490000018",
|
44
|
+
:account_number => "123456789",
|
45
|
+
})
|
46
|
+
expect(response[:status]).to eq("Approved")
|
47
|
+
expect(response[:message]).to match(/CrossRef: #{t}/)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|