roar 1.0.2 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
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