glimr-api-client 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/bin/rails ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails gems
3
+ # installed from the root of your application.
4
+
5
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
6
+ ENGINE_PATH = File.expand_path('../../lib/glimr_api_client/engine', __FILE__)
7
+
8
+ # Set up gems listed in the Gemfile.
9
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
10
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
11
+
12
+ require 'rails/all'
13
+ require 'rails/engine/commands'
data/circle.yml ADDED
@@ -0,0 +1,7 @@
1
+ database:
2
+ override:
3
+ - bundle exec rake db:setup
4
+
5
+ test:
6
+ override:
7
+ - bundle exec rake
@@ -0,0 +1,30 @@
1
+ $LOAD_PATH.push File.expand_path("../lib", __FILE__)
2
+
3
+ require 'glimr_api_client/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'glimr-api-client'
7
+ spec.version = GlimrApiClient::VERSION
8
+ spec.authors = ['Todd Tyree']
9
+ spec.email = ['todd.tyree@digital.justice.gov.uk']
10
+
11
+ spec.summary = 'Easy integration with the glimr case management system'
12
+ spec.homepage = 'https://github.com/ministryofjustice/glimr-api-client'
13
+ spec.licenses = 'MIT'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+
17
+ spec.add_development_dependency 'bundler', '~> 1.10'
18
+ spec.add_development_dependency 'capybara', '~> 2.7'
19
+ spec.add_development_dependency 'codeclimate-test-reporter', '~> 0.2'
20
+ spec.add_development_dependency 'fuubar', '~> 2.0'
21
+ spec.add_development_dependency 'mutant-rspec', '~> 0.8'
22
+ spec.add_development_dependency 'pry-byebug', '~> 3.4'
23
+ spec.add_development_dependency 'rails', '~> 5.0'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'rspec-rails', '~> 3.5'
26
+ spec.add_development_dependency 'rubocop', '~> 0.41'
27
+ spec.add_development_dependency 'sqlite3', '~> 1.3'
28
+
29
+ spec.add_dependency 'excon', '~> 0.51'
30
+ end
@@ -0,0 +1,14 @@
1
+ require 'glimr_api_client/railtie' if defined?(Rails)
2
+ require 'glimr_api_client/version'
3
+ require 'glimr_api_client/api'
4
+ require 'glimr_api_client/available'
5
+ require 'glimr_api_client/case'
6
+ require 'glimr_api_client/update'
7
+
8
+
9
+ module GlimrApiClient
10
+ class PaymentNotificationFailure < StandardError; end
11
+ class Unavailable < StandardError; end
12
+ class CaseNotFound < StandardError; end
13
+ class RequestError < StandardError; end;
14
+ end
@@ -0,0 +1,47 @@
1
+ module GlimrApiClient
2
+ module Api
3
+
4
+ def post
5
+ @post ||=
6
+ client.post(path: endpoint, body: request_body.to_query).tap { |resp|
7
+ # Only timeouts and network issues raise errors.
8
+ handle_response_errors(resp)
9
+ @body = resp.body
10
+ @status = resp.status
11
+ }
12
+ rescue Excon::Error => e
13
+ if endpoint == '/paymenttaken'
14
+ raise GlimrApiClient::PaymentNotificationFailure, e
15
+ else
16
+ raise GlimrApiClient::Unavailable, e
17
+ end
18
+ end
19
+
20
+ def response_body
21
+ @response_body ||= JSON.parse(@body, symbolize_names: true)
22
+ end
23
+
24
+ private
25
+
26
+ def handle_response_errors(resp)
27
+ if resp.status == 404
28
+ raise GlimrApiClient::CaseNotFound
29
+ elsif (400..599).cover?(resp.status) && endpoint == '/paymenttaken'
30
+ raise GlimrApiClient::PaymentNotificationFailure, resp.status
31
+ elsif (400..599).cover?(resp.status)
32
+ raise GlimrApiClient::Unavailable, resp.status
33
+ end
34
+ end
35
+
36
+ def client
37
+ @client ||= Excon.new(
38
+ ENV.fetch('GLIMR_API_URL', 'https://glimr-test.dsd.io'),
39
+ headers: {
40
+ 'Content-Type' => 'application/json',
41
+ 'Accept' => 'application/json'
42
+ },
43
+ persistent: true
44
+ )
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,32 @@
1
+ module GlimrApiClient
2
+ class Available
3
+ include GlimrApiClient::Api
4
+
5
+ class << self
6
+ delegate :call, to: :new
7
+ end
8
+
9
+ def call
10
+ post
11
+ self
12
+ end
13
+
14
+ def available?
15
+ response_body.fetch(:glimrAvailable).eql?('yes').tap { |status|
16
+ if status.equal?(false)
17
+ raise Unavailable
18
+ end
19
+ }
20
+ end
21
+
22
+ private
23
+
24
+ def endpoint
25
+ '/glimravailable'
26
+ end
27
+
28
+ def request_body
29
+ {}
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,45 @@
1
+ module GlimrApiClient
2
+ class Case
3
+ include GlimrApiClient::Api
4
+
5
+ def self.find(case_reference = nil)
6
+ new(case_reference).call
7
+ end
8
+
9
+ def initialize(case_reference = nil)
10
+ @case_reference = case_reference
11
+ end
12
+
13
+ def call
14
+ post
15
+ self
16
+ end
17
+
18
+ def title
19
+ @title ||= response_body.fetch(:caseTitle)
20
+ end
21
+
22
+ def fees
23
+ @fees ||= response_body.fetch(:feeLiabilities).map do |fee|
24
+ OpenStruct.new(
25
+ glimr_id: Integer(fee.fetch(:feeLiabilityId)),
26
+ description: fee.fetch(:onlineFeeTypeDescription),
27
+ amount: Integer(fee.fetch(:payableWithUnclearedInPence))
28
+ )
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def endpoint
35
+ '/requestpayablecasefees'
36
+ end
37
+
38
+ def request_body
39
+ {
40
+ jurisdictionId: 8, # TODO: Remove when no longer required in API
41
+ caseNumber: @case_reference
42
+ }
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,11 @@
1
+ require 'glimr_api_client'
2
+ require 'rails'
3
+ module GlimrApiClient
4
+ class Railtie < Rails::Railtie
5
+ railtie_name :glimr_api_client
6
+
7
+ rake_tasks do
8
+ load 'tasks/spec_setup.rake'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,42 @@
1
+ module GlimrApiClient
2
+ class Update
3
+ include GlimrApiClient::Api
4
+
5
+ def self.call(*args)
6
+ new(*args).call
7
+ end
8
+
9
+ def initialize(fee)
10
+ @fee = fee
11
+ end
12
+
13
+ def call
14
+ check_request!
15
+ post
16
+ self
17
+ end
18
+
19
+ private
20
+
21
+ def check_request!
22
+ errors = []
23
+ [:feeLiabilityId, :paymentReference, :govpayReference, :paidAmountInPence].each do |required|
24
+ errors << required if request_body.fetch(required).blank?
25
+ end
26
+ raise RequestError, errors unless errors.blank?
27
+ end
28
+
29
+ def endpoint
30
+ '/paymenttaken'
31
+ end
32
+
33
+ def request_body
34
+ {
35
+ feeLiabilityId: @fee.glimr_id,
36
+ paymentReference: @fee.govpay_reference,
37
+ govpayReference: @fee.govpay_payment_id,
38
+ paidAmountInPence: @fee.amount
39
+ }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ module GlimrApiClient
2
+ VERSION = '0.1.1'
3
+ end
@@ -0,0 +1,49 @@
1
+ task :mutant do
2
+ classes_to_mutate.each do |klass|
3
+ vars = 'NOCOVERAGE=true'
4
+ flags = '--include lib --use rspec'
5
+ unless system("#{vars} mutant #{flags} #{klass}")
6
+ raise 'Mutation testing failed'
7
+ end
8
+ end
9
+ end
10
+
11
+ task(:default).prerequisites << task(:mutant)
12
+
13
+ private
14
+
15
+ def classes_to_mutate
16
+ files = grep_files_for_classes
17
+ klasses = extract_classes_for_mutation(files)
18
+ klasses.map { |k|
19
+ setup_class_for_run(k)
20
+ }
21
+ end
22
+
23
+ # This is a nasty hack because we’re using POROs; otherwise, we could just use
24
+ # ApplicationRecord.descendants...
25
+ #
26
+ # Grepping through the source code seemed to be the most pragmatic solution
27
+ # so that developers don’t need to remember to add new classes to a list for
28
+ # mutation testing, but it’s not ideal
29
+ def grep_files_for_classes
30
+ Dir.glob('lib/**/*.rb').
31
+ map { |f|
32
+ # There are some examples of `class << self` in codebase.
33
+ File.readlines(f).grep(/\bclass(?!\s<<)/)
34
+ }.flatten
35
+ end
36
+
37
+ def extract_classes_for_mutation(files)
38
+ re = /class (?<klass>\w+)/
39
+
40
+ files.map { |s|
41
+ re.match(s)[:klass]
42
+ }.compact
43
+ end
44
+
45
+ def setup_class_for_run(klass)
46
+ Object.const_get(klass)
47
+ rescue NameError
48
+ Object.const_get("GlimrApiClient::#{klass}")
49
+ end
@@ -0,0 +1,6 @@
1
+ if Gem.loaded_specs.key?('rubocop')
2
+ require 'rubocop/rake_task'
3
+ RuboCop::RakeTask.new
4
+
5
+ task(:default).prerequisites << task(:rubocop)
6
+ end
@@ -0,0 +1,11 @@
1
+ namespace :glimr_api_client do
2
+ desc 'Copy the shared examples for glimr rspec testing'
3
+ task :install_shared_examples
4
+ source = File.join(
5
+ Gem.loaded_specs['glimr-api-client'].full_gem_path,
6
+ 'shared_examples',
7
+ 'shared_examples_for_glimr.rb'
8
+ )
9
+ target = File.join(Rails.root, 'spec', 'support', 'shared_examples_for_glimr.rb')
10
+ FileUtils.cp_r source, target
11
+ end
@@ -0,0 +1,212 @@
1
+ RSpec.shared_examples 'glimr availability request' do |glimr_response|
2
+ before do
3
+ Excon.stub(
4
+ { host: 'glimr-test.dsd.io', path: '/glimravailable' },
5
+ status: 200, body: glimr_response.to_json
6
+ )
7
+ end
8
+ end
9
+
10
+ RSpec.shared_examples 'glimr availability request returns a 500' do
11
+ before do
12
+ Excon.stub(
13
+ { host: 'glimr-test.dsd.io', path: '/glimravailable' },
14
+ status: 500
15
+ )
16
+ end
17
+ end
18
+
19
+ RSpec.shared_examples 'service is not available' do
20
+ scenario do
21
+ visit '/'
22
+ expect(page).not_to have_text('Start now')
23
+ expect(page).to have_text('The service is currently unavailable')
24
+ end
25
+ end
26
+
27
+ RSpec.shared_examples 'generic glimr response' do |case_number, _confirmation_code, status, glimr_response|
28
+ before do
29
+ Excon.stub(
30
+ {
31
+ host: 'glimr-test.dsd.io',
32
+ body: /caseNumber=#{CGI.escape(case_number)}/,
33
+ path: '/requestpayablecasefees'
34
+ },
35
+ status: status, body: glimr_response.to_json
36
+ )
37
+ end
38
+ end
39
+
40
+ RSpec.shared_examples 'case not found' do
41
+ before do
42
+ Excon.stub(
43
+ {
44
+ host: 'glimr-test.dsd.io',
45
+ path: '/requestpayablecasefees'
46
+ },
47
+ status: 404
48
+ )
49
+ end
50
+ end
51
+
52
+ RSpec.shared_examples 'no new fees are due' do |case_number, _confirmation_code|
53
+ let(:response_body) {
54
+ {
55
+ 'jurisdictionId' => 8,
56
+ 'tribunalCaseId' => 60_029,
57
+ 'caseTitle' => 'You vs HM Revenue & Customs',
58
+ 'feeLiabilities' => []
59
+ }
60
+ }
61
+
62
+ before do
63
+ Excon.stub(
64
+ {
65
+ host: 'glimr-test.dsd.io',
66
+ body: /caseNumber=#{CGI.escape(case_number)}/,
67
+ path: '/requestpayablecasefees'
68
+ },
69
+ status: 200, body: response_body.to_json
70
+ )
71
+ end
72
+ end
73
+
74
+ RSpec.shared_examples 'a case fee of £20 is due' do |case_number, _confirmation_code|
75
+ let(:response_body) {
76
+ {
77
+ 'jurisdictionId' => 8,
78
+ 'tribunalCaseId' => 60_029,
79
+ 'caseTitle' => 'You vs HM Revenue & Customs',
80
+ 'feeLiabilities' =>
81
+ [{ 'feeLiabilityId' => '7',
82
+ 'onlineFeeTypeDescription' => 'Lodgement Fee',
83
+ 'payableWithUnclearedInPence' => '2000' }]
84
+ }.to_json
85
+ }
86
+
87
+ before do
88
+ Excon.stub(
89
+ {
90
+ method: :post,
91
+ host: 'glimr-test.dsd.io',
92
+ body: /caseNumber=#{CGI.escape(case_number)}&jurisdictionId=8/,
93
+ path: '/requestpayablecasefees'
94
+ },
95
+ status: 200, body: response_body
96
+ )
97
+ end
98
+ end
99
+
100
+ RSpec.shared_examples 'no fees then a £20 fee' do |case_number, _confirmation_code|
101
+ let(:no_fees) {
102
+ {
103
+ 'jurisdictionId' => 8,
104
+ 'tribunalCaseId' => 60_029,
105
+ 'caseTitle' => 'You vs HM Revenue & Customs',
106
+ 'feeLiabilities' => []
107
+ }
108
+ }
109
+
110
+ let(:twenty_pound_fee) {
111
+ {
112
+ 'jurisdictionId' => 8,
113
+ 'tribunalCaseId' => 60_029,
114
+ 'caseTitle' => 'You vs HM Revenue & Customs',
115
+ 'feeLiabilities' =>
116
+ [{ 'feeLiabilityId' => 7,
117
+ 'onlineFeeTypeDescription' => 'Lodgement Fee',
118
+ 'payableWithUnclearedInPence' => 2000 }]
119
+ }
120
+ }
121
+
122
+ before do
123
+ Excon.stub(
124
+ {
125
+ method: :post,
126
+ host: 'glimr-test.dsd.io',
127
+ body: /caseNumber=#{CGI.escape(case_number)}/,
128
+ path: '/requestpayablecasefees'
129
+ },
130
+ status: 200, body: no_fees.to_json
131
+ )
132
+
133
+ Excon.stub(
134
+ {
135
+ method: :post,
136
+ host: 'glimr-test.dsd.io',
137
+ body: /caseNumber=#{CGI.escape(case_number)}/,
138
+ path: '/requestpayablecasefees'
139
+ },
140
+ status: 200, body: twenty_pound_fee.to_json
141
+ )
142
+ end
143
+ end
144
+
145
+ RSpec.shared_examples 'report payment taken to glimr' do |req_body|
146
+ let(:paymenttaken_response) {
147
+ {
148
+ feeLiabilityId: 1234,
149
+ feeTransactionId: 1234,
150
+ paidAmountInPence: 9999
151
+ }
152
+ }
153
+
154
+ before do
155
+ Excon.stub(
156
+ {
157
+ method: :post,
158
+ host: 'glimr-test.dsd.io',
159
+ body: req_body,
160
+ path: '/paymenttaken'
161
+ },
162
+ status: 200, body: paymenttaken_response.to_json
163
+ )
164
+ end
165
+ end
166
+
167
+ RSpec.shared_examples 'glimr fee_paid returns a 500' do
168
+ before do
169
+ Excon.stub(
170
+ {
171
+ method: :post,
172
+ host: 'glimr-test.dsd.io',
173
+ path: '/paymenttaken'
174
+ },
175
+ status: 500
176
+ )
177
+ end
178
+ end
179
+
180
+ RSpec.shared_examples 'glimr times out' do
181
+ let(:glimr_check) {
182
+ class_double(Excon, 'glimr availability')
183
+ }
184
+
185
+ before do
186
+ expect(glimr_check).
187
+ to receive(:post).
188
+ with(path: '/glimravailable', body: '').
189
+ and_raise(Excon::Errors::Timeout)
190
+
191
+ expect(Excon).to receive(:new).
192
+ with(Rails.configuration.glimr_api_url, anything).
193
+ and_return(glimr_check)
194
+ end
195
+ end
196
+
197
+ RSpec.shared_examples 'glimr has a socket error' do
198
+ let(:glimr_check) {
199
+ class_double(Excon, 'glimr availability')
200
+ }
201
+
202
+ before do
203
+ expect(glimr_check).
204
+ to receive(:post).
205
+ with(path: '/glimravailable', body: '').
206
+ and_raise(Excon::Errors::SocketError)
207
+
208
+ expect(Excon).to receive(:new).
209
+ with('https://glimr-test.dsd.io', anything).
210
+ and_return(glimr_check)
211
+ end
212
+ end