sinatra_resource 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/.document +5 -0
  2. data/.gitignore +7 -0
  3. data/LICENSE +20 -0
  4. data/README.mdown +28 -0
  5. data/Rakefile +34 -0
  6. data/VERSION +1 -0
  7. data/examples/datacatalog/Rakefile +23 -0
  8. data/examples/datacatalog/app.rb +15 -0
  9. data/examples/datacatalog/config/config.rb +66 -0
  10. data/examples/datacatalog/config/config.yml +11 -0
  11. data/examples/datacatalog/config.ru +6 -0
  12. data/examples/datacatalog/lib/base.rb +9 -0
  13. data/examples/datacatalog/lib/resource.rb +73 -0
  14. data/examples/datacatalog/lib/roles.rb +15 -0
  15. data/examples/datacatalog/models/categorization.rb +31 -0
  16. data/examples/datacatalog/models/category.rb +28 -0
  17. data/examples/datacatalog/models/source.rb +33 -0
  18. data/examples/datacatalog/models/user.rb +51 -0
  19. data/examples/datacatalog/resources/categories.rb +32 -0
  20. data/examples/datacatalog/resources/sources.rb +35 -0
  21. data/examples/datacatalog/resources/users.rb +26 -0
  22. data/examples/datacatalog/tasks/db.rake +29 -0
  23. data/examples/datacatalog/tasks/test.rake +16 -0
  24. data/examples/datacatalog/test/helpers/assertions/assert_include.rb +17 -0
  25. data/examples/datacatalog/test/helpers/assertions/assert_not_include.rb +17 -0
  26. data/examples/datacatalog/test/helpers/lib/model_factories.rb +49 -0
  27. data/examples/datacatalog/test/helpers/lib/model_helpers.rb +30 -0
  28. data/examples/datacatalog/test/helpers/lib/request_helpers.rb +53 -0
  29. data/examples/datacatalog/test/helpers/model_test_helper.rb +5 -0
  30. data/examples/datacatalog/test/helpers/resource_test_helper.rb +5 -0
  31. data/examples/datacatalog/test/helpers/shared/api_keys.rb +48 -0
  32. data/examples/datacatalog/test/helpers/shared/common_body_responses.rb +15 -0
  33. data/examples/datacatalog/test/helpers/shared/status_codes.rb +61 -0
  34. data/examples/datacatalog/test/helpers/test_cases/model_test_case.rb +6 -0
  35. data/examples/datacatalog/test/helpers/test_cases/resource_test_case.rb +36 -0
  36. data/examples/datacatalog/test/helpers/test_helper.rb +36 -0
  37. data/examples/datacatalog/test/models/categorization_test.rb +40 -0
  38. data/examples/datacatalog/test/models/category_test.rb +35 -0
  39. data/examples/datacatalog/test/models/source_test.rb +37 -0
  40. data/examples/datacatalog/test/models/user_test.rb +77 -0
  41. data/examples/datacatalog/test/resources/categories/categories_delete_test.rb +112 -0
  42. data/examples/datacatalog/test/resources/categories/categories_get_many_test.rb +58 -0
  43. data/examples/datacatalog/test/resources/categories/categories_get_one_test.rb +75 -0
  44. data/examples/datacatalog/test/resources/categories/categories_post_test.rb +135 -0
  45. data/examples/datacatalog/test/resources/categories/categories_put_test.rb +140 -0
  46. data/examples/datacatalog/test/resources/sources/sources_delete_test.rb +112 -0
  47. data/examples/datacatalog/test/resources/sources/sources_get_many_test.rb +58 -0
  48. data/examples/datacatalog/test/resources/sources/sources_get_one_test.rb +74 -0
  49. data/examples/datacatalog/test/resources/sources/sources_post_test.rb +184 -0
  50. data/examples/datacatalog/test/resources/sources/sources_put_test.rb +227 -0
  51. data/examples/datacatalog/test/resources/users/users_delete_test.rb +134 -0
  52. data/examples/datacatalog/test/resources/users/users_get_many_test.rb +111 -0
  53. data/examples/datacatalog/test/resources/users/users_get_one_test.rb +75 -0
  54. data/examples/datacatalog/test/resources/users/users_post_test.rb +142 -0
  55. data/examples/datacatalog/test/resources/users/users_put_test.rb +171 -0
  56. data/lib/builder/helpers.rb +319 -0
  57. data/lib/builder/mongo_helpers.rb +70 -0
  58. data/lib/builder.rb +84 -0
  59. data/lib/exceptions.rb +10 -0
  60. data/lib/resource.rb +171 -0
  61. data/lib/roles.rb +163 -0
  62. data/lib/sinatra_resource.rb +6 -0
  63. data/notes/keywords.mdown +1 -0
  64. data/notes/permissions.mdown +181 -0
  65. data/notes/questions.mdown +18 -0
  66. data/notes/see_also.mdown +3 -0
  67. data/notes/synonyms.mdown +7 -0
  68. data/notes/to_do.mdown +7 -0
  69. data/notes/uniform_interface.mdown +22 -0
  70. data/sinatra_resource.gemspec +183 -0
  71. data/spec/sinatra_resource_spec.rb +7 -0
  72. data/spec/spec_helper.rb +9 -0
  73. data/tasks/spec.rake +13 -0
  74. data/tasks/yard.rake +13 -0
  75. metadata +253 -0
