spree_api 1.0.7 → 1.1.0.rc1

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 (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