standardapi 6.0.0.30 → 6.0.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|