roar 1.0.2 → 1.1.1

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.
Files changed (52) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE.md +20 -0
  3. data/.travis.yml +16 -11
  4. data/CHANGES.markdown +86 -57
  5. data/CONTRIBUTING.md +31 -0
  6. data/Gemfile +7 -4
  7. data/LICENSE +1 -1
  8. data/README.markdown +133 -255
  9. data/Rakefile +3 -1
  10. data/examples/example.rb +0 -0
  11. data/examples/example_server.rb +0 -0
  12. data/lib/roar/client.rb +8 -3
  13. data/lib/roar/decorator.rb +2 -2
  14. data/lib/roar/http_verbs.rb +0 -16
  15. data/lib/roar/hypermedia.rb +30 -56
  16. data/lib/roar/json/collection.rb +10 -2
  17. data/lib/roar/json/hal.rb +74 -83
  18. data/lib/roar/json.rb +5 -5
  19. data/lib/roar/version.rb +1 -1
  20. data/lib/roar/xml.rb +1 -1
  21. data/lib/roar.rb +3 -3
  22. data/roar.gemspec +7 -5
  23. data/test/client_test.rb +1 -1
  24. data/test/coercion_feature_test.rb +7 -2
  25. data/test/decorator_test.rb +17 -7
  26. data/test/hal_json_test.rb +101 -94
  27. data/test/hypermedia_feature_test.rb +13 -31
  28. data/test/hypermedia_test.rb +26 -92
  29. data/test/{decorator_client_test.rb → integration/decorator_client_test.rb} +5 -4
  30. data/test/{faraday_http_transport_test.rb → integration/faraday_http_transport_test.rb} +1 -0
  31. data/test/{http_verbs_test.rb → integration/http_verbs_test.rb} +3 -2
  32. data/test/integration/json_collection_test.rb +35 -0
  33. data/test/{net_http_transport_test.rb → integration/net_http_transport_test.rb} +1 -0
  34. data/test/integration/runner.rb +2 -3
  35. data/test/integration/server.rb +6 -0
  36. data/test/json_representer_test.rb +2 -29
  37. data/test/lonely_test.rb +1 -2
  38. data/test/ssl_client_certs_test.rb +1 -1
  39. data/test/test_helper.rb +21 -3
  40. data/test/xml_representer_test.rb +6 -5
  41. metadata +21 -37
  42. data/gemfiles/Gemfile.representable-1.7 +0 -6
  43. data/gemfiles/Gemfile.representable-1.8 +0 -6
  44. data/gemfiles/Gemfile.representable-2.0 +0 -5
  45. data/gemfiles/Gemfile.representable-2.1 +0 -5
  46. data/gemfiles/Gemfile.representable-head +0 -6
  47. data/lib/roar/json/collection_json.rb +0 -208
  48. data/lib/roar/json/json_api.rb +0 -233
  49. data/test/collection_json_test.rb +0 -132
  50. data/test/hal_links_test.rb +0 -31
  51. data/test/json_api_test.rb +0 -451
  52. data/test/lib/runner.rb +0 -134
