standardapi 6.0.0.30 → 6.0.0.32
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/standard_api/version.rb +1 -1
- data/lib/standard_api/views/application/_record.json.jbuilder +34 -31
- data/test/standard_api/caching_test.rb +33 -0
- data/test/standard_api/helpers_test.rb +156 -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 +591 -0
- data/test/standard_api/test_app/controllers.rb +1 -1
- data/test/standard_api/test_app/models.rb +17 -0
- data/test/standard_api/test_helper.rb +226 -0
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f8e9f6eee237614e9c303c8fe9e2e624b081272627f9a7aa3f680276872c1a9
|
4
|
+
data.tar.gz: 73ede0c9b6229117b4781071441ac0581a06b16592f95f04959932c010ecb8f0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 904192ff81007b628b672e094dfaad19f4b2c49a0e4119631dd46c320a85d0e2c06200b5570d29d3b70ded73046b843dfd28ce3e40358d3b2294bbf9bb7b50f6
|
7
|
+
data.tar.gz: f9ed735aabd0830d6b2c7a390d4f2bca3bf8bcc1d8b806b615a76462404f58c9b65241c75e7b7e62abe84539705249ed10c82f9f5dcc4cc1ee4911e51513200f
|
data/lib/standard_api/version.rb
CHANGED
@@ -6,39 +6,42 @@ end
|
|
6
6
|
|
7
7
|
includes.each do |inc, subinc|
|
8
8
|
next if ["limit", "offset", "order", "when", "where", "distinct", "distinct_on"].include?(inc)
|
9
|
-
|
9
|
+
|
10
|
+
|
10
11
|
case association = record.class.reflect_on_association(inc)
|
11
|
-
when ActiveRecord::Reflection::
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
12
|
+
when ActiveRecord::Reflection::AbstractReflection
|
13
|
+
if association.collection?
|
14
|
+
can_cache = can_cache_relation?(record.class, inc, subinc)
|
15
|
+
json.cache_if!(can_cache, can_cache ? association_cache_key(record, inc, subinc) : nil) do
|
16
|
+
partial = model_partial(association.klass)
|
17
|
+
json.set! inc do
|
18
|
+
# TODO limit causes preloaded assocations to reload
|
19
|
+
sub_records = record.send(inc)
|
20
|
+
|
21
|
+
sub_records = sub_records.limit(subinc['limit']) if subinc['limit']
|
22
|
+
sub_records = sub_records.offset(subinc['offset']) if subinc['offset']
|
23
|
+
sub_records = sub_records.order(subinc['order']) if subinc['order']
|
24
|
+
sub_records = sub_records.filter(subinc['where']) if subinc['where']
|
25
|
+
sub_records = sub_records.distinct if subinc['distinct']
|
26
|
+
sub_records = sub_records.distinct_on(subinc['distinct_on']) if subinc['distinct_on']
|
25
27
|
|
26
|
-
|
28
|
+
json.array! sub_records, partial: partial, as: partial.split('/').last, locals: { includes: subinc }
|
29
|
+
end
|
27
30
|
end
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
31
|
+
else
|
32
|
+
can_cache = can_cache_relation?(record.class, inc, subinc)
|
33
|
+
if association.is_a?(ActiveRecord::Reflection::BelongsToReflection)
|
34
|
+
can_cache = can_cache && !record.send(association.foreign_key).nil?
|
35
|
+
end
|
36
|
+
json.cache_if!(can_cache, can_cache ? association_cache_key(record, inc, subinc) : nil) do
|
37
|
+
value = record.send(inc)
|
38
|
+
if value.nil?
|
39
|
+
json.set! inc, nil
|
40
|
+
else
|
41
|
+
partial = model_partial(value)
|
42
|
+
json.set! inc do
|
43
|
+
json.partial! partial, partial.split('/').last.to_sym => value, includes: subinc
|
44
|
+
end
|
42
45
|
end
|
43
46
|
end
|
44
47
|
end
|
@@ -57,7 +60,7 @@ includes.each do |inc, subinc|
|
|
57
60
|
end
|
58
61
|
end
|
59
62
|
end
|
60
|
-
|
63
|
+
|
61
64
|
end
|
62
65
|
|
63
66
|
if !record.errors.blank?
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'standard_api/test_helper'
|
2
|
+
|
3
|
+
class AccountsControllerTest < ActionController::TestCase
|
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 :show, params: {id: account.id, 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 :show, params: {id: account.id, 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 :show, params: {id: account.id, include: :photos}, format: :json
|
30
|
+
assert_equal [], JSON(response.body)['photos'].map{|x| x['id']}
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,156 @@
|
|
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?' do
|
49
|
+
Account.expects(:column_names).returns(['id', 'cached_at'])
|
50
|
+
assert !can_cache_relation?(Account, :photos, {})
|
51
|
+
|
52
|
+
Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at'])
|
53
|
+
assert can_cache_relation?(Account, :photos, {})
|
54
|
+
|
55
|
+
Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at'])
|
56
|
+
assert !can_cache_relation?(Account, :photos, {account: {}})
|
57
|
+
|
58
|
+
Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at', 'photos_account_cached_at'])
|
59
|
+
assert can_cache_relation?(Account, :photos, {account: {}})
|
60
|
+
end
|
61
|
+
|
62
|
+
test '::association_cache_key(record, relation, subincludes)' do
|
63
|
+
account = create(:account)
|
64
|
+
t1 = Time.now
|
65
|
+
t2 = 1.day.from_now
|
66
|
+
t3 = 2.days.from_now
|
67
|
+
|
68
|
+
account.expects(:photos_cached_at).returns(t1)
|
69
|
+
|
70
|
+
assert_equal(
|
71
|
+
"accounts/#{account.id}/photos-#{t1.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
|
72
|
+
association_cache_key(account, :photos, {})
|
73
|
+
)
|
74
|
+
|
75
|
+
|
76
|
+
account.expects(:photos_cached_at).returns(t1)
|
77
|
+
account.expects(:photos_property_cached_at).returns(t2)
|
78
|
+
assert_equal(
|
79
|
+
"accounts/#{account.id}/photos-2ea683a694a33359514c41435f8f0646-#{t2.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
|
80
|
+
association_cache_key(account, :photos, {property: {}})
|
81
|
+
)
|
82
|
+
|
83
|
+
account.expects(:photos_cached_at).returns(t1)
|
84
|
+
account.expects(:photos_property_cached_at).returns(t2)
|
85
|
+
assert_equal(
|
86
|
+
"accounts/#{account.id}/photos-779c17ef027655fd8c06c3083d2df64b-#{t2.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
|
87
|
+
association_cache_key(account, :photos, { "property" => { "order" => { "x" => "desc" }}})
|
88
|
+
)
|
89
|
+
|
90
|
+
account.expects(:photos_cached_at).returns(t1)
|
91
|
+
account.expects(:photos_property_cached_at).returns(t2)
|
92
|
+
account.expects(:photos_agents_cached_at).returns(t3)
|
93
|
+
assert_equal(
|
94
|
+
"accounts/#{account.id}/photos-abbee2d4535400c162c8dbf14bbef6d5-#{t3.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
|
95
|
+
association_cache_key(account, :photos, {property: {}, agents: {}})
|
96
|
+
)
|
97
|
+
|
98
|
+
account.expects(:photos_cached_at).returns(t1)
|
99
|
+
account.expects(:photos_property_cached_at).returns(t2)
|
100
|
+
account.expects(:photos_property_agents_cached_at).returns(t3)
|
101
|
+
assert_equal(
|
102
|
+
"accounts/#{account.id}/photos-0962ae73347c5c605d329eaa25e2be49-#{t3.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
|
103
|
+
association_cache_key(account, :photos, {property: {agents: {}}})
|
104
|
+
)
|
105
|
+
|
106
|
+
account.expects(:photos_cached_at).returns(t1)
|
107
|
+
account.expects(:photos_property_cached_at).returns(t2)
|
108
|
+
account.expects(:photos_agents_cached_at).returns(t2)
|
109
|
+
account.expects(:photos_property_addresses_cached_at).returns(t3)
|
110
|
+
assert_equal(
|
111
|
+
"accounts/#{account.id}/photos-00ea6afe3ff68037f8b4dcdb275e2a24-#{t3.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
|
112
|
+
association_cache_key(account, :photos, {property: {addresses: {}}, agents: {}})
|
113
|
+
)
|
114
|
+
|
115
|
+
# Belongs to
|
116
|
+
photo = create(:photo, account: account)
|
117
|
+
photo.expects(:account_cached_at).returns(t1)
|
118
|
+
assert_equal(
|
119
|
+
"accounts/#{account.id}-#{t1.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
|
120
|
+
association_cache_key(photo, :account, {})
|
121
|
+
)
|
122
|
+
|
123
|
+
photo = create(:photo, account: account)
|
124
|
+
photo.expects(:account_cached_at).returns(t1)
|
125
|
+
photo.expects(:account_photos_cached_at).returns(t2)
|
126
|
+
assert_equal(
|
127
|
+
"accounts/#{account.id}/07437ce3863467f4cd715ae1ef930f08-#{t2.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
|
128
|
+
association_cache_key(photo, :account, {photos: {}})
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
test '::json_column_type(sql_type)' do
|
133
|
+
assert_equal 'string', json_column_type('character varying')
|
134
|
+
assert_equal 'string', json_column_type('character varying(2)')
|
135
|
+
assert_equal 'string', json_column_type('character varying(255)')
|
136
|
+
assert_equal 'datetime', json_column_type('timestamp without time zone')
|
137
|
+
assert_equal 'datetime', json_column_type('time without time zone')
|
138
|
+
assert_equal 'string', json_column_type('text')
|
139
|
+
assert_equal 'hash', json_column_type('json')
|
140
|
+
assert_equal 'hash', json_column_type('jsonb')
|
141
|
+
assert_equal 'integer', json_column_type('bigint')
|
142
|
+
assert_equal 'integer', json_column_type('integer')
|
143
|
+
assert_equal 'string', json_column_type('inet')
|
144
|
+
assert_equal 'hash', json_column_type('hstore')
|
145
|
+
assert_equal 'datetime', json_column_type('date')
|
146
|
+
assert_equal 'decimal', json_column_type('numeric')
|
147
|
+
assert_equal 'decimal', json_column_type('numeric(12)')
|
148
|
+
assert_equal 'decimal', json_column_type('numeric(12,2)')
|
149
|
+
assert_equal 'decimal', json_column_type('double precision')
|
150
|
+
assert_equal 'string', json_column_type('ltree')
|
151
|
+
assert_equal 'boolean', json_column_type('boolean')
|
152
|
+
assert_equal 'ewkb', json_column_type('geometry')
|
153
|
+
assert_equal 'string', json_column_type('uuid')
|
154
|
+
end
|
155
|
+
|
156
|
+
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,591 @@
|
|
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.to_i }
|
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
|
+
assert_equal @controller.send(:model_params), []
|
98
|
+
end
|
99
|
+
|
100
|
+
test 'Controller#current_mask' do
|
101
|
+
@controller = ReferencesController.new
|
102
|
+
@controller.instance_variable_set('@current_mask', { 'references' => { 'subject_id' => 1 }})
|
103
|
+
@controller.params = {}
|
104
|
+
assert_equal 'SELECT "references".* FROM "references" WHERE "references"."subject_id" = 1', @controller.send(:resources).to_sql
|
105
|
+
end
|
106
|
+
|
107
|
+
test 'ApplicationController#schema.json' do
|
108
|
+
get schema_path(format: 'json')
|
109
|
+
|
110
|
+
schema = JSON(response.body)
|
111
|
+
controllers = ApplicationController.descendants
|
112
|
+
controllers.select! { |c| c.ancestors.include?(StandardAPI::Controller) && c != StandardAPI::Controller }
|
113
|
+
|
114
|
+
@controller.send(:models).reject { |x| x.name == 'Photo' }.each do |model|
|
115
|
+
assert_equal true, schema['models'].has_key?(model.name)
|
116
|
+
|
117
|
+
model_comment = model.connection.table_comment(model.table_name)
|
118
|
+
if model_comment.nil? then
|
119
|
+
assert_nil schema.dig('models', model.name, 'comment')
|
120
|
+
else
|
121
|
+
assert_equal model_comment, schema.dig('models', model.name, 'comment')
|
122
|
+
end
|
123
|
+
|
124
|
+
model.columns.each do |column|
|
125
|
+
assert_equal json_column_type(column.sql_type), schema.dig('models', model.name, 'attributes', column.name, 'type')
|
126
|
+
default = column.default || column.default_function
|
127
|
+
if default then
|
128
|
+
assert_equal default, schema.dig('models', model.name, 'attributes', column.name, 'default')
|
129
|
+
else
|
130
|
+
assert_nil schema.dig('models', model.name, 'attributes', column.name, 'default')
|
131
|
+
end
|
132
|
+
assert_equal column.name == model.primary_key, schema.dig('models', model.name, 'attributes', column.name, 'primary_key')
|
133
|
+
assert_equal column.null, schema.dig('models', model.name, 'attributes', column.name, 'null')
|
134
|
+
assert_equal column.array, schema.dig('models', model.name, 'attributes', column.name, 'array')
|
135
|
+
if column.comment then
|
136
|
+
assert_equal column.comment, schema.dig('models', model.name, 'attributes', column.name, 'comment')
|
137
|
+
else
|
138
|
+
assert_nil schema.dig('models', model.name, 'attributes', column.name, 'comment')
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
assert_equal 'test comment', schema['comment']
|
144
|
+
end
|
145
|
+
|
146
|
+
test 'Controller#schema.json' do
|
147
|
+
get schema_references_path(format: 'json')
|
148
|
+
|
149
|
+
schema = JSON(response.body)
|
150
|
+
assert_equal true, schema.has_key?('attributes')
|
151
|
+
assert_equal true, schema['attributes']['id']['primary_key']
|
152
|
+
assert_equal 1000, schema['limit']
|
153
|
+
end
|
154
|
+
|
155
|
+
test 'Controller#schema.json w/o limit' do
|
156
|
+
get schema_unlimited_index_path(format: 'json')
|
157
|
+
|
158
|
+
schema = JSON(response.body)
|
159
|
+
assert_equal true, schema.has_key?('attributes')
|
160
|
+
assert_equal true, schema['attributes']['id']['primary_key']
|
161
|
+
assert_nil schema['limit']
|
162
|
+
end
|
163
|
+
|
164
|
+
test 'Controller#index w/o limit' do
|
165
|
+
account = create(:account)
|
166
|
+
get unlimited_index_path(format: 'json')
|
167
|
+
|
168
|
+
assert_equal [account.id], JSON(response.body).map { |x| x['id'] }
|
169
|
+
end
|
170
|
+
|
171
|
+
test 'Controller#index with default limit' do
|
172
|
+
create(:account)
|
173
|
+
get default_limit_index_path(format: 'json')
|
174
|
+
assert_response :ok
|
175
|
+
end
|
176
|
+
|
177
|
+
test 'Controller#create redirects to correct route with STI models' do
|
178
|
+
attrs = attributes_for(:pdf)
|
179
|
+
post documents_path, params: { document: attrs }
|
180
|
+
assert_response :redirect
|
181
|
+
end
|
182
|
+
|
183
|
+
test 'Controller#update redirects to correct route with STI models' do
|
184
|
+
pdf = create(:pdf)
|
185
|
+
patch document_path(pdf), params: { document: pdf.attributes }
|
186
|
+
assert_redirected_to document_path(pdf)
|
187
|
+
end
|
188
|
+
|
189
|
+
test 'Controller#add_resource' do
|
190
|
+
property = create(:property, photos: [])
|
191
|
+
photo = create(:photo)
|
192
|
+
|
193
|
+
post "/properties/#{property.id}/photos/#{photo.id}.json"
|
194
|
+
assert_equal property.photos.reload.map(&:id), [photo.id]
|
195
|
+
assert_response :created
|
196
|
+
|
197
|
+
post "/properties/#{property.id}/photos/9999999"
|
198
|
+
assert_response :not_found
|
199
|
+
end
|
200
|
+
|
201
|
+
test 'Controller#remove_resource' do
|
202
|
+
photo = create(:photo)
|
203
|
+
property = create(:property, photos: [photo])
|
204
|
+
delete "/properties/#{property.id}/photos/#{photo.id}"
|
205
|
+
assert_equal property.photos.reload, []
|
206
|
+
assert_response :no_content
|
207
|
+
|
208
|
+
delete "/properties/#{property.id}/photos/9999999"
|
209
|
+
assert_response :not_found
|
210
|
+
end
|
211
|
+
|
212
|
+
# = View Tests
|
213
|
+
|
214
|
+
test 'rendering tables' do
|
215
|
+
get tables_path(format: 'json')
|
216
|
+
assert_response :ok
|
217
|
+
# assert_equal ['properties', 'accounts', 'photos', 'references', 'sessions', 'unlimited'], response.parsed_body
|
218
|
+
# Multiple 'accounts' because multiple controllers with that model for testing.
|
219
|
+
assert_equal ["properties", "accounts", "documents", "photos", "references", "accounts", 'accounts'].sort, response.parsed_body.sort
|
220
|
+
end
|
221
|
+
|
222
|
+
test 'rendering null attribute' do
|
223
|
+
property = create(:property)
|
224
|
+
get property_path(property, format: 'json'), params: { id: property.id, include: [:landlord] }
|
225
|
+
assert_equal true, JSON(response.body).has_key?('landlord')
|
226
|
+
assert_nil JSON(response.body)['landlord']
|
227
|
+
end
|
228
|
+
|
229
|
+
test 'rendering null attribute for has_one through' do
|
230
|
+
property = create(:property)
|
231
|
+
get property_path(property, format: 'json'), params: { id: property.id, include: [:document] }
|
232
|
+
assert JSON(response.body).has_key?('document')
|
233
|
+
assert_nil JSON(response.body)['document']
|
234
|
+
end
|
235
|
+
|
236
|
+
test '#index.json uses overridden partial' do
|
237
|
+
create(:property, photos: [create(:photo)])
|
238
|
+
get properties_path(format: 'json'), params: { limit: 100, include: [{:photos => { order: :id }}] }
|
239
|
+
|
240
|
+
photo = JSON(response.body)[0]['photos'][0]
|
241
|
+
assert_equal true, photo.has_key?('template')
|
242
|
+
assert_equal 'photos/_photo', photo['template']
|
243
|
+
end
|
244
|
+
|
245
|
+
test '#show.json uses overridden partial' do
|
246
|
+
property = create(:property, photos: [create(:photo)])
|
247
|
+
get property_path(property, format: 'json'), params: { id: property.id, include: [:photos] }
|
248
|
+
|
249
|
+
photo = JSON(response.body)['photos'][0]
|
250
|
+
assert_equal true, photo.has_key?('template')
|
251
|
+
assert_equal 'photos/_photo', photo['template']
|
252
|
+
end
|
253
|
+
|
254
|
+
test '#schema.json uses overridden partial' do
|
255
|
+
get schema_photos_path(format: 'json')
|
256
|
+
|
257
|
+
schema = JSON(response.body)
|
258
|
+
assert_rendered 'photos/schema', format: 'json', handler: 'jbuilder'
|
259
|
+
assert_equal true, schema.has_key?('template')
|
260
|
+
assert_equal 'photos/schema', schema['template']
|
261
|
+
end
|
262
|
+
|
263
|
+
test 'application#schema.json renders overridden #schema.json partials' do
|
264
|
+
get schema_path(format: 'json')
|
265
|
+
|
266
|
+
schema = JSON(response.body)
|
267
|
+
assert_rendered 'application/schema', format: 'json', handler: 'jbuilder'
|
268
|
+
assert_equal 'photos/schema', schema.dig('models', 'Photo', 'template')
|
269
|
+
end
|
270
|
+
|
271
|
+
test 'belongs_to polymorphic association' do
|
272
|
+
photo = create(:photo)
|
273
|
+
reference = create(:reference, subject: photo)
|
274
|
+
get reference_path(reference, include: :subject, format: 'json')
|
275
|
+
|
276
|
+
json = JSON(response.body)
|
277
|
+
assert_equal 'photos/_photo', json['subject']['template']
|
278
|
+
end
|
279
|
+
|
280
|
+
test '#index.json includes polymorphic association' do
|
281
|
+
property1 = create(:property)
|
282
|
+
property2 = create(:property)
|
283
|
+
photo = create(:photo)
|
284
|
+
create(:reference, subject: property1)
|
285
|
+
create(:reference, subject: property2)
|
286
|
+
create(:reference, subject: photo)
|
287
|
+
|
288
|
+
get references_path(format: 'json'), params: { include: [:subject], limit: 10 }
|
289
|
+
|
290
|
+
json = JSON(response.body)
|
291
|
+
assert_equal 'photos/_photo', json.find { |x| x['subject_type'] == "Photo"}['subject']['template']
|
292
|
+
end
|
293
|
+
|
294
|
+
test 'has_many association' do
|
295
|
+
p = create(:property, photos: [create(:photo)])
|
296
|
+
get properties_path(format: 'json'), params: { limit: 100, include: [:photos] }
|
297
|
+
assert_equal p.photos.first.id, JSON(response.body)[0]['photos'][0]['id']
|
298
|
+
end
|
299
|
+
|
300
|
+
test 'belongs_to association' do
|
301
|
+
account = create(:account)
|
302
|
+
photo = create(:photo, account: account)
|
303
|
+
get photo_path(photo, include: 'account', format: 'json')
|
304
|
+
assert_equal account.id, JSON(response.body)['account']['id']
|
305
|
+
end
|
306
|
+
|
307
|
+
test 'has_one association' do
|
308
|
+
account = create(:account)
|
309
|
+
property = create(:property, landlord: account)
|
310
|
+
get property_path(property, include: 'landlord', format: 'json')
|
311
|
+
assert_equal account.id, JSON(response.body)['landlord']['id']
|
312
|
+
end
|
313
|
+
|
314
|
+
test 'include method' do
|
315
|
+
property = create(:property)
|
316
|
+
get property_path(property, include: 'english_name', format: 'json')
|
317
|
+
assert_equal 'A Name', JSON(response.body)['english_name']
|
318
|
+
end
|
319
|
+
|
320
|
+
test 'include with where key' do
|
321
|
+
photo_a = create(:photo)
|
322
|
+
photo_b = create(:photo)
|
323
|
+
|
324
|
+
property = create(:property, photos: [photo_b])
|
325
|
+
get property_path(property, include: { photos: { where: { id: photo_a.id } } }, format: :json)
|
326
|
+
assert_equal [], JSON(response.body)['photos']
|
327
|
+
|
328
|
+
property.photos << photo_a
|
329
|
+
get property_path(property, include: { photos: { where: { id: photo_a.id } } }, format: :json)
|
330
|
+
assert_equal [photo_a.id], JSON(response.body)['photos'].map { |x| x['id'] }
|
331
|
+
end
|
332
|
+
|
333
|
+
test 'include with order key' do
|
334
|
+
photos = Array.new(5) { create(:photo) }
|
335
|
+
property = create(:property, photos: photos)
|
336
|
+
|
337
|
+
get property_path(property, include: { photos: { order: { id: :asc } } }, format: 'json')
|
338
|
+
assert_equal photos.map(&:id).sort, JSON(response.body)['photos'].map { |x| x['id'] }
|
339
|
+
end
|
340
|
+
|
341
|
+
test 'include with limit key' do
|
342
|
+
5.times { create(:property, photos: Array.new(5) { create(:photo) }) }
|
343
|
+
get properties_path(include: { photos: { limit: 1 } }, limit: 5, format: 'json')
|
344
|
+
|
345
|
+
properties = JSON(response.body)
|
346
|
+
assert_equal 5, properties.length
|
347
|
+
|
348
|
+
properties.each do |property|
|
349
|
+
assert_equal 1, property['photos'].length
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
test 'include with when key' do
|
354
|
+
photo = create(:photo)
|
355
|
+
account = create(:account, photos: [ photo ])
|
356
|
+
account_reference = create(:reference, subject: account)
|
357
|
+
|
358
|
+
property = create(:property, landlord: account)
|
359
|
+
property_reference = create(:reference, subject: property)
|
360
|
+
|
361
|
+
|
362
|
+
get references_path(
|
363
|
+
include: {
|
364
|
+
"subject" => {
|
365
|
+
"landlord" => {
|
366
|
+
"when" => {
|
367
|
+
"subject_type" => 'Property'
|
368
|
+
}
|
369
|
+
},
|
370
|
+
"photos" => {
|
371
|
+
"when" => {
|
372
|
+
"subject_type" => 'Account'
|
373
|
+
}
|
374
|
+
}
|
375
|
+
}
|
376
|
+
},
|
377
|
+
limit: 20,
|
378
|
+
format: 'json'
|
379
|
+
)
|
380
|
+
|
381
|
+
json = JSON(response.body)
|
382
|
+
|
383
|
+
assert_equal json.find { |x| x['id'] == account_reference.id }.dig('subject', 'photos', 0, 'id'), photo.id
|
384
|
+
assert_equal json.find { |x| x['id'] == property_reference.id }.dig('subject', 'landlord', 'id'), account.id
|
385
|
+
end
|
386
|
+
|
387
|
+
test 'include with distinct key' do
|
388
|
+
account = create(:account)
|
389
|
+
photos = Array.new(5) { create(:photo, account: account) }
|
390
|
+
property = create(:property, photos: photos)
|
391
|
+
|
392
|
+
get property_path(property, include: { photos: { distinct: true } }, format: 'json')
|
393
|
+
assert_equal 5, JSON(response.body)['photos'].size
|
394
|
+
end
|
395
|
+
|
396
|
+
test 'include with distinct_on key' do
|
397
|
+
account = create(:account)
|
398
|
+
photos = Array.new(5) { create(:photo, account: account) }
|
399
|
+
property = create(:property, photos: photos)
|
400
|
+
|
401
|
+
get property_path(property,
|
402
|
+
include: {
|
403
|
+
photos: {
|
404
|
+
distinct_on: :account_id,
|
405
|
+
# order: [:account_id, { id: :asc }]
|
406
|
+
order: { account_id: :asc, id: :asc }
|
407
|
+
}
|
408
|
+
},
|
409
|
+
format: 'json')
|
410
|
+
|
411
|
+
assert_equal [photos.first.id], JSON(response.body)['photos'].map { |x| x['id'] }
|
412
|
+
|
413
|
+
get property_path(property,
|
414
|
+
include: {
|
415
|
+
photos: {
|
416
|
+
distinct_on: :account_id,
|
417
|
+
order: { account_id: :asc, id: :desc }
|
418
|
+
}
|
419
|
+
}, format: 'json')
|
420
|
+
|
421
|
+
assert_equal [photos.last.id], JSON(response.body)['photos'].map { |x| x['id'] }
|
422
|
+
end
|
423
|
+
|
424
|
+
test 'unknown inlcude' do
|
425
|
+
property = create(:property, accounts: [ create(:account) ])
|
426
|
+
get property_path(property, include: [:accounts], format: 'json')
|
427
|
+
assert_response :bad_request
|
428
|
+
assert_equal 'found unpermitted parameter: "accounts"', response.body
|
429
|
+
end
|
430
|
+
|
431
|
+
test 'unknown order' do
|
432
|
+
create(:property)
|
433
|
+
get properties_path(order: 'updated_at', limit: 1, format: 'json')
|
434
|
+
assert_response :bad_request
|
435
|
+
assert_equal 'found unpermitted parameter: "updated_at"', response.body
|
436
|
+
end
|
437
|
+
|
438
|
+
# Includes Test
|
439
|
+
|
440
|
+
test 'Includes::normailze' do
|
441
|
+
method = StandardAPI::Includes.method(:normalize)
|
442
|
+
assert_equal method.call(:x), { 'x' => {} }
|
443
|
+
assert_equal method.call([:x, :y]), { 'x' => {}, 'y' => {} }
|
444
|
+
assert_equal method.call([ { x: true }, { y: true } ]), { 'x' => {}, 'y' => {} }
|
445
|
+
assert_equal method.call({ x: true, y: true }), { 'x' => {}, 'y' => {} }
|
446
|
+
assert_equal method.call({ x: { y: true } }), { 'x' => { 'y' => {} } }
|
447
|
+
assert_equal method.call({ x: { y: {} } }), { 'x' => { 'y' => {} } }
|
448
|
+
assert_equal method.call({ x: [:y] }), { 'x' => { 'y' => {} } }
|
449
|
+
|
450
|
+
|
451
|
+
assert_equal method.call({ x: { where: { y: false } } }), { 'x' => { 'where' => { 'y' => false } } }
|
452
|
+
assert_equal method.call({ x: { order: { y: :asc } } }), { 'x' => { 'order' => { 'y' => :asc } } }
|
453
|
+
end
|
454
|
+
|
455
|
+
# sanitize({:key => {}}, [:key]) # => {:key => {}}
|
456
|
+
# sanitize({:key => {}}, {:key => true}) # => {:key => {}}
|
457
|
+
# sanitize({:key => {}}, :value => {}}, [:key]) => # Raises ParseError
|
458
|
+
# sanitize({:key => {}}, :value => {}}, {:key => true}) => # Raises ParseError
|
459
|
+
# sanitize({:key => {:value => {}}}, {:key => [:value]}) # => {:key => {:value => {}}}
|
460
|
+
# sanitize({:key => {:value => {}}}, {:key => {:value => true}}) # => {:key => {:value => {}}}
|
461
|
+
# sanitize({:key => {:value => {}}}, [:key]) => # Raises ParseError
|
462
|
+
test 'Includes::sanitize' do
|
463
|
+
method = StandardAPI::Includes.method(:sanitize)
|
464
|
+
assert_equal method.call(:x, [:x]), { 'x' => {} }
|
465
|
+
assert_equal method.call(:x, {:x => true}), { 'x' => {} }
|
466
|
+
|
467
|
+
assert_raises(StandardAPI::UnpermittedParameters) do
|
468
|
+
method.call([:x, :y], [:x])
|
469
|
+
end
|
470
|
+
|
471
|
+
assert_raises(StandardAPI::UnpermittedParameters) do
|
472
|
+
method.call([:x, :y], {:x => true})
|
473
|
+
end
|
474
|
+
|
475
|
+
assert_raises(StandardAPI::UnpermittedParameters) do
|
476
|
+
method.call({:x => true, :y => true}, [:x])
|
477
|
+
end
|
478
|
+
assert_raises(StandardAPI::UnpermittedParameters) do
|
479
|
+
method.call({:x => true, :y => true}, {:x => true})
|
480
|
+
end
|
481
|
+
assert_raises(StandardAPI::UnpermittedParameters) do
|
482
|
+
method.call({ x: { y: true }}, { x: true })
|
483
|
+
end
|
484
|
+
|
485
|
+
assert_equal method.call({ x: { y: true }}, { x: { y: true } }), { 'x' => { 'y' => {} } }
|
486
|
+
end
|
487
|
+
|
488
|
+
# Order Test
|
489
|
+
|
490
|
+
test 'Orders::sanitize(:column, [:column])' do
|
491
|
+
method = StandardAPI::Orders.method(:sanitize)
|
492
|
+
|
493
|
+
assert_equal :x, method.call(:x, :x)
|
494
|
+
assert_equal :x, method.call(:x, [:x])
|
495
|
+
assert_equal :x, method.call([:x], [:x])
|
496
|
+
assert_raises(StandardAPI::UnpermittedParameters) do
|
497
|
+
method.call(:x, :y)
|
498
|
+
end
|
499
|
+
|
500
|
+
assert_equal({ x: :asc }, method.call({ x: :asc }, :x))
|
501
|
+
assert_equal({ x: :desc }, method.call({ x: :desc }, :x))
|
502
|
+
assert_equal([{ x: :desc}, {y: :desc }], method.call({ x: :desc, y: :desc }, [:x, :y]))
|
503
|
+
assert_equal({ x: :asc }, method.call([{ x: :asc }], :x))
|
504
|
+
assert_equal({ x: :desc }, method.call([{ x: :desc }], :x))
|
505
|
+
assert_equal({ x: { asc: :nulls_last } }, method.call([{ x: { asc: :nulls_last } }], :x))
|
506
|
+
assert_equal({ x: { asc: :nulls_first } }, method.call([{ x: { asc: :nulls_first } }], :x))
|
507
|
+
assert_equal({ x: { desc: :nulls_last } }, method.call([{ x: { desc: :nulls_last } }], :x))
|
508
|
+
assert_equal({ x: { desc: :nulls_first }}, method.call([{ x: { desc: :nulls_first } }], :x))
|
509
|
+
assert_equal({ relation: :id }, method.call(['relation.id'], { relation: :id }))
|
510
|
+
assert_equal({ relation: :id }, method.call([{ relation: :id }], { relation: :id }))
|
511
|
+
assert_equal({ relation: :id }, method.call([{ relation: :id }], [{ relation: :id }]))
|
512
|
+
assert_equal({ relation: :id }, method.call([{ relation: [:id] }], { relation: [:id] }))
|
513
|
+
assert_equal({ relation: :id }, method.call([{ relation: [:id] }], [{ relation: [:id] }]))
|
514
|
+
assert_equal({ relation: { id: :desc } }, method.call([{'relation.id' => :desc}], { relation: :id }))
|
515
|
+
assert_equal({ relation: { id: :desc } }, method.call([{ relation: { id: :desc } }], { relation: [:id] }))
|
516
|
+
assert_equal({ relation: { id: :desc } }, method.call([{ relation: { id: :desc } }], [{ relation: [:id] }]))
|
517
|
+
assert_equal({ relation: { id: :desc } }, method.call([{ relation: [{ id: :desc }] }], [{ relation: [:id] }]))
|
518
|
+
assert_equal({ relation: { id: :desc } }, method.call([{ relation: [{ id: :desc }] }], [{ relation: [:id] }]))
|
519
|
+
assert_equal({ relation: {:id => {:asc => :nulls_last}} }, method.call([{ relation: {:id => {:asc => :nulls_last}} }], [{ relation: [:id] }]))
|
520
|
+
assert_equal({ relation: {:id => {:asc => :nulls_last}} }, method.call([{ relation: {:id => {:asc => :nulls_last}} }], [{ relation: [:id] }]))
|
521
|
+
assert_equal({ relation: {:id => [{:asc => :nulls_last}]} }, method.call([{ relation: {:id => [{:asc => :nulls_last}]} }], [{ relation: [:id] }]))
|
522
|
+
assert_equal({ relation: {:id => [{:asc => :nulls_last}]} }, method.call([{ relation: {:id => [{:asc => :nulls_last}]} }], [{ relation: [:id] }]))
|
523
|
+
end
|
524
|
+
|
525
|
+
test 'order: :attribute' do
|
526
|
+
properties = Array.new(2) { create(:property) }
|
527
|
+
|
528
|
+
get properties_path(order: :id, limit: 100, format: 'json')
|
529
|
+
assert_equal properties.map(&:id).sort, JSON(response.body).map { |x| x['id'] }
|
530
|
+
end
|
531
|
+
|
532
|
+
test 'order: { attribute: :direction }' do
|
533
|
+
properties = Array.new(2) { create(:property) }
|
534
|
+
|
535
|
+
get properties_path(order: { id: :asc }, limit: 100, format: 'json')
|
536
|
+
assert_equal properties.map(&:id).sort, JSON(response.body).map { |x| x['id'] }
|
537
|
+
|
538
|
+
get properties_path(order: { id: :desc }, limit: 100, format: 'json')
|
539
|
+
assert_equal properties.map(&:id).sort.reverse, JSON(response.body).map { |x| x['id'] }
|
540
|
+
end
|
541
|
+
|
542
|
+
test 'order: { attribute: { direction: :nulls } }' do
|
543
|
+
properties = [ create(:property), create(:property, description: nil) ]
|
544
|
+
|
545
|
+
get properties_path(order: { description: { asc: :nulls_last } }, limit: 100, format: 'json')
|
546
|
+
assert_equal properties.map(&:id).sort, JSON(response.body).map { |x| x['id'] }
|
547
|
+
|
548
|
+
get properties_path(order: { description: { asc: :nulls_first } }, limit: 100, format: 'json')
|
549
|
+
assert_equal properties.map(&:id).sort.reverse, JSON(response.body).map { |x| x['id'] }
|
550
|
+
end
|
551
|
+
|
552
|
+
# Calculate Test
|
553
|
+
test 'calculate' do
|
554
|
+
create(:photo)
|
555
|
+
get '/photos/calculate', params: {select: {count: "*"}}
|
556
|
+
assert_equal [1], JSON(response.body)
|
557
|
+
end
|
558
|
+
|
559
|
+
test 'calculate group_by' do
|
560
|
+
create(:photo, format: 'jpg')
|
561
|
+
create(:photo, format: 'jpg')
|
562
|
+
create(:photo, format: 'png')
|
563
|
+
get '/photos/calculate', params: {select: {count: "*"}, group_by: 'format'}
|
564
|
+
assert_equal ({'png' => 1, 'jpg' => 2}), JSON(response.body)
|
565
|
+
end
|
566
|
+
|
567
|
+
test 'calculate join' do
|
568
|
+
p1 = create(:property)
|
569
|
+
p2 = create(:property)
|
570
|
+
create(:account, photos_count: 1, property: p1)
|
571
|
+
create(:account, photos_count: 2, property: p2)
|
572
|
+
|
573
|
+
get '/properties/calculate', params: {select: {sum: "accounts.photos_count"}, join: 'accounts'}
|
574
|
+
assert_equal [3], JSON(response.body)
|
575
|
+
end
|
576
|
+
|
577
|
+
test 'calculate count distinct' do
|
578
|
+
photo = create(:photo)
|
579
|
+
landlord = create(:account)
|
580
|
+
create(:property, landlord: landlord, photos: [photo])
|
581
|
+
create(:property, landlord: landlord, photos: [photo])
|
582
|
+
|
583
|
+
get '/photos/calculate', params: {select: {count: "*"},
|
584
|
+
where: {properties: {landlord: {id: landlord.id}}},
|
585
|
+
distinct: true
|
586
|
+
}
|
587
|
+
|
588
|
+
assert_equal [1], JSON(response.body)
|
589
|
+
end
|
590
|
+
|
591
|
+
end
|
@@ -21,6 +21,8 @@ class Property < ActiveRecord::Base
|
|
21
21
|
has_and_belongs_to_many :photos
|
22
22
|
has_many :accounts
|
23
23
|
has_one :landlord, class_name: 'Account'
|
24
|
+
has_one :document_attachments, class_name: "Attachment", as: :record, inverse_of: :record
|
25
|
+
has_one :document, through: "document_attachments"
|
24
26
|
|
25
27
|
validates :name, presence: true
|
26
28
|
accepts_nested_attributes_for :photos
|
@@ -34,6 +36,15 @@ class Reference < ActiveRecord::Base
|
|
34
36
|
belongs_to :subject, polymorphic: true
|
35
37
|
end
|
36
38
|
|
39
|
+
class Document < ActiveRecord::Base
|
40
|
+
attr_accessor :file
|
41
|
+
end
|
42
|
+
|
43
|
+
class Attachment < ActiveRecord::Base
|
44
|
+
belongs_to :record, polymorphic: true
|
45
|
+
belongs_to :document
|
46
|
+
end
|
47
|
+
|
37
48
|
# = Migration
|
38
49
|
|
39
50
|
class CreateModelTables < ActiveRecord::Migration[6.0]
|
@@ -87,6 +98,12 @@ class CreateModelTables < ActiveRecord::Migration[6.0]
|
|
87
98
|
create_table "documents", force: :cascade do |t|
|
88
99
|
t.string 'type'
|
89
100
|
end
|
101
|
+
|
102
|
+
create_table "attachments", force: :cascade do |t|
|
103
|
+
t.string 'record_type'
|
104
|
+
t.integer 'record_id'
|
105
|
+
t.integer 'document_id'
|
106
|
+
end
|
90
107
|
end
|
91
108
|
|
92
109
|
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require File.expand_path('../test_app', __FILE__)
|
2
|
+
|
3
|
+
require "minitest/autorun"
|
4
|
+
require 'minitest/unit'
|
5
|
+
require 'factory_bot'
|
6
|
+
require 'faker'
|
7
|
+
require 'standard_api/test_case'
|
8
|
+
require 'byebug'
|
9
|
+
require 'mocha/minitest'
|
10
|
+
|
11
|
+
# Setup the test db
|
12
|
+
ActiveSupport.test_order = :random
|
13
|
+
|
14
|
+
include ActionDispatch::TestProcess
|
15
|
+
|
16
|
+
class ActiveSupport::TestCase
|
17
|
+
include ActiveRecord::TestFixtures
|
18
|
+
include FactoryBot::Syntax::Methods
|
19
|
+
|
20
|
+
def setup
|
21
|
+
@routes ||= TestApplication.routes
|
22
|
+
@subscribers, @layouts, @partials = [], {}, {}
|
23
|
+
|
24
|
+
Rails.cache.clear
|
25
|
+
|
26
|
+
@subscribers << ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
|
27
|
+
path = payload[:identifier]
|
28
|
+
virtual_path = payload[:virtual_path]
|
29
|
+
format, handler = *path.split("/").last.split('.').last(2)
|
30
|
+
|
31
|
+
partial = virtual_path =~ /^.*\/_[^\/]*$/
|
32
|
+
|
33
|
+
if partial
|
34
|
+
if @partials[virtual_path]
|
35
|
+
@partials[virtual_path][:count] += 1
|
36
|
+
else
|
37
|
+
@partials[virtual_path] = {
|
38
|
+
count: 1,
|
39
|
+
path: virtual_path,
|
40
|
+
format: format,
|
41
|
+
handler: handler
|
42
|
+
}
|
43
|
+
end
|
44
|
+
else
|
45
|
+
if @layouts[virtual_path]
|
46
|
+
@layouts[virtual_path][:count] += 1
|
47
|
+
else
|
48
|
+
@layouts[virtual_path] = {
|
49
|
+
count: 1,
|
50
|
+
path: virtual_path,
|
51
|
+
format: format,
|
52
|
+
handler: handler
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def teardown
|
60
|
+
@subscribers.each do |subscriber|
|
61
|
+
ActiveSupport::Notifications.unsubscribe(subscriber)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# = Helper Methods
|
66
|
+
|
67
|
+
def controller_path
|
68
|
+
if defined?(@controller)
|
69
|
+
@controller.controller_path
|
70
|
+
else
|
71
|
+
controller_class.new.controller_path
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def path_with_action(action, options={})
|
76
|
+
{ :controller => controller_path, :action => action }.merge(options)
|
77
|
+
end
|
78
|
+
|
79
|
+
def assert_rendered(options = {}, message = nil)
|
80
|
+
options = case options
|
81
|
+
when NilClass, Regexp, String, Symbol
|
82
|
+
{ layout: options }
|
83
|
+
when Hash
|
84
|
+
options
|
85
|
+
else
|
86
|
+
raise ArgumentError, "assert_template only accepts a String, Symbol, Hash, Regexp, or nil"
|
87
|
+
end
|
88
|
+
|
89
|
+
options.assert_valid_keys(:layout, :partial, :count, :format, :handler)
|
90
|
+
|
91
|
+
if expected_layout = options[:layout]
|
92
|
+
case expected_layout
|
93
|
+
when String, Symbol
|
94
|
+
msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
|
95
|
+
expected_layout, @layouts.keys)
|
96
|
+
assert_includes @layouts.keys, expected_layout.to_s, msg
|
97
|
+
|
98
|
+
key = expected_layout.to_s
|
99
|
+
value = @layouts[key]
|
100
|
+
|
101
|
+
if expected_count = options[:count]
|
102
|
+
actual_count = value[:count]
|
103
|
+
msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)",
|
104
|
+
expected_partial, expected_count, actual_count)
|
105
|
+
assert_equal expected_count, actual_count, msg
|
106
|
+
end
|
107
|
+
|
108
|
+
if expected_format = options[:format]
|
109
|
+
actual_format = value[:format]
|
110
|
+
msg = message || sprintf("expecting %s to be rendered as %s but rendered as %s",
|
111
|
+
expected_partial, expected_format, actual_format)
|
112
|
+
assert_equal expected_format, actual_format, msg
|
113
|
+
end
|
114
|
+
|
115
|
+
if expected_handler = options[:handler]
|
116
|
+
actual_handler = value[:handler]
|
117
|
+
msg = message || sprintf("expecting %s to be rendered as %s but rendered as %s",
|
118
|
+
expected_partial, expected_handler, actual_handler)
|
119
|
+
assert_equal expected_handler, actual_handler, msg
|
120
|
+
end
|
121
|
+
when Regexp
|
122
|
+
msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
|
123
|
+
expected_layout, @layouts.keys)
|
124
|
+
assert(@layouts.keys.any? {|l| l =~ expected_layout }, msg)
|
125
|
+
|
126
|
+
key = @layouts.keys.find {|l| l =~ expected_layout }
|
127
|
+
value = @layouts[key]
|
128
|
+
|
129
|
+
if expected_count = options[:count]
|
130
|
+
actual_count = value[:count]
|
131
|
+
msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)",
|
132
|
+
expected_partial, expected_count, actual_count)
|
133
|
+
assert_equal expected_count, actual_count, msg
|
134
|
+
end
|
135
|
+
|
136
|
+
if expected_format = options[:format]
|
137
|
+
actual_format = value[:format]
|
138
|
+
msg = message || sprintf("expecting %s to be rendered as %s but rendered as %s",
|
139
|
+
expected_partial, expected_format, actual_format)
|
140
|
+
assert_equal expected_format, actual_format, msg
|
141
|
+
end
|
142
|
+
|
143
|
+
if expected_handler = options[:handler]
|
144
|
+
actual_handler = value[:handler]
|
145
|
+
msg = message || sprintf("expecting %s to be rendered as %s but rendered as %s",
|
146
|
+
expected_partial, expected_handler, actual_handler)
|
147
|
+
assert_equal expected_handler, actual_handler, msg
|
148
|
+
end
|
149
|
+
when nil, false
|
150
|
+
assert(@layouts.empty?, msg)
|
151
|
+
end
|
152
|
+
elsif expected_partial = options[:partial]
|
153
|
+
case expected_partial
|
154
|
+
when String, Symbol
|
155
|
+
msg = message || sprintf("expecting partial <%s> but action rendered <%s>",
|
156
|
+
expected_partial, @partials.keys)
|
157
|
+
assert_includes @partials.keys, expected_partial.to_s, msg
|
158
|
+
|
159
|
+
key = expected_partial.to_s
|
160
|
+
value = @partials[key]
|
161
|
+
|
162
|
+
if expected_count = options[:count]
|
163
|
+
actual_count = value[:count]
|
164
|
+
msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)",
|
165
|
+
expected_partial, expected_count, actual_count)
|
166
|
+
assert_equal expected_count, actual_count, msg
|
167
|
+
end
|
168
|
+
|
169
|
+
if expected_format = options[:format]
|
170
|
+
actual_format = value[:format]
|
171
|
+
msg = message || sprintf("expecting %s to be rendered as %s but rendered as %s",
|
172
|
+
expected_partial, expected_format, actual_format)
|
173
|
+
assert_equal expected_format, actual_format, msg
|
174
|
+
end
|
175
|
+
|
176
|
+
if expected_handler = options[:handler]
|
177
|
+
actual_handler = value[:handler]
|
178
|
+
msg = message || sprintf("expecting %s to be rendered as %s but rendered as %s",
|
179
|
+
expected_partial, expected_handler, actual_handler)
|
180
|
+
assert_equal expected_handler, actual_handler, msg
|
181
|
+
end
|
182
|
+
when Regexp
|
183
|
+
msg = message || sprintf("expecting partial <%s> but action rendered <%s>",
|
184
|
+
expected_partial, @partials.keys)
|
185
|
+
assert(@partials.keys.any? {|l| l =~ expected_partial }, msg)
|
186
|
+
|
187
|
+
key = @partials.keys.find {|l| l =~ expected_partial }
|
188
|
+
value = @partials[key]
|
189
|
+
|
190
|
+
if expected_count = options[:count]
|
191
|
+
actual_count = value[:count]
|
192
|
+
msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)",
|
193
|
+
expected_partial, expected_count, actual_count)
|
194
|
+
assert_equal expected_count, actual_count, msg
|
195
|
+
end
|
196
|
+
|
197
|
+
if expected_format = options[:format]
|
198
|
+
actual_format = value[:format]
|
199
|
+
msg = message || sprintf("expecting %s to be rendered as %s but rendered as %s",
|
200
|
+
expected_partial, expected_format, actual_format)
|
201
|
+
assert_equal expected_format, actual_format, msg
|
202
|
+
end
|
203
|
+
|
204
|
+
if expected_handler = options[:handler]
|
205
|
+
actual_handler = value[:handler]
|
206
|
+
msg = message || sprintf("expecting %s to be rendered as %s but rendered as %s",
|
207
|
+
expected_partial, expected_handler, actual_handler)
|
208
|
+
assert_equal expected_handler, actual_handler, msg
|
209
|
+
end
|
210
|
+
when nil, false
|
211
|
+
assert(@partials.empty?, msg)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
class ActionController::TestCase
|
219
|
+
|
220
|
+
def assigns(key = nil)
|
221
|
+
assigns = {}.with_indifferent_access
|
222
|
+
@controller.view_assigns.each { |k, v| assigns.regular_writer(k, v) }
|
223
|
+
key.nil? ? assigns : assigns[key]
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: standardapi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.0.0.
|
4
|
+
version: 6.0.0.32
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Bracy
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-02-
|
11
|
+
date: 2020-02-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -276,6 +276,11 @@ files:
|
|
276
276
|
- lib/standard_api/views/application/schema.streamer
|
277
277
|
- lib/standard_api/views/application/show.json.jbuilder
|
278
278
|
- lib/standard_api/views/application/show.streamer
|
279
|
+
- test/standard_api/caching_test.rb
|
280
|
+
- test/standard_api/helpers_test.rb
|
281
|
+
- test/standard_api/performance.rb
|
282
|
+
- test/standard_api/route_helpers_test.rb
|
283
|
+
- test/standard_api/standard_api_test.rb
|
279
284
|
- test/standard_api/test_app.rb
|
280
285
|
- test/standard_api/test_app/config/database.yml
|
281
286
|
- test/standard_api/test_app/controllers.rb
|
@@ -290,6 +295,7 @@ files:
|
|
290
295
|
- test/standard_api/test_app/views/photos/schema.streamer
|
291
296
|
- test/standard_api/test_app/views/properties/edit.html.erb
|
292
297
|
- test/standard_api/test_app/views/sessions/new.html.erb
|
298
|
+
- test/standard_api/test_helper.rb
|
293
299
|
homepage: https://github.com/waratuman/standardapi
|
294
300
|
licenses:
|
295
301
|
- MIT
|
@@ -312,7 +318,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
312
318
|
- !ruby/object:Gem::Version
|
313
319
|
version: '0'
|
314
320
|
requirements: []
|
315
|
-
rubygems_version: 3.
|
321
|
+
rubygems_version: 3.1.2
|
316
322
|
signing_key:
|
317
323
|
specification_version: 4
|
318
324
|
summary: StandardAPI makes it easy to expose a query interface for your Rails models
|