standardapi 6.0.0.26 → 6.1.0

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.
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