alf-rest 0.14.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.
Files changed (44) hide show
  1. data/CHANGELOG.md +5 -0
  2. data/Gemfile +21 -0
  3. data/Gemfile.lock +71 -0
  4. data/LICENCE.md +22 -0
  5. data/Manifest.txt +12 -0
  6. data/README.md +11 -0
  7. data/Rakefile +11 -0
  8. data/lib/alf-rest.rb +1 -0
  9. data/lib/alf/rest.rb +30 -0
  10. data/lib/alf/rest/alf-ext/renderer.rb +16 -0
  11. data/lib/alf/rest/alf-ext/unit_of_work.rb +3 -0
  12. data/lib/alf/rest/alf-ext/unit_of_work/delete.rb +21 -0
  13. data/lib/alf/rest/alf-ext/unit_of_work/insert.rb +22 -0
  14. data/lib/alf/rest/alf-ext/unit_of_work/update.rb +22 -0
  15. data/lib/alf/rest/config.rb +55 -0
  16. data/lib/alf/rest/errors.rb +5 -0
  17. data/lib/alf/rest/helpers.rb +68 -0
  18. data/lib/alf/rest/loader.rb +5 -0
  19. data/lib/alf/rest/middleware.rb +19 -0
  20. data/lib/alf/rest/payload.rb +12 -0
  21. data/lib/alf/rest/payload/client.rb +22 -0
  22. data/lib/alf/rest/request.rb +43 -0
  23. data/lib/alf/rest/response.rb +21 -0
  24. data/lib/alf/rest/test.rb +16 -0
  25. data/lib/alf/rest/test/client.rb +83 -0
  26. data/lib/alf/rest/test/ext.rb +7 -0
  27. data/lib/alf/rest/test/steps.rb +286 -0
  28. data/lib/alf/rest/version.rb +16 -0
  29. data/lib/sinatra/alf-rest.rb +73 -0
  30. data/spec/fixtures/sap.db +0 -0
  31. data/spec/integration/sinatra/rest_get/test_accept.rb +98 -0
  32. data/spec/integration/spec_helper.rb +27 -0
  33. data/spec/test_rest.rb +10 -0
  34. data/spec/unit/config/test_database.rb +28 -0
  35. data/spec/unit/config/test_viewpoint.rb +18 -0
  36. data/spec/unit/ext/renderer/test_from_http_accept.rb +50 -0
  37. data/spec/unit/ext/renderer/test_supported_media_types.rb +10 -0
  38. data/spec/unit/middleware/test_behavior.rb +55 -0
  39. data/spec/unit/request/test_to_relation.rb +56 -0
  40. data/spec/unit/spec_helper.rb +35 -0
  41. data/spec/unit/test_rest.rb +10 -0
  42. data/tasks/gem.rake +8 -0
  43. data/tasks/test.rake +17 -0
  44. metadata +251 -0
