gds-api-adapters 4.8.0 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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