spree_api 1.0.7 → 1.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +6 -0
  3. data/LICENSE +19 -23
  4. data/README.md +22 -10
  5. data/Rakefile +30 -0
  6. data/app/controllers/spree/api/v1/base_controller.rb +71 -0
  7. data/app/controllers/spree/api/v1/images_controller.rb +46 -0
  8. data/app/controllers/spree/api/v1/line_items_controller.rb +40 -0
  9. data/app/controllers/spree/api/v1/orders_controller.rb +53 -0
  10. data/app/controllers/spree/api/v1/products_controller.rb +46 -0
  11. data/app/controllers/spree/api/v1/variants_controller.rb +56 -0
  12. data/app/helpers/spree/api/api_helpers.rb +44 -0
  13. data/app/models/spree/line_item_decorator.rb +3 -0
  14. data/app/models/spree/option_value_decorator.rb +5 -0
  15. data/app/models/spree/order_decorator.rb +12 -0
  16. data/app/models/spree/user_decorator.rb +11 -0
  17. data/app/views/spree/api/v1/errors/invalid_api_key.rabl +2 -0
  18. data/app/views/spree/api/v1/errors/invalid_resource.rabl +3 -0
  19. data/app/views/spree/api/v1/errors/must_specify_api_key.rabl +2 -0
  20. data/app/views/spree/api/v1/errors/not_found.rabl +2 -0
  21. data/app/views/spree/api/v1/errors/unauthorized.rabl +2 -0
  22. data/app/views/spree/api/v1/images/show.rabl +2 -0
  23. data/app/views/spree/api/v1/line_items/new.rabl +3 -0
  24. data/app/views/spree/api/v1/line_items/show.rabl +5 -0
  25. data/app/views/spree/api/v1/orders/address.rabl +0 -0
  26. data/app/views/spree/api/v1/orders/cart.rabl +0 -0
  27. data/app/views/spree/api/v1/orders/complete.rabl +0 -0
  28. data/app/views/spree/api/v1/orders/could_not_transition.rabl +3 -0
  29. data/app/views/spree/api/v1/orders/delivery.rabl +3 -0
  30. data/app/views/spree/api/v1/orders/index.rabl +7 -0
  31. data/app/views/spree/api/v1/orders/invalid_shipping_method.rabl +2 -0
  32. data/app/views/spree/api/v1/orders/payment.rabl +0 -0
  33. data/app/views/spree/api/v1/orders/show.rabl +3 -0
  34. data/app/views/spree/api/v1/products/index.rabl +8 -0
  35. data/app/views/spree/api/v1/products/new.rabl +3 -0
  36. data/app/views/spree/api/v1/products/product.rabl +1 -0
  37. data/app/views/spree/api/v1/products/show.rabl +21 -0
  38. data/app/views/spree/api/v1/variants/index.rabl +3 -0
  39. data/app/views/spree/api/v1/variants/new.rabl +2 -0
  40. data/app/views/spree/api/v1/variants/show.rabl +3 -0
  41. data/app/views/spree/api/v1/variants/variant.rabl +1 -0
  42. data/config/locales/en.yml +11 -15
  43. data/config/routes.rb +14 -28
  44. data/db/migrate/{20100107141738_add_api_key_to_users.rb → 20100107141738_add_api_key_to_spree_users.rb} +1 -1
  45. data/lib/spree/api.rb +7 -4
  46. data/lib/spree/api/controller_setup.rb +27 -0
  47. data/lib/spree/api/engine.rb +3 -13
  48. data/lib/spree/api/version.rb +5 -0
  49. data/lib/spree_api.rb +0 -2
  50. data/script/rails +5 -0
  51. data/spec/controllers/spree/api/v1/base_controller_spec.rb +29 -0
  52. data/spec/controllers/spree/api/v1/images_controller_spec.rb +50 -0
  53. data/spec/controllers/spree/api/v1/line_items_controller_spec.rb +77 -0
  54. data/spec/controllers/spree/api/v1/orders_controller_spec.rb +148 -0
  55. data/spec/controllers/spree/api/v1/products_controller_spec.rb +159 -0
  56. data/spec/controllers/spree/api/v1/variants_controller_spec.rb +90 -0
  57. data/spec/fixtures/thinking-cat.jpg +0 -0
  58. data/spec/models/spree/order_spec.rb +18 -0
  59. data/spec/models/spree/user_spec.rb +19 -0
  60. data/spec/spec_helper.rb +28 -0
  61. data/spec/support/api_helpers.rb +68 -0
  62. data/spec/support/controller_hacks.rb +27 -0
  63. data/spree_api.gemspec +24 -0
  64. metadata +123 -56
  65. data/app/assets/javascripts/admin/spree.js +0 -4
  66. data/app/assets/javascripts/admin/spree_api.js +0 -2
  67. data/app/assets/javascripts/store/spree.js +0 -4
  68. data/app/assets/javascripts/store/spree_api.js +0 -2
  69. data/app/assets/stylesheets/admin/spree.css +0 -6
  70. data/app/assets/stylesheets/admin/spree_api.css +0 -4
  71. data/app/assets/stylesheets/store/spree.css +0 -6
  72. data/app/assets/stylesheets/store/spree_api.css +0 -4
  73. data/app/controllers/spree/admin/users_controller_decorator.rb +0 -17
  74. data/app/controllers/spree/api/base_controller.rb +0 -172
  75. data/app/controllers/spree/api/countries_controller.rb +0 -3
  76. data/app/controllers/spree/api/inventory_units_controller.rb +0 -18
  77. data/app/controllers/spree/api/line_items_controller.rb +0 -20
  78. data/app/controllers/spree/api/orders_controller.rb +0 -19
  79. data/app/controllers/spree/api/products_controller.rb +0 -14
  80. data/app/controllers/spree/api/shipments_controller.rb +0 -35
  81. data/app/controllers/spree/api/states_controller.rb +0 -8
  82. data/app/models/line_item_decorator.rb +0 -7
  83. data/app/models/order_decorator.rb +0 -9
  84. data/app/models/shipment_decorator.rb +0 -9
  85. data/app/models/user_decorator.rb +0 -19
  86. data/app/overrides/api_admin_user_edit_form.rb +0 -6
  87. data/app/views/spree/admin/users/_api_fields.html.erb +0 -16
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ module Spree
4
+ describe Api::V1::LineItemsController do
5
+ render_views
6
+
7
+ let!(:order) do
8
+ order = Factory(:order)
9
+ order.line_items << Factory(:line_item)
10
+ order
11
+ end
12
+
13
+ let(:product) { Factory(:product) }
14
+ let(:attributes) { [:quantity, :price, :variant] }
15
+ let(:resource_scoping) { { :order_id => order.to_param } }
16
+
17
+ before do
18
+ stub_authentication!
19
+ end
20
+
21
+ it "can learn how to create a new line item" do
22
+ api_get :new
23
+ json_response["attributes"].should == ["quantity", "price", "variant_id"]
24
+ required_attributes = json_response["required_attributes"]
25
+ required_attributes.should include("quantity", "variant_id")
26
+ end
27
+
28
+ context "as the order owner" do
29
+ before do
30
+ Order.any_instance.stub :user => current_api_user
31
+ end
32
+
33
+ it "can add a new line item to an existing order" do
34
+ api_post :create, :line_item => { :variant_id => product.master.to_param, :quantity => 1 }
35
+ response.status.should == 201
36
+ json_response.should have_attributes(attributes)
37
+ json_response["line_item"]["variant"]["name"].should_not be_blank
38
+ end
39
+
40
+ it "can update a line item on the order" do
41
+ line_item = order.line_items.first
42
+ api_put :update, :id => line_item.id, :line_item => { :quantity => 1000 }
43
+ response.status.should == 200
44
+ json_response.should have_attributes(attributes)
45
+ end
46
+
47
+ it "can delete a line item on the order" do
48
+ line_item = order.line_items.first
49
+ api_delete :destroy, :id => line_item.id
50
+ response.status.should == 200
51
+ lambda { line_item.reload }.should raise_error(ActiveRecord::RecordNotFound)
52
+ end
53
+ end
54
+
55
+ context "as just another user" do
56
+ it "cannot add a new line item to the order" do
57
+ api_post :create, :line_item => { :variant_id => product.master.to_param, :quantity => 1 }
58
+ assert_unauthorized!
59
+ end
60
+
61
+ it "cannot update a line item on the order" do
62
+ line_item = order.line_items.first
63
+ api_put :update, :id => line_item.id, :line_item => { :quantity => 1000 }
64
+ assert_unauthorized!
65
+ line_item.reload.quantity.should_not == 1000
66
+ end
67
+
68
+ it "cannot delete a line item on the order" do
69
+ line_item = order.line_items.first
70
+ api_delete :destroy, :id => line_item.id
71
+ assert_unauthorized!
72
+ lambda { line_item.reload }.should_not raise_error(ActiveRecord::RecordNotFound)
73
+ end
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,148 @@
1
+ require 'spec_helper'
2
+
3
+ module Spree
4
+ describe Api::V1::OrdersController do
5
+ render_views
6
+
7
+ let!(:order) { Factory(:order) }
8
+ let(:attributes) { [:number, :item_total, :total,
9
+ :state, :adjustment_total, :credit_total,
10
+ :user_id, :created_at, :updated_at,
11
+ :completed_at, :payment_total, :shipment_state,
12
+ :payment_state, :email, :special_instructions] }
13
+
14
+
15
+ before do
16
+ stub_authentication!
17
+ end
18
+
19
+ it "cannot view all orders" do
20
+ api_get :index
21
+ assert_unauthorized!
22
+ end
23
+
24
+ it "can view their own order" do
25
+ Order.any_instance.stub :user => current_api_user
26
+ api_get :show, :id => order.to_param
27
+ response.status.should == 200
28
+ json_response.should have_attributes(attributes)
29
+ end
30
+
31
+ it "can not view someone else's order" do
32
+ Order.any_instance.stub :user => stub_model(User)
33
+ api_get :show, :id => order.to_param
34
+ assert_unauthorized!
35
+ end
36
+
37
+ it "can create an order" do
38
+ variant = Factory(:variant)
39
+ api_post :create, :order => { :line_items => { variant.to_param => 5 } }
40
+ response.status.should == 200
41
+ order = Order.last
42
+ order.line_items.count.should == 1
43
+ order.line_items.first.variant.should == variant
44
+ order.line_items.first.quantity.should == 5
45
+ json_response["order"]["state"].should == "address"
46
+ end
47
+
48
+ context "working with an order" do
49
+ before do
50
+ Factory(:payment_method)
51
+ order.next # Switch from cart to address
52
+ order.ship_address.should be_nil
53
+ order.state.should == "address"
54
+ end
55
+
56
+ def clean_address(address)
57
+ address.delete(:state)
58
+ address.delete(:country)
59
+ address
60
+ end
61
+
62
+ let(:address_params) { { :country_id => Country.first.id, :state_id => State.first.id } }
63
+ let(:shipping_address) { clean_address(Factory.attributes_for(:address).merge!(address_params)) }
64
+ let(:billing_address) { clean_address(Factory.attributes_for(:address).merge!(address_params)) }
65
+ let!(:shipping_method) { Factory(:shipping_method) }
66
+ let!(:payment_method) { Factory(:payment_method) }
67
+
68
+ it "can add address information to an order" do
69
+ api_put :address, :id => order.to_param, :shipping_address => shipping_address, :billing_address => billing_address
70
+
71
+ response.status.should == 200
72
+ order.reload
73
+ order.shipping_address.reload
74
+ order.billing_address.reload
75
+ # We can assume the rest of the parameters are set if these two are
76
+ order.shipping_address.firstname.should == shipping_address[:firstname]
77
+ order.billing_address.firstname.should == billing_address[:firstname]
78
+ order.state.should == "delivery"
79
+ json_response["order"]["shipping_methods"].should_not be_empty
80
+ end
81
+
82
+ it "cannot use an address that has no valid shipping methods" do
83
+ shipping_method.destroy
84
+ api_put :address, :id => order.to_param, :shipping_address => shipping_address, :billing_address => billing_address
85
+ response.status.should == 422
86
+ json_response["errors"]["base"].should == ["No shipping methods available for selected location, please change your address and try again."]
87
+ end
88
+
89
+ it "can not add invalid ship address information to an order" do
90
+ shipping_address[:firstname] = ""
91
+ api_put :address, :id => order.to_param, :shipping_address => shipping_address, :billing_address => billing_address
92
+
93
+ response.status.should == 422
94
+ json_response["errors"]["ship_address.firstname"].should_not be_blank
95
+ end
96
+
97
+ it "can not add invalid ship address information to an order" do
98
+ billing_address[:firstname] = ""
99
+ api_put :address, :id => order.to_param, :shipping_address => shipping_address, :billing_address => billing_address
100
+
101
+ response.status.should == 422
102
+ json_response["errors"]["bill_address.firstname"].should_not be_blank
103
+ end
104
+
105
+ context "with a line item" do
106
+ before do
107
+ order.line_items << Factory(:line_item)
108
+ end
109
+
110
+ context "for delivery" do
111
+ before do
112
+ order.update_attribute(:state, "delivery")
113
+ end
114
+
115
+ it "can select a shipping method for an order" do
116
+ order.shipping_method.should be_nil
117
+ api_put :delivery, :id => order.to_param, :shipping_method_id => shipping_method.id
118
+ response.status.should == 200
119
+ order.reload
120
+ order.state.should == "payment"
121
+ order.shipping_method.should == shipping_method
122
+ end
123
+
124
+ it "cannot select an invalid shipping method for an order" do
125
+ order.shipping_method.should be_nil
126
+ api_put :delivery, :id => order.to_param, :shipping_method_id => '1234567890'
127
+ response.status.should == 422
128
+ json_response["errors"].should include("Invalid shipping method specified.")
129
+ end
130
+ end
131
+ end
132
+
133
+
134
+ end
135
+
136
+ context "as an admin" do
137
+ sign_in_as_admin!
138
+
139
+ it "can view all orders" do
140
+ api_get :index
141
+ json_response["orders"].first.should have_attributes(attributes)
142
+ json_response["count"].should == 1
143
+ json_response["current_page"].should == 1
144
+ json_response["pages"].should == 1
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,159 @@
1
+ require 'spec_helper'
2
+
3
+ module Spree
4
+ describe Spree::Api::V1::ProductsController do
5
+ render_views
6
+
7
+ let!(:product) { Factory(:product) }
8
+ let!(:inactive_product) { Factory(:product, :available_on => Time.now.tomorrow, :name => "inactive") }
9
+ let(:attributes) { [:id, :name, :description, :price, :available_on, :permalink, :count_on_hand, :meta_description, :meta_keywords] }
10
+
11
+ before do
12
+ stub_authentication!
13
+ end
14
+
15
+ context "as a normal user" do
16
+ it "retrieves a list of products" do
17
+ api_get :index
18
+ json_response["products"].first.should have_attributes(attributes)
19
+ json_response["count"].should == 1
20
+ json_response["current_page"].should == 1
21
+ json_response["pages"].should == 1
22
+ end
23
+
24
+ it "does not list unavailable products" do
25
+ api_get :index
26
+ json_response["products"].first["name"].should_not eq("inactive")
27
+ end
28
+
29
+ context "pagination" do
30
+ default_per_page(1)
31
+
32
+ it "can select the next page of products" do
33
+ second_product = Factory(:product)
34
+ api_get :index, :page => 2
35
+ json_response["products"].first.should have_attributes(attributes)
36
+ json_response["count"].should == 2
37
+ json_response["current_page"].should == 2
38
+ json_response["pages"].should == 2
39
+ end
40
+ end
41
+
42
+ it "gets a single product" do
43
+ product.master.images.create!(:attachment => image("thinking-cat.jpg"))
44
+ api_get :show, :id => product.to_param
45
+ json_response.should have_attributes(attributes)
46
+ product_json = json_response["product"]
47
+ product_json["variants"].first.should have_attributes([:name,
48
+ :is_master,
49
+ :count_on_hand,
50
+ :price])
51
+
52
+ product_json["images"].first.should have_attributes([:attachment_file_name,
53
+ :attachment_width,
54
+ :attachment_height,
55
+ :attachment_content_type])
56
+ end
57
+
58
+
59
+ context "finds a product by permalink first then by id" do
60
+ let!(:other_product) { Factory(:product, :permalink => "these-are-not-the-droids-you-are-looking-for") }
61
+
62
+ before do
63
+ product.update_attribute(:permalink, "#{other_product.id}-and-1-ways")
64
+ end
65
+
66
+ specify do
67
+ api_get :show, :id => product.to_param
68
+ json_response["product"]["permalink"].should =~ /and-1-ways/
69
+ product.destroy
70
+
71
+ api_get :show, :id => other_product.id
72
+ json_response["product"]["permalink"].should =~ /droids/
73
+ end
74
+ end
75
+
76
+ it "cannot see inactive products" do
77
+ api_get :show, :id => inactive_product.to_param
78
+ json_response["error"].should == "The resource you were looking for could not be found."
79
+ response.status.should == 404
80
+ end
81
+
82
+ it "returns a 404 error when it cannot find a product" do
83
+ api_get :show, :id => "non-existant"
84
+ json_response["error"].should == "The resource you were looking for could not be found."
85
+ response.status.should == 404
86
+ end
87
+
88
+ it "can learn how to create a new product" do
89
+ api_get :new
90
+ json_response["attributes"].should == attributes.map(&:to_s)
91
+ required_attributes = json_response["required_attributes"]
92
+ required_attributes.should include("name")
93
+ required_attributes.should include("price")
94
+ end
95
+
96
+ it "cannot create a new product if not an admin" do
97
+ api_post :create, :product => { :name => "Brand new product!" }
98
+ assert_unauthorized!
99
+ end
100
+
101
+ it "cannot update a product" do
102
+ api_put :update, :id => product.to_param, :product => { :name => "I hacked your store!" }
103
+ assert_unauthorized!
104
+ end
105
+
106
+ it "cannot delete a product" do
107
+ api_delete :destroy, :id => product.to_param
108
+ assert_unauthorized!
109
+ end
110
+ end
111
+
112
+ context "as an admin" do
113
+ sign_in_as_admin!
114
+
115
+ it "can see all products" do
116
+ api_get :index
117
+ json_response["products"].count.should == 2
118
+ json_response["count"].should == 2
119
+ json_response["current_page"].should == 1
120
+ json_response["pages"].should == 1
121
+ end
122
+
123
+ it "can create a new product" do
124
+ api_post :create, :product => { :name => "The Other Product",
125
+ :price => 19.99 }
126
+ json_response.should have_attributes(attributes)
127
+ response.status.should == 201
128
+ end
129
+
130
+ it "cannot create a new product with invalid attributes" do
131
+ api_post :create, :product => {}
132
+ response.status.should == 422
133
+ json_response["error"].should == "Invalid resource. Please fix errors and try again."
134
+ errors = json_response["errors"]
135
+ errors.delete("permalink") # Don't care about this one.
136
+ errors.keys.should =~ ["name", "price"]
137
+ end
138
+
139
+ it "can update a product" do
140
+ api_put :update, :id => product.to_param, :product => { :name => "New and Improved Product!" }
141
+ response.status.should == 200
142
+ end
143
+
144
+ it "cannot update a product with an invalid attribute" do
145
+ api_put :update, :id => product.to_param, :product => { :name => "" }
146
+ response.status.should == 422
147
+ json_response["error"].should == "Invalid resource. Please fix errors and try again."
148
+ json_response["errors"]["name"].should == ["can't be blank"]
149
+ end
150
+
151
+ it "can delete a product" do
152
+ product.deleted_at.should be_nil
153
+ api_delete :destroy, :id => product.to_param
154
+ response.status.should == 200
155
+ product.reload.deleted_at.should_not be_nil
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ module Spree
4
+ describe Api::V1::VariantsController do
5
+ render_views
6
+
7
+
8
+ let!(:product) { Factory(:product) }
9
+ let!(:variant) do
10
+ variant = product.master
11
+ variant.option_values << Factory(:option_value)
12
+ variant
13
+ end
14
+ let!(:attributes) { [:id, :name, :count_on_hand,
15
+ :sku, :price, :weight, :height,
16
+ :width, :depth, :is_master, :cost_price] }
17
+
18
+ before do
19
+ stub_authentication!
20
+ end
21
+
22
+ it "can see a list of all variants" do
23
+ api_get :index
24
+ json_response.first.should have_attributes(attributes)
25
+ option_values = json_response.last["variant"]["option_values"]
26
+ option_values.first.should have_attributes([:name,
27
+ :presentation,
28
+ :option_type_name,
29
+ :option_type_id])
30
+ end
31
+
32
+ it "can see a single variant" do
33
+ api_get :show, :id => variant.to_param
34
+ json_response.should have_attributes(attributes)
35
+ option_values = json_response["variant"]["option_values"]
36
+ option_values.first.should have_attributes([:name,
37
+ :presentation,
38
+ :option_type_name,
39
+ :option_type_id])
40
+ end
41
+
42
+ it "can learn how to create a new variant" do
43
+ api_get :new
44
+ json_response["attributes"].should == attributes.map(&:to_s)
45
+ json_response["required_attributes"].should be_empty
46
+ end
47
+
48
+ it "cannot create a new variant if not an admin" do
49
+ api_post :create, :variant => { :sku => "12345" }
50
+ assert_unauthorized!
51
+ end
52
+
53
+ it "cannot update a variant" do
54
+ api_put :update, :id => variant.to_param, :variant => { :sku => "12345" }
55
+ assert_unauthorized!
56
+ end
57
+
58
+ it "cannot delete a variant" do
59
+ api_delete :destroy, :id => variant.to_param
60
+ assert_unauthorized!
61
+ lambda { variant.reload }.should_not raise_error
62
+ end
63
+
64
+ context "as an admin" do
65
+ sign_in_as_admin!
66
+ let(:resource_scoping) { { :product_id => variant.product.to_param } }
67
+
68
+ it "can create a new variant" do
69
+ api_post :create, :variant => { :sku => "12345" }
70
+ json_response.should have_attributes(attributes)
71
+ response.status.should == 201
72
+
73
+ variant.product.variants.count.should == 1
74
+ end
75
+
76
+ it "can update a variant" do
77
+ api_put :update, :id => variant.to_param, :variant => { :sku => "12345" }
78
+ response.status.should == 200
79
+ end
80
+
81
+ it "can delete a variant" do
82
+ api_delete :destroy, :id => variant.to_param
83
+ response.status.should == 200
84
+ lambda { variant.reload }.should raise_error(ActiveRecord::RecordNotFound)
85
+ end
86
+ end
87
+
88
+
89
+ end
90
+ end