@@ -0,0 +1,16 @@
1
+ module Alf
2
+ module Rest
3
+ module Version
4
+
5
+ MAJOR = 0
6
+ MINOR = 14
7
+ TINY = 0
8
+
9
+ def self.to_s
10
+ [ MAJOR, MINOR, TINY ].join('.')
11
+ end
12
+
13
+ end
14
+ VERSION = Version.to_s
15
+ end
16
+ end
@@ -0,0 +1,73 @@
1
+ require 'alf-rest'
2
+ module Sinatra
3
+ module AlfRest
4
+
5
+ def rest_get(url, &bl)
6
+ get(url) do
7
+ Alf::Rest::Response.new(env){|r|
8
+ r.body = instance_exec(&bl)
9
+ }.finish
10
+ end
11
+ end
12
+
13
+ def rest_post(url, heading, &bl)
14
+ post(url) do
15
+ input = Alf::Rest::Request.new(env, heading).to_tuple
16
+ result = instance_exec(input, &bl)
17
+
18
+ Alf::Rest::Response.new(env){|r|
19
+ r.status = result.rack_status
20
+ r.body = result.rack_body
21
+ if not(location_set?) and (loc = result.rack_location(request))
22
+ r["Location"] = loc
23
+ end
24
+ }.finish
25
+ end
26
+ end
27
+
28
+ def rest_put(url, heading, &bl)
29
+ put(url) do
30
+ input = Alf::Rest::Request.new(env, heading).to_tuple
31
+ result = instance_exec(input, &bl)
32
+
33
+ Alf::Rest::Response.new(env){|r|
34
+ r.status = result.rack_status
35
+ r.body = result.rack_body
36
+ r["Location"] = request.path unless location_set?
37
+ }.finish
38
+ end
39
+ end
40
+
41
+ def rest_delete(url, heading, &bl)
42
+ delete(url) do
43
+ input = Tuple[heading].coerce(params.select{|k| heading[k.to_sym]})
44
+ result = instance_exec(input, &bl)
45
+
46
+ Alf::Rest::Response.new(env){|r|
47
+ r.status = result.rack_status
48
+ r.body = result.rack_body
49
+ if not(location_set?) and (loc = result.rack_location(request))
50
+ r["Location"] = loc
51
+ end
52
+ }.finish
53
+ end
54
+ end
55
+
56
+ def alf_rest
57
+ if block_given?
58
+ yield(settings.alf_configuration)
59
+ else
60
+ settings.alf_configuration
61
+ end
62
+ end
63
+
64
+ def self.registered(app)
65
+ config = Alf::Rest::Config.new
66
+ app.set :alf_configuration, config
67
+ app.use Alf::Rest::Middleware, config
68
+ app.helpers Alf::Rest::Helpers
69
+ end
70
+
71
+ end
72
+ register AlfRest
73
+ end
Binary file
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+ module Alf
3
+ module Rest
4
+ describe "rest_get", "Accept header" do
5
+ include Rack::Test::Methods
6
+
7
+ def app
8
+ mock_app do
9
+ rest_get '/suppliers' do
10
+ relvar{ suppliers }
11
+ end
12
+ end
13
+ end
14
+
15
+ def last_meta
16
+ [last_response.status, last_response.content_type]
17
+ end
18
+
19
+ it 'defaults to application/json when unspecified' do
20
+ #
21
+ get '/suppliers'
22
+ #
23
+ last_meta.should eq([200, "application/json"])
24
+ #
25
+ result = JSON.load(last_response.body)
26
+ result.should be_a(Array)
27
+ result.size.should eq(5)
28
+ end
29
+
30
+ it 'supports */* and fallbacks to application/json' do
31
+ #
32
+ header "Accept", "*/*"
33
+ get '/suppliers'
34
+ #
35
+ last_meta.should eq([200, "application/json"])
36
+ #
37
+ result = JSON.load(last_response.body)
38
+ result.should be_a(Array)
39
+ result.size.should eq(5)
40
+ end
41
+
42
+ it 'supports application/json' do
43
+ #
44
+ header "Accept", "application/json"
45
+ get '/suppliers'
46
+ #
47
+ last_meta.should eq([200, "application/json"])
48
+ #
49
+ result = JSON.load(last_response.body)
50
+ result.should be_a(Array)
51
+ result.size.should eq(5)
52
+ end
53
+
54
+ it 'supports text/csv' do
55
+ #
56
+ header "Accept", "text/csv"
57
+ get '/suppliers'
58
+ #
59
+ last_meta.should eq([200, "text/csv"])
60
+ #
61
+ last_response.body.should =~ /1,Smith,20,London/
62
+ end
63
+
64
+ it 'supports text/plain' do
65
+ #
66
+ header "Accept", "text/plain"
67
+ get '/suppliers'
68
+ #
69
+ last_meta.should eq([200, "text/plain"])
70
+ #
71
+ last_response.body.should =~ /\|\s+1\s+\|\s+Smith\s+\|/
72
+ end
73
+
74
+ it 'supports text/x-yaml' do
75
+ #
76
+ header "Accept", "text/x-yaml"
77
+ get '/suppliers'
78
+ #
79
+ last_meta.should eq([200, "text/x-yaml"])
80
+ #
81
+ last_response.body.should =~ /- :sid: 1/
82
+ end
83
+
84
+ it 'supports a complex specification' do
85
+ #
86
+ header "Accept", "application/*, text/*;q=0.8"
87
+ get '/suppliers'
88
+ #
89
+ last_meta.should eq([200, "application/json"])
90
+ #
91
+ result = JSON.load(last_response.body)
92
+ result.should be_a(Array)
93
+ result.size.should eq(5)
94
+ end
95
+
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,27 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../../lib', __FILE__)
2
+ require 'alf-sequel'
3
+ require 'sinatra/alf-rest'
4
+ require 'rspec'
5
+ require 'path'
6
+ require 'rack/test'
7
+
8
+ module Helpers
9
+
10
+ def mock_app(&bl)
11
+ Class.new(Sinatra::Base){
12
+ register Sinatra::AlfRest
13
+ alf_rest do |cfg|
14
+ cfg.database = Alf.database(Path.backfind('fixtures/sap.db'))
15
+ end
16
+ enable :raise_errors
17
+ disable :show_exceptions
18
+ instance_eval(&bl)
19
+ }
20
+ end
21
+
22
+ end
23
+
24
+ RSpec.configure do |c|
25
+ c.include Helpers
26
+ c.extend Helpers
27
+ end
data/spec/test_rest.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+ module Alf
3
+ describe Rest do
4
+
5
+ it "should have a version number" do
6
+ Rest.const_defined?(:VERSION).should be_true
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+ module Alf
3
+ module Rest
4
+ describe Config, "database=" do
5
+
6
+ let(:config){ Config.new }
7
+
8
+ subject{
9
+ config.database = db
10
+ config.database
11
+ }
12
+
13
+ context 'with a Alf::Database' do
14
+ let(:db){ Alf.database(Path.dir) }
15
+
16
+ it{ should be_a(Alf::Database) }
17
+ it{ should be(db) }
18
+ end
19
+
20
+ context 'with an adapter' do
21
+ let(:db){ Path.dir }
22
+
23
+ it{ should be_a(Alf::Database) }
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+ module Alf
3
+ module Rest
4
+ describe Config, "viewpoint=" do
5
+
6
+ let(:config){ Config.new }
7
+
8
+ subject{ config.viewpoint = Alf::Viewpoint::NATIVE }
9
+
10
+ it 'sets it on the connection options' do
11
+ subject
12
+ config.connection_options[:viewpoint].should be(Alf::Viewpoint::NATIVE)
13
+ config.viewpoint.should be(Alf::Viewpoint::NATIVE)
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+ module Alf
3
+ describe Renderer, "from_http_accept" do
4
+
5
+ subject{ Renderer.from_http_accept(accept) }
6
+
7
+ context 'application/json' do
8
+ let(:accept){ "application/json" }
9
+
10
+ it{ should be(Renderer::JSON) }
11
+ end
12
+
13
+ context 'text/plain' do
14
+ let(:accept){ "text/plain" }
15
+
16
+ it{ should be(Renderer::Text) }
17
+ end
18
+
19
+ context 'text/csv' do
20
+ let(:accept){ "text/csv" }
21
+
22
+ it{ should be(Renderer::CSV) }
23
+ end
24
+
25
+ context 'text/x-yaml' do
26
+ let(:accept){ "text/x-yaml" }
27
+
28
+ it{ should be(Renderer::YAML) }
29
+ end
30
+
31
+ context '*/*' do
32
+ let(:accept){ "*/*" }
33
+
34
+ it{ should be(Renderer::JSON) }
35
+ end
36
+
37
+ context 'text/unknown' do
38
+ let(:accept){ "text/unknown" }
39
+
40
+ it{ should be(nil) }
41
+ end
42
+
43
+ context 'a complex one' do
44
+ let(:accept){ "text/unknown, text/*;q=0.8, */*;q=0.5" }
45
+
46
+ it{ should be(Renderer::CSV) }
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+ module Alf
3
+ describe Renderer, "supported_media_types" do
4
+
5
+ subject{ Renderer.supported_media_types.sort }
6
+
7
+ it{ should eq(["application/json", "text/csv", "text/plain", "text/x-yaml"]) }
8
+
9
+ end
10
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+ module Alf
3
+ module Rest
4
+ describe Middleware, 'its Rack behavior' do
5
+ include Rack::Test::Methods
6
+
7
+ def mock_app(&bl)
8
+ Class.new(Sinatra::Base) do
9
+ set :environment, :test
10
+
11
+ disable :dump_errors
12
+ disable :logging
13
+ disable :dump_errors
14
+ enable :raise_errors
15
+ disable :show_exceptions
16
+
17
+ class_config = nil
18
+ use Alf::Rest do |cfg|
19
+ cfg.database = Alf::Database.new(Path.dir)
20
+ class_config = cfg
21
+ bl.call(cfg) if bl
22
+ end
23
+
24
+ get '/check-config' do
25
+ check = env[Rest::RACK_CONFIG_KEY].is_a?(Config)
26
+ check &= env[Rest::RACK_CONFIG_KEY] != class_config
27
+ check.to_s
28
+ end
29
+
30
+ get '/generate-error' do
31
+ raise "blah"
32
+ end
33
+ end
34
+ end
35
+
36
+ context 'on a default app' do
37
+ let(:app){ mock_app }
38
+
39
+ it 'sets a duplicata of the configuration' do
40
+ get '/check-config'
41
+ last_response.body.should eq("true")
42
+ end
43
+ end
44
+
45
+ context 'when an error occurs' do
46
+ let(:app){ mock_app }
47
+
48
+ it 'raises the Error outside the app' do
49
+ lambda{ get '/generate-error' }.should raise_error(/blah/)
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+ module Alf
3
+ module Rest
4
+ describe Request, "to_relation" do
5
+
6
+ let(:request){ Alf::Rest::Request.new(env, heading) }
7
+
8
+ let(:heading){ {status: Integer, city: String} }
9
+
10
+ subject{ request.to_relation }
11
+
12
+ context 'with application/x-www-form-urlencoded' do
13
+ let(:env) { env_for("/foo", method: 'POST', params: tuple) }
14
+ let(:tuple){ {city: "London", status: "200"} }
15
+
16
+ it 'gets the iterated tuples from POST' do
17
+ subject.should eq(Relation(city: "London", status: 200))
18
+ end
19
+ end
20
+
21
+ context 'with a body conforming to Rack spec strictly' do
22
+ let(:env) { json_post_env(tuple) }
23
+ let(:tuple){ {city: "London", status: "200"} }
24
+
25
+ before do
26
+ env['rack.input'] = Struct.new(:read, :rewind).new(env['rack.input'].read)
27
+ end
28
+
29
+ it 'gets the iterated tuples from POST' do
30
+ subject.should eq(Relation(city: "London", status: 200))
31
+ end
32
+ end
33
+
34
+ context 'with a JSON encoded body' do
35
+ let(:env){ json_post_env(tuples) }
36
+
37
+ context 'when the input matches exactly' do
38
+ let(:tuples){ [{city: "London", status: "200"}] }
39
+
40
+ it 'gets the expected relation' do
41
+ subject.should eq(Relation(status: 200, city: "London"))
42
+ end
43
+ end
44
+
45
+ context 'when a projection is needed' do
46
+ let(:tuples){ [{name: "Smith", status: "200"}] }
47
+
48
+ it 'gets the expected relation' do
49
+ subject.should eq(Relation(status: 200))
50
+ end
51
+ end
52
+ end
53
+
54
+ end
55
+ end
56
+ end