gds-api-adapters 4.8.0 → 4.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,10 +1,21 @@
1
1
  require_relative 'base'
2
2
  require_relative 'exceptions'
3
+ require 'gds_api/content_api/response'
3
4
  require 'gds_api/content_api/list_response'
4
5
 
5
6
  class GdsApi::ContentApi < GdsApi::Base
6
7
  include GdsApi::ExceptionHandling
7
8
 
9
+ def initialize(endpoint_url, options = {})
10
+ # If the `web_urls_relative_to` option is given, the adapter will convert
11
+ # any `web_url` values to relative URLs if they are from the same host.
12
+ #
13
+ # For example: "https://www.gov.uk"
14
+
15
+ @web_urls_relative_to = options.delete(:web_urls_relative_to)
16
+ super
17
+ end
18
+
8
19
  def sections
9
20
  get_list!("#{base_url}/tags.json?type=section")
10
21
  end
@@ -86,11 +97,29 @@ class GdsApi::ContentApi < GdsApi::Base
86
97
  end
87
98
 
88
99
  def get_list!(url)
89
- get_json!(url) { |r| ListResponse.new(r, self) }
100
+ get_json!(url) { |r|
101
+ ListResponse.new(r, self, web_urls_relative_to: @web_urls_relative_to)
102
+ }
90
103
  end
91
104
 
92
105
  def get_list(url)
93
- get_json(url) { |r| ListResponse.new(r, self) }
106
+ get_json(url) { |r|
107
+ ListResponse.new(r, self, web_urls_relative_to: @web_urls_relative_to)
108
+ }
109
+ end
110
+
111
+ def get_json(url, &create_response)
112
+ create_response = create_response || Proc.new { |r|
113
+ GdsApi::ContentApi::Response.new(r, web_urls_relative_to: @web_urls_relative_to)
114
+ }
115
+ super(url, &create_response)
116
+ end
117
+
118
+ def get_json!(url, &create_response)
119
+ create_response = create_response || Proc.new { |r|
120
+ GdsApi::ContentApi::Response.new(r, web_urls_relative_to: @web_urls_relative_to)
121
+ }
122
+ super(url, &create_response)
94
123
  end
95
124
 
96
125
  def countries
@@ -9,12 +9,12 @@ class GdsApi::ContentApi < GdsApi::Base
9
9
  # These responses are in a common format, with the list of results contained
10
10
  # under the `results` key. The response may also have previous and subsequent
11
11
  # pages, indicated by entries in the response's `Link` header.
12
- class ListResponse < GdsApi::Response
12
+ class ListResponse < Response
13
13
 
14
14
  # The ListResponse is instantiated with a reference back to the API client,
15
15
  # so it can make requests for the subsequent pages
16
- def initialize(response, api_client)
17
- super(response)
16
+ def initialize(response, api_client, options = {})
17
+ super(response, options)
18
18
  @api_client = api_client
19
19
  end
20
20
 
