moonrope 1.3.3 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +9 -0
  3. data/Gemfile.lock +47 -0
  4. data/MIT-LICENCE +20 -0
  5. data/README.md +24 -0
  6. data/bin/moonrope +28 -0
  7. data/docs/authentication.md +114 -0
  8. data/docs/controllers.md +106 -0
  9. data/docs/exceptions.md +27 -0
  10. data/docs/introduction.md +29 -0
  11. data/docs/structures.md +214 -0
  12. data/example/authentication.rb +50 -0
  13. data/example/controllers/meta_controller.rb +14 -0
  14. data/example/controllers/users_controller.rb +92 -0
  15. data/example/structures/pet_structure.rb +12 -0
  16. data/example/structures/user_structure.rb +35 -0
  17. data/lib/moonrope.rb +5 -4
  18. data/lib/moonrope/action.rb +170 -40
  19. data/lib/moonrope/authenticator.rb +42 -0
  20. data/lib/moonrope/base.rb +67 -6
  21. data/lib/moonrope/controller.rb +4 -2
  22. data/lib/moonrope/doc_context.rb +94 -0
  23. data/lib/moonrope/doc_server.rb +123 -0
  24. data/lib/moonrope/dsl/action_dsl.rb +159 -9
  25. data/lib/moonrope/dsl/authenticator_dsl.rb +35 -0
  26. data/lib/moonrope/dsl/base_dsl.rb +21 -18
  27. data/lib/moonrope/dsl/controller_dsl.rb +60 -9
  28. data/lib/moonrope/dsl/filterable_dsl.rb +27 -0
  29. data/lib/moonrope/dsl/structure_dsl.rb +28 -2
  30. data/lib/moonrope/errors.rb +13 -0
  31. data/lib/moonrope/eval_environment.rb +82 -3
  32. data/lib/moonrope/eval_helpers.rb +47 -8
  33. data/lib/moonrope/eval_helpers/filter_helper.rb +82 -0
  34. data/lib/moonrope/guard.rb +35 -0
  35. data/lib/moonrope/html_generator.rb +65 -0
  36. data/lib/moonrope/param_set.rb +11 -1
  37. data/lib/moonrope/rack_middleware.rb +66 -37
  38. data/lib/moonrope/railtie.rb +31 -14
  39. data/lib/moonrope/request.rb +43 -15
  40. data/lib/moonrope/structure.rb +100 -18
  41. data/lib/moonrope/structure_attribute.rb +39 -0
  42. data/lib/moonrope/version.rb +1 -1
  43. data/moonrope.gemspec +21 -0
  44. data/spec/spec_helper.rb +32 -0
  45. data/spec/specs/action_spec.rb +455 -0
  46. data/spec/specs/base_spec.rb +29 -0
  47. data/spec/specs/controller_spec.rb +31 -0
  48. data/spec/specs/param_set_spec.rb +31 -0
  49. data/templates/basic/_action_form.erb +77 -0
  50. data/templates/basic/_errors_table.erb +32 -0
  51. data/templates/basic/_structure_attributes_list.erb +55 -0
  52. data/templates/basic/action.erb +168 -0
  53. data/templates/basic/assets/lock.svg +3 -0
  54. data/templates/basic/assets/reset.css +101 -0
  55. data/templates/basic/assets/style.css +348 -0
  56. data/templates/basic/assets/tool.svg +4 -0
  57. data/templates/basic/assets/try.js +157 -0
  58. data/templates/basic/authenticator.erb +52 -0
  59. data/templates/basic/controller.erb +20 -0
  60. data/templates/basic/index.erb +114 -0
  61. data/templates/basic/layout.erb +46 -0
  62. data/templates/basic/structure.erb +23 -0
  63. data/test/test_helper.rb +81 -0
  64. data/test/tests/action_access_test.rb +63 -0
  65. data/test/tests/actions_test.rb +524 -0
  66. data/test/tests/authenticators_test.rb +87 -0
  67. data/test/tests/base_test.rb +35 -0
  68. data/test/tests/controllers_test.rb +49 -0
  69. data/test/tests/eval_environment_test.rb +136 -0
  70. data/test/tests/evel_helpers_test.rb +60 -0
  71. data/test/tests/examples_test.rb +11 -0
  72. data/test/tests/helpers_test.rb +97 -0
  73. data/test/tests/param_set_test.rb +44 -0
  74. data/test/tests/rack_middleware_test.rb +131 -0
  75. data/test/tests/request_test.rb +232 -0
  76. data/test/tests/structures_param_extensions_test.rb +159 -0
  77. data/test/tests/structures_test.rb +398 -0
  78. metadata +71 -56
