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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf7ed75a06eda9f024ebd87caccab44573802435d0494944137d92da6403fca2
4
- data.tar.gz: f44ad9cdd0d8bb87bc971f0b11b8f50b5f5f1289911d7bf4764f6f867b59b407
3
+ metadata.gz: 6f8e9f6eee237614e9c303c8fe9e2e624b081272627f9a7aa3f680276872c1a9
4
+ data.tar.gz: 73ede0c9b6229117b4781071441ac0581a06b16592f95f04959932c010ecb8f0
5
5
  SHA512:
6
- metadata.gz: 2f0f4ba53a851f87467a584b0a37708a1d2c6a52fc3360c31fbd50462af17918b7c43b435001410984291db24a451940e97487fc549f993d35ecc63d832d3b20
7
- data.tar.gz: 26d70be42fd115039c04a4a0ba614225c05a549af0847d3473659c9d593002cd11addcfd330000163eb9d1597843c274593ba99ddd6f9a57ee39b83e8a05fcbb
6
+ metadata.gz: 904192ff81007b628b672e094dfaad19f4b2c49a0e4119631dd46c320a85d0e2c06200b5570d29d3b70ded73046b843dfd28ce3e40358d3b2294bbf9bb7b50f6
7
+ data.tar.gz: f9ed735aabd0830d6b2c7a390d4f2bca3bf8bcc1d8b806b615a76462404f58c9b65241c75e7b7e62abe84539705249ed10c82f9f5dcc4cc1ee4911e51513200f
@@ -1,3 +1,3 @@
1
1
  module StandardAPI
2
- VERSION = '6.0.0.30'
2
+ VERSION = '6.0.0.32'
3
3
  end
@@ -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::HasManyReflection, ActiveRecord::Reflection::HasAndBelongsToManyReflection, ActiveRecord::Reflection::ThroughReflection
12
- can_cache = can_cache_relation?(record.class, inc, subinc)
13
- json.cache_if!(can_cache, can_cache ? association_cache_key(record, inc, subinc) : nil) do
14
- partial = model_partial(association.klass)
15
- json.set! inc do
16
- # TODO limit causes preloaded assocations to reload
17
- sub_records = record.send(inc)
18
-
19
- sub_records = sub_records.limit(subinc['limit']) if subinc['limit']
20
- sub_records = sub_records.offset(subinc['offset']) if subinc['offset']
21
- sub_records = sub_records.order(subinc['order']) if subinc['order']
22
- sub_records = sub_records.filter(subinc['where']) if subinc['where']
23
- sub_records = sub_records.distinct if subinc['distinct']
24
- sub_records = sub_records.distinct_on(subinc['distinct_on']) if subinc['distinct_on']
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
- json.array! sub_records, partial: partial, as: partial.split('/').last, locals: { includes: subinc }
28
+ json.array! sub_records, partial: partial, as: partial.split('/').last, locals: { includes: subinc }
29
+ end
27
30
  end
28
- end
29
- when ActiveRecord::Reflection::BelongsToReflection, ActiveRecord::Reflection::HasOneReflection
30
- can_cache = can_cache_relation?(record.class, inc, subinc)
31
- if association.is_a?(ActiveRecord::Reflection::BelongsToReflection)
32
- can_cache = can_cache && !record.send(association.foreign_key).nil?
33
- end
34
- json.cache_if!(can_cache, can_cache ? association_cache_key(record, inc, subinc) : nil) do
35
- value = record.send(inc)
36
- if value.nil?
37
- json.set! inc, nil
38
- else
39
- partial = model_partial(value)
40
- json.set! inc do
41
- json.partial! partial, partial.split('/').last.to_sym => value, includes: subinc
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
@@ -33,7 +33,7 @@ class ApplicationController < ActionController::Base
33
33
  end
34
34
 
35
35
  def property_includes
36
- [:photos, :landlord, :english_name]
36
+ [:photos, :landlord, :english_name, :document]
37
37
  end
38
38
 
39
39
  def reference_includes
@@ -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.30
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-10 00:00:00.000000000 Z
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.0.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