@@ -0,0 +1,49 @@
1
+ module ModelFactories
2
+
3
+ def create_source(custom={})
4
+ create_model!(DataCatalog::Source, custom, {
5
+ :title => "Healthcare Spending Data",
6
+ :url => "http://data.gov/details/23",
7
+ })
8
+ end
9
+
10
+ def create_category(custom={})
11
+ create_model!(DataCatalog::Category, custom, {
12
+ :name => "Sample Category",
13
+ })
14
+ end
15
+
16
+ def create_categorization(custom={})
17
+ create_model!(DataCatalog::Categorization, custom, {
18
+ :source_id => "",
19
+ :category_id => "",
20
+ })
21
+ end
22
+
23
+ def create_user(custom={})
24
+ create_model!(DataCatalog::User, custom, {
25
+ :name => "Sample User",
26
+ :email => "sample.user@inter.net",
27
+ :role => "basic",
28
+ })
29
+ end
30
+
31
+ protected
32
+
33
+ def create_model!(klass, custom, required)
34
+ model = klass.create(required.merge(custom))
35
+ unless model.valid?
36
+ raise "Invalid #{klass}: #{model.errors.errors.inspect}"
37
+ end
38
+ model
39
+ end
40
+
41
+ def new_model!(klass, custom, required)
42
+ model = klass.new(required.merge(custom))
43
+ unless model.valid?
44
+ raise "Invalid #{klass}: #{model.errors.errors.inspect}"
45
+ end
46
+ model
47
+ end
48
+
49
+ end
@@ -0,0 +1,30 @@
1
+ module ModelHelpers
2
+
3
+ def self.included(includee)
4
+ includee.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+
9
+ # Is a document (looked up from +symbol+) missing +key+?
10
+ #
11
+ # @param [Symbol] document
12
+ #
13
+ # @param [Symbol] missing
14
+ def missing_key(symbol, key)
15
+ test "should be invalid" do
16
+ document = instance_variable_get("@#{symbol}")
17
+ assert_equal false, document.valid?
18
+ end
19
+
20
+ test "should have errors" do
21
+ document = instance_variable_get("@#{symbol}")
22
+ document.valid?
23
+ expected = "can't be empty"
24
+ assert_include expected, document.errors.errors[key]
25
+ end
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,53 @@
1
+ module RequestHelpers
2
+
3
+ def parsed_response_body
4
+ s = last_response.body
5
+ # puts "\n== parsed_response_body"
6
+ # puts "s : #{s.inspect}"
7
+ if s == ""
8
+ nil
9
+ else
10
+ Crack::JSON.parse(s)
11
+ end
12
+ end
13
+
14
+ def assert_properties(correct, parsed_document)
15
+ correct.each do |property|
16
+ assert_include property, parsed_document
17
+ end
18
+ assert_equal [], parsed_document.keys - correct
19
+ end
20
+
21
+ def self.included(includee)
22
+ includee.extend(ClassMethods)
23
+ end
24
+
25
+ module ClassMethods
26
+ def doc_properties(correct)
27
+ test "document should only have correct attributes" do
28
+ assert_properties(correct, parsed_response_body)
29
+ end
30
+ end
31
+
32
+ def docs_properties(correct)
33
+ test "documents should only have correct attributes" do
34
+ parsed_response_body.each do |parsed|
35
+ assert_properties(correct, parsed)
36
+ end
37
+ end
38
+ end
39
+
40
+ def invalid_param(s)
41
+ test "should report #{s} as invalid" do
42
+ assert_include s.to_s, parsed_response_body["errors"]["invalid_params"]
43
+ end
44
+ end
45
+
46
+ def missing_param(s)
47
+ test "should report missing #{s}" do
48
+ assert_include "can't be empty", parsed_response_body["errors"][s.to_s]
49
+ end
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,5 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ Config.setup_mongomapper
4
+ base = File.dirname(__FILE__)
5
+ Dir.glob(base + '/../../models/*.rb').each { |f| require f }
@@ -0,0 +1,5 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ require File.dirname(__FILE__) + '/../../app'
3
+
4
+ BAD_API_KEY = "123400005678"
5
+ FAKE_ID = Mongo::ObjectID.new.to_s
@@ -0,0 +1,48 @@
1
+ class ResourceTestCase
2
+
3
+ shared "return 400 because no parameters were given" do
4
+ use "return 400 Bad Request"
5
+
6
+ test "body should say no parameters were given" do
7
+ assert_include "errors", parsed_response_body
8
+ assert_include "no_params", parsed_response_body["errors"]
9
+ end
10
+ end
11
+
12
+ shared "return 400 because parameters were not empty" do
13
+ use "return 400 Bad Request"
14
+
15
+ test "body should say parameters were non-empty" do
16
+ assert_include "errors", parsed_response_body
17
+ assert_include "non_empty_params", parsed_response_body["errors"]
18
+ end
19
+ end
20
+
21
+ shared "return 401 because the API key is invalid" do
22
+ use "return 401 Unauthorized"
23
+
24
+ test "body should say the API key is invalid" do
25
+ assert_include "errors", parsed_response_body
26
+ assert_include "invalid_api_key", parsed_response_body["errors"]
27
+ end
28
+ end
29
+
30
+ shared "return 401 because the API key is missing" do
31
+ use "return 401 Unauthorized"
32
+
33
+ test "body should say the API key is missing" do
34
+ assert_include "errors", parsed_response_body
35
+ assert_include "missing_api_key", parsed_response_body["errors"]
36
+ end
37
+ end
38
+
39
+ shared "return 401 because the API key is unauthorized" do
40
+ use "return 401 Unauthorized"
41
+
42
+ test "body should say the API key is unauthorized" do
43
+ assert_include "errors", parsed_response_body
44
+ assert_include "unauthorized_api_key", parsed_response_body["errors"]
45
+ end
46
+ end
47
+
48
+ end
@@ -0,0 +1,15 @@
1
+ class ResourceTestCase
2
+
3
+ shared "return an empty response body" do
4
+ test "should return nil" do
5
+ assert_equal nil, parsed_response_body
6
+ end
7
+ end
8
+
9
+ shared "return an empty list response body" do
10
+ test "should return []" do
11
+ assert_equal [], parsed_response_body
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,61 @@
1
+ class ResourceTestCase
2
+
3
+ shared "return 200 Ok" do
4
+ test "status should be 200 Ok" do
5
+ assert_equal 200, last_response.status
6
+ end
7
+ end
8
+
9
+ shared "return 201 Created" do
10
+ test "status should be 201 Created" do
11
+ assert_equal 201, last_response.status
12
+ end
13
+
14
+ test "location header should start with http://localhost" do
15
+ assert_include "Location", last_response.headers
16
+ generic_uri = %r{^http://localhost}
17
+ assert_match generic_uri, last_response.headers["Location"]
18
+ end
19
+ end
20
+
21
+ shared "return 204 No Content" do
22
+ test "status should be 204 No Content" do
23
+ assert_equal 204, last_response.status
24
+ end
25
+
26
+ test "body should be empty" do
27
+ assert_equal "", last_response.body
28
+ end
29
+ end
30
+
31
+ shared "return 400 Bad Request" do
32
+ test "status should be 400 Bad Request" do
33
+ assert_equal 400, last_response.status
34
+ end
35
+ end
36
+
37
+ shared "return 401 Unauthorized" do
38
+ test "status should be 401 Unauthorized" do
39
+ assert_equal 401, last_response.status
40
+ end
41
+ end
42
+
43
+ shared "return 403 Forbidden" do
44
+ test "status should be 403 Forbidden" do
45
+ assert_equal 403, last_response.status
46
+ end
47
+ end
48
+
49
+ shared "return 404 Not Found" do
50
+ test "status should be 404 Not Found" do
51
+ assert_equal 404, last_response.status
52
+ end
53
+ end
54
+
55
+ shared "return 409 Conflict" do
56
+ test "status should be 409 Conflict" do
57
+ assert_equal 409, last_response.status
58
+ end
59
+ end
60
+
61
+ end
@@ -0,0 +1,6 @@
1
+ class ModelTestCase < Test::Unit::TestCase
2
+
3
+ include ModelFactories
4
+ include ModelHelpers
5
+
6
+ end
@@ -0,0 +1,36 @@
1
+ class ResourceTestCase < Test::Unit::TestCase
2
+
3
+ include Rack::Test::Methods
4
+ include RequestHelpers
5
+ include ModelFactories
6
+
7
+ before :all do
8
+ @users_by_role = {}
9
+ %w(basic curator admin).map do |role|
10
+ @users_by_role[role] = create_user(
11
+ :name => "#{role} User",
12
+ :email => "#{role}-user@inter.net",
13
+ :role => role
14
+ )
15
+ end
16
+ end
17
+
18
+ after :all do
19
+ @users_by_role.each_pair { |role, user| user.destroy }
20
+ end
21
+
22
+ def user_for(role)
23
+ @users_by_role[role]
24
+ end
25
+
26
+ def api_key_for(role)
27
+ key = @users_by_role[role]._api_key
28
+ raise "API key not found" unless key
29
+ key
30
+ end
31
+
32
+ def valid_params_for(role)
33
+ @valid_params.merge(:api_key => api_key_for(role))
34
+ end
35
+
36
+ end
@@ -0,0 +1,36 @@
1
+ require 'rubygems'
2
+
3
+ require 'test/unit'
4
+
5
+ require 'rack/test'
6
+ require 'rr'
7
+
8
+ gem 'crack', '>= 0.1.4'
9
+ require 'crack/json'
10
+
11
+ gem 'djsun-context', '>= 0.5.6'
12
+ require 'context'
13
+
14
+ gem 'jeremymcanally-pending', '>= 0.1'
15
+ require 'pending'
16
+
17
+ base = File.dirname(__FILE__)
18
+ Dir.glob(base + '/lib/*.rb' ).each { |f| require f }
19
+ Dir.glob(base + '/test_cases/*.rb').each { |f| require f }
20
+ Dir.glob(base + '/assertions/*.rb').each { |f| require f }
21
+ Dir.glob(base + '/shared/*.rb' ).each { |f| require f }
22
+
23
+ require File.dirname(__FILE__) + '/../../config/config'
24
+ Config.environment = 'test'
25
+
26
+ class Test::Unit::TestCase
27
+ include RR::Adapters::TestUnit
28
+ end
29
+
30
+ # If you use `rake test` the database will automatically get dropped.
31
+ # But if you are running inside TextMate, it is handy to automatically drop the
32
+ # database here:
33
+ if ENV["TM_APP_PATH"]
34
+ puts "TextMate environment detected. Dropping test database..."
35
+ Config.drop_database
36
+ end
@@ -0,0 +1,40 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../helpers/model_test_helper')
2
+
3
+ class CategorizationTest < ModelTestCase
4
+
5
+ include DataCatalog
6
+
7
+ context "Categorization" do
8
+ before do
9
+ @source = create_source
10
+ @category = create_category
11
+ @categorization = create_categorization(
12
+ :source_id => @source.id,
13
+ :category_id => @category.id
14
+ )
15
+ end
16
+
17
+ after do
18
+ @source.destroy
19
+ @category.destroy
20
+ @categorization.destroy
21
+ end
22
+
23
+ test "Categorization#source is correct" do
24
+ assert_equal @source, @categorization.source
25
+ end
26
+
27
+ test "Categorization#category is correct" do
28
+ assert_equal @category, @categorization.category
29
+ end
30
+
31
+ test "Source#categorization is correct" do
32
+ assert_equal [@categorization], @source.categorizations
33
+ end
34
+
35
+ test "Category#categorization is correct" do
36
+ assert_equal [@categorization], @category.categorizations
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,35 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../helpers/model_test_helper')
2
+
3
+ class CategoryTest < ModelTestCase
4
+
5
+ include DataCatalog
6
+
7
+ context "Category" do
8
+ before do
9
+ @required = {
10
+ :name => "Science & Technology"
11
+ }
12
+ end
13
+
14
+ context "correct params" do
15
+ before do
16
+ @category = Category.new(@required)
17
+ end
18
+
19
+ test "should be valid" do
20
+ assert_equal true, @category.valid?
21
+ end
22
+ end
23
+
24
+ [:name].each do |missing|
25
+ context "missing #{missing}" do
26
+ before do
27
+ @category = Category.new(@required.delete_if { |k, v| k == missing })
28
+ end
29
+
30
+ missing_key(:category, missing)
31
+ end
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,37 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../helpers/model_test_helper')
2
+
3
+ class SourceTest < ModelTestCase
4
+
5
+ include DataCatalog
6
+
7
+ context "Source" do
8
+ before do
9
+ @required = {
10
+ :title => "Treasury 2009 Summary",
11
+ :url => "http://moneybags.gov/data/2009"
12
+ }
13
+ end
14
+
15
+ context "correct params" do
16
+ before do
17
+ @source = Source.new(@required)
18
+ end
19
+
20
+ test "should be valid" do
21
+ assert_equal true, @source.valid?
22
+ end
23
+ end
24
+
25
+ [:title, :url].each do |missing|
26
+ context "missing #{missing}" do
27
+ before do
28
+ @source = Source.new(@required.delete_if { |k, v| k == missing })
29
+ end
30
+
31
+ missing_key(:source, missing)
32
+ end
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,77 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../helpers/model_test_helper')
2
+
3
+ class UserTest < ModelTestCase
4
+
5
+ include DataCatalog
6
+
7
+ context "User#new" do
8
+ before do
9
+ @required = {
10
+ :name => "Sample User",
11
+ :role => "basic"
12
+ }
13
+ end
14
+
15
+ context "correct params" do
16
+ before do
17
+ @user = User.new(@required)
18
+ end
19
+
20
+ test "should be valid" do
21
+ assert_equal true, @user.valid?
22
+ end
23
+
24
+ test "should not set api_key" do
25
+ assert_equal nil, @user._api_key
26
+ end
27
+ end
28
+
29
+ [:name, :role].each do |missing|
30
+ context "missing #{missing}" do
31
+ before do
32
+ @user = User.new(@required.delete_if { |k, v| k == missing })
33
+ end
34
+
35
+ missing_key(:user, missing)
36
+ end
37
+ end
38
+
39
+ context "invalid role" do
40
+ before do
41
+ @user = User.new(@required.merge(:role => "owner"))
42
+ # owner is not a 'fixed' role, it is a 'relative' role
43
+ end
44
+
45
+ test "should be invalid" do
46
+ assert_equal false, @user.valid?
47
+ end
48
+
49
+ test "should have errors" do
50
+ @user.valid?
51
+ expected = %(must be in ["basic", "curator", "admin"])
52
+ assert_include expected, @user.errors.errors[:role]
53
+ end
54
+ end
55
+
56
+ context "User#create" do
57
+ context "correct params" do
58
+ before do
59
+ @user = User.create(@required)
60
+ end
61
+
62
+ after do
63
+ @user.destroy
64
+ end
65
+
66
+ test "should be valid" do
67
+ assert_equal true, @user.valid?
68
+ end
69
+
70
+ test "should set api_key" do
71
+ assert_not_equal nil, @user._api_key
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ end
@@ -0,0 +1,112 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../helpers/resource_test_helper')
2
+
3
+ class CategoriesDeleteResourceTest < ResourceTestCase
4
+
5
+ include DataCatalog
6
+
7
+ def app; Categories end
8
+
9
+ before do
10
+ @category = create_category
11
+ @category_count = Category.all.length
12
+ end
13
+
14
+ after do
15
+ @category.destroy
16
+ end
17
+
18
+ shared "no change in category count" do
19
+ test "should not change number of category documents in database" do
20
+ assert_equal @category_count, Category.all.length
21
+ end
22
+ end
23
+
24
+ shared "one less category" do
25
+ test "should remove one category document from database" do
26
+ assert_equal @category_count - 1, Category.all.length
27
+ end
28
+ end
29
+
30
+ context "delete /:id" do
31
+ context "anonymous" do
32
+ before do
33
+ delete "/#{@category.id}"
34
+ end
35
+
36
+ use "return 401 because the API key is missing"
37
+ use "no change in category count"
38
+ end
39
+
40
+ context "incorrect API key" do
41
+ before do
42
+ delete "/#{@category.id}", :api_key => BAD_API_KEY
43
+ end
44
+
45
+ use "return 401 because the API key is invalid"
46
+ use "no change in category count"
47
+ end
48
+ end
49
+
50
+ %w(basic).each do |role|
51
+ context "#{role} : delete /:fake_id" do
52
+ before do
53
+ delete "/#{FAKE_ID}", :api_key => api_key_for(role)
54
+ end
55
+
56
+ use "return 401 Unauthorized"
57
+ use "no change in category count"
58
+ end
59
+
60
+ context "#{role} : delete /:id" do
61
+ before do
62
+ delete "/#{@category.id}",
63
+ :api_key => api_key_for(role),
64
+ :key => "value"
65
+ end
66
+
67
+ use "return 401 Unauthorized"
68
+ use "no change in category count"
69
+ end
70
+
71
+ context "#{role} : delete /:id" do
72
+ before do
73
+ delete "/#{@category.id}", :api_key => api_key_for(role)
74
+ end
75
+
76
+ use "return 401 Unauthorized"
77
+ use "no change in category count"
78
+ end
79
+ end
80
+
81
+ %w(curator admin).each do |role|
82
+ context "#{role} : delete /:fake_id" do
83
+ before do
84
+ delete "/#{FAKE_ID}", :api_key => api_key_for(role)
85
+ end
86
+
87
+ use "return 404 Not Found"
88
+ use "no change in category count"
89
+ end
90
+
91
+ context "#{role} : delete /:id" do
92
+ before do
93
+ delete "/#{@category.id}",
94
+ :api_key => api_key_for(role),
95
+ :key => "value"
96
+ end
97
+
98
+ use "return 400 because parameters were not empty"
99
+ use "no change in category count"
100
+ end
101
+
102
+ context "#{role} : delete /:id" do
103
+ before do
104
+ delete "/#{@category.id}", :api_key => api_key_for(role)
105
+ end
106
+
107
+ use "return 204 No Content"
108
+ use "one less category"
109
+ end
110
+ end
111
+
112
+ end