@@ -0,0 +1,52 @@
1
+
2
+ <% if authenticator.name == :default %>
3
+ <% set_page_title "Authentication" %>
4
+ <% set_active_nav "authenticator-default" %>
5
+ <h1>Authentication</h1>
6
+ <% else %>
7
+ <% set_page_title "#{humanize(authenticator.name.to_s.capitalize)} Authenticator" %>
8
+ <h1><%= humanize(authenticator.name.to_s.capitalize) %> Authenticator</h1>
9
+ <% set_active_nav "authenticator-#{authenticator.name.to_s}" %>
10
+ <% end %>
11
+
12
+ <p class='text'>
13
+ <%= authenticator.description %>
14
+ </p>
15
+
16
+ <h2>Authentication Headers</h2>
17
+ <p class='text'>
18
+ The following headers are used to identify yourself to the API client. These should be
19
+ sent as standard HTTP headers with any API request.
20
+ </p>
21
+ <table class='table paramTable'>
22
+ <thead>
23
+ <tr>
24
+ <th width="60%">Header</th>
25
+ <th width="40%">Example</th>
26
+ </tr>
27
+ </thead>
28
+ <% for name, options in authenticator.headers %>
29
+ <tr>
30
+ <td>
31
+ <p>
32
+ <span class='paramTable__name'><%= name %></span>
33
+ </p>
34
+ <% if options[:description] %>
35
+ <p class='paramTable__description'><%= options[:description] %></p>
36
+ <% end %>
37
+ </td>
38
+ <td><%= options[:eg] || options[:example] %> </td>
39
+ </tr>
40
+ <% end %>
41
+ </table>
42
+
43
+ <h2>Errors</h2>
44
+ <p class='text'>
45
+ The errors listed below may be raised if any issues occur when verifying your
46
+ identity with the API.
47
+ </p>
48
+ <% if authenticator.errors.empty? %>
49
+ <p><em>There are no errors which can be raised.</em></p>
50
+ <% else %>
51
+ <%= partial "errors_table", :errors => authenticator.errors %>
52
+ <% end %>
@@ -0,0 +1,20 @@
1
+ <% set_page_title controller.friendly_name || controller.name %>
2
+ <% set_active_nav "controller-#{controller.name}" %>
3
+ <h1><%= controller.friendly_name || controller.name %></h1>
4
+ <% if controller.description %>
5
+ <p class='text'><%= controller.description %></p>
6
+ <% end %>
7
+ <h2>Action</h2>
8
+ <p class='text'>
9
+ The following actions are available. Choose from the list below
10
+ to view full details of how to access them.
11
+ </p>
12
+ <ul class='standardList'>
13
+ <% for action in controller.actions.values.select { |a| a.doc != false } %>
14
+ <li>
15
+ <a class='link' href='<%= path("controllers/#{action.controller.name}/#{action.name}") %>'><%= action.title || action.name %></a>
16
+ <p class='apiURL'><span><%= full_prefix %>/</span><b><%= action.controller.name %>/<%= action.name %></b></p>
17
+ <% if action.description %><p class='meta'><%= action.description %></p><% end %>
18
+ </li>
19
+ <% end %>
20
+ </ul>
@@ -0,0 +1,114 @@
1
+ <% set_page_title "Welcome" %>
2
+ <% set_active_nav "home" %>
3
+ <h1>Welcome to our API documentation</h1>
4
+ <p class='text'>
5
+ From here you can browse the full documentation for our HTTP
6
+ API. Our API is split into sections which you can browse using
7
+ the menu on the right. If you have any questions, you can
8
+ <a href='#'>contact our team</a> and we'll be happy to help out.
9
+ </p>
10
+ <p class='text'>
11
+ Before you get started, take a few minutes to review the
12
+ information below about how to interact with our API. It includes
13
+ information about how to send requests, what response data is
14
+ sent in and how to handle errors.
15
+ </p>
16
+
17
+ <h2>Making requests</h2>
18
+ <p class='text'>
19
+ Our API works over the HTTP protocol with JSON. It is implemented
20
+ in an RPC-like manner and everything you can do with the API has
21
+ its own <em>action</em>.
22
+ </p>
23
+ <p class='text'>
24
+ All HTTP requests must be made over HTTPS to the URL shown on the
25
+ action's page in this documentation. All responses you receive from
26
+ the API will be returned in JSON. Requests should be made using the
27
+ <code>POST</code> method with any parameters encoded as JSON in the
28
+ body of the request.
29
+ </p>
30
+
31
+ <h2>Receiving responses</h2>
32
+ <p class='text'>
33
+ All responses will be returned to you encoded as JSON. You will always
34
+ receive a hash as the response which will look like the JSON below:
35
+ </p>
36
+ <pre class='code'>
37
+ {
38
+ <span class='jsonKey'>"status"</span>:<span class='jsonString'>"success"</span>,
39
+ <span class='jsonKey'>"time"</span>:<span class='jsonString'>0.123</span>,
40
+ <span class='jsonKey'>"flags"</span>:{
41
+ <span class='jsonComment'>... additional information about the request ...</span>
42
+ },
43
+ <span class='jsonKey'>"data"</span>:{
44
+ <span class='jsonComment'>... the data returned from the action ...</span>
45
+ }
46
+ }
47
+ </pre>
48
+ <p class='text'>
49
+ The <b>status</b> attribute will give you can indication about whether the
50
+ request was performed successfully or whether an error occurred. Values which
51
+ may be returned are shown below:
52
+ </p>
53
+ <ul class='standardList'>
54
+ <li>
55
+ <code>success</code> - this means that the request completed successfully
56
+ and returned the data that was expected.
57
+ </li>
58
+ <li>
59
+ <code>parameter-error</code> - the parameters provided for the action are
60
+ not valid and should be revised.
61
+ </li>
62
+ <li>
63
+ <code>error</code> - an error occurred that didn't fit into the above categories.
64
+ This will be accompanied with an error code, a descriptive message and further
65
+ attributes which may be useful. The actual potential errors for each action are
66
+ shown in the documentation.
67
+ </li>
68
+ </ul>
69
+ <p class='text'>
70
+ The <b>time</b> attribute shows how long the request took to complete on the
71
+ server side.
72
+ </p>
73
+ <p class='text'>
74
+ The <b>flags</b> attribute contains a hash of additional attributes
75
+ which are relevant to your request. For example, if you receive an array of data
76
+ it may be paginated and this pagination data will be returned in this
77
+ hash.
78
+ </p>
79
+ <p class='text'>
80
+ The <b>data</b> attribute contains the result of your request. Depending on the
81
+ status, this will either contain the data requested or details of any
82
+ error which has occurred.
83
+ </p>
84
+ <h3>A note about HTTP status code</h3>
85
+ <p class='text'>
86
+ The API does not generally use HTTP status codes to return information
87
+ about the outcome of a request. There are two supported statuses with
88
+ the API:
89
+ </p>
90
+ <ul class='standardList'>
91
+ <li>
92
+ <code>200 OK</code> - This is the code you'll usually receive.
93
+ It indicates that the response was successfully delivered and
94
+ returned to our service (although does not nessesary mean that
95
+ the action you were expecting was successful). Further status
96
+ information will be provided in the <code>status</code> attribute
97
+ on your response body.
98
+ </li>
99
+ <li>
100
+ <code>301 Moved Permanently</code> or <code>308 Permanent Redirect</code> -
101
+ This means that the API request should be sent to an alternative
102
+ URL. This may just mean you need to send your request using <code>https</code>
103
+ rather than <code>http</code> as the protocol.
104
+ </li>
105
+ <li>
106
+ <code>500 Internal Server Error</code> - This will be returned
107
+ when an error occurred within the API itself. This was not
108
+ anticipated by us and should be reported to us.
109
+ </li>
110
+ <li>
111
+ <code>503 Service Unavailable</code> - This will be returned if
112
+ API is currently unavailable for maintenance or other issue.
113
+ </li>
114
+ </ul>
@@ -0,0 +1,46 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= page_title %> - API Documentation</title>
5
+ <link href='https://fonts.googleapis.com/css?family=Lato:400,700,900' rel='stylesheet' type='text/css'>
6
+ <link href='https://fonts.googleapis.com/css?family=Droid+Sans+Mono' rel='stylesheet' type='text/css'>
7
+ <link rel="stylesheet" href="<%= asset_path('reset.css') %>">
8
+ <link rel="stylesheet" href="<%= asset_path('style.css') %>">
9
+ </head>
10
+ <body>
11
+ <section class='sidebarBackground'></section>
12
+ <section class="sidebar">
13
+ <nav>
14
+ <ul>
15
+ <li>
16
+ <a href='<%= path(:root) %>' class="<%= active_nav == 'home' ? 'active' : '' %>">
17
+ Home
18
+ </a>
19
+ </li>
20
+ <% base.authenticators.select { |k,v| v.friendly_name }.each do |id, authenticator| %>
21
+ <li>
22
+ <a href='<%= path('authenticators/' + id.to_s) %>' class="<%= active_nav == "authenticator-" + id.to_s ? 'active' : '' %>">
23
+ <%= authenticator.friendly_name %>
24
+ </a>
25
+ </li>
26
+ <% end %>
27
+ <% for controller in base.controllers.select { |c| c.doc != false }.sort_by { |c| c.name.to_s } %>
28
+ <li>
29
+ <a href='<%= path("controllers/#{controller.name}") %>' class="<%= active_nav == "controller-#{controller.name}" ? 'active' : '' %>">
30
+ <%= controller.friendly_name || controller.name %>
31
+ </a>
32
+ </li>
33
+ <% end %>
34
+ </ul>
35
+ </nav>
36
+ </section>
37
+ <section class='content'>
38
+ <%= body %>
39
+ </section>
40
+ <footer class='footer'>
41
+ <p>Generated by Moonrope at <%= Time.now.strftime("%H:%M on %A %e %B %Y") %> for <%= git_version[0,6] %></p>
42
+ </footer>
43
+ <script src='https://code.jquery.com/jquery-1.12.0.min.js'></script>
44
+ <script src='<%= asset_path('try.js') %>'></script>
45
+ </body>
46
+ </html>
@@ -0,0 +1,23 @@
1
+ <% set_page_title "#{humanize(structure.name.capitalize)} Structure" %>
2
+
3
+ <h1><%= humanize(structure.name.capitalize) %> Structure</h1>
4
+
5
+ <h2>Base Attributes</h2>
6
+ <%= partial 'structure_attributes_list', :structure => structure, :attributes => structure.attributes[:basic].select { |a| a.doc != false } %>
7
+
8
+ <% full_attrs = structure.attributes[:full].select { |a| a.doc != false } %>
9
+ <% unless full_attrs.empty? %>
10
+ <h2>Extended Attributes</h2>
11
+ <%= partial 'structure_attributes_list', :structure => structure, :attributes => full_attrs %>
12
+ <% end %>
13
+
14
+ <% if !structure.attributes[:expansion].empty? || !structure.expansions.empty? %>
15
+ <h2>Expansions</h2>
16
+ <p class='text'>
17
+ Expansions are embedded structures of other objects that are related to the structure
18
+ that you're viewing. Which expansions are returned by a specific action are shown on that
19
+ action's documentation however some actions allow you to choose which expansions are
20
+ returned.
21
+ </p>
22
+ <%= partial 'structure_attributes_list', :structure => structure, :attributes => structure.attributes[:expansion], :expansions => structure.expansions.select { |k,v| v[:doc] != false} %>
23
+ <% end %>
@@ -0,0 +1,81 @@
1
+ require 'test/unit'
2
+ require 'rack/test'
3
+ require 'moonrope'
4
+
5
+ class Test::Unit::TestCase
6
+
7
+ private
8
+
9
+ def make_rack_env_hash(path, params = {}, other_env = {})
10
+ request = Rack::Test::Session.new(nil)
11
+ request.send :env_for, path, {:params => params, :method => 'POST'}.merge(other_env)
12
+ end
13
+
14
+ end
15
+
16
+ #
17
+ # A fake base object for models
18
+ #
19
+ class ModelBase
20
+ def initialize(attributes = {})
21
+ attributes.each do |key, value|
22
+ instance_variable_set("@#{key}", value)
23
+ end
24
+ end
25
+ end
26
+
27
+ class Animal < ModelBase
28
+ attr_accessor :id, :name, :color, :user
29
+ end
30
+
31
+ class User < ModelBase
32
+ attr_accessor :id, :username, :private_code, :admin
33
+ def animals
34
+ @animals ||= []
35
+ end
36
+ end
37
+
38
+ class UserWithUnderscore < User
39
+ class << self
40
+ def name
41
+ s = Struct.new(:underscore, :to_s).new
42
+ s.to_s = 'User'
43
+ s.underscore = 'user'
44
+ s
45
+ end
46
+ end
47
+ end
48
+
49
+ #
50
+ # A fake request class for use in some tests
51
+ #
52
+ class FakeRequest
53
+
54
+ def initialize(options = {})
55
+ @options = options
56
+ end
57
+
58
+ def params
59
+ @params ||= Moonrope::ParamSet.new(@options[:params] || {})
60
+ end
61
+
62
+ def version
63
+ @options[:version]
64
+ end
65
+
66
+ def identity
67
+ @options[:identity]
68
+ end
69
+
70
+ def action
71
+ nil
72
+ end
73
+
74
+ end
75
+
76
+ #
77
+ # Require all tests
78
+ #
79
+ Dir[File.expand_path("../tests/**/*.rb", __FILE__)].each do |filename|
80
+ require filename
81
+ end
@@ -0,0 +1,63 @@
1
+ class ActionAccessTest < Test::Unit::TestCase
2
+
3
+ def setup
4
+ @base = Moonrope::Base.new do
5
+ authenticator :default do
6
+ rule :default, "AccessDenied" do
7
+ identity == :admin
8
+ end
9
+
10
+ rule :anonymous, "MustBeAnonymous" do
11
+ identity.nil?
12
+ end
13
+ end
14
+ end
15
+ @controller = Moonrope::Controller.new(@base, :users)
16
+ end
17
+
18
+ def test_action_uses_default_access_rule_by_default
19
+ action = Moonrope::Action.new(@controller, :list)
20
+ # no authentication has been provided
21
+ assert_equal false, action.check_access
22
+ # authentication which is not correct
23
+ authenticated_request = FakeRequest.new(:identity => :dave)
24
+ assert_equal false, action.check_access(authenticated_request)
25
+ # authentication which is correct
26
+ authenticated_request = FakeRequest.new(:identity => :admin)
27
+ assert_equal true, action.check_access(authenticated_request)
28
+ end
29
+
30
+ def test_action_can_use_controller_rule
31
+ controller = Moonrope::Controller.new(@base, :users) do
32
+ access_rule :anonymous
33
+ end
34
+ action = Moonrope::Action.new(controller, :list)
35
+ # anonymous is ok
36
+ assert_equal true, action.check_access
37
+ # with a user is not
38
+ authenticated_request = FakeRequest.new(:identity => :dave)
39
+ assert_equal false, action.check_access(authenticated_request)
40
+ end
41
+
42
+ def test_action_can_use_action_rule
43
+ action = Moonrope::Action.new(@controller, :list) do
44
+ access_rule :anonymous
45
+ end
46
+ # anonymous is ok
47
+ assert_equal true, action.check_access
48
+ # with a user is not
49
+ authenticated_request = FakeRequest.new(:identity => :dave)
50
+ assert_equal false, action.check_access(authenticated_request)
51
+ end
52
+
53
+ def test_that_invalid_rule_names_raise_errors
54
+ action = Moonrope::Action.new(@controller, :list) do
55
+ access_rule :missing
56
+ end
57
+ # anonymous is ok
58
+ assert_raises Moonrope::Errors::MissingAccessRule do
59
+ action.check_access
60
+ end
61
+ end
62
+
63
+ end
@@ -0,0 +1,524 @@
1
+ class ActionsTest < Test::Unit::TestCase
2
+
3
+ def setup
4
+ @base = Moonrope::Base.new
5
+ @controller = Moonrope::Controller.new(@base, :users)
6
+ end
7
+
8
+ def test_basic_definition
9
+ action = Moonrope::Action.new(@controller, :list) do
10
+ description "An example action with a description"
11
+ end
12
+ assert action.is_a?(Moonrope::Action)
13
+ assert_equal :list, action.name
14
+ assert action.description.is_a?(String)
15
+ assert action.description.length > 0
16
+ end
17
+
18
+ def test_defining_params
19
+ action = Moonrope::Action.new(@controller, :list) do
20
+ param :page
21
+ param :limit
22
+ end
23
+ assert action.params.is_a?(Hash)
24
+ assert_equal [:page, :limit], action.params.keys
25
+ assert action.params.values.all? { |p| p.is_a?(Hash) }
26
+ end
27
+
28
+ def test_using_shares
29
+ controller = Moonrope::Controller.new(@base, :users) do
30
+ shared_action :user_properties do
31
+ error 'InvalidUsername', "Some description"
32
+ param :username, "Blah"
33
+ param :first_name
34
+ end
35
+
36
+ action :create do
37
+ use :user_properties
38
+ param :last_name
39
+ end
40
+ end
41
+
42
+ action = controller / :create
43
+ assert_equal(Hash, action.params.class)
44
+ assert_equal(Hash, action.params[:username].class)
45
+ assert_equal("Blah", action.params[:username][:description])
46
+ assert_equal(Hash, action.errors['InvalidUsername'].class)
47
+ end
48
+
49
+ def test_action
50
+ action = Moonrope::Action.new(@controller, :list) do
51
+ action { true }
52
+ end
53
+ assert action.actions.is_a?(Array)
54
+ assert action.actions.first.is_a?(Proc)
55
+ assert_equal true, action.actions.first.call
56
+ end
57
+
58
+ def test_calling_actions
59
+ action = Moonrope::Action.new(@controller, :list) do
60
+ action { [1,2,3,4] }
61
+ end
62
+ assert result = action.execute
63
+ assert result.is_a?(Moonrope::ActionResult)
64
+ assert_equal 'success', result.status
65
+ assert_equal [1,2,3,4], result.data
66
+ assert_equal Float, result.time.class
67
+ assert_equal({}, result.flags)
68
+ assert_equal({}, result.headers)
69
+ end
70
+
71
+ def test_structure_method_can_be_called
72
+ # Create a new structure to test with
73
+ user_structure = Moonrope::Structure.new(@base, :user) do
74
+ basic { {:id => o.id, :username => o.username}}
75
+ end
76
+
77
+ # Create an action which uses this structure
78
+ action = Moonrope::Action.new(@controller, :list) do
79
+ action do
80
+ user = User.new(:id => 1, :username => 'adamcooke')
81
+ structure user_structure, user
82
+ end
83
+ end
84
+
85
+ # Test the structure was returned
86
+ assert result = action.execute
87
+ assert result.is_a?(Moonrope::ActionResult), "result is not a ActionResult"
88
+ assert_equal 1, result.data[:id]
89
+ assert_equal 'adamcooke', result.data[:username]
90
+ end
91
+
92
+ def test_structure_methods_can_be_called_with_opts_from_dsl
93
+ # Create a new structure to test with
94
+ user_structure = Moonrope::Structure.new(@base, :user) do
95
+ basic :id
96
+ full :username
97
+ end
98
+
99
+ # Create an action which uses this structure
100
+ action = Moonrope::Action.new(@controller, :list) do
101
+ returns :hash, :structure => :user, :structure_opts => {:full => true}
102
+ action do
103
+ user = User.new(:id => 1, :username => 'adamcooke')
104
+ structure user_structure, user, :return => true
105
+ end
106
+ end
107
+
108
+ # Test the structure was returned
109
+ assert result = action.execute
110
+ assert_equal 1, result.data[:id]
111
+ assert_equal 'adamcooke', result.data[:username]
112
+ end
113
+
114
+ def test_default_params
115
+ action = Moonrope::Action.new(@controller, :default_params_test) do
116
+ param :page, :default => 1234
117
+ param :limit
118
+ action { {:page => params.page, :limit => params.limit} }
119
+ end
120
+ result = action.execute
121
+ assert_equal({'page' => 1234}, action.default_params)
122
+ assert_equal 1234, result.data[:page]
123
+ assert_equal nil, result.data[:limit]
124
+ end
125
+
126
+ def test_before_filters_are_executed
127
+ controller = Moonrope::Controller.new(@base, :users) do
128
+ before { set_flag :before_all, true }
129
+ before(:other) { set_flag :before_other, true }
130
+ before(:list) { set_flag :before_list, true }
131
+ before(:list, :potato) { set_flag :before_list_and_potato, true }
132
+ end
133
+
134
+ action = Moonrope::Action.new(controller, :list) do
135
+ action { true }
136
+ end
137
+
138
+ assert result = action.execute
139
+ assert_equal true, result.flags[:before_all]
140
+ assert_equal true, result.flags[:before_list]
141
+ assert_equal true, result.flags[:before_list_and_potato]
142
+ assert_equal nil, result.flags[:before_other]
143
+ end
144
+
145
+ def test_result_can_be_expressed_as_a_hash
146
+ action = Moonrope::Action.new(@controller, :list) do
147
+ action { [1,2,3] }
148
+ end
149
+ assert result = action.execute
150
+ assert hash = result.to_hash
151
+ assert hash.is_a?(Hash), "result.to_hash does not return a hash"
152
+ assert_equal 'success', hash[:status]
153
+ assert hash[:time].is_a?(Float)
154
+ assert hash[:flags].is_a?(Hash)
155
+ end
156
+
157
+ def test_result_can_be_expressed_as_json
158
+ action = Moonrope::Action.new(@controller, :list) do
159
+ action { [1,2,3] }
160
+ end
161
+ assert result = action.execute
162
+ assert json = result.to_json
163
+ assert json.is_a?(String)
164
+ end
165
+
166
+ def test_that_param_validation_happens_on_executin
167
+ action = Moonrope::Action.new(@controller, :list) do
168
+ param :page, "Page number", :required => true
169
+ action { [1,2,3] }
170
+ end
171
+ assert result = action.execute
172
+ assert_equal 'parameter-error', result.status
173
+ end
174
+
175
+ def test_actions_params_can_be_validated_for_presence
176
+ action = Moonrope::Action.new(@controller, :list) do
177
+ param :page, "Page number", :required => true
178
+ end
179
+
180
+ # request without the param
181
+ assert_raises Moonrope::Errors::ParameterError do
182
+ action.validate_parameters(Moonrope::ParamSet.new)
183
+ end
184
+
185
+ # request with the param
186
+ assert_equal true, action.validate_parameters(Moonrope::ParamSet.new('page' => 1))
187
+ end
188
+
189
+ def test_actions_params_can_be_validated_for_type
190
+ action = Moonrope::Action.new(@controller, :list) do
191
+ param :page, "Page number", :type => Integer
192
+ end
193
+
194
+ # request with a string valuee
195
+ assert_raises Moonrope::Errors::ParameterError do
196
+ action.validate_parameters(Moonrope::ParamSet.new('page' => 'stringy'))
197
+ end
198
+
199
+ # request with an integer value
200
+ assert_equal true, action.validate_parameters(Moonrope::ParamSet.new('page' => 1))
201
+
202
+ # request with an nil value
203
+ assert_equal true, action.validate_parameters(Moonrope::ParamSet.new('page' => nil))
204
+ end
205
+
206
+ def test_actions_params_can_be_validated_for_boolean_types
207
+ action = Moonrope::Action.new(@controller, :list) do
208
+ param :hungry, "Are you hungry", :type => :boolean
209
+ end
210
+
211
+ # request with a string valuee
212
+ assert_raises Moonrope::Errors::ParameterError do
213
+ action.validate_parameters(Moonrope::ParamSet.new('hungry' => 'randomstring'))
214
+ end
215
+
216
+ assert_raises Moonrope::Errors::ParameterError do
217
+ action.validate_parameters(Moonrope::ParamSet.new('hungry' => 2))
218
+ end
219
+
220
+ assert_raises Moonrope::Errors::ParameterError do
221
+ action.validate_parameters(Moonrope::ParamSet.new('hungry' => 123))
222
+ end
223
+
224
+ # request with an boolean value
225
+ assert_equal true, action.validate_parameters(Moonrope::ParamSet.new('hungry' => true))
226
+ assert_equal true, action.validate_parameters(Moonrope::ParamSet.new('hungry' => false))
227
+
228
+ # request with string values
229
+ set = Moonrope::ParamSet.new('hungry' => 'true')
230
+ assert_equal true, action.validate_parameters(set)
231
+ assert_equal true, set.hungry
232
+
233
+ set = Moonrope::ParamSet.new('hungry' => 'false')
234
+ assert_equal true, action.validate_parameters(set)
235
+ assert_equal false, set.hungry
236
+
237
+ # request with an numeric values
238
+ assert_equal true, action.validate_parameters(Moonrope::ParamSet.new('hungry' => 1))
239
+ assert_equal true, action.validate_parameters(Moonrope::ParamSet.new('hungry' => 0))
240
+
241
+ # request with nil vlaues
242
+ assert_equal true, action.validate_parameters(Moonrope::ParamSet.new('hungry' => nil))
243
+ end
244
+
245
+ def test_actions_params_can_have_symbols_as_types_which_do_nothing
246
+ action = Moonrope::Action.new(@controller, :list) do
247
+ param :created_at, "Timestamp", :type => :timestamp
248
+ end
249
+ assert_equal true, action.validate_parameters(Moonrope::ParamSet.new('created_at' => 'something'))
250
+ assert_equal true, action.validate_parameters(Moonrope::ParamSet.new('created_at' => nil))
251
+ end
252
+
253
+ def test_actions_params_can_be_validated_for_regex_matches
254
+ action = Moonrope::Action.new(@controller, :list) do
255
+ param :username, "Username", :regex => /\A[a-z]+\z/
256
+ end
257
+ # request with a nil value
258
+ assert_equal true, action.validate_parameters(Moonrope::ParamSet.new)
259
+
260
+ # request with a matching value
261
+ assert_equal true, action.validate_parameters(Moonrope::ParamSet.new('username' => 'adam'))
262
+
263
+ # request with a string valuee
264
+ assert_raises Moonrope::Errors::ParameterError do
265
+ action.validate_parameters(Moonrope::ParamSet.new('username' => 'invalid-username1234'))
266
+ end
267
+ end
268
+
269
+ def test_actions_params_can_be_validated_for_option_matches
270
+ action = Moonrope::Action.new(@controller, :list) do
271
+ param :sort_by, :options => ["name", "age"]
272
+ end
273
+ # request with a nil value
274
+ assert_equal true, action.validate_parameters(Moonrope::ParamSet.new)
275
+
276
+ # request with a matching value
277
+ assert_equal true, action.validate_parameters(Moonrope::ParamSet.new('sort_by' => 'name'))
278
+
279
+ # request with a string valuee
280
+ assert_raises Moonrope::Errors::ParameterError do
281
+ action.validate_parameters(Moonrope::ParamSet.new('sort_by' => 'somethingelse'))
282
+ end
283
+ end
284
+
285
+
286
+ def test_actions_can_raise_errors
287
+ action = Moonrope::Action.new(@controller, :list) do
288
+ action do
289
+ error :not_found, "Something wasn't found"
290
+ end
291
+ end
292
+ assert result = action.execute
293
+ assert_equal "not-found", result.status
294
+ assert_equal({:message => "Something wasn't found"}, result.data)
295
+ end
296
+
297
+ def test_actions_can_raise_structured_errors
298
+ action = Moonrope::Action.new(@controller, :list) do
299
+ action do
300
+ structured_error 'feature-disabled', "The feature you have requested is not currently available for this resource.", :number => 1000
301
+ end
302
+ end
303
+ assert result = action.execute
304
+ assert_equal "error", result.status
305
+ assert_equal("feature-disabled", result.data[:code])
306
+ assert_equal("The feature you have requested is not currently available for this resource.", result.data[:message])
307
+ assert_equal(1000, result.data[:number])
308
+ end
309
+
310
+ def test_actions_can_raise_structured_errors_through_the_error_method
311
+ action = Moonrope::Action.new(@controller, :list) do
312
+ action do
313
+ error :structured_error, 'feature-disabled', "The feature you have requested is not currently available for this resource."
314
+ end
315
+ end
316
+ assert result = action.execute
317
+ assert_equal "error", result.status
318
+ assert_equal("feature-disabled", result.data[:code])
319
+ assert_equal("The feature you have requested is not currently available for this resource.", result.data[:message])
320
+ end
321
+
322
+ def test_actions_can_raise_structured_errors_through_the_error_method_using_a_string
323
+ action = Moonrope::Action.new(@controller, :list) do
324
+ action do
325
+ error 'feature-disabled', "The feature you have requested is not currently available for this resource."
326
+ end
327
+ end
328
+ assert result = action.execute
329
+ assert_equal "error", result.status
330
+ assert_equal("feature-disabled", result.data[:code])
331
+ assert_equal("The feature you have requested is not currently available for this resource.", result.data[:message])
332
+ end
333
+
334
+ def test_actions_can_raise_structured_errors_referencing_action_errors
335
+ action = Moonrope::Action.new(@controller, :list) do
336
+ error "NoWidgetsFound", "No widgets were found with level {widget_level}"
337
+ action do
338
+ error 'NoWidgetsFound', :widget_level => 42
339
+ end
340
+ end
341
+ assert result = action.execute
342
+ assert_equal "error", result.status
343
+ assert_equal "NoWidgetsFound", result.data[:code]
344
+ assert_equal "No widgets were found with level 42", result.data[:message]
345
+ assert_equal 42, result.data[:widget_level]
346
+ end
347
+
348
+ class DummyError < StandardError; end
349
+
350
+ def test_catching_external_errors
351
+ @controller.base.register_external_error DummyError do |exp, res|
352
+ res.status = 'dummy-error'
353
+ res.data = {:message => exp.message}
354
+ end
355
+
356
+ action = Moonrope::Action.new(@controller, :list) do
357
+ action do
358
+ raise DummyError, "Something happened"
359
+ end
360
+ end
361
+
362
+ assert result = action.execute
363
+ assert_equal 'dummy-error', result.status
364
+ assert_equal({:message => 'Something happened'}, result.data)
365
+ end
366
+
367
+ class DummyError2 < StandardError; end
368
+
369
+ def test_non_defined_errors_are_raised
370
+ action = Moonrope::Action.new(@controller, :list) do
371
+ action do
372
+ raise DummyError2, "Something happened"
373
+ end
374
+ end
375
+ assert_raises(DummyError2) { action.execute }
376
+ end
377
+
378
+ def test_can_change_full_attribute
379
+ # can't change when no ops
380
+ action = Moonrope::Action.new(@controller, :list) do
381
+ returns :hash, :structure => :animal
382
+ end
383
+ assert_equal(false, action.can_change_full?)
384
+
385
+ # can change when paramable is true
386
+ action = Moonrope::Action.new(@controller, :list) do
387
+ returns :hash, :structure => :animal, :structure_opts => {:paramable => true}
388
+ end
389
+ assert_equal(true, action.can_change_full?)
390
+
391
+ # can change when specified
392
+ action = Moonrope::Action.new(@controller, :list) do
393
+ returns :hash, :structure => :animal, :structure_opts => {:paramable => {:full => true}}
394
+ end
395
+ assert_equal(true, action.can_change_full?)
396
+
397
+ # can't change when not specified
398
+ action = Moonrope::Action.new(@controller, :list) do
399
+ returns :hash, :structure => :animal, :structure_opts => {:paramable => {}}
400
+ end
401
+ assert_equal(false, action.can_change_full?)
402
+ end
403
+
404
+ def test_includes_full_attributes
405
+ #not included by default
406
+ action = Moonrope::Action.new(@controller, :list) do
407
+ returns :hash, :structure => :animal
408
+ end
409
+ assert_equal(false, action.includes_full_attributes?)
410
+
411
+ # not included when paramable is just true
412
+ action = Moonrope::Action.new(@controller, :list) do
413
+ returns :hash, :structure => :animal, :structure_opts => {:paramable => true}
414
+ end
415
+ assert_equal(false, action.includes_full_attributes?)
416
+
417
+ # included when paramable sets the default to true
418
+ action = Moonrope::Action.new(@controller, :list) do
419
+ returns :hash, :structure => :animal, :structure_opts => {:paramable => {:full => true}}
420
+ end
421
+ assert_equal(true, action.includes_full_attributes?)
422
+
423
+ # included when it's full anyway
424
+ action = Moonrope::Action.new(@controller, :list) do
425
+ returns :hash, :structure => :animal, :structure_opts => {:full => true}
426
+ end
427
+ assert_equal(true, action.includes_full_attributes?)
428
+ end
429
+
430
+
431
+ def test_can_change_expansions_attribute
432
+ # can't change when no ops
433
+ action = Moonrope::Action.new(@controller, :list) do
434
+ returns :hash, :structure => :animal
435
+ end
436
+ assert_equal(false, action.can_change_expansions?)
437
+
438
+ # can change when paramable is true
439
+ action = Moonrope::Action.new(@controller, :list) do
440
+ returns :hash, :structure => :animal, :structure_opts => {:paramable => true}
441
+ end
442
+ assert_equal(true, action.can_change_expansions?)
443
+
444
+ # can change when specified
445
+ action = Moonrope::Action.new(@controller, :list) do
446
+ returns :hash, :structure => :animal, :structure_opts => {:paramable => {:expansions => true}}
447
+ end
448
+ assert_equal(true, action.can_change_expansions?)
449
+
450
+ # can't change when not specified
451
+ action = Moonrope::Action.new(@controller, :list) do
452
+ returns :hash, :structure => :animal, :structure_opts => {:paramable => {}}
453
+ end
454
+ assert_equal(false, action.can_change_expansions?)
455
+ end
456
+
457
+ def test_includes_expansion
458
+ #not included by default
459
+ action = Moonrope::Action.new(@controller, :list) do
460
+ returns :hash, :structure => :animal
461
+ end
462
+ assert_equal(false, action.includes_expansion?(:blah))
463
+
464
+ # not included when paramable is just true
465
+ action = Moonrope::Action.new(@controller, :list) do
466
+ returns :hash, :structure => :animal, :structure_opts => {:paramable => true}
467
+ end
468
+ assert_equal(false, action.includes_expansion?(:blah))
469
+
470
+ # included when paramable sets the default to true
471
+ action = Moonrope::Action.new(@controller, :list) do
472
+ returns :hash, :structure => :animal, :structure_opts => {:paramable => {:expansions => true}}
473
+ end
474
+ assert_equal(true, action.includes_expansion?(:blah))
475
+
476
+ # included when it's expansions anyway
477
+ action = Moonrope::Action.new(@controller, :list) do
478
+ returns :hash, :structure => :animal, :structure_opts => {:expansions => true}
479
+ end
480
+ assert_equal(true, action.includes_expansion?(:blah))
481
+
482
+ # included when expansions is an array
483
+ action = Moonrope::Action.new(@controller, :list) do
484
+ returns :hash, :structure => :animal, :structure_opts => {:expansions => [:blah]}
485
+ end
486
+ assert_equal(true, action.includes_expansion?(:blah))
487
+ assert_equal(false, action.includes_expansion?(:another))
488
+
489
+ # included when paramable expansions is an array
490
+ action = Moonrope::Action.new(@controller, :list) do
491
+ returns :hash, :structure => :animal, :structure_opts => {:paramable => {:expansions => [:blah]}}
492
+ end
493
+ assert_equal(true, action.includes_expansion?(:blah))
494
+ assert_equal(false, action.includes_expansion?(:another))
495
+ end
496
+
497
+ def test_available_expansions_array_on_actions
498
+ # if an array is provided, it should return the items in the array
499
+ action = Moonrope::Action.new(@controller, :list) do
500
+ returns :hash, :structure => :animal, :structure_opts => {:paramable => {:expansions => [:user]}}
501
+ end
502
+ assert_equal([:user], action.available_expansions)
503
+ end
504
+
505
+ def test_that_param_can_copy_data_from_structures
506
+ base = Moonrope::Base.new do
507
+ structure :user do
508
+ basic :username, "The username for the user", :type => String, :eg => 123
509
+ end
510
+
511
+ controller :users do
512
+ action :save do
513
+ param :username, :from_structure => :user
514
+ end
515
+ end
516
+ end
517
+
518
+ action = base/:users/:save
519
+ assert_equal "The username for the user", action.params[:username][:description]
520
+ assert_equal String, action.params[:username][:type]
521
+ end
522
+
523
+
524
+ end