sinatra_resource 0.1.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 (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