@@ -1,451 +0,0 @@
1
- require 'test_helper'
2
- require 'roar/json/json_api'
3
- require 'json'
4
-
5
- require "representable/version"
6
- if Gem::Version.new(Representable::VERSION) >= Gem::Version.new("2.1.4") # TODO: remove check once we bump representable dependency.
7
- class JSONAPITest < MiniTest::Spec
8
- let(:song) {
9
- s = OpenStruct.new(
10
- bla: "halo",
11
- id: "1",
12
- title: 'Computadores Fazem Arte',
13
- album: OpenStruct.new(id: 9, title: "Hackers"),
14
- :album_id => "9",
15
- :musician_ids => ["1","2"],
16
- :composer_id => "10",
17
- :listener_ids => ["8"],
18
- musicians: [OpenStruct.new(id: 1, name: "Eddie Van Halen"), OpenStruct.new(id: 2, name: "Greg Howe")]
19
- )
20
-
21
- }
22
-
23
- # minimal resource, singular
24
- module MinimalSingular
25
- include Roar::JSON::JSONAPI
26
- type :songs
27
-
28
- property :id
29
- end
30
-
31
- class MinimalSingularDecorator < Roar::Decorator
32
- include Roar::JSON::JSONAPI
33
- type :songs
34
-
35
- property :id
36
- end
37
-
38
- [MinimalSingular, MinimalSingularDecorator].each do |representer|
39
- describe "minimal singular with #{representer}" do
40
- subject { representer.prepare(song) }
41
-
42
- it { subject.to_json.must_equal "{\"songs\":{\"id\":\"1\"}}" }
43
- it { subject.from_json("{\"songs\":{\"id\":\"2\"}}").id.must_equal "2" }
44
- end
45
- end
46
-
47
- module Singular
48
- include Roar::JSON::JSONAPI
49
- type :songs
50
-
51
- property :id
52
- property :title, if: lambda { |args| args[:omit_title] != true }
53
-
54
- # local per-model "id" links
55
- links do
56
- property :album_id, :as => :album
57
- collection :musician_ids, :as => :musicians
58
- end
59
- has_one :composer
60
- has_many :listeners
61
-
62
-
63
- # global document links.
64
- link "songs.album" do
65
- {
66
- type: "album",
67
- href: "http://example.com/albums/{songs.album}"
68
- }
69
- end
70
-
71
- compound do
72
- property :album do
73
- property :title
74
- end
75
-
76
- collection :musicians do
77
- property :name
78
- end
79
- end
80
- end
81
-
82
- class SingularDecorator < Roar::Decorator
83
- include Roar::JSON::JSONAPI
84
- type :songs
85
-
86
- property :id
87
- property :title, if: lambda { |args| args[:omit_title] != true }
88
-
89
- # NOTE: it is important to call has_one, then links, then has_many to assert that they all write
90
- #to the same _links property and do NOT override things.
91
- has_one :composer
92
- # local per-model "id" links
93
- links do
94
- property :album_id, :as => :album
95
- collection :musician_ids, :as => :musicians
96
- end
97
- has_many :listeners
98
-
99
-
100
- # global document links.
101
- link "songs.album" do
102
- {
103
- type: "album",
104
- href: "http://example.com/albums/{songs.album}"
105
- }
106
- end
107
-
108
- compound do
109
- property :album do
110
- property :title
111
- end
112
-
113
- collection :musicians do
114
- property :name
115
- end
116
- end
117
- end
118
-
119
- [Singular, SingularDecorator].each do |representer|
120
- describe "singular with #{representer}" do
121
- subject { song.extend(Singular) }
122
-
123
- let (:document) do
124
- {
125
- "songs" => {
126
- "id" => "1",
127
- "title" => "Computadores Fazem Arte",
128
- "links" => {
129
- "album" => "9",
130
- "musicians" => [ "1", "2" ],
131
- "composer"=>"10",
132
- "listeners"=>["8"]
133
- }
134
- },
135
- "links" => {
136
- "songs.album"=> {
137
- "href"=>"http://example.com/albums/{songs.album}", "type"=>"album"
138
- }
139
- },
140
- "linked" => {
141
- "album"=> [{"title"=>"Hackers"}],
142
- "musicians"=> [
143
- {"name"=>"Eddie Van Halen"},
144
- {"name"=>"Greg Howe"}
145
- ]
146
- }
147
- }
148
- end
149
-
150
- # to_hash
151
- it do
152
- subject.to_hash.must_equal document
153
- end
154
-
155
- # to_hash(options)
156
- it do
157
- subject.to_hash(omit_title: true)['songs'].wont_include('title')
158
- end
159
-
160
- # #to_json
161
- it do
162
- subject.to_json.must_equal JSON.generate(document)
163
- end
164
-
165
- # #from_json
166
- it do
167
- song = OpenStruct.new.extend(Singular)
168
- song.from_json(
169
- JSON.generate(
170
- {
171
- "songs" => {
172
- "id" => "1",
173
- "title" => "Computadores Fazem Arte",
174
- "links" => {
175
- "album" => "9",
176
- "musicians" => [ "1", "2" ],
177
- "composer"=>"10",
178
- "listeners"=>["8"]
179
- }
180
- },
181
- "links" => {
182
- "songs.album"=> {
183
- "href"=>"http://example.com/albums/{songs.album}", "type"=>"album"
184
- }
185
- }
186
- }
187
- )
188
- )
189
-
190
- song.id.must_equal "1"
191
- song.title.must_equal "Computadores Fazem Arte"
192
- song.album_id.must_equal "9"
193
- song.musician_ids.must_equal ["1", "2"]
194
- song.composer_id.must_equal "10"
195
- song.listener_ids.must_equal ["8"]
196
- end
197
- end
198
- end
199
-
200
-
201
- # collection with links
202
- [Singular, SingularDecorator].each do |representer|
203
- describe "collection with links and compound with #{representer}" do
204
- subject { representer.for_collection.prepare([song, song]) }
205
-
206
- let (:document) do
207
- {
208
- "songs" => [
209
- {
210
- "id" => "1",
211
- "title" => "Computadores Fazem Arte",
212
- "links" => {
213
- "composer"=>"10",
214
- "album" => "9",
215
- "musicians" => [ "1", "2" ],
216
- "listeners"=>["8"]
217
- }
218
- }, {
219
- "id" => "1",
220
- "title" => "Computadores Fazem Arte",
221
- "links" => {
222
- "composer"=>"10",
223
- "album" => "9",
224
- "musicians" => [ "1", "2" ],
225
- "listeners"=>["8"]
226
- }
227
- }
228
- ],
229
- "links" => {
230
- "songs.album" => {
231
- "href" => "http://example.com/albums/{songs.album}",
232
- "type" => "album" # DISCUSS: does that have to be albums ?
233
- },
234
- },
235
- "linked"=>{
236
- "album" =>[{"title"=>"Hackers"}], # only once!
237
- "musicians"=>[{"name"=>"Eddie Van Halen"}, {"name"=>"Greg Howe"}]
238
- }
239
- }
240
- end
241
-
242
- # to_hash
243
- it do
244
- subject.to_hash.must_equal document
245
- end
246
-
247
- # to_hash(options)
248
- it do
249
- subject.to_hash(omit_title: true)['songs'].each do |song|
250
- song.wont_include('title')
251
- end
252
- end
253
-
254
- # #to_json
255
- it { subject.to_json.must_match /linked/ } # hash ordering changes, and i don't care why.
256
- end
257
-
258
-
259
- # from_json
260
- it do
261
- song1, song2 = Singular.for_collection.prepare([OpenStruct.new, OpenStruct.new]).from_json(
262
- JSON.generate(
263
- {
264
- "songs" => [
265
- {
266
- "id" => "1",
267
- "title" => "Computadores Fazem Arte",
268
- "links" => {
269
- "album" => "9",
270
- "musicians" => [ "1", "2" ],
271
- "composer"=>"10",
272
- "listeners"=>["8"]
273
- },
274
- },
275
- {
276
- "id" => "2",
277
- "title" => "Talking To Remind Me",
278
- "links" => {
279
- "album" => "1",
280
- "musicians" => [ "3", "4" ],
281
- "composer"=>"2",
282
- "listeners"=>["6"]
283
- }
284
- },
285
- ],
286
- "links" => {
287
- "songs.album"=> {
288
- "href"=>"http://example.com/albums/{songs.album}", "type"=>"album"
289
- }
290
- }
291
- }
292
- )
293
- )
294
-
295
- song1.id.must_equal "1"
296
- song1.title.must_equal "Computadores Fazem Arte"
297
- song1.album_id.must_equal "9"
298
- song1.musician_ids.must_equal ["1", "2"]
299
- song1.composer_id.must_equal "10"
300
- song1.listener_ids.must_equal ["8"]
301
-
302
- song2.id.must_equal "2"
303
- song2.title.must_equal "Talking To Remind Me"
304
- song2.album_id.must_equal "1"
305
- song2.musician_ids.must_equal ["3", "4"]
306
- song2.composer_id.must_equal "2"
307
- song2.listener_ids.must_equal ["6"]
308
- end
309
- end
310
-
311
-
312
- class CollectionWithoutCompound < self
313
- module Representer
314
- include Roar::JSON::JSONAPI
315
- type :songs
316
-
317
- property :id
318
- property :title
319
-
320
- # local per-model "id" links
321
- links do
322
- property :album_id, :as => :album
323
- collection :musician_ids, :as => :musicians
324
- end
325
- has_one :composer
326
- has_many :listeners
327
-
328
-
329
- # global document links.
330
- link "songs.album" do
331
- {
332
- type: "album",
333
- href: "http://example.com/albums/{songs.album}"
334
- }
335
- end
336
- end
337
-
338
- subject { [song, song].extend(Singular.for_collection) }
339
-
340
- # to_json
341
- it do
342
- subject.extend(Representer.for_collection).to_hash.must_equal(
343
- {
344
- "songs"=>[{"id"=>"1", "title"=>"Computadores Fazem Arte", "links"=>{"album"=>"9", "musicians"=>["1", "2"], "composer"=>"10", "listeners"=>["8"]}}, {"id"=>"1", "title"=>"Computadores Fazem Arte", "links"=>{"album"=>"9", "musicians"=>["1", "2"], "composer"=>"10", "listeners"=>["8"]}}],
345
- "links"=>{"songs.album"=>{"href"=>"http://example.com/albums/{songs.album}", "type"=>"album"}
346
- }
347
- }
348
- )
349
- end
350
- end
351
-
352
- class CompoundCollectionUsingExtend < self
353
- module SongRepresenter
354
- include Roar::JSON::JSONAPI
355
-
356
- type :songs
357
- property :id
358
- property :title
359
- end
360
-
361
- module AlbumRepresenter
362
- include Roar::JSON::JSONAPI
363
-
364
- type :albums
365
- property :id
366
- compound do
367
- collection :songs, extend: SongRepresenter
368
- end
369
- end
370
-
371
- let(:songs) do
372
- struct = Struct.new(:id, :title)
373
- [struct.new(1, 'Stand Up'), struct.new(2, 'Audition Mantra')]
374
- end
375
-
376
- let(:album) { Struct.new(:id, :songs).new(1, songs) }
377
-
378
- subject { album.extend(AlbumRepresenter) }
379
-
380
- # to_hash
381
- it do
382
- subject.to_hash.must_equal({
383
- 'albums' => { 'id' => 1 },
384
- 'linked' => {
385
- 'songs' => [
386
- {'id' => 1, 'title' => 'Stand Up'},
387
- {'id' => 2, 'title' => 'Audition Mantra'}
388
- ]
389
- }
390
- })
391
- end
392
- end
393
-
394
- class ExplicitMeta < self
395
- module Representer
396
- include Roar::JSON::JSONAPI
397
-
398
- type :songs
399
- property :id
400
-
401
- meta do
402
- property :page
403
- end
404
- end
405
-
406
- module Page
407
- def page
408
- 2
409
- end
410
- end
411
-
412
- let (:song) { Struct.new(:id).new(1) }
413
-
414
- subject { [song, song].extend(Representer.for_collection).extend(Page) }
415
-
416
- # to_json
417
- it do
418
- subject.to_hash.must_equal(
419
- {
420
- "songs"=>[{"id"=>1}, {"id"=>1}],
421
- "meta" =>{"page"=>2}
422
- }
423
- )
424
- end
425
- end
426
-
427
-
428
- class ImplicitMeta < self
429
- module Representer
430
- include Roar::JSON::JSONAPI
431
-
432
- type :songs
433
- property :id
434
- end
435
-
436
- let (:song) { Struct.new(:id).new(1) }
437
-
438
- subject { [song, song].extend(Representer.for_collection) }
439
-
440
- # to_json
441
- it do
442
- subject.to_hash("meta" => {"page" => 2}).must_equal(
443
- {
444
- "songs"=>[{"id"=>1}, {"id"=>1}],
445
- "meta" =>{"page"=>2}
446
- }
447
- )
448
- end
449
- end
450
- end
451
- end
data/test/lib/runner.rb DELETED
@@ -1,134 +0,0 @@
1
- require 'open-uri'
2
- require 'net/http'
3
- require 'timeout'
4
-
5
- # Helps you spinning up and shutting down your own sinatra app. This is especially helpful for running
6
- # real network tests against a sinatra backend.
7
- #
8
- # The backend server could look like the following (in test/server.rb).
9
- #
10
- # require "sinatra"
11
- #
12
- # get "/" do
13
- # "Cheers from test server"
14
- # end
15
- #
16
- # get "/ping" do
17
- # "1"
18
- # end
19
- #
20
- # Note that you need to implement a ping action for internal use.
21
- #
22
- # Next, you need to write your runner.
23
- #
24
- # require 'sinatra/runner'
25
- #
26
- # class Runner < Sinatra::Runner
27
- # def app_file
28
- # File.expand_path("../server.rb", __FILE__)
29
- # end
30
- # end
31
- #
32
- # Override Runner#app_file, #command, #port, #protocol and #ping_path for customization.
33
- #
34
- # Whereever you need this test backend, here's how you manage it. The following example assumes you
35
- # have a test in your app that needs to be run against your test backend.
36
- #
37
- # runner = ServerRunner.new
38
- # runner.run
39
- #
40
- # # ..tests against localhost:4567 here..
41
- #
42
- # runner.kill
43
- #
44
- # For an example, check https://github.com/apotonick/roar/blob/master/test/test_helper.rb
45
- module Sinatra
46
- class Runner
47
- def app_file
48
- File.expand_path("../server.rb", __FILE__)
49
- end
50
-
51
- def run
52
- #puts command
53
- @pipe = start
54
- @started = Time.now
55
- warn "#{server} up and running on port #{port}" if ping
56
- end
57
-
58
- def kill
59
- return unless pipe
60
- Process.kill("KILL", pipe.pid)
61
- rescue NotImplementedError
62
- system "kill -9 #{pipe.pid}"
63
- rescue Errno::ESRCH
64
- end
65
-
66
- def get(url)
67
- Timeout.timeout(1) { get_url("#{protocol}://127.0.0.1:#{port}#{url}") }
68
- end
69
-
70
- def log
71
- @log ||= ""
72
- loop { @log << pipe.read_nonblock(1) }
73
- rescue Exception
74
- @log
75
- end
76
-
77
- private
78
- attr_accessor :pipe
79
-
80
- def start
81
- IO.popen(command)
82
- end
83
-
84
- def command # to be overwritten
85
- "bundle exec ruby #{app_file} -p #{port} -e production"
86
- end
87
-
88
- def ping(timeout=30)
89
- loop do
90
- return if alive?
91
- if Time.now - @started > timeout
92
- $stderr.puts command, log
93
- fail "timeout"
94
- else
95
- sleep 0.1
96
- end
97
- end
98
- end
99
-
100
- def alive?
101
- 3.times { get(ping_path) }
102
- true
103
- rescue Errno::ECONNREFUSED, Errno::ECONNRESET, EOFError, SystemCallError, OpenURI::HTTPError, Timeout::Error
104
- false
105
- end
106
-
107
- def ping_path # to be overwritten
108
- '/ping'
109
- end
110
-
111
- def port # to be overwritten
112
- 4567
113
- end
114
-
115
- def protocol
116
- "http"
117
- end
118
-
119
- def get_url(url)
120
- uri = URI.parse(url)
121
-
122
- return uri.read unless protocol == "https"
123
- get_https_url(uri)
124
- end
125
-
126
- def get_https_url(uri)
127
- http = Net::HTTP.new(uri.host, uri.port)
128
- http.use_ssl = true
129
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
130
- request = Net::HTTP::Get.new(uri.request_uri)
131
- http.request(request).body
132
- end
133
- end
134
- end