standardapi 6.0.0.26 → 6.1.0

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