gun_broker 1.3.2 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/gun_broker.rb +2 -0
- data/lib/gun_broker/api.rb +2 -2
- data/lib/gun_broker/order.rb +106 -0
- data/lib/gun_broker/orders_as_page.rb +22 -0
- data/lib/gun_broker/user.rb +16 -0
- data/lib/gun_broker/user/items_as_pages_delegate.rb +1 -1
- data/lib/gun_broker/user/items_delegate.rb +1 -1
- data/lib/gun_broker/user/orders_as_pages_delegate.rb +56 -0
- data/lib/gun_broker/user/orders_delegate.rb +94 -0
- data/lib/gun_broker/version.rb +1 -1
- data/spec/gun_broker/user/items_delegate_spec.rb +9 -9
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c7c5d35e48c658ec3bb20f91f9324d87689576eb
|
4
|
+
data.tar.gz: 190619c274d40d9ca6c5ebc3b4a006262d8655ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 560eab3e870fb03dd1f02909b89506a9e9f85b39199475081761811f23ce8b3c15eaa0a98bac1e6e32776949e679aa995aa62898da3be94956b20021e0a09688
|
7
|
+
data.tar.gz: 298d96e0ceb32d73c4b397a6049db34b0544aa411d19b07e3895fe821f7e2f9564721cd0d03960ecb8b9f5291248bca54792ae4b3d2dc827ca0ee279890bfb8c
|
data/lib/gun_broker.rb
CHANGED
data/lib/gun_broker/api.rb
CHANGED
@@ -15,8 +15,8 @@ module GunBroker
|
|
15
15
|
# Used to return the maximum number of results from paginated responses.
|
16
16
|
PAGE_SIZE = 300
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
MAX_ITEMS_TIME_FRAME = 1
|
19
|
+
MAX_ORDERS_TIME_FRAME = 7
|
20
20
|
|
21
21
|
USER_AGENT = "gun_broker rubygems.org/gems/gun_broker v(#{GunBroker::VERSION})"
|
22
22
|
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module GunBroker
|
2
|
+
# Represents a GunBroker order (listing).
|
3
|
+
class Order
|
4
|
+
|
5
|
+
# TODO: Refactor this, #attributes, and #[] into a module.
|
6
|
+
# @return [Hash] Attributes parsed from the JSON response.
|
7
|
+
attr_reader :attrs
|
8
|
+
|
9
|
+
# @param order_id [Integer, String] The ID of the Order to find.
|
10
|
+
# @return [Order] An Order instance or `nil` if no Order with `order_id` exists.
|
11
|
+
def self.find(order_id, params = {}, headers = {})
|
12
|
+
find!(order_id, params, headers)
|
13
|
+
rescue GunBroker::Error::NotFound
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# Same as {.find} but raises GunBroker::Error::NotFound if no Order is found.
|
18
|
+
# @param (see .find)
|
19
|
+
# @raise [GunBroker::Error::NotFound] If no Order with `order_id` exists.
|
20
|
+
# @return (see .find)
|
21
|
+
def self.find!(order_id, params = {}, headers = {})
|
22
|
+
response = GunBroker::API.get("/Orders/#{order_id}", params, headers)
|
23
|
+
new(response.body)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param attrs [Hash] The JSON attributes from the API response.
|
27
|
+
def initialize(attrs = {})
|
28
|
+
@attrs = attrs
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Hash] Attributes parsed from the JSON response.
|
32
|
+
def attributes
|
33
|
+
@attrs
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Integer] The Order ID.
|
37
|
+
def id
|
38
|
+
@attrs['orderID']
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [String] FFL Number (if applicable) for this Order.
|
42
|
+
def ffl_number
|
43
|
+
@attrs['fflNumber']
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [Array] Item IDs of associated items for this Order.
|
47
|
+
def item_ids
|
48
|
+
@attrs['itemIDs']
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [Hash] Billing info for this Order.
|
52
|
+
def bill_to
|
53
|
+
{
|
54
|
+
name: @attrs['billToName'],
|
55
|
+
address_1: @attrs['billToAddress1'],
|
56
|
+
address_2: @attrs['billToAddress2'],
|
57
|
+
city: @attrs['billToCity'],
|
58
|
+
state: @attrs['billToState'],
|
59
|
+
zip: @attrs['billToPostalCode'],
|
60
|
+
email: @attrs['billToEmail'],
|
61
|
+
phone: @attrs['billToPhone']
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [Hash] Shipping info for this Order.
|
66
|
+
def ship_to
|
67
|
+
{
|
68
|
+
name: @attrs['shipToName'],
|
69
|
+
address_1: @attrs['shipToAddress1'],
|
70
|
+
address_2: @attrs['shipToAddress2'],
|
71
|
+
city: @attrs['shipToCity'],
|
72
|
+
state: @attrs['shipToState'],
|
73
|
+
zip: @attrs['shipToPostalCode'],
|
74
|
+
email: @attrs['shipToEmail'],
|
75
|
+
phone: @attrs['shipToPhone']
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
# @return [Float] Total shipping amount for this Order.
|
80
|
+
def shipping_total
|
81
|
+
@attrs['shipCost']
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [Float] Total sales tax for this Order.
|
85
|
+
def sales_tax_toal
|
86
|
+
@attrs['salesTaxTotal']
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [Float] Total sales amount for this Order.
|
90
|
+
def order_total
|
91
|
+
@attrs['orderTotal']
|
92
|
+
end
|
93
|
+
|
94
|
+
# @return [String] Payment methods used for this Order.
|
95
|
+
def payment_methods
|
96
|
+
@attrs['paymentMethod'].values
|
97
|
+
end
|
98
|
+
|
99
|
+
# @param key [String] An Order attribute name (from the JSON response).
|
100
|
+
# @return The value of the given `key` or `nil`.
|
101
|
+
def [](key)
|
102
|
+
@attrs[key]
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module GunBroker
|
2
|
+
# Represents a page of GunBroker orders.
|
3
|
+
class OrdersAsPage
|
4
|
+
|
5
|
+
# @param attrs [Hash] The attributes required to fetch orders from the API.
|
6
|
+
def initialize(attributes = {})
|
7
|
+
@attributes = attributes
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [Array<Order>]
|
11
|
+
def fetch_orders
|
12
|
+
@attributes[:params].merge!({
|
13
|
+
'PageIndex' => @attributes[:page_index],
|
14
|
+
'PageSize' => @attributes[:page_size],
|
15
|
+
})
|
16
|
+
response = GunBroker::API.get(@attributes[:endpoint], @attributes[:params], @attributes[:token_header])
|
17
|
+
|
18
|
+
response['results'].map { |result| GunBroker::Order.new(result) }
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
data/lib/gun_broker/user.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'gun_broker/token_header'
|
2
2
|
require 'gun_broker/user/items_delegate'
|
3
3
|
require 'gun_broker/user/items_as_pages_delegate'
|
4
|
+
require 'gun_broker/user/orders_delegate'
|
5
|
+
require 'gun_broker/user/orders_as_pages_delegate'
|
4
6
|
|
5
7
|
module GunBroker
|
6
8
|
# Represents a GunBroker User.
|
@@ -94,6 +96,20 @@ module GunBroker
|
|
94
96
|
@items_as_pages_delegate ||= ItemsAsPagesDelegate.new(self, options)
|
95
97
|
end
|
96
98
|
|
99
|
+
# (see OrdersDelegate)
|
100
|
+
# See the {OrdersDelegate} docs.
|
101
|
+
# @return [OrdersDelegate]
|
102
|
+
def orders
|
103
|
+
@orders_delegate ||= OrdersDelegate.new(self)
|
104
|
+
end
|
105
|
+
|
106
|
+
# (see OrdersAsPagesDelegate)
|
107
|
+
# See the {OrdersAsPagesDelegate} docs.
|
108
|
+
# @return [OrdersAsPagesDelegate]
|
109
|
+
def orders_as_pages(options = {})
|
110
|
+
@orders_as_pages_delegate ||= OrdersAsPagesDelegate.new(self, options)
|
111
|
+
end
|
112
|
+
|
97
113
|
private
|
98
114
|
|
99
115
|
# @return [Boolean] `true` if `@username` is present and either `@password` *or* `@token` is present.
|
@@ -99,7 +99,7 @@ module GunBroker
|
|
99
99
|
when :sellername
|
100
100
|
{ 'SellerName' => @user.username }
|
101
101
|
when :timeframe
|
102
|
-
{ 'TimeFrame' => GunBroker::API::
|
102
|
+
{ 'TimeFrame' => GunBroker::API::MAX_ITEMS_TIME_FRAME }
|
103
103
|
else
|
104
104
|
raise GunBroker::Error.new 'Unrecognized `params_for` key.'
|
105
105
|
end
|
@@ -180,7 +180,7 @@ module GunBroker
|
|
180
180
|
when :sellername
|
181
181
|
{ 'SellerName' => @user.username }
|
182
182
|
when :timeframe
|
183
|
-
{ 'TimeFrame' => GunBroker::API::
|
183
|
+
{ 'TimeFrame' => GunBroker::API::MAX_ITEMS_TIME_FRAME }
|
184
184
|
when :itemid
|
185
185
|
{ 'ItemID' => (options[:item_id] || options["ItemID"]) }
|
186
186
|
else
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'gun_broker/token_header'
|
2
|
+
|
3
|
+
module GunBroker
|
4
|
+
class User
|
5
|
+
# Used to scope {OrdersAsPage} actions by {User}.
|
6
|
+
class OrdersAsPagesDelegate
|
7
|
+
|
8
|
+
include GunBroker::TokenHeader
|
9
|
+
|
10
|
+
# @param user [User] A {User} instance to scope order pages by.
|
11
|
+
# @param options [Hash] { orders_per_page => <number of desired orders per page> (Integer) }.
|
12
|
+
def initialize(user, options = {})
|
13
|
+
max_page_size = GunBroker::API::PAGE_SIZE
|
14
|
+
@user = user
|
15
|
+
@orders_per_page = options.fetch(:orders_per_page, max_page_size)
|
16
|
+
|
17
|
+
if @orders_per_page > max_page_size
|
18
|
+
raise ArgumentError.new("`orders_per_page` may not exceed #{max_page_size}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns pages for orders the User has sold.
|
23
|
+
# @note {API#get! GET} /OrdersSold
|
24
|
+
# @return [Array<OrdersAsPage>]
|
25
|
+
def sold
|
26
|
+
@sold ||= build_pages_for(:OrdersSold, { 'TimeFrame' => GunBroker::API::MAX_ORDERS_TIME_FRAME })
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def build_pages_for(endpoint, params = {})
|
32
|
+
endpoint = ['/', endpoint.to_s].join
|
33
|
+
_token_header = token_header(@user.token)
|
34
|
+
response = GunBroker::API.get(endpoint, params.merge({ 'PageSize' => 1 }), _token_header)
|
35
|
+
number_of_pages = (response['count'] / @orders_per_page.to_f).ceil
|
36
|
+
orders_as_pages = []
|
37
|
+
|
38
|
+
number_of_pages.times do |page_number|
|
39
|
+
page_number += 1
|
40
|
+
attrs = {
|
41
|
+
page_size: @orders_per_page,
|
42
|
+
page_index: page_number,
|
43
|
+
endpoint: endpoint,
|
44
|
+
params: params,
|
45
|
+
token_header: _token_header
|
46
|
+
}
|
47
|
+
|
48
|
+
orders_as_pages << GunBroker::OrdersAsPage.new(attrs)
|
49
|
+
end
|
50
|
+
|
51
|
+
orders_as_pages
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'gun_broker/token_header'
|
2
|
+
|
3
|
+
module GunBroker
|
4
|
+
class User
|
5
|
+
# Used to scope {Order} actions by {User}.
|
6
|
+
class OrdersDelegate
|
7
|
+
|
8
|
+
include GunBroker::TokenHeader
|
9
|
+
|
10
|
+
# @param user [User] A {User} instance to scope orders by.
|
11
|
+
def initialize(user)
|
12
|
+
@user = user
|
13
|
+
end
|
14
|
+
|
15
|
+
# Finds a specific User's Order by ID. Calls {Order.find} to get full Order details.
|
16
|
+
# @raise (see #sold)
|
17
|
+
# @return [Order] Returns the Order or `nil` if no Order found.
|
18
|
+
def find(order_id)
|
19
|
+
GunBroker::Order.find(order_id, {}, token_header(@user.token))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Same as {#find} but raises GunBroker::Error::NotFound if no order is found.
|
23
|
+
# @raise [GunBroker::Error::NotFound] If the User has no Order with `order_id`.
|
24
|
+
# @return [Order] Returns the Order.
|
25
|
+
def find!(order_id)
|
26
|
+
order = find(order_id)
|
27
|
+
raise GunBroker::Error::NotFound.new("Couldn't find order with ID '#{order_id}'") if order.nil?
|
28
|
+
order
|
29
|
+
end
|
30
|
+
|
31
|
+
# Sold Orders for the User.
|
32
|
+
# @param options [Hash] {ItemID=>ItemID}
|
33
|
+
# @note {API#get! GET} /OrdersSold
|
34
|
+
# @return [Array<Order>]
|
35
|
+
def sold(options = {})
|
36
|
+
params = [
|
37
|
+
*params_for(:timeframe),
|
38
|
+
*params_for(:itemid, options)
|
39
|
+
].to_h
|
40
|
+
|
41
|
+
@sold ||= fetch_orders(:OrdersSold, params)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def fetch_orders(endpoint, params = {})
|
47
|
+
cleanup_nil_params(params)
|
48
|
+
params.merge!('PageSize' => GunBroker::API::PAGE_SIZE)
|
49
|
+
|
50
|
+
endpoint = ['/', endpoint.to_s].join
|
51
|
+
response = GunBroker::API.get(endpoint, params, token_header(@user.token))
|
52
|
+
number_of_pages = (response['count'] / GunBroker::API::PAGE_SIZE.to_f).ceil
|
53
|
+
|
54
|
+
if number_of_pages > 1
|
55
|
+
_orders_from_results = orders_from_results(response['results'])
|
56
|
+
|
57
|
+
number_of_pages.times do |page_number|
|
58
|
+
page_number += 1
|
59
|
+
next if page_number == 1
|
60
|
+
|
61
|
+
params.merge!({ 'PageIndex' => page_number })
|
62
|
+
response = GunBroker::API.get(endpoint, params, token_header(@user.token))
|
63
|
+
_orders_from_results.concat(orders_from_results(response['results']))
|
64
|
+
end
|
65
|
+
|
66
|
+
_orders_from_results
|
67
|
+
else
|
68
|
+
orders_from_results(response['results'])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def orders_from_results(results)
|
73
|
+
# TODO: Ignore non-US orders.
|
74
|
+
results.map { |result| GunBroker::Order.new(result) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def params_for(key, options = {})
|
78
|
+
case key
|
79
|
+
when :timeframe
|
80
|
+
{ 'TimeFrame' => GunBroker::API::MAX_ORDERS_TIME_FRAME }
|
81
|
+
when :itemid
|
82
|
+
{ 'ItemID' => (options[:item_id] || options["ItemID"]) }
|
83
|
+
else
|
84
|
+
raise GunBroker::Error.new 'Unrecognized `params_for` key.'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def cleanup_nil_params(params)
|
89
|
+
params.delete_if { |k, v| v.nil? }
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/gun_broker/version.rb
CHANGED
@@ -160,7 +160,7 @@ describe GunBroker::User::ItemsDelegate do
|
|
160
160
|
headers: headers('X-AccessToken' => token),
|
161
161
|
query: {
|
162
162
|
'PageSize' => GunBroker::API::PAGE_SIZE,
|
163
|
-
'TimeFrame' => GunBroker::API::
|
163
|
+
'TimeFrame' => GunBroker::API::MAX_ITEMS_TIME_FRAME,
|
164
164
|
}
|
165
165
|
)
|
166
166
|
.to_return(body: response_fixture('items'))
|
@@ -178,7 +178,7 @@ describe GunBroker::User::ItemsDelegate do
|
|
178
178
|
headers: headers('X-AccessToken' => token),
|
179
179
|
query: {
|
180
180
|
'PageSize' => GunBroker::API::PAGE_SIZE,
|
181
|
-
'TimeFrame' => GunBroker::API::
|
181
|
+
'TimeFrame' => GunBroker::API::MAX_ITEMS_TIME_FRAME,
|
182
182
|
}
|
183
183
|
)
|
184
184
|
.to_return(body: response_fixture('not_authorized'), status: 401)
|
@@ -199,7 +199,7 @@ describe GunBroker::User::ItemsDelegate do
|
|
199
199
|
headers: headers('X-AccessToken' => token),
|
200
200
|
query: {
|
201
201
|
'PageSize' => GunBroker::API::PAGE_SIZE,
|
202
|
-
'TimeFrame' => GunBroker::API::
|
202
|
+
'TimeFrame' => GunBroker::API::MAX_ITEMS_TIME_FRAME,
|
203
203
|
}
|
204
204
|
)
|
205
205
|
.to_return(body: response_fixture('items'))
|
@@ -216,7 +216,7 @@ describe GunBroker::User::ItemsDelegate do
|
|
216
216
|
query: {
|
217
217
|
'PageSize' => GunBroker::API::PAGE_SIZE,
|
218
218
|
'ItemID' => '123',
|
219
|
-
'TimeFrame' => GunBroker::API::
|
219
|
+
'TimeFrame' => GunBroker::API::MAX_ITEMS_TIME_FRAME,
|
220
220
|
}
|
221
221
|
)
|
222
222
|
.to_return(body: response_fixture('item_id'))
|
@@ -234,7 +234,7 @@ describe GunBroker::User::ItemsDelegate do
|
|
234
234
|
headers: headers('X-AccessToken' => token),
|
235
235
|
query: {
|
236
236
|
'PageSize' => GunBroker::API::PAGE_SIZE,
|
237
|
-
'TimeFrame' => GunBroker::API::
|
237
|
+
'TimeFrame' => GunBroker::API::MAX_ITEMS_TIME_FRAME,
|
238
238
|
}
|
239
239
|
)
|
240
240
|
.to_return(body: response_fixture('not_authorized'), status: 401)
|
@@ -255,7 +255,7 @@ describe GunBroker::User::ItemsDelegate do
|
|
255
255
|
headers: headers('X-AccessToken' => token),
|
256
256
|
query: {
|
257
257
|
'PageSize' => GunBroker::API::PAGE_SIZE,
|
258
|
-
'TimeFrame' => GunBroker::API::
|
258
|
+
'TimeFrame' => GunBroker::API::MAX_ITEMS_TIME_FRAME,
|
259
259
|
}
|
260
260
|
)
|
261
261
|
.to_return(body: response_fixture('items'))
|
@@ -273,7 +273,7 @@ describe GunBroker::User::ItemsDelegate do
|
|
273
273
|
headers: headers('X-AccessToken' => token),
|
274
274
|
query: {
|
275
275
|
'PageSize' => GunBroker::API::PAGE_SIZE,
|
276
|
-
'TimeFrame' => GunBroker::API::
|
276
|
+
'TimeFrame' => GunBroker::API::MAX_ITEMS_TIME_FRAME,
|
277
277
|
}
|
278
278
|
)
|
279
279
|
.to_return(body: response_fixture('not_authorized'), status: 401)
|
@@ -294,7 +294,7 @@ describe GunBroker::User::ItemsDelegate do
|
|
294
294
|
headers: headers('X-AccessToken' => token),
|
295
295
|
query: {
|
296
296
|
'PageSize' => GunBroker::API::PAGE_SIZE,
|
297
|
-
'TimeFrame' => GunBroker::API::
|
297
|
+
'TimeFrame' => GunBroker::API::MAX_ITEMS_TIME_FRAME,
|
298
298
|
}
|
299
299
|
)
|
300
300
|
.to_return(body: response_fixture('items'))
|
@@ -312,7 +312,7 @@ describe GunBroker::User::ItemsDelegate do
|
|
312
312
|
headers: headers('X-AccessToken' => token),
|
313
313
|
query: {
|
314
314
|
'PageSize' => GunBroker::API::PAGE_SIZE,
|
315
|
-
'TimeFrame' => GunBroker::API::
|
315
|
+
'TimeFrame' => GunBroker::API::MAX_ITEMS_TIME_FRAME,
|
316
316
|
}
|
317
317
|
)
|
318
318
|
.to_return(body: response_fixture('not_authorized'), status: 401)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gun_broker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dale Campbell
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-10-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -137,11 +137,15 @@ files:
|
|
137
137
|
- lib/gun_broker/item.rb
|
138
138
|
- lib/gun_broker/item/constants.rb
|
139
139
|
- lib/gun_broker/items_as_page.rb
|
140
|
+
- lib/gun_broker/order.rb
|
141
|
+
- lib/gun_broker/orders_as_page.rb
|
140
142
|
- lib/gun_broker/response.rb
|
141
143
|
- lib/gun_broker/token_header.rb
|
142
144
|
- lib/gun_broker/user.rb
|
143
145
|
- lib/gun_broker/user/items_as_pages_delegate.rb
|
144
146
|
- lib/gun_broker/user/items_delegate.rb
|
147
|
+
- lib/gun_broker/user/orders_as_pages_delegate.rb
|
148
|
+
- lib/gun_broker/user/orders_delegate.rb
|
145
149
|
- lib/gun_broker/version.rb
|
146
150
|
- spec/fixtures/authenticate.json
|
147
151
|
- spec/fixtures/categories.json
|