@@ -0,0 +1,56 @@
1
+ module GdsApi
2
+ class ContentApi < GdsApi::Base
3
+ class Response < GdsApi::Response
4
+ # Responses from the content API can be configured to use relative URLs
5
+ # for `web_url` properties. This is useful on non-canonical frontends,
6
+ # such as those in staging environments.
7
+ #
8
+ # Example:
9
+ #
10
+ # r = Response.new(response, web_urls_relative_to: "https://www.gov.uk")
11
+ # r.results[0].web_url
12
+ # => "/bank-holidays"
13
+
14
+ WEB_URL_KEYS = ["web_url"]
15
+
16
+ def initialize(http_response, options = {})
17
+ if options[:web_urls_relative_to]
18
+ @web_urls_relative_to = URI.parse(options[:web_urls_relative_to])
19
+ else
20
+ @web_urls_relative_to = nil
21
+ end
22
+
23
+ super(http_response)
24
+ end
25
+
26
+ def to_hash
27
+ @parsed ||= transform_parsed(JSON.parse(@http_response.body))
28
+ end
29
+
30
+ private
31
+ def transform_parsed(value)
32
+ case value
33
+ when Hash
34
+ Hash[value.map { |k, v|
35
+ # NOTE: Don't bother transforming if the value is nil
36
+ if @web_urls_relative_to && WEB_URL_KEYS.include?(k) && v
37
+ # Use relative URLs to route when the web_url value is on the
38
+ # same domain as the site root. Note that we can't just use the
39
+ # `route_to` method, as this would give us technically correct
40
+ # but potentially confusing `//host/path` URLs for URLs with the
41
+ # same scheme but different hosts.
42
+ relative_url = @web_urls_relative_to.route_to(v)
43
+ [k, relative_url.host ? v : relative_url.to_s]
44
+ else
45
+ [k, transform_parsed(v)]
46
+ end
47
+ }]
48
+ when Array
49
+ value.map { |v| transform_parsed(v) }
50
+ else
51
+ value
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -49,15 +49,15 @@ module GdsApi
49
49
  # and return nil.
50
50
  [:get, :post, :put, :delete].each do |http_method|
51
51
  method_name = "#{http_method}_json"
52
- define_method method_name do |url, *args|
52
+ define_method method_name do |url, *args, &block|
53
53
  ignoring GdsApi::HTTPNotFound do
54
- send (method_name + "!"), url, *args
54
+ send (method_name + "!"), url, *args, &block
55
55
  end
56
56
  end
57
57
  end
58
58
 
59
59
  def get_json!(url, &create_response)
60
- @cache[url] ||= do_json_request(:get, url, nil, &create_response)
60
+ do_json_request(:get, url, nil, &create_response)
61
61
  end
62
62
 
63
63
  def post_json!(url, params)
@@ -92,7 +92,7 @@ module GdsApi
92
92
  def do_json_request(method, url, params = nil, &create_response)
93
93
 
94
94
  begin
95
- response = do_request(method, url, params)
95
+ response = do_request_with_cache(method, url, params)
96
96
 
97
97
  rescue RestClient::ResourceNotFound => e
98
98
  raise GdsApi::HTTPNotFound.new(e.http_code)
@@ -149,6 +149,16 @@ module GdsApi
149
149
  )
150
150
  end
151
151
 
152
+ def do_request_with_cache(method, url, params = nil)
153
+ # Only read GET requests from the cache: any other request methods should
154
+ # always be passed through
155
+ if method == :get
156
+ @cache[url] ||= do_request(method, url, params)
157
+ else
158
+ do_request(method, url, params)
159
+ end
160
+ end
161
+
152
162
  def do_request(method, url, params = nil)
153
163
  loggable = {request_uri: url, start_time: Time.now.to_f}
154
164
  start_logging = loggable.merge(action: 'start')
@@ -220,6 +220,7 @@ module GdsApi
220
220
  "details" => {
221
221
  "type" => tag_type
222
222
  },
