standardapi 6.0.0.26 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +15 -6
- data/lib/standard_api.rb +5 -0
- data/lib/standard_api/access_control_list.rb +114 -0
- data/lib/standard_api/active_record/connection_adapters/postgresql/schema_statements.rb +21 -0
- data/lib/standard_api/controller.rb +75 -78
- data/lib/standard_api/errors.rb +9 -0
- data/lib/standard_api/helpers.rb +66 -13
- data/lib/standard_api/includes.rb +9 -0
- data/lib/standard_api/middleware/query_encoding.rb +3 -3
- data/lib/standard_api/railtie.rb +13 -2
- data/lib/standard_api/route_helpers.rb +5 -5
- data/lib/standard_api/test_case.rb +24 -14
- data/lib/standard_api/test_case/calculate_tests.rb +10 -4
- data/lib/standard_api/test_case/create_tests.rb +13 -15
- data/lib/standard_api/test_case/index_tests.rb +14 -4
- data/lib/standard_api/test_case/schema_tests.rb +25 -3
- data/lib/standard_api/test_case/show_tests.rb +1 -0
- data/lib/standard_api/test_case/update_tests.rb +8 -9
- data/lib/standard_api/version.rb +1 -1
- data/lib/standard_api/views/application/_record.json.jbuilder +33 -30
- data/lib/standard_api/views/application/_record.streamer +36 -34
- data/lib/standard_api/views/application/_schema.json.jbuilder +68 -0
- data/lib/standard_api/views/application/_schema.streamer +78 -0
- data/lib/standard_api/views/application/new.streamer +1 -1
- data/lib/standard_api/views/application/schema.json.jbuilder +1 -12
- data/lib/standard_api/views/application/schema.streamer +1 -16
- data/test/standard_api/caching_test.rb +43 -0
- data/test/standard_api/helpers_test.rb +172 -0
- data/test/standard_api/performance.rb +39 -0
- data/test/standard_api/route_helpers_test.rb +33 -0
- data/test/standard_api/standard_api_test.rb +699 -0
- data/test/standard_api/test_app.rb +1 -0
- data/test/standard_api/test_app/app/controllers/acl/account_acl.rb +15 -0
- data/test/standard_api/test_app/app/controllers/acl/property_acl.rb +27 -0
- data/test/standard_api/test_app/app/controllers/acl/reference_acl.rb +7 -0
- data/test/standard_api/test_app/controllers.rb +13 -45
- data/test/standard_api/test_app/models.rb +38 -4
- data/test/standard_api/test_app/test/factories.rb +4 -3
- data/test/standard_api/test_app/views/photos/_photo.json.jbuilder +1 -0
- data/test/standard_api/test_app/views/photos/_photo.streamer +18 -0
- data/test/standard_api/test_app/views/photos/_schema.json.jbuilder +1 -0
- data/test/standard_api/test_app/views/photos/_schema.streamer +3 -0
- data/test/standard_api/test_app/views/photos/schema.json.jbuilder +1 -1
- data/test/standard_api/test_app/views/photos/schema.streamer +1 -0
- data/test/standard_api/test_helper.rb +238 -0
- metadata +33 -17
- data/test/standard_api/test_app/log/test.log +0 -129516
@@ -1 +1 @@
|
|
1
|
-
json.partial! model_partial(model), model_partial(model).split('/').last.to_sym => instance_variable_get("@#{model.model_name.singular}"), includes: includes
|
1
|
+
json.partial! model_partial(model), model_partial(model).split('/').last.to_sym => instance_variable_get("@#{model.model_name.singular}"), includes: includes
|
@@ -1,12 +1 @@
|
|
1
|
-
json.
|
2
|
-
model.columns.each do |column|
|
3
|
-
json.set! column.name, {
|
4
|
-
type: json_column_type(column.sql_type),
|
5
|
-
primary_key: column.name == model.primary_key,
|
6
|
-
null: column.null,
|
7
|
-
array: column.array
|
8
|
-
}
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
json.set! 'limit', resource_limit
|
1
|
+
json.partial!('schema', model: model)
|
@@ -1,16 +1 @@
|
|
1
|
-
json.
|
2
|
-
json.set! 'columns' do
|
3
|
-
json.object! do
|
4
|
-
model.columns.each do |column|
|
5
|
-
json.set! column.name, {
|
6
|
-
type: json_column_type(column.sql_type),
|
7
|
-
primary_key: column.name == model.primary_key,
|
8
|
-
null: column.null,
|
9
|
-
array: column.array
|
10
|
-
}
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
json.set! 'limit', resource_limit
|
16
|
-
end
|
1
|
+
json.partial!('schema', model: model)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'standard_api/test_helper'
|
2
|
+
|
3
|
+
class AccountsControllerTest < ActionDispatch::IntegrationTest
|
4
|
+
|
5
|
+
test 'include with cache' do
|
6
|
+
account = create(:account, photos: [])
|
7
|
+
photo = create(:photo, account_id: account.id)
|
8
|
+
|
9
|
+
t1 = 1.day.from_now
|
10
|
+
t2 = 2.days.from_now
|
11
|
+
|
12
|
+
columns = Account.column_names + ['photos_account_cached_at', 'photos_cached_at']
|
13
|
+
Account.stubs(:column_names).returns(columns)
|
14
|
+
|
15
|
+
# Cache Miss
|
16
|
+
Account.any_instance.stubs(:photos_cached_at).returns(t1)
|
17
|
+
get account_path(account, include: :photos, format: :json)
|
18
|
+
assert_equal [photo.id], JSON(response.body)['photos'].map{|x| x['id']}
|
19
|
+
|
20
|
+
# Cache Hit
|
21
|
+
Account.any_instance.stubs(:photos).returns([])
|
22
|
+
Account.any_instance.stubs(:photos_cached_at).returns(t1)
|
23
|
+
get account_path(account, include: :photos, format: :json)
|
24
|
+
assert_equal [photo.id], JSON(response.body)['photos'].map{|x| x['id']}
|
25
|
+
|
26
|
+
# Cache Miss, photos_cached_at updated
|
27
|
+
Account.any_instance.stubs(:photos).returns(Photo.where('false = true'))
|
28
|
+
Account.any_instance.stubs(:photos_cached_at).returns(t2)
|
29
|
+
get account_path(account, include: :photos, format: :json)
|
30
|
+
assert_equal [], JSON(response.body)['photos'].map{|x| x['id']}
|
31
|
+
|
32
|
+
# Two associations that reference the same model
|
33
|
+
property = create(:property)
|
34
|
+
account = create(:account, property: property, subject: property)
|
35
|
+
Account.any_instance.expects(:property_cached_at).returns(t1)
|
36
|
+
Account.any_instance.expects(:subject_cached_at).returns(t1)
|
37
|
+
get account_path(account, include: { property: true, subject: true }, format: 'json')
|
38
|
+
json = JSON(response.body)
|
39
|
+
assert json.has_key?('property')
|
40
|
+
assert json.has_key?('subject')
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'standard_api/test_helper'
|
2
|
+
|
3
|
+
class HelpersTest < ActionView::TestCase
|
4
|
+
include StandardAPI::Helpers
|
5
|
+
|
6
|
+
test "::cached_at_columns_for_includes(includes)" do
|
7
|
+
assert_equal(
|
8
|
+
['photos_cached_at'],
|
9
|
+
cached_at_columns_for_includes({photos: {}})
|
10
|
+
)
|
11
|
+
|
12
|
+
assert_equal(
|
13
|
+
['photos_cached_at', 'photos_account_cached_at'],
|
14
|
+
cached_at_columns_for_includes({photos: {account: {}}})
|
15
|
+
)
|
16
|
+
|
17
|
+
assert_equal(
|
18
|
+
['photos_cached_at', 'photos_account_cached_at', 'photos_properties_cached_at'],
|
19
|
+
cached_at_columns_for_includes({photos: {account: {}, properties: {}}})
|
20
|
+
)
|
21
|
+
|
22
|
+
assert_equal(
|
23
|
+
['photos_cached_at', 'photos_account_cached_at', 'photos_properties_cached_at', 'photos_properties_landlord_cached_at'],
|
24
|
+
cached_at_columns_for_includes({photos: {account: {}, properties: {landlord: {}}}})
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
test "::can_cache?" do
|
29
|
+
Account.expects(:column_names).returns(['id'])
|
30
|
+
assert !can_cache?(Account, {})
|
31
|
+
|
32
|
+
Account.expects(:column_names).returns(['id', 'cached_at'])
|
33
|
+
assert can_cache?(Account, {})
|
34
|
+
|
35
|
+
Account.expects(:column_names).returns(['id', 'cached_at'])
|
36
|
+
assert !can_cache?(Account, {photos: {}})
|
37
|
+
|
38
|
+
Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at'])
|
39
|
+
assert can_cache?(Account, {photos: {}})
|
40
|
+
|
41
|
+
Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at'])
|
42
|
+
assert !can_cache?(Account, {photos: {account: {}}})
|
43
|
+
|
44
|
+
Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at', 'photos_account_cached_at'])
|
45
|
+
assert can_cache?(Account, {photos: {account: {}}})
|
46
|
+
end
|
47
|
+
|
48
|
+
test '::can_cache_relation? with non-persisted record' do
|
49
|
+
account = build(:account)
|
50
|
+
assert !can_cache_relation?(account, :photos, {})
|
51
|
+
assert !can_cache_relation?(account, :photos, {})
|
52
|
+
assert !can_cache_relation?(account, :photos, {account: {}})
|
53
|
+
assert !can_cache_relation?(account, :photos, {account: {}})
|
54
|
+
end
|
55
|
+
|
56
|
+
test '::can_cache_relation? with persisted record' do
|
57
|
+
account = create(:account)
|
58
|
+
|
59
|
+
Account.expects(:column_names).returns(['id', 'cached_at'])
|
60
|
+
assert !can_cache_relation?(account, :photos, {})
|
61
|
+
|
62
|
+
Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at'])
|
63
|
+
assert can_cache_relation?(account, :photos, {})
|
64
|
+
|
65
|
+
Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at'])
|
66
|
+
assert !can_cache_relation?(account, :photos, {account: {}})
|
67
|
+
|
68
|
+
Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at', 'photos_account_cached_at'])
|
69
|
+
assert can_cache_relation?(account, :photos, {account: {}})
|
70
|
+
end
|
71
|
+
|
72
|
+
test '::association_cache_key(record, relation, subincludes)' do
|
73
|
+
account = create(:account)
|
74
|
+
photo = create(:photo, account: account)
|
75
|
+
t1 = Time.now
|
76
|
+
t2 = 1.day.from_now
|
77
|
+
t3 = 2.days.from_now
|
78
|
+
|
79
|
+
Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at', 'photos_property_cached_at'])
|
80
|
+
account.expects(:photos_cached_at).returns(t1)
|
81
|
+
|
82
|
+
assert_equal(
|
83
|
+
"accounts/#{account.id}/photos-#{t1.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
|
84
|
+
association_cache_key(account, :photos, {})
|
85
|
+
)
|
86
|
+
|
87
|
+
Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at', 'photos_property_cached_at'])
|
88
|
+
account.expects(:photos_cached_at).returns(t1)
|
89
|
+
account.expects(:photos_property_cached_at).returns(t2)
|
90
|
+
assert_equal(
|
91
|
+
"accounts/#{account.id}/photos-2ea683a694a33359514c41435f8f0646-#{t2.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
|
92
|
+
association_cache_key(account, :photos, {property: {}})
|
93
|
+
)
|
94
|
+
|
95
|
+
Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at', 'photos_property_cached_at'])
|
96
|
+
account.expects(:photos_cached_at).returns(t1)
|
97
|
+
account.expects(:photos_property_cached_at).returns(t2)
|
98
|
+
assert_equal(
|
99
|
+
"accounts/#{account.id}/photos-779c17ef027655fd8c06c3083d2df64b-#{t2.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
|
100
|
+
association_cache_key(account, :photos, { "property" => { "order" => { "x" => "desc" }}})
|
101
|
+
)
|
102
|
+
|
103
|
+
Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at', 'photos_property_cached_at', 'photos_agents_cached_at'])
|
104
|
+
account.expects(:photos_cached_at).returns(t1)
|
105
|
+
account.expects(:photos_property_cached_at).returns(t2)
|
106
|
+
account.expects(:photos_agents_cached_at).returns(t3)
|
107
|
+
assert_equal(
|
108
|
+
"accounts/#{account.id}/photos-abbee2d4535400c162c8dbf14bbef6d5-#{t3.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
|
109
|
+
association_cache_key(account, :photos, {property: {}, agents: {}})
|
110
|
+
)
|
111
|
+
|
112
|
+
Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at', 'photos_property_cached_at', 'photos_property_agents_cached_at'])
|
113
|
+
account.expects(:photos_cached_at).returns(t1)
|
114
|
+
account.expects(:photos_property_cached_at).returns(t2)
|
115
|
+
account.expects(:photos_property_agents_cached_at).returns(t3)
|
116
|
+
assert_equal(
|
117
|
+
"accounts/#{account.id}/photos-0962ae73347c5c605d329eaa25e2be49-#{t3.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
|
118
|
+
association_cache_key(account, :photos, {property: {agents: {}}})
|
119
|
+
)
|
120
|
+
|
121
|
+
Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at', 'photos_property_cached_at', 'photos_agents_cached_at', 'photos_property_addresses_cached_at'])
|
122
|
+
account.expects(:photos_cached_at).returns(t1)
|
123
|
+
account.expects(:photos_property_cached_at).returns(t2)
|
124
|
+
account.expects(:photos_agents_cached_at).returns(t2)
|
125
|
+
account.expects(:photos_property_addresses_cached_at).returns(t3)
|
126
|
+
assert_equal(
|
127
|
+
"accounts/#{account.id}/photos-00ea6afe3ff68037f8b4dcdb275e2a24-#{t3.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
|
128
|
+
association_cache_key(account, :photos, {property: {addresses: {}}, agents: {}})
|
129
|
+
)
|
130
|
+
|
131
|
+
# Belongs to
|
132
|
+
Photo.expects(:column_names).returns(['id', 'cached_at', 'account_cached_at'])
|
133
|
+
photo.expects(:account_cached_at).returns(t1)
|
134
|
+
assert_equal(
|
135
|
+
"accounts/#{account.id}-#{t1.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
|
136
|
+
association_cache_key(photo, :account, {})
|
137
|
+
)
|
138
|
+
|
139
|
+
Photo.expects(:column_names).returns(['id', 'cached_at', 'account_cached_at', 'account_photos_cached_at'])
|
140
|
+
photo.expects(:account_cached_at).returns(t1)
|
141
|
+
photo.expects(:account_photos_cached_at).returns(t2)
|
142
|
+
assert_equal(
|
143
|
+
"accounts/#{account.id}/07437ce3863467f4cd715ae1ef930f08-#{t2.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
|
144
|
+
association_cache_key(photo, :account, {photos: {}})
|
145
|
+
)
|
146
|
+
end
|
147
|
+
|
148
|
+
test '::json_column_type(sql_type)' do
|
149
|
+
assert_equal 'string', json_column_type('character varying')
|
150
|
+
assert_equal 'string', json_column_type('character varying(2)')
|
151
|
+
assert_equal 'string', json_column_type('character varying(255)')
|
152
|
+
assert_equal 'datetime', json_column_type('timestamp without time zone')
|
153
|
+
assert_equal 'datetime', json_column_type('time without time zone')
|
154
|
+
assert_equal 'string', json_column_type('text')
|
155
|
+
assert_equal 'hash', json_column_type('json')
|
156
|
+
assert_equal 'hash', json_column_type('jsonb')
|
157
|
+
assert_equal 'integer', json_column_type('bigint')
|
158
|
+
assert_equal 'integer', json_column_type('integer')
|
159
|
+
assert_equal 'string', json_column_type('inet')
|
160
|
+
assert_equal 'hash', json_column_type('hstore')
|
161
|
+
assert_equal 'datetime', json_column_type('date')
|
162
|
+
assert_equal 'decimal', json_column_type('numeric')
|
163
|
+
assert_equal 'decimal', json_column_type('numeric(12)')
|
164
|
+
assert_equal 'decimal', json_column_type('numeric(12,2)')
|
165
|
+
assert_equal 'decimal', json_column_type('double precision')
|
166
|
+
assert_equal 'string', json_column_type('ltree')
|
167
|
+
assert_equal 'boolean', json_column_type('boolean')
|
168
|
+
assert_equal 'ewkb', json_column_type('geometry')
|
169
|
+
assert_equal 'string', json_column_type('uuid')
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'standard_api/test_helper'
|
2
|
+
require "benchmark/ips"
|
3
|
+
|
4
|
+
TIME = (ENV["BENCHMARK_TIME"] || 20).to_i
|
5
|
+
RECORDS = (ENV["BENCHMARK_RECORDS"] || TIME * 1000).to_i
|
6
|
+
|
7
|
+
app = ::ActionDispatch::IntegrationTest.new(:app)
|
8
|
+
properties = Array.new(1000) do
|
9
|
+
photos = Array.new(10) { FactoryBot.create(:photo) }
|
10
|
+
FactoryBot.create(:property, photos: photos)
|
11
|
+
end
|
12
|
+
|
13
|
+
Benchmark.ips(TIME) do |x|
|
14
|
+
|
15
|
+
# ActionView::Template.unregister_template_handler :streamer
|
16
|
+
# ActionView::Template.register_template_handler :jbuilder, JbuilderHandler
|
17
|
+
|
18
|
+
x.report("#index.json.jbuilder") do
|
19
|
+
app.get('/properties.json', params: { limit: 100 })
|
20
|
+
end
|
21
|
+
|
22
|
+
x.report("#show.json.jbuilder") do
|
23
|
+
app.get("/properties/#{properties.sample.id}.json")
|
24
|
+
end
|
25
|
+
|
26
|
+
x.report("#show.json.jbuilder w/ include=photos") do
|
27
|
+
app.get("/properties/#{properties.sample.id}.json", params: { include: :photos })
|
28
|
+
end
|
29
|
+
|
30
|
+
# ActionView::Template.unregister_template_handler :jbuilder
|
31
|
+
# ActionView::Template.register_template_handler :streamer, TurboStreamer::Handler
|
32
|
+
|
33
|
+
# x.report("#index.json.streamer") do
|
34
|
+
# app.get('/properties.json', params: { limit: 100 })
|
35
|
+
# end
|
36
|
+
|
37
|
+
# x.compare!
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'standard_api/test_helper'
|
2
|
+
|
3
|
+
class RouteHelpersTest < ActionDispatch::IntegrationTest
|
4
|
+
|
5
|
+
test 'standard_resources' do
|
6
|
+
assert_routing({ path: '/properties', method: :get }, { controller: 'properties', action: 'index' })
|
7
|
+
assert_routing({ path: '/properties/1', method: :get }, { controller: 'properties', action: 'show', id: '1' })
|
8
|
+
assert_routing({ path: '/properties/new', method: :get }, { controller: 'properties', action: 'new' })
|
9
|
+
assert_routing({ path: '/properties', method: :post }, { controller: 'properties', action: 'create' })
|
10
|
+
assert_routing({ path: '/properties/1', method: :put }, { controller: 'properties', action: 'update', id: '1' })
|
11
|
+
assert_routing({ path: '/properties/1', method: :patch }, { controller: 'properties', action: 'update', id: '1' })
|
12
|
+
assert_routing({ path: '/properties/1', method: :delete }, { controller: 'properties', action: 'destroy', id: '1' })
|
13
|
+
assert_routing({ path: '/properties/schema', method: :get }, { controller: 'properties', action: 'schema' })
|
14
|
+
assert_routing({ path: '/properties/calculate', method: :get }, { controller: 'properties', action: 'calculate' })
|
15
|
+
end
|
16
|
+
|
17
|
+
test 'standard_resource' do
|
18
|
+
assert_routing({ path: '/account', method: :get }, { controller: 'accounts', action: 'show' })
|
19
|
+
assert_routing({ path: '/account/new', method: :get }, { controller: 'accounts', action: 'new' })
|
20
|
+
assert_routing({ path: '/account', method: :post }, { controller: 'accounts', action: 'create' })
|
21
|
+
assert_routing({ path: '/account', method: :put }, { controller: 'accounts', action: 'update' })
|
22
|
+
assert_routing({ path: '/account', method: :patch }, { controller: 'accounts', action: 'update' })
|
23
|
+
assert_routing({ path: '/account', method: :delete }, { controller: 'accounts', action: 'destroy' })
|
24
|
+
assert_routing({ path: '/account/schema', method: :get }, { controller: 'accounts', action: 'schema' })
|
25
|
+
assert_routing({ path: '/account/calculate', method: :get }, { controller: 'accounts', action: 'calculate' })
|
26
|
+
end
|
27
|
+
|
28
|
+
test 'standard_resources subresource routes' do
|
29
|
+
assert_routing({ path: '/photos/1/properties/1', method: :post }, { controller: 'photos', action: 'add_resource', id: '1', relationship: 'properties', resource_id: '1' })
|
30
|
+
assert_routing({ path: '/photos/1/properties/1', method: :delete }, { controller: 'photos', action: 'remove_resource', id: '1', relationship: 'properties', resource_id: '1' })
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,699 @@
|
|
1
|
+
require 'standard_api/test_helper'
|
2
|
+
|
3
|
+
class PropertiesControllerTest < ActionDispatch::IntegrationTest
|
4
|
+
include StandardAPI::TestCase
|
5
|
+
include StandardAPI::Helpers
|
6
|
+
|
7
|
+
self.includes = [ :photos, :landlord, :english_name ]
|
8
|
+
|
9
|
+
def normalizers
|
10
|
+
{
|
11
|
+
Property => {
|
12
|
+
"size" => lambda { |value| value.round(4).to_s }
|
13
|
+
}
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
# = Routing Tests
|
18
|
+
#
|
19
|
+
# These also can't be included in StandardAPI::TestCase because we don't know
|
20
|
+
# how the other's routes are setup
|
21
|
+
|
22
|
+
# test 'route to #metadata' do
|
23
|
+
# assert_routing '/metadata', path_with_action('metadata')
|
24
|
+
# assert_recognizes path_with_action('metadata'), "/metadata"
|
25
|
+
# end
|
26
|
+
|
27
|
+
test 'route to #tables.json' do
|
28
|
+
assert_recognizes({"controller"=>"application", "action"=>"tables"}, { method: :get, path: "/tables" })
|
29
|
+
end
|
30
|
+
|
31
|
+
test 'route to #create.json' do
|
32
|
+
assert_routing({ method: :post, path: "/#{plural_name}" }, path_with_action('create'))
|
33
|
+
assert_recognizes(path_with_action('create'), { method: :post, path: "/#{plural_name}" })
|
34
|
+
end
|
35
|
+
|
36
|
+
test 'route to #calculate.json' do
|
37
|
+
assert_routing "/#{plural_name}/calculate", path_with_action('calculate')
|
38
|
+
assert_recognizes(path_with_action('calculate'), "/#{plural_name}/calculate")
|
39
|
+
end
|
40
|
+
|
41
|
+
test 'route to #destroy.json' do
|
42
|
+
assert_routing({ method: :delete, path: "/#{plural_name}/1" }, path_with_action('destroy', id: '1'))
|
43
|
+
assert_recognizes(path_with_action('destroy', id: '1'), { method: :delete, path: "/#{plural_name}/1" })
|
44
|
+
end
|
45
|
+
|
46
|
+
test 'route to #index.json' do
|
47
|
+
assert_routing "/#{plural_name}", path_with_action('index')
|
48
|
+
assert_recognizes path_with_action('index'), "/#{plural_name}"
|
49
|
+
end
|
50
|
+
|
51
|
+
test 'route to #show.json' do
|
52
|
+
assert_routing "/#{plural_name}/1", path_with_action('show', id: '1')
|
53
|
+
assert_recognizes(path_with_action('show', id: '1'), "/#{plural_name}/1")
|
54
|
+
end
|
55
|
+
|
56
|
+
test 'route to #update.json' do
|
57
|
+
assert_routing({ method: :put, path: "#{plural_name}/1" }, path_with_action('update', id: '1'))
|
58
|
+
assert_recognizes(path_with_action('update', id: '1'), { method: :put, path: "/#{plural_name}/1" })
|
59
|
+
assert_routing({ method: :patch, path: "/#{plural_name}/1" }, path_with_action('update', id: '1'))
|
60
|
+
assert_recognizes(path_with_action('update', id: '1'), { method: :patch, path: "/#{plural_name}/1" })
|
61
|
+
end
|
62
|
+
|
63
|
+
test 'route to #schema.json' do
|
64
|
+
assert_routing({ method: :get, path: "/#{plural_name}/schema" }, path_with_action('schema'))
|
65
|
+
assert_recognizes(path_with_action('schema'), { method: :get, path: "/#{plural_name}/schema" })
|
66
|
+
end
|
67
|
+
|
68
|
+
# = Controller Tests
|
69
|
+
|
70
|
+
test 'StandardAPI-Version' do
|
71
|
+
get schema_references_path(format: 'json')
|
72
|
+
assert_equal StandardAPI::VERSION, response.headers['StandardAPI-Version']
|
73
|
+
end
|
74
|
+
|
75
|
+
test 'Controller#new' do
|
76
|
+
@controller = ReferencesController.new
|
77
|
+
assert_equal @controller.send(:model), Reference
|
78
|
+
|
79
|
+
@controller = SessionsController.new
|
80
|
+
assert_nil @controller.send(:model)
|
81
|
+
get new_session_path
|
82
|
+
assert_response :ok
|
83
|
+
end
|
84
|
+
|
85
|
+
test 'Controller#model_orders defaults to []' do
|
86
|
+
@controller = ReferencesController.new
|
87
|
+
assert_equal @controller.send(:model_orders), []
|
88
|
+
end
|
89
|
+
|
90
|
+
test 'Controller#model_includes defaults to []' do
|
91
|
+
@controller = DocumentsController.new
|
92
|
+
assert_equal @controller.send(:model_includes), []
|
93
|
+
end
|
94
|
+
|
95
|
+
test 'Controller#model_params defaults to []' do
|
96
|
+
@controller = ReferencesController.new
|
97
|
+
@controller.params = {}
|
98
|
+
assert_equal @controller.send(:model_params), ActionController::Parameters.new
|
99
|
+
end
|
100
|
+
|
101
|
+
test 'Controller#current_mask' do
|
102
|
+
@controller = ReferencesController.new
|
103
|
+
@controller.instance_variable_set('@current_mask', { 'references' => { 'subject_id' => 1 }})
|
104
|
+
@controller.params = {}
|
105
|
+
assert_equal 'SELECT "references".* FROM "references" WHERE "references"."subject_id" = 1', @controller.send(:resources).to_sql
|
106
|
+
end
|
107
|
+
|
108
|
+
test 'ApplicationController#schema.json' do
|
109
|
+
get schema_path(format: 'json')
|
110
|
+
|
111
|
+
schema = JSON(response.body)
|
112
|
+
controllers = ApplicationController.descendants
|
113
|
+
controllers.select! { |c| c.ancestors.include?(StandardAPI::Controller) && c != StandardAPI::Controller }
|
114
|
+
|
115
|
+
@controller.send(:models).reject { |x| x.name == 'Photo' }.each do |model|
|
116
|
+
assert_equal true, schema['models'].has_key?(model.name)
|
117
|
+
|
118
|
+
model_comment = model.connection.table_comment(model.table_name)
|
119
|
+
if model_comment.nil? then
|
120
|
+
assert_nil schema.dig('models', model.name, 'comment')
|
121
|
+
else
|
122
|
+
assert_equal model_comment, schema.dig('models', model.name, 'comment')
|
123
|
+
end
|
124
|
+
|
125
|
+
model.columns.each do |column|
|
126
|
+
assert_equal json_column_type(column.sql_type), schema.dig('models', model.name, 'attributes', column.name, 'type')
|
127
|
+
default = column.default
|
128
|
+
if default then
|
129
|
+
default = model.connection.lookup_cast_type_from_column(column).deserialize(default)
|
130
|
+
assert_equal default, schema.dig('models', model.name, 'attributes', column.name, 'default')
|
131
|
+
else
|
132
|
+
assert_nil schema.dig('models', model.name, 'attributes', column.name, 'default')
|
133
|
+
end
|
134
|
+
assert_equal column.name == model.primary_key, schema.dig('models', model.name, 'attributes', column.name, 'primary_key')
|
135
|
+
assert_equal column.null, schema.dig('models', model.name, 'attributes', column.name, 'null')
|
136
|
+
assert_equal column.array, schema.dig('models', model.name, 'attributes', column.name, 'array')
|
137
|
+
if column.comment then
|
138
|
+
assert_equal column.comment, schema.dig('models', model.name, 'attributes', column.name, 'comment')
|
139
|
+
else
|
140
|
+
assert_nil schema.dig('models', model.name, 'attributes', column.name, 'comment')
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
assert_equal 'test comment', schema['comment']
|
146
|
+
end
|
147
|
+
|
148
|
+
test 'Controller#schema.json' do
|
149
|
+
get schema_references_path(format: 'json')
|
150
|
+
|
151
|
+
schema = JSON(response.body)
|
152
|
+
assert_equal true, schema.has_key?('attributes')
|
153
|
+
assert_equal true, schema['attributes']['id']['primary_key']
|
154
|
+
assert_equal 1000, schema['limit']
|
155
|
+
end
|
156
|
+
|
157
|
+
test 'Controller#schema.json w/o limit' do
|
158
|
+
get schema_unlimited_index_path(format: 'json')
|
159
|
+
|
160
|
+
schema = JSON(response.body)
|
161
|
+
assert_equal true, schema.has_key?('attributes')
|
162
|
+
assert_equal true, schema['attributes']['id']['primary_key']
|
163
|
+
assert_nil schema['limit']
|
164
|
+
end
|
165
|
+
|
166
|
+
test 'Controller#index w/o limit' do
|
167
|
+
account = create(:account)
|
168
|
+
get unlimited_index_path(format: 'json')
|
169
|
+
|
170
|
+
assert_equal [account.id], JSON(response.body).map { |x| x['id'] }
|
171
|
+
end
|
172
|
+
|
173
|
+
test 'Controller#index with default limit' do
|
174
|
+
create(:account)
|
175
|
+
get default_limit_index_path(format: 'json')
|
176
|
+
assert_response :ok
|
177
|
+
end
|
178
|
+
|
179
|
+
test 'Controller#create redirects to correct route with STI models' do
|
180
|
+
attrs = attributes_for(:pdf)
|
181
|
+
post documents_path, params: { document: attrs }
|
182
|
+
assert_response :redirect
|
183
|
+
end
|
184
|
+
|
185
|
+
test 'Controller#update redirects to correct route with STI models' do
|
186
|
+
pdf = create(:pdf)
|
187
|
+
patch document_path(pdf), params: { document: pdf.attributes }
|
188
|
+
assert_redirected_to document_path(pdf)
|
189
|
+
end
|
190
|
+
|
191
|
+
test 'Controller#add_resource' do
|
192
|
+
property = create(:property, photos: [])
|
193
|
+
photo = create(:photo)
|
194
|
+
|
195
|
+
post "/properties/#{property.id}/photos/#{photo.id}"
|
196
|
+
assert_equal property.photos.reload.map(&:id), [photo.id]
|
197
|
+
assert_response :created
|
198
|
+
|
199
|
+
post "/properties/#{property.id}/photos/9999999"
|
200
|
+
assert_response :not_found
|
201
|
+
end
|
202
|
+
|
203
|
+
test 'Controller#add_resource with has_one' do
|
204
|
+
photo = create(:document)
|
205
|
+
property = create(:property)
|
206
|
+
post "/properties/#{property.id}/document/#{photo.id}"
|
207
|
+
assert_equal property.reload.document, photo
|
208
|
+
assert_response :created
|
209
|
+
end
|
210
|
+
|
211
|
+
test 'Controller#remove_resource' do
|
212
|
+
photo = create(:photo)
|
213
|
+
property = create(:property, photos: [photo])
|
214
|
+
assert_equal property.photos.reload, [photo]
|
215
|
+
delete "/properties/#{property.id}/photos/#{photo.id}"
|
216
|
+
assert_equal property.photos.reload, []
|
217
|
+
assert_response :no_content
|
218
|
+
|
219
|
+
delete "/properties/#{property.id}/photos/9999999"
|
220
|
+
assert_response :not_found
|
221
|
+
end
|
222
|
+
|
223
|
+
test 'Controller#remove_resource with has_one' do
|
224
|
+
photo = create(:document)
|
225
|
+
property = create(:property, document: photo)
|
226
|
+
assert_equal property.document, photo
|
227
|
+
delete "/properties/#{property.id}/document/#{photo.id}"
|
228
|
+
assert_nil property.reload.document
|
229
|
+
assert_response :no_content
|
230
|
+
end
|
231
|
+
|
232
|
+
# = View Tests
|
233
|
+
|
234
|
+
test 'rendering tables' do
|
235
|
+
get tables_path(format: 'json')
|
236
|
+
assert_response :ok
|
237
|
+
# assert_equal ['properties', 'accounts', 'photos', 'references', 'sessions', 'unlimited'], response.parsed_body
|
238
|
+
# Multiple 'accounts' because multiple controllers with that model for testing.
|
239
|
+
assert_equal ["properties", "accounts", "documents", "photos", "references", "accounts", 'accounts'].sort, response.parsed_body.sort
|
240
|
+
end
|
241
|
+
|
242
|
+
test 'rendering null attribute' do
|
243
|
+
property = create(:property)
|
244
|
+
get property_path(property, format: 'json'), params: { id: property.id, include: [:landlord] }
|
245
|
+
assert JSON(response.body).has_key?('landlord')
|
246
|
+
assert_nil JSON(response.body)['landlord']
|
247
|
+
end
|
248
|
+
|
249
|
+
test 'rendering null attribute for has_one through' do
|
250
|
+
property = create(:property)
|
251
|
+
get property_path(property, format: 'json'), params: { id: property.id, include: [:document] }
|
252
|
+
assert JSON(response.body).has_key?('document')
|
253
|
+
assert_nil JSON(response.body)['document']
|
254
|
+
end
|
255
|
+
|
256
|
+
test '#index.json uses overridden partial' do
|
257
|
+
create(:property, photos: [create(:photo)])
|
258
|
+
get properties_path(format: 'json'), params: { limit: 100, include: [{:photos => { order: :id }}] }
|
259
|
+
|
260
|
+
photo = JSON(response.body)[0]['photos'][0]
|
261
|
+
assert_equal true, photo.has_key?('template')
|
262
|
+
assert_equal 'photos/_photo', photo['template']
|
263
|
+
end
|
264
|
+
|
265
|
+
test '#show.json uses overridden partial' do
|
266
|
+
property = create(:property, photos: [create(:photo)])
|
267
|
+
get property_path(property, format: 'json'), params: { id: property.id, include: [:photos] }
|
268
|
+
|
269
|
+
photo = JSON(response.body)['photos'][0]
|
270
|
+
assert_equal true, photo.has_key?('template')
|
271
|
+
assert_equal 'photos/_photo', photo['template']
|
272
|
+
end
|
273
|
+
|
274
|
+
test '#schema.json uses overridden partial' do
|
275
|
+
get schema_photos_path(format: 'json')
|
276
|
+
|
277
|
+
schema = JSON(response.body)
|
278
|
+
assert_rendered 'photos/schema', format: 'json', handler: 'jbuilder'
|
279
|
+
assert_equal true, schema.has_key?('template')
|
280
|
+
assert_equal 'photos/schema', schema['template']
|
281
|
+
end
|
282
|
+
|
283
|
+
test 'application#schema.json renders overridden #schema.json partials' do
|
284
|
+
get schema_path(format: 'json')
|
285
|
+
|
286
|
+
schema = JSON(response.body)
|
287
|
+
assert_rendered 'application/schema', format: 'json', handler: 'jbuilder'
|
288
|
+
assert_equal 'photos/schema', schema.dig('models', 'Photo', 'template')
|
289
|
+
end
|
290
|
+
|
291
|
+
test 'belongs_to polymorphic association' do
|
292
|
+
photo = create(:photo)
|
293
|
+
reference = create(:reference, subject: photo)
|
294
|
+
get reference_path(reference, include: :subject, format: 'json')
|
295
|
+
|
296
|
+
json = JSON(response.body)
|
297
|
+
assert_equal 'photos/_photo', json['subject']['template']
|
298
|
+
end
|
299
|
+
|
300
|
+
test '#index.json includes polymorphic association' do
|
301
|
+
property1 = create(:property)
|
302
|
+
property2 = create(:property)
|
303
|
+
photo = create(:photo)
|
304
|
+
create(:reference, subject: property1)
|
305
|
+
create(:reference, subject: property2)
|
306
|
+
create(:reference, subject: photo)
|
307
|
+
|
308
|
+
get references_path(format: 'json'), params: { include: [:subject], limit: 10 }
|
309
|
+
|
310
|
+
json = JSON(response.body)
|
311
|
+
assert_equal 'photos/_photo', json.find { |x| x['subject_type'] == "Photo"}['subject']['template']
|
312
|
+
end
|
313
|
+
|
314
|
+
test 'has_many association' do
|
315
|
+
p = create(:property, photos: [create(:photo)])
|
316
|
+
get properties_path(format: 'json'), params: { limit: 100, include: [:photos] }
|
317
|
+
assert_equal p.photos.first.id, JSON(response.body)[0]['photos'][0]['id']
|
318
|
+
end
|
319
|
+
|
320
|
+
test 'belongs_to association' do
|
321
|
+
account = create(:account)
|
322
|
+
photo = create(:photo, account: account)
|
323
|
+
get photo_path(photo, include: 'account', format: 'json')
|
324
|
+
assert_equal account.id, JSON(response.body)['account']['id']
|
325
|
+
end
|
326
|
+
|
327
|
+
test 'has_one association' do
|
328
|
+
account = create(:account)
|
329
|
+
property = create(:property, landlord: account)
|
330
|
+
get property_path(property, include: 'landlord', format: 'json')
|
331
|
+
assert_equal account.id, JSON(response.body)['landlord']['id']
|
332
|
+
end
|
333
|
+
|
334
|
+
test 'include method' do
|
335
|
+
property = create(:property)
|
336
|
+
get property_path(property, include: 'english_name', format: 'json')
|
337
|
+
assert_equal 'A Name', JSON(response.body)['english_name']
|
338
|
+
end
|
339
|
+
|
340
|
+
test 'include with where key' do
|
341
|
+
photo_a = create(:photo)
|
342
|
+
photo_b = create(:photo)
|
343
|
+
photo_c = create(:photo)
|
344
|
+
|
345
|
+
property = create(:property, photos: [photo_b, photo_c])
|
346
|
+
get property_path(property, include: { photos: { where: { id: photo_a.id } } }, format: :json)
|
347
|
+
assert_equal [], JSON(response.body)['photos']
|
348
|
+
|
349
|
+
property.photos << photo_a
|
350
|
+
get property_path(property, include: { photos: { where: { id: photo_a.id } } }, format: :json)
|
351
|
+
assert_equal [photo_a.id], JSON(response.body)['photos'].map { |x| x['id'] }
|
352
|
+
get property_path(property, include: { photos: { where: [
|
353
|
+
{ id: photo_a.id },
|
354
|
+
'OR',
|
355
|
+
{ id: photo_c.id}
|
356
|
+
] } }, format: :json)
|
357
|
+
assert_equal [photo_a.id, photo_c.id].sort,
|
358
|
+
JSON(response.body)['photos'].map { |x| x['id'] }.sort
|
359
|
+
end
|
360
|
+
|
361
|
+
test 'include with order key' do
|
362
|
+
photos = Array.new(5) { create(:photo) }
|
363
|
+
property = create(:property, photos: photos)
|
364
|
+
|
365
|
+
get property_path(property, include: { photos: { order: { id: :asc } } }, format: 'json')
|
366
|
+
assert_equal photos.map(&:id).sort, JSON(response.body)['photos'].map { |x| x['id'] }
|
367
|
+
end
|
368
|
+
|
369
|
+
test 'include relation with default order using an order key' do
|
370
|
+
p1 = create(:photo)
|
371
|
+
p2 = create(:photo)
|
372
|
+
account = create(:account, photos: [ p1, p2 ])
|
373
|
+
get account_path(account, include: { photos: { order: { created_at: :desc } } }, format: 'json')
|
374
|
+
assert_equal [ p2.id, p1.id ], JSON(response.body)['photos'].map { |x| x["id"] }
|
375
|
+
end
|
376
|
+
|
377
|
+
test 'include with limit key' do
|
378
|
+
5.times { create(:property, photos: Array.new(5) { create(:photo) }) }
|
379
|
+
get properties_path(include: { photos: { limit: 1 } }, limit: 5, format: 'json')
|
380
|
+
|
381
|
+
properties = JSON(response.body)
|
382
|
+
assert_equal 5, properties.length
|
383
|
+
|
384
|
+
properties.each do |property|
|
385
|
+
assert_equal 1, property['photos'].length
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
test 'include with when key' do
|
390
|
+
photo = create(:photo)
|
391
|
+
account = create(:account, photos: [ photo ])
|
392
|
+
account_reference = create(:reference, subject: account)
|
393
|
+
|
394
|
+
property = create(:property, landlord: account)
|
395
|
+
property_reference = create(:reference, subject: property)
|
396
|
+
|
397
|
+
|
398
|
+
get references_path(
|
399
|
+
include: {
|
400
|
+
"subject" => {
|
401
|
+
"landlord" => {
|
402
|
+
"when" => {
|
403
|
+
"subject_type" => 'Property'
|
404
|
+
}
|
405
|
+
},
|
406
|
+
"photos" => {
|
407
|
+
"when" => {
|
408
|
+
"subject_type" => 'Account'
|
409
|
+
}
|
410
|
+
}
|
411
|
+
}
|
412
|
+
},
|
413
|
+
limit: 20,
|
414
|
+
format: 'json'
|
415
|
+
)
|
416
|
+
|
417
|
+
json = JSON(response.body)
|
418
|
+
|
419
|
+
assert_equal photo.id, json.find { |x| x['id'] == account_reference.id }.dig('subject', 'photos', 0, 'id')
|
420
|
+
assert_equal account.id, json.find { |x| x['id'] == property_reference.id }.dig('subject', 'landlord', 'id')
|
421
|
+
end
|
422
|
+
|
423
|
+
test 'include with distinct key' do
|
424
|
+
account = create(:account)
|
425
|
+
photos = Array.new(5) { create(:photo, account: account) }
|
426
|
+
property = create(:property, photos: photos)
|
427
|
+
|
428
|
+
get property_path(property, include: { photos: { distinct: true } }, format: 'json')
|
429
|
+
assert_equal 5, JSON(response.body)['photos'].size
|
430
|
+
end
|
431
|
+
|
432
|
+
test 'include with distinct_on key' do
|
433
|
+
account = create(:account)
|
434
|
+
photos = Array.new(5) { create(:photo, account: account) }
|
435
|
+
property = create(:property, photos: photos)
|
436
|
+
|
437
|
+
get property_path(property,
|
438
|
+
include: {
|
439
|
+
photos: {
|
440
|
+
distinct_on: :account_id,
|
441
|
+
# order: [:account_id, { id: :asc }]
|
442
|
+
order: { account_id: :asc, id: :asc }
|
443
|
+
}
|
444
|
+
},
|
445
|
+
format: 'json')
|
446
|
+
|
447
|
+
assert_equal [photos.first.id], JSON(response.body)['photos'].map { |x| x['id'] }
|
448
|
+
|
449
|
+
get property_path(property,
|
450
|
+
include: {
|
451
|
+
photos: {
|
452
|
+
distinct_on: :account_id,
|
453
|
+
order: { account_id: :asc, id: :desc }
|
454
|
+
}
|
455
|
+
}, format: 'json')
|
456
|
+
|
457
|
+
assert_equal [photos.last.id], JSON(response.body)['photos'].map { |x| x['id'] }
|
458
|
+
end
|
459
|
+
|
460
|
+
test 'unknown inlcude' do
|
461
|
+
property = create(:property, accounts: [ create(:account) ])
|
462
|
+
get property_path(property, include: [:accounts], format: 'json')
|
463
|
+
assert_response :bad_request
|
464
|
+
assert_equal 'found unpermitted parameter: "accounts"', response.body
|
465
|
+
end
|
466
|
+
|
467
|
+
test 'unknown order' do
|
468
|
+
create(:property)
|
469
|
+
get properties_path(order: 'updated_at', limit: 1, format: 'json')
|
470
|
+
assert_response :bad_request
|
471
|
+
assert_equal 'found unpermitted parameter: "updated_at"', response.body
|
472
|
+
end
|
473
|
+
|
474
|
+
# Includes Test
|
475
|
+
|
476
|
+
test 'Includes::normailze' do
|
477
|
+
method = StandardAPI::Includes.method(:normalize)
|
478
|
+
assert_equal method.call(:x), { 'x' => {} }
|
479
|
+
assert_equal method.call([:x, :y]), { 'x' => {}, 'y' => {} }
|
480
|
+
assert_equal method.call([ { x: true }, { y: true } ]), { 'x' => {}, 'y' => {} }
|
481
|
+
assert_equal method.call({ x: true, y: true }), { 'x' => {}, 'y' => {} }
|
482
|
+
assert_equal method.call({ x: { y: true } }), { 'x' => { 'y' => {} } }
|
483
|
+
assert_equal method.call({ x: { y: {} } }), { 'x' => { 'y' => {} } }
|
484
|
+
assert_equal method.call({ x: [:y] }), { 'x' => { 'y' => {} } }
|
485
|
+
|
486
|
+
|
487
|
+
assert_equal method.call({ x: { where: { y: false } } }), { 'x' => { 'where' => { 'y' => false } } }
|
488
|
+
assert_equal method.call({ x: { order: { y: :asc } } }), { 'x' => { 'order' => { 'y' => :asc } } }
|
489
|
+
end
|
490
|
+
|
491
|
+
# sanitize({:key => {}}, [:key]) # => {:key => {}}
|
492
|
+
# sanitize({:key => {}}, {:key => true}) # => {:key => {}}
|
493
|
+
# sanitize({:key => {}}, :value => {}}, [:key]) => # Raises ParseError
|
494
|
+
# sanitize({:key => {}}, :value => {}}, {:key => true}) => # Raises ParseError
|
495
|
+
# sanitize({:key => {:value => {}}}, {:key => [:value]}) # => {:key => {:value => {}}}
|
496
|
+
# sanitize({:key => {:value => {}}}, {:key => {:value => true}}) # => {:key => {:value => {}}}
|
497
|
+
# sanitize({:key => {:value => {}}}, [:key]) => # Raises ParseError
|
498
|
+
test 'Includes::sanitize' do
|
499
|
+
method = StandardAPI::Includes.method(:sanitize)
|
500
|
+
assert_equal method.call(:x, [:x]), { 'x' => {} }
|
501
|
+
assert_equal method.call(:x, {:x => true}), { 'x' => {} }
|
502
|
+
|
503
|
+
assert_raises(StandardAPI::UnpermittedParameters) do
|
504
|
+
method.call([:x, :y], [:x])
|
505
|
+
end
|
506
|
+
|
507
|
+
assert_raises(StandardAPI::UnpermittedParameters) do
|
508
|
+
method.call([:x, :y], {:x => true})
|
509
|
+
end
|
510
|
+
|
511
|
+
assert_raises(StandardAPI::UnpermittedParameters) do
|
512
|
+
method.call({:x => true, :y => true}, [:x])
|
513
|
+
end
|
514
|
+
assert_raises(StandardAPI::UnpermittedParameters) do
|
515
|
+
method.call({:x => true, :y => true}, {:x => true})
|
516
|
+
end
|
517
|
+
assert_raises(StandardAPI::UnpermittedParameters) do
|
518
|
+
method.call({ x: { y: true }}, { x: true })
|
519
|
+
end
|
520
|
+
|
521
|
+
assert_equal method.call({ x: { y: true }}, { x: { y: true } }), { 'x' => { 'y' => {} } }
|
522
|
+
end
|
523
|
+
|
524
|
+
# Order Test
|
525
|
+
|
526
|
+
test 'Orders::sanitize(:column, [:column])' do
|
527
|
+
method = StandardAPI::Orders.method(:sanitize)
|
528
|
+
|
529
|
+
assert_equal :x, method.call(:x, :x)
|
530
|
+
assert_equal :x, method.call(:x, [:x])
|
531
|
+
assert_equal :x, method.call([:x], [:x])
|
532
|
+
assert_raises(StandardAPI::UnpermittedParameters) do
|
533
|
+
method.call(:x, :y)
|
534
|
+
end
|
535
|
+
|
536
|
+
assert_equal({ x: :asc }, method.call({ x: :asc }, :x))
|
537
|
+
assert_equal({ x: :desc }, method.call({ x: :desc }, :x))
|
538
|
+
assert_equal([{ x: :desc}, {y: :desc }], method.call({ x: :desc, y: :desc }, [:x, :y]))
|
539
|
+
assert_equal({ x: :asc }, method.call([{ x: :asc }], :x))
|
540
|
+
assert_equal({ x: :desc }, method.call([{ x: :desc }], :x))
|
541
|
+
assert_equal({ x: { asc: :nulls_last } }, method.call([{ x: { asc: :nulls_last } }], :x))
|
542
|
+
assert_equal({ x: { asc: :nulls_first } }, method.call([{ x: { asc: :nulls_first } }], :x))
|
543
|
+
assert_equal({ x: { desc: :nulls_last } }, method.call([{ x: { desc: :nulls_last } }], :x))
|
544
|
+
assert_equal({ x: { desc: :nulls_first }}, method.call([{ x: { desc: :nulls_first } }], :x))
|
545
|
+
assert_equal({ relation: :id }, method.call(['relation.id'], { relation: :id }))
|
546
|
+
assert_equal({ relation: :id }, method.call([{ relation: :id }], { relation: :id }))
|
547
|
+
assert_equal({ relation: :id }, method.call([{ relation: :id }], [{ relation: :id }]))
|
548
|
+
assert_equal({ relation: :id }, method.call([{ relation: [:id] }], { relation: [:id] }))
|
549
|
+
assert_equal({ relation: :id }, method.call([{ relation: [:id] }], [{ relation: [:id] }]))
|
550
|
+
assert_equal({ relation: { id: :desc } }, method.call([{'relation.id' => :desc}], { relation: :id }))
|
551
|
+
assert_equal({ relation: { id: :desc } }, method.call([{ relation: { id: :desc } }], { relation: [:id] }))
|
552
|
+
assert_equal({ relation: { id: :desc } }, method.call([{ relation: { id: :desc } }], [{ relation: [:id] }]))
|
553
|
+
assert_equal({ relation: { id: :desc } }, method.call([{ relation: [{ id: :desc }] }], [{ relation: [:id] }]))
|
554
|
+
assert_equal({ relation: { id: :desc } }, method.call([{ relation: [{ id: :desc }] }], [{ relation: [:id] }]))
|
555
|
+
assert_equal({ relation: {:id => {:asc => :nulls_last}} }, method.call([{ relation: {:id => {:asc => :nulls_last}} }], [{ relation: [:id] }]))
|
556
|
+
assert_equal({ relation: {:id => {:asc => :nulls_last}} }, method.call([{ relation: {:id => {:asc => :nulls_last}} }], [{ relation: [:id] }]))
|
557
|
+
assert_equal({ relation: {:id => [{:asc => :nulls_last}]} }, method.call([{ relation: {:id => [{:asc => :nulls_last}]} }], [{ relation: [:id] }]))
|
558
|
+
assert_equal({ relation: {:id => [{:asc => :nulls_last}]} }, method.call([{ relation: {:id => [{:asc => :nulls_last}]} }], [{ relation: [:id] }]))
|
559
|
+
end
|
560
|
+
|
561
|
+
test 'order: :attribute' do
|
562
|
+
properties = Array.new(2) { create(:property) }
|
563
|
+
|
564
|
+
get properties_path(order: :id, limit: 100, format: 'json')
|
565
|
+
assert_equal properties.map(&:id).sort, JSON(response.body).map { |x| x['id'] }
|
566
|
+
end
|
567
|
+
|
568
|
+
test 'order: { attribute: :direction }' do
|
569
|
+
properties = Array.new(2) { create(:property) }
|
570
|
+
|
571
|
+
get properties_path(order: { id: :asc }, limit: 100, format: 'json')
|
572
|
+
assert_equal properties.map(&:id).sort, JSON(response.body).map { |x| x['id'] }
|
573
|
+
|
574
|
+
get properties_path(order: { id: :desc }, limit: 100, format: 'json')
|
575
|
+
assert_equal properties.map(&:id).sort.reverse, JSON(response.body).map { |x| x['id'] }
|
576
|
+
end
|
577
|
+
|
578
|
+
test 'order: { attribute: { direction: :nulls } }' do
|
579
|
+
properties = [ create(:property), create(:property, description: nil) ]
|
580
|
+
|
581
|
+
get properties_path(order: { description: { asc: :nulls_last } }, limit: 100, format: 'json')
|
582
|
+
assert_equal properties.map(&:id).sort, JSON(response.body).map { |x| x['id'] }
|
583
|
+
|
584
|
+
get properties_path(order: { description: { asc: :nulls_first } }, limit: 100, format: 'json')
|
585
|
+
assert_equal properties.map(&:id).sort.reverse, JSON(response.body).map { |x| x['id'] }
|
586
|
+
end
|
587
|
+
|
588
|
+
test 'ordering via nulls_first/last' do
|
589
|
+
p1 = create(:property, description: 'test')
|
590
|
+
p2 = create(:property, description: nil)
|
591
|
+
|
592
|
+
get properties_path(format: 'json'), params: { limit: 100, order: { description: { desc: 'nulls_last' } } }
|
593
|
+
properties = JSON(response.body)
|
594
|
+
assert_equal p1.id, properties.first['id']
|
595
|
+
|
596
|
+
get properties_path(format: 'json'), params: { limit: 100, order: { description: { asc: 'nulls_last' } } }
|
597
|
+
properties = JSON(response.body)
|
598
|
+
assert_equal p1.id, properties.first['id']
|
599
|
+
end
|
600
|
+
|
601
|
+
# Calculate Test
|
602
|
+
test 'calculate' do
|
603
|
+
create(:photo)
|
604
|
+
get '/photos/calculate', params: {select: {count: "*"}}
|
605
|
+
assert_equal [1], JSON(response.body)
|
606
|
+
end
|
607
|
+
|
608
|
+
test 'calculate distinct aggregation' do
|
609
|
+
assert_sql(<<-SQL) do
|
610
|
+
SELECT DISTINCT COUNT("properties"."id") FROM "properties"
|
611
|
+
SQL
|
612
|
+
create(:property)
|
613
|
+
create(:property)
|
614
|
+
get '/properties/calculate', params: {
|
615
|
+
select: { count: "id" },
|
616
|
+
distinct: true
|
617
|
+
}
|
618
|
+
assert_equal [2], JSON(response.body)
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
622
|
+
test 'calculate aggregation distinctly' do
|
623
|
+
assert_sql(<<-SQL) do
|
624
|
+
SELECT COUNT(DISTINCT "properties"."id") FROM "properties"
|
625
|
+
SQL
|
626
|
+
create(:property)
|
627
|
+
create(:property)
|
628
|
+
get '/properties/calculate', params: {
|
629
|
+
select: { count: { distinct: "id" } }
|
630
|
+
}
|
631
|
+
assert_equal [2], JSON(response.body)
|
632
|
+
end
|
633
|
+
end
|
634
|
+
|
635
|
+
test 'calculate distinct aggregation distinctly' do
|
636
|
+
assert_sql(<<-SQL) do
|
637
|
+
SELECT DISTINCT COUNT(DISTINCT "properties"."id") FROM "properties"
|
638
|
+
SQL
|
639
|
+
create(:property)
|
640
|
+
create(:property)
|
641
|
+
get '/properties/calculate', params: {
|
642
|
+
select: { count: { distinct: "id" } },
|
643
|
+
distinct: true
|
644
|
+
}
|
645
|
+
assert_equal [2], JSON(response.body)
|
646
|
+
end
|
647
|
+
end
|
648
|
+
|
649
|
+
test 'calculate distinct count' do
|
650
|
+
p1 = create(:property)
|
651
|
+
p2 = create(:property)
|
652
|
+
a1 = create(:account, property: p1)
|
653
|
+
a2 = create(:account, property: p2)
|
654
|
+
get '/properties/calculate', params: {
|
655
|
+
select: { count: 'id' },
|
656
|
+
where: {
|
657
|
+
accounts: {
|
658
|
+
id: [a1.id, a2.id]
|
659
|
+
}
|
660
|
+
},
|
661
|
+
group_by: 'id',
|
662
|
+
distinct: true
|
663
|
+
}
|
664
|
+
assert_equal Hash[p1.id.to_s, 1, p2.id.to_s, 1], JSON(response.body)
|
665
|
+
end
|
666
|
+
|
667
|
+
test 'calculate group_by' do
|
668
|
+
create(:photo, format: 'jpg')
|
669
|
+
create(:photo, format: 'jpg')
|
670
|
+
create(:photo, format: 'png')
|
671
|
+
get '/photos/calculate', params: {select: {count: "*"}, group_by: 'format'}
|
672
|
+
assert_equal ({'png' => 1, 'jpg' => 2}), JSON(response.body)
|
673
|
+
end
|
674
|
+
|
675
|
+
test 'calculate join' do
|
676
|
+
p1 = create(:property)
|
677
|
+
p2 = create(:property)
|
678
|
+
create(:account, photos_count: 1, property: p1)
|
679
|
+
create(:account, photos_count: 2, property: p2)
|
680
|
+
|
681
|
+
get '/properties/calculate', params: {select: {sum: "accounts.photos_count"}, join: 'accounts'}
|
682
|
+
assert_equal [3], JSON(response.body)
|
683
|
+
end
|
684
|
+
|
685
|
+
test 'calculate count distinct' do
|
686
|
+
photo = create(:photo)
|
687
|
+
landlord = create(:account)
|
688
|
+
create(:property, landlord: landlord, photos: [photo])
|
689
|
+
create(:property, landlord: landlord, photos: [photo])
|
690
|
+
|
691
|
+
get '/photos/calculate', params: {select: {count: "*"},
|
692
|
+
where: {properties: {landlord: {id: landlord.id}}},
|
693
|
+
distinct: true
|
694
|
+
}
|
695
|
+
|
696
|
+
assert_equal [1], JSON(response.body)
|
697
|
+
end
|
698
|
+
|
699
|
+
end
|