223
+ "web_url" => nil,
223
224
  "content_with_tag" => {
224
225
  "id" => "#{CONTENT_API_ENDPOINT}/with_tag.json?tag=#{CGI.escape(slug)}",
225
226
  "web_url" => "https://www.test.gov.uk/browse/#{slug}",
@@ -1,3 +1,3 @@
1
1
  module GdsApi
2
- VERSION = '4.8.0'
2
+ VERSION = '4.9.0'
3
3
  end
@@ -0,0 +1,97 @@
1
+ require "test_helper"
2
+ require "gds_api/content_api"
3
+
4
+ describe "GdsApi::ContentApi::Response" do
5
+
6
+ DummyNetResponse = Struct.new(:body)
7
+
8
+ def response_for(net_response)
9
+ root = "https://www.gov.uk"
10
+ GdsApi::ContentApi::Response.new(net_response, web_urls_relative_to: root)
11
+ end
12
+
13
+ it "should map web URLs" do
14
+ body = {
15
+ "web_url" => "https://www.gov.uk/test"
16
+ }.to_json
17
+ assert_equal "/test", response_for(DummyNetResponse.new(body)).web_url
18
+ end
19
+
20
+ it "should leave other properties alone" do
21
+ body = {
22
+ "title" => "Title",
23
+ "description" => "Description"
24
+ }.to_json
25
+ response = response_for(DummyNetResponse.new(body))
26
+ assert_equal "Title", response.title
27
+ assert_equal "Description", response.description
28
+ end
29
+
30
+ it "should traverse into arrays" do
31
+ body = {
32
+ "other_urls" => [
33
+ { "title" => "Pies", "web_url" => "https://www.gov.uk/pies" },
34
+ { "title" => "Cheese", "web_url" => "https://www.gov.uk/cheese" }
35
+ ]
36
+ }.to_json
37
+
38
+ response = response_for(DummyNetResponse.new(body))
39
+ assert_equal "/pies", response.other_urls[0].web_url
40
+ assert_equal "/cheese", response.other_urls[1].web_url
41
+ end
42
+
43
+ it "should traverse into hashes" do
44
+ body = {
45
+ "details" => {
46
+ "chirality" => "widdershins",
47
+ "web_url" => "https://www.gov.uk/left"
48
+ }
49
+ }.to_json
50
+
51
+ response = response_for(DummyNetResponse.new(body))
52
+ assert_equal "/left", response.details.web_url
53
+ end
54
+
55
+ it "should handle nil values" do
56
+ body = {"web_url" => nil}.to_json
57
+
58
+ response = response_for(DummyNetResponse.new(body))
59
+ assert_nil response.web_url
60
+ end
61
+
62
+ it "should handle query parameters" do
63
+ body = {
64
+ "web_url" => "https://www.gov.uk/thing?does=stuff"
65
+ }.to_json
66
+
67
+ response = response_for(DummyNetResponse.new(body))
68
+ assert_equal "/thing?does=stuff", response.web_url
69
+ end
70
+
71
+ it "should handle fragments" do
72
+ body = {
73
+ "web_url" => "https://www.gov.uk/thing#part-2"
74
+ }.to_json
75
+
76
+ response = response_for(DummyNetResponse.new(body))
77
+ assert_equal "/thing#part-2", response.web_url
78
+ end
79
+
80
+ it "should keep URLs from other domains absolute" do
81
+ body = {
82
+ "web_url" => "https://www.example.com/example"
83
+ }.to_json
84
+
85
+ response = response_for(DummyNetResponse.new(body))
86
+ assert_equal "https://www.example.com/example", response.web_url
87
+ end
88
+
89
+ it "should keep URLs with other schemes absolute" do
90
+ body = {
91
+ "web_url" => "http://www.example.com/example"
92
+ }.to_json
93
+
94
+ response = response_for(DummyNetResponse.new(body))
95
+ assert_equal "http://www.example.com/example", response.web_url
96
+ end
97
+ end
@@ -10,6 +10,70 @@ describe GdsApi::ContentApi do
10
10
  @api = GdsApi::ContentApi.new(@base_api_url)
11
11
  end
12
12
 
13
+ describe "when asked for relative web URLs" do
14
+ before do
15
+ @api = GdsApi::ContentApi.new(
16
+ @base_api_url,
17
+ web_urls_relative_to: "http://www.test.gov.uk"
18
+ )
19
+ end
20
+
21
+ it "should use relative URLs for an artefact" do
22
+ artefact_response = artefact_for_slug_in_a_section("bank-holidays", "cheese")
23
+
24
+ # Rewrite the web_url fields to have a common prefix
25
+ # The helper's default is to point the web_url for an artefact at the
26
+ # frontend app, and the web_url for a tag's content to www: to test the
27
+ # rewriting properly, they need to be the same
28
+ artefact_response["web_url"] = "http://www.test.gov.uk/bank-holidays"
29
+ section_tag_content = artefact_response["tags"][0]["content_with_tag"]
30
+ section_tag_content["web_url"] = "http://www.test.gov.uk/browse/cheese"
31
+
32
+ content_api_has_an_artefact("bank-holidays", artefact_response)
33
+ artefact = @api.artefact("bank-holidays")
34
+
35
+ assert_equal "Bank holidays", artefact.title
36
+ assert_equal "/bank-holidays", artefact.web_url
37
+
38
+ assert_equal "/browse/cheese", artefact.tags[0].content_with_tag.web_url
39
+ end
40
+
41
+ it "should use relative URLs for tag listings" do
42
+ content_api_has_root_sections %w(housing benefits tax)
43
+ tags = @api.root_sections
44
+
45
+ assert_equal 3, tags.count
46
+ tags.each do |tag|
47
+ web_url = tag.content_with_tag.web_url
48
+ assert web_url.start_with?("/browse/"), web_url
49
+ end
50
+ end
51
+
52
+ describe "with caching enabled" do
53
+ before do
54
+ @original_cache = GdsApi::JsonClient.cache
55
+ GdsApi::JsonClient.cache = LRUCache.new(max_size: 10, ttl: 10)
56
+ end
57
+
58
+ it "should not pollute the cache with relative URLs" do
59
+ artefact_response = artefact_for_slug("bank-holidays")
60
+ artefact_response["web_url"] = "http://www.test.gov.uk/bank-holidays"
61
+ content_api_has_an_artefact("bank-holidays", artefact_response)
62
+
63
+ assert_equal "/bank-holidays", @api.artefact("bank-holidays").web_url
64
+
65
+ clean_api = GdsApi::ContentApi.new(@base_api_url)
66
+ clean_artefact = clean_api.artefact("bank-holidays")
67
+
68
+ assert_equal "http://www.test.gov.uk/bank-holidays", clean_artefact.web_url
69
+ end
70
+
71
+ after do
72
+ GdsApi::JsonClient.cache = @original_cache
73
+ end
74
+ end
75
+ end
76
+
13
77
  describe "sections" do
14
78
  it "should show a list of sections" do
15
79
  content_api_has_root_sections(["crime"])
@@ -73,7 +73,7 @@ class JsonClientTest < MiniTest::Spec
73
73
  stub_request(:get, url).to_return(:body => JSON.dump(result), :status => 200)
74
74
  response_a = GdsApi::JsonClient.new.get_json(url)
75
75
  response_b = GdsApi::JsonClient.new.get_json(url)
76
- assert_equal response_a.object_id, response_b.object_id
76
+ assert_equal response_a.to_hash, response_b.to_hash
77
77
  assert_requested :get, url, times: 1
78
78
  end
79
79
 
@@ -131,13 +131,13 @@ class JsonClientTest < MiniTest::Spec
131
131
  response_b = GdsApi::JsonClient.new.get_json(url)
132
132
 
133
133
  assert_requested :get, url, times: 1
134
- assert_equal response_a.object_id, response_b.object_id
134
+ assert_equal response_a.to_hash, response_b.to_hash
135
135
 
136
136
  Timecop.travel( 15 * 60 - 30) do # now + 14 mins 30 secs
137
137
  response_c = GdsApi::JsonClient.new.get_json(url)
138
138
 
139
139
  assert_requested :get, url, times: 1
140
- assert_same response_a, response_c
140
+ assert_equal response_a.to_hash, response_c.to_hash
141
141
  end
142
142
 
143
143
  Timecop.travel( 15 * 60 + 30) do # now + 15 mins 30 secs
@@ -158,13 +158,13 @@ class JsonClientTest < MiniTest::Spec
158
158
  response_b = GdsApi::JsonClient.new.get_json(url)
159
159
 
160
160
  assert_requested :get, url, times: 1
161
- assert_equal response_a.object_id, response_b.object_id
161
+ assert_equal response_a.to_hash, response_b.to_hash
162
162
 
163
163
  Timecop.travel( 5 * 60 - 30) do # now + 4 mins 30 secs
164
164
  response_c = GdsApi::JsonClient.new.get_json(url)
165
165
 
166
166
  assert_requested :get, url, times: 1
167
- assert_same response_a, response_c
167
+ assert_equal response_a.to_hash, response_c.to_hash
168
168
  end
169
169
 
170
170
  Timecop.travel( 5 * 60 + 30) do # now + 5 mins 30 secs
@@ -347,6 +347,32 @@ class JsonClientTest < MiniTest::Spec
347
347
  assert_equal({}, @client.put_json(url, payload).to_hash)
348
348
  end
349
349
 
350
+ def test_can_build_custom_response_object
351
+ url = "http://some.endpoint/some.json"
352
+ stub_request(:get, url).to_return(:body => "Hello there!")
353
+
354
+ response = @client.get_json(url) { |http_response| http_response.body }
355
+ assert response.is_a? String
356
+ assert_equal "Hello there!", response
357
+ end
358
+
359
+ def test_responds_with_nil_on_custom_response_404
360
+ url = "http://some.endpoint/some.json"
361
+ stub_request(:get, url).to_return(:body => "", :status => 404)
362
+
363
+ response = @client.get_json(url) { |http_response| http_response.body }
364
+ assert_nil response
365
+ end
366
+
367
+ def test_can_build_custom_response_object_in_bang_method
368
+ url = "http://some.endpoint/some.json"
369
+ stub_request(:get, url).to_return(:body => "Hello there!")
370
+
371
+ response = @client.get_json!(url) { |http_response| http_response.body }
372
+ assert response.is_a? String
373
+ assert_equal "Hello there!", response
374
+ end
375
+
350
376
  def test_can_convert_response_to_ostruct
351
377
  url = "http://some.endpoint/some.json"
352
378
  payload = {a: 1}
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: gds-api-adapters
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 4.8.0
5
+ version: 4.9.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - James Stewart
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2013-02-14 00:00:00 Z
13
+ date: 2013-02-18 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: plek
@@ -201,6 +201,7 @@ files:
201
201
  - lib/gds_api/core-ext/openstruct.rb
202
202
  - lib/gds_api/response.rb
203
203
  - lib/gds_api/content_api/list_response.rb
204
+ - lib/gds_api/content_api/response.rb
204
205
  - lib/gds_api/publisher.rb
205
206
  - lib/gds_api/test_helpers/licence_application.rb
206
207
  - lib/gds_api/test_helpers/imminence.rb
@@ -217,6 +218,7 @@ files:
217
218
  - Rakefile
218
219
  - test/rummager_test.rb
219
220
  - test/publisher_api_test.rb
221
+ - test/content_api_response_test.rb
220
222
  - test/licence_application_api_test.rb
221
223
  - test/mapit_test.rb
222
224
  - test/panopticon_api_test.rb
@@ -239,7 +241,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
239
241
  requirements:
240
242
  - - ">="
241
243
  - !ruby/object:Gem::Version
242
- hash: 1465373721783648305
244
+ hash: -2287437040986270059
243
245
  segments:
244
246
  - 0
245
247
  version: "0"
@@ -248,7 +250,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
248
250
  requirements:
249
251
  - - ">="
250
252
  - !ruby/object:Gem::Version
251
- hash: 1465373721783648305
253
+ hash: -2287437040986270059
252
254
  segments:
253
255
  - 0
254
256
  version: "0"
@@ -262,6 +264,7 @@ summary: Adapters to work with GDS APIs
262
264
  test_files:
263
265
  - test/rummager_test.rb
264
266
  - test/publisher_api_test.rb
267
+ - test/content_api_response_test.rb
265
268
  - test/licence_application_api_test.rb
266
269
  - test/mapit_test.rb
267
270
  - test/panopticon_api_test.rb