activity_notification 2.0.0 → 2.1.4

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 (202) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +22 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
  4. data/.github/pull_request_template.md +13 -0
  5. data/.gitignore +10 -3
  6. data/.travis.yml +6 -5
  7. data/CHANGELOG.md +60 -0
  8. data/Gemfile +8 -3
  9. data/Procfile +1 -1
  10. data/README.md +153 -1510
  11. data/activity_notification.gemspec +4 -1
  12. data/app/channels/activity_notification/notification_api_channel.rb +12 -0
  13. data/app/channels/activity_notification/notification_api_with_devise_channel.rb +46 -0
  14. data/app/channels/activity_notification/notification_channel.rb +2 -2
  15. data/app/channels/activity_notification/notification_with_devise_channel.rb +2 -2
  16. data/app/controllers/activity_notification/apidocs_controller.rb +75 -0
  17. data/app/controllers/activity_notification/notifications_api_controller.rb +143 -0
  18. data/app/controllers/activity_notification/notifications_api_with_devise_controller.rb +7 -0
  19. data/app/controllers/activity_notification/notifications_controller.rb +79 -53
  20. data/app/controllers/activity_notification/subscriptions_api_controller.rb +197 -0
  21. data/app/controllers/activity_notification/subscriptions_api_with_devise_controller.rb +7 -0
  22. data/app/controllers/activity_notification/subscriptions_controller.rb +78 -69
  23. data/app/views/activity_notification/notifications/default/_default.html.erb +18 -18
  24. data/app/views/activity_notification/notifications/default/_default_without_grouping.html.erb +14 -14
  25. data/app/views/activity_notification/notifications/default/index.html.erb +6 -6
  26. data/app/views/activity_notification/optional_targets/default/action_cable_channel/_default.html.erb +176 -0
  27. data/app/views/activity_notification/subscriptions/default/_form.html.erb +1 -1
  28. data/app/views/activity_notification/subscriptions/default/_notification_keys.html.erb +3 -31
  29. data/app/views/activity_notification/subscriptions/default/_subscription.html.erb +7 -7
  30. data/app/views/activity_notification/subscriptions/default/index.html.erb +11 -7
  31. data/bin/deploy_on_heroku.sh +3 -1
  32. data/docs/CODE_OF_CONDUCT.md +76 -0
  33. data/docs/CONTRIBUTING.md +36 -0
  34. data/docs/Functions.md +1130 -0
  35. data/docs/Setup.md +801 -0
  36. data/docs/Testing.md +148 -0
  37. data/gemfiles/Gemfile.rails-4.2 +5 -1
  38. data/gemfiles/Gemfile.rails-5.0 +6 -1
  39. data/gemfiles/Gemfile.rails-5.1 +6 -1
  40. data/gemfiles/Gemfile.rails-5.2 +6 -1
  41. data/gemfiles/{Gemfile.rails-6.0.rc → Gemfile.rails-6.0} +6 -5
  42. data/lib/activity_notification.rb +13 -0
  43. data/lib/activity_notification/apis/notification_api.rb +37 -93
  44. data/lib/activity_notification/apis/subscription_api.rb +20 -8
  45. data/lib/activity_notification/apis/swagger.rb +6 -0
  46. data/lib/activity_notification/common.rb +4 -1
  47. data/lib/activity_notification/config.rb +41 -21
  48. data/lib/activity_notification/controllers/common_api_controller.rb +30 -0
  49. data/lib/activity_notification/controllers/common_controller.rb +45 -21
  50. data/lib/activity_notification/controllers/concerns/swagger/error_responses.rb +55 -0
  51. data/lib/activity_notification/controllers/concerns/swagger/notifications_api.rb +273 -0
  52. data/lib/activity_notification/controllers/concerns/swagger/notifications_parameters.rb +92 -0
  53. data/lib/activity_notification/controllers/concerns/swagger/subscriptions_api.rb +405 -0
  54. data/lib/activity_notification/controllers/concerns/swagger/subscriptions_parameters.rb +50 -0
  55. data/lib/activity_notification/controllers/devise_authentication_controller.rb +7 -6
  56. data/lib/activity_notification/gem_version.rb +14 -0
  57. data/lib/activity_notification/helpers/errors.rb +2 -0
  58. data/lib/activity_notification/helpers/view_helpers.rb +4 -0
  59. data/lib/activity_notification/mailers/helpers.rb +17 -10
  60. data/lib/activity_notification/models/concerns/notifiable.rb +31 -15
  61. data/lib/activity_notification/models/concerns/subscriber.rb +12 -1
  62. data/lib/activity_notification/models/concerns/swagger/error_schema.rb +36 -0
  63. data/lib/activity_notification/models/concerns/swagger/notification_schema.rb +209 -0
  64. data/lib/activity_notification/models/concerns/swagger/subscription_schema.rb +162 -0
  65. data/lib/activity_notification/models/concerns/target.rb +36 -10
  66. data/lib/activity_notification/models/notification.rb +1 -0
  67. data/lib/activity_notification/models/subscription.rb +1 -0
  68. data/lib/activity_notification/optional_targets/action_cable_api_channel.rb +69 -0
  69. data/lib/activity_notification/optional_targets/action_cable_channel.rb +68 -0
  70. data/lib/activity_notification/optional_targets/base.rb +7 -13
  71. data/lib/activity_notification/orm/active_record/notification.rb +17 -1
  72. data/lib/activity_notification/orm/active_record/subscription.rb +1 -1
  73. data/lib/activity_notification/orm/dynamoid.rb +38 -3
  74. data/lib/activity_notification/orm/dynamoid/extension.rb +79 -1
  75. data/lib/activity_notification/orm/dynamoid/notification.rb +49 -14
  76. data/lib/activity_notification/orm/dynamoid/subscription.rb +2 -2
  77. data/lib/activity_notification/orm/mongoid.rb +32 -3
  78. data/lib/activity_notification/orm/mongoid/notification.rb +24 -6
  79. data/lib/activity_notification/orm/mongoid/subscription.rb +1 -1
  80. data/lib/activity_notification/rails/routes.rb +132 -48
  81. data/lib/activity_notification/renderable.rb +13 -2
  82. data/lib/activity_notification/roles/acts_as_notifiable.rb +39 -20
  83. data/lib/activity_notification/version.rb +1 -1
  84. data/lib/generators/activity_notification/controllers_generator.rb +2 -1
  85. data/lib/generators/templates/activity_notification.rb +8 -0
  86. data/lib/generators/templates/controllers/notifications_api_controller.rb +31 -0
  87. data/lib/generators/templates/controllers/notifications_api_with_devise_controller.rb +31 -0
  88. data/lib/generators/templates/controllers/notifications_controller.rb +1 -37
  89. data/lib/generators/templates/controllers/notifications_with_devise_controller.rb +1 -45
  90. data/lib/generators/templates/controllers/subscriptions_api_controller.rb +61 -0
  91. data/lib/generators/templates/controllers/subscriptions_api_with_devise_controller.rb +61 -0
  92. data/lib/generators/templates/controllers/subscriptions_controller.rb +14 -37
  93. data/lib/generators/templates/controllers/subscriptions_with_devise_controller.rb +14 -45
  94. data/lib/generators/templates/models/README +8 -4
  95. data/lib/generators/templates/models/notification.rb +1 -1
  96. data/lib/generators/templates/models/subscription.rb +1 -1
  97. data/package.json +8 -0
  98. data/spec/channels/notification_api_channel_shared_examples.rb +59 -0
  99. data/spec/channels/notification_api_channel_spec.rb +51 -0
  100. data/spec/channels/notification_api_with_devise_channel_spec.rb +78 -0
  101. data/spec/concerns/apis/notification_api_spec.rb +38 -3
  102. data/spec/concerns/models/notifiable_spec.rb +82 -18
  103. data/spec/concerns/models/subscriber_spec.rb +13 -16
  104. data/spec/concerns/models/target_spec.rb +32 -0
  105. data/spec/concerns/renderable_spec.rb +2 -2
  106. data/spec/config_spec.rb +26 -15
  107. data/spec/controllers/controller_spec_utility.rb +136 -0
  108. data/spec/controllers/notifications_api_controller_shared_examples.rb +506 -0
  109. data/spec/controllers/notifications_api_controller_spec.rb +19 -0
  110. data/spec/controllers/notifications_api_with_devise_controller_spec.rb +60 -0
  111. data/spec/controllers/notifications_controller_shared_examples.rb +54 -79
  112. data/spec/controllers/notifications_controller_spec.rb +1 -2
  113. data/spec/controllers/notifications_with_devise_controller_spec.rb +3 -12
  114. data/spec/controllers/subscriptions_api_controller_shared_examples.rb +750 -0
  115. data/spec/controllers/subscriptions_api_controller_spec.rb +19 -0
  116. data/spec/controllers/subscriptions_api_with_devise_controller_spec.rb +60 -0
  117. data/spec/controllers/subscriptions_controller_shared_examples.rb +94 -121
  118. data/spec/controllers/subscriptions_controller_spec.rb +1 -2
  119. data/spec/controllers/subscriptions_with_devise_controller_spec.rb +3 -12
  120. data/spec/helpers/view_helpers_spec.rb +4 -11
  121. data/spec/mailers/mailer_spec.rb +41 -0
  122. data/spec/models/notification_spec.rb +17 -0
  123. data/spec/models/subscription_spec.rb +8 -13
  124. data/spec/optional_targets/action_cable_api_channel_spec.rb +37 -0
  125. data/spec/optional_targets/action_cable_channel_spec.rb +44 -0
  126. data/spec/optional_targets/amazon_sns_spec.rb +0 -2
  127. data/spec/optional_targets/slack_spec.rb +0 -2
  128. data/spec/rails_app/Rakefile +9 -0
  129. data/spec/rails_app/app/assets/config/manifest.js +3 -0
  130. data/spec/rails_app/app/assets/images/.keep +0 -0
  131. data/spec/rails_app/app/controllers/admins_controller.rb +21 -0
  132. data/spec/rails_app/app/controllers/application_controller.rb +1 -1
  133. data/spec/rails_app/app/controllers/articles_controller.rb +6 -3
  134. data/spec/rails_app/app/controllers/spa_controller.rb +7 -0
  135. data/spec/rails_app/app/controllers/users/notifications_controller.rb +0 -65
  136. data/spec/rails_app/app/controllers/users/notifications_with_devise_controller.rb +0 -73
  137. data/spec/rails_app/app/controllers/users/subscriptions_controller.rb +0 -77
  138. data/spec/rails_app/app/controllers/users/subscriptions_with_devise_controller.rb +0 -85
  139. data/spec/rails_app/app/controllers/users_controller.rb +26 -0
  140. data/spec/rails_app/app/javascript/App.vue +40 -0
  141. data/spec/rails_app/app/javascript/components/DeviseTokenAuth.vue +82 -0
  142. data/spec/rails_app/app/javascript/components/Top.vue +98 -0
  143. data/spec/rails_app/app/javascript/components/notifications/Index.vue +200 -0
  144. data/spec/rails_app/app/javascript/components/notifications/Notification.vue +133 -0
  145. data/spec/rails_app/app/javascript/components/notifications/NotificationContent.vue +122 -0
  146. data/spec/rails_app/app/javascript/components/subscriptions/Index.vue +279 -0
  147. data/spec/rails_app/app/javascript/components/subscriptions/NewSubscription.vue +112 -0
  148. data/spec/rails_app/app/javascript/components/subscriptions/NotificationKey.vue +141 -0
  149. data/spec/rails_app/app/javascript/components/subscriptions/Subscription.vue +226 -0
  150. data/spec/rails_app/app/javascript/config/development.js +5 -0
  151. data/spec/rails_app/app/javascript/config/environment.js +7 -0
  152. data/spec/rails_app/app/javascript/config/production.js +5 -0
  153. data/spec/rails_app/app/javascript/config/test.js +5 -0
  154. data/spec/rails_app/app/javascript/packs/application.js +18 -0
  155. data/spec/rails_app/app/javascript/packs/spa.js +14 -0
  156. data/spec/rails_app/app/javascript/router/index.js +73 -0
  157. data/spec/rails_app/app/javascript/store/index.js +37 -0
  158. data/spec/rails_app/app/models/admin.rb +16 -15
  159. data/spec/rails_app/app/models/article.rb +26 -21
  160. data/spec/rails_app/app/models/comment.rb +24 -71
  161. data/spec/rails_app/app/models/dummy/dummy_group.rb +8 -0
  162. data/spec/rails_app/app/models/dummy/dummy_notifiable_target.rb +8 -0
  163. data/spec/rails_app/app/models/user.rb +44 -20
  164. data/spec/rails_app/app/views/activity_notification/notifications/default/article/_update.html.erb +146 -0
  165. data/spec/rails_app/app/views/articles/index.html.erb +51 -7
  166. data/spec/rails_app/app/views/articles/show.html.erb +1 -1
  167. data/spec/rails_app/app/views/layouts/_header.html.erb +8 -10
  168. data/spec/rails_app/app/views/spa/index.html.erb +2 -0
  169. data/spec/rails_app/babel.config.js +72 -0
  170. data/spec/rails_app/bin/webpack +18 -0
  171. data/spec/rails_app/bin/webpack-dev-server +18 -0
  172. data/spec/rails_app/config/application.rb +18 -2
  173. data/spec/rails_app/config/dynamoid.rb +11 -3
  174. data/spec/rails_app/config/environment.rb +2 -1
  175. data/spec/rails_app/config/environments/development.rb +5 -0
  176. data/spec/rails_app/config/environments/production.rb +6 -0
  177. data/spec/rails_app/config/environments/test.rb +5 -0
  178. data/spec/rails_app/config/initializers/activity_notification.rb +11 -3
  179. data/spec/rails_app/config/initializers/copy_it.aws.rb.template +6 -0
  180. data/spec/rails_app/config/initializers/devise_token_auth.rb +55 -0
  181. data/spec/rails_app/config/initializers/mysql.rb +9 -0
  182. data/spec/rails_app/config/locales/activity_notification.en.yml +2 -2
  183. data/spec/rails_app/config/routes.rb +37 -1
  184. data/spec/rails_app/config/webpack/development.js +5 -0
  185. data/spec/rails_app/config/webpack/environment.js +7 -0
  186. data/spec/rails_app/config/webpack/loaders/vue.js +6 -0
  187. data/spec/rails_app/config/webpack/production.js +5 -0
  188. data/spec/rails_app/config/webpack/test.js +5 -0
  189. data/spec/rails_app/config/webpacker.yml +97 -0
  190. data/spec/rails_app/db/migrate/20191201000000_add_tokens_to_users.rb +10 -0
  191. data/spec/rails_app/db/schema.rb +4 -1
  192. data/spec/rails_app/db/seeds.rb +10 -2
  193. data/spec/rails_app/lib/custom_optional_targets/raise_error.rb +14 -0
  194. data/spec/rails_app/package.json +23 -0
  195. data/spec/rails_app/postcss.config.js +12 -0
  196. data/spec/roles/acts_as_group_spec.rb +0 -2
  197. data/spec/roles/acts_as_notifiable_spec.rb +6 -8
  198. data/spec/roles/acts_as_notifier_spec.rb +0 -2
  199. data/spec/roles/acts_as_target_spec.rb +0 -4
  200. data/spec/spec_helper.rb +7 -15
  201. data/spec/version_spec.rb +31 -0
  202. metadata +191 -13
@@ -74,10 +74,9 @@ shared_examples_for :subscriber do
74
74
  end
75
75
 
76
76
  context "without params" do
77
- it "does not create a new subscription since it is invalid" do
78
- new_subscription = test_instance.create_subscription
79
- expect(new_subscription).to be_nil
80
- expect(test_instance.subscriptions.reload.to_a).to be_empty
77
+ it "raises ActivityNotification::RecordInvalidError it is invalid" do
78
+ expect { test_instance.create_subscription }
79
+ .to raise_error(ActivityNotification::RecordInvalidError)
81
80
  end
82
81
  end
83
82
 
@@ -122,16 +121,14 @@ shared_examples_for :subscriber do
122
121
  end
123
122
 
124
123
  context "with false as subscribing and true as subscribing_to_email params" do
125
- it "does not create a new subscription since it is invalid" do
126
- params = { key: 'key_1', subscribing: false, subscribing_to_email: true }
127
- new_subscription = test_instance.create_subscription(params)
128
- expect(new_subscription).to be_nil
129
- expect(test_instance.subscriptions.reload.to_a).to be_empty
124
+ it "raises ActivityNotification::RecordInvalidError it is invalid" do
125
+ expect {
126
+ params = { key: 'key_1', subscribing: false, subscribing_to_email: true }
127
+ test_instance.create_subscription(params)
128
+ }.to raise_error(ActivityNotification::RecordInvalidError)
130
129
  end
131
130
  end
132
131
 
133
-
134
-
135
132
  context "with true as optional_targets params" do
136
133
  it "creates a new subscription" do
137
134
  params = { key: 'key_1', optional_targets: { subscribing_to_console_output: true } }
@@ -163,11 +160,11 @@ shared_examples_for :subscriber do
163
160
  end
164
161
 
165
162
  context "with false as subscribing and true as optional_targets params" do
166
- it "does not create a new subscription since it is invalid" do
167
- params = { key: 'key_1', subscribing: false, optional_targets: { subscribing_to_console_output: true } }
168
- new_subscription = test_instance.create_subscription(params)
169
- expect(new_subscription).to be_nil
170
- expect(test_instance.subscriptions.reload.to_a).to be_empty
163
+ it "raises ActivityNotification::RecordInvalidError it is invalid" do
164
+ expect {
165
+ params = { key: 'key_1', subscribing: false, optional_targets: { subscribing_to_console_output: true } }
166
+ test_instance.create_subscription(params)
167
+ }.to raise_error(ActivityNotification::RecordInvalidError)
171
168
  end
172
169
  end
173
170
  end
@@ -729,6 +729,22 @@ shared_examples_for :target do
729
729
  end
730
730
  end
731
731
 
732
+ context 'with later_than options' do
733
+ it "returns filtered notifications only" do
734
+ options = { later_than: (@notification1.created_at.in_time_zone + 0.001).iso8601(3) }
735
+ expect(test_instance.notification_index(options)[0]).to eq(@notification3)
736
+ expect(test_instance.notification_index(options).size).to eq(1)
737
+ end
738
+ end
739
+
740
+ context 'with earlier_than options' do
741
+ it "returns filtered notifications only" do
742
+ options = { earlier_than: @notification1.created_at.iso8601(3) }
743
+ expect(test_instance.notification_index(options)[0]).to eq(@notification2)
744
+ expect(test_instance.notification_index(options).size).to eq(1)
745
+ end
746
+ end
747
+
732
748
  context 'with custom_filter options' do
733
749
  it "returns filtered notifications only" do
734
750
  options = { custom_filter: { key: @key } }
@@ -1009,6 +1025,22 @@ shared_examples_for :target do
1009
1025
  expect(test_instance.notification_index_with_attributes(options).size).to eq(1)
1010
1026
  end
1011
1027
  end
1028
+
1029
+ context 'with later_than options' do
1030
+ it "returns filtered notifications only" do
1031
+ options = { later_than: (@notification1.created_at.in_time_zone + 0.001).iso8601(3) }
1032
+ expect(test_instance.notification_index_with_attributes(options)[0]).to eq(@notification3)
1033
+ expect(test_instance.notification_index_with_attributes(options).size).to eq(1)
1034
+ end
1035
+ end
1036
+
1037
+ context 'with earlier_than options' do
1038
+ it "returns filtered notifications only" do
1039
+ options = { earlier_than: @notification1.created_at.iso8601(3) }
1040
+ expect(test_instance.notification_index_with_attributes(options)[0]).to eq(@notification2)
1041
+ expect(test_instance.notification_index_with_attributes(options).size).to eq(1)
1042
+ end
1043
+ end
1012
1044
  end
1013
1045
 
1014
1046
  context "when the target has no unopened notifications" do
@@ -13,11 +13,11 @@ shared_examples_for :renderable do
13
13
  let(:group_text_key) { 'comment.reply' }
14
14
  let(:plural_text_key) { 'comment.post' }
15
15
  let(:simple_text_original) { 'Article has been created' }
16
- let(:params_text_original) { "Article %{article_title} has been updated" }
16
+ let(:params_text_original) { 'Article "%{article_title}" has been updated' }
17
17
  let(:plural_text_original_one) { "<p>%{notifier_name} posted a comment on your article %{article_title}</p>" }
18
18
  let(:plural_text_original_other) { "<p>%{notifier_name} posted %{count} comments on your article %{article_title}</p>" }
19
19
  let(:group_text_original) { "<p>%{notifier_name} and %{group_member_count} other people replied %{group_notification_count} times to your comment</p>" }
20
- let(:params_text_embedded) { "Article bar has been updated" }
20
+ let(:params_text_embedded) { 'Article "bar" has been updated' }
21
21
  let(:group_text_embedded) { "<p>foo and 3 other people replied 4 times to your comment</p>" }
22
22
  let(:plural_text_embedded_one) { "<p>foo posted a comment on your article bar</p>" }
23
23
  let(:plural_text_embedded_other) { "<p>foo posted 4 comments on your article bar</p>" }
@@ -35,38 +35,49 @@ describe ActivityNotification::Config do
35
35
  describe "config.store_with_associated_records" do
36
36
  let(:target) { create(:confirmed_user) }
37
37
 
38
- context "false as default" do
39
- before do
40
- @notification = create(:notification, target: target)
41
- end
42
-
43
- it "stores notification without associated records" do
44
- expect(@notification.target).to eq(target)
45
- expect { @notification.target_record }.to raise_error(NoMethodError)
46
- end
47
- end
48
-
49
38
  context "when it is configured as true" do
50
39
  if ActivityNotification.config.orm == :active_record
51
40
  it "raises ActivityNotification::ConfigError when you use active_record ORM" do
52
41
  expect { ActivityNotification.config.store_with_associated_records = true }.to raise_error(ActivityNotification::ConfigError)
53
42
  end
54
-
55
43
  else
56
44
  before do
45
+ @original = ActivityNotification.config.store_with_associated_records
57
46
  ActivityNotification.config.store_with_associated_records = true
58
47
  load Rails.root.join("../../lib/activity_notification/orm/#{ActivityNotification.config.orm}/notification.rb").to_s
59
48
  @notification = create(:notification, target: target)
60
49
  end
61
50
 
62
51
  after do
63
- ActivityNotification.config.store_with_associated_records = false
52
+ ActivityNotification.config.store_with_associated_records = @original
64
53
  load Rails.root.join("../../lib/activity_notification/orm/#{ActivityNotification.config.orm}/notification.rb").to_s
65
54
  end
66
55
 
67
- it "stores notification without associated records" do
56
+ it "stores notification with associated records" do
68
57
  expect(@notification.target).to eq(target)
69
- expect(@notification.target_record).to eq(target.to_json)
58
+ expect(@notification.stored_target["id"].to_s).to eq(target.id.to_s)
59
+ end
60
+ end
61
+ end
62
+
63
+ context "when it is configured as false" do
64
+ before do
65
+ @original = ActivityNotification.config.store_with_associated_records
66
+ ActivityNotification.config.store_with_associated_records = false
67
+ load Rails.root.join("../../lib/activity_notification/orm/#{ActivityNotification.config.orm}/notification.rb").to_s
68
+ @notification = create(:notification, target: target)
69
+ end
70
+
71
+ after do
72
+ ActivityNotification.config.store_with_associated_records = @original
73
+ load Rails.root.join("../../lib/activity_notification/orm/#{ActivityNotification.config.orm}/notification.rb").to_s
74
+ end
75
+
76
+ it "does not store notification with associated records" do
77
+ expect(@notification.target).to eq(target)
78
+ begin
79
+ expect(@notification.stored_target).to be_nil
80
+ rescue NoMethodError
70
81
  end
71
82
  end
72
83
  end
@@ -0,0 +1,136 @@
1
+ module ActivityNotification
2
+ module ControllerSpec
3
+ module RequestUtility
4
+ def get_with_compatibility action, params, session
5
+ if Rails::VERSION::MAJOR <= 4
6
+ get action, params, session
7
+ else
8
+ get action, params: params, session: session
9
+ end
10
+ end
11
+
12
+ def post_with_compatibility action, params, session
13
+ if Rails::VERSION::MAJOR <= 4
14
+ post action, params, session
15
+ else
16
+ post action, params: params, session: session
17
+ end
18
+ end
19
+
20
+ def put_with_compatibility action, params, session
21
+ if Rails::VERSION::MAJOR <= 4
22
+ put action, params, session
23
+ else
24
+ put action, params: params, session: session
25
+ end
26
+ end
27
+
28
+ def delete_with_compatibility action, params, session
29
+ if Rails::VERSION::MAJOR <= 4
30
+ delete action, params, session
31
+ else
32
+ delete action, params: params, session: session
33
+ end
34
+ end
35
+
36
+ def xhr_with_compatibility method, action, params, session
37
+ if Rails::VERSION::MAJOR <= 4
38
+ xhr method, action, params, session
39
+ else
40
+ send method.to_s, action, xhr: true, params: params, session: session
41
+ end
42
+ end
43
+ end
44
+
45
+ module ApiResponseUtility
46
+ def response_json
47
+ JSON.parse(response.body)
48
+ end
49
+
50
+ def assert_json_with_array_size(json_array, size)
51
+ expect(json_array.size).to eq(size)
52
+ end
53
+
54
+ def assert_json_with_object(json_object, object)
55
+ expect(json_object['id'].to_s).to eq(object.id.to_s)
56
+ end
57
+
58
+ def assert_json_with_object_array(json_array, expected_object_array)
59
+ assert_json_with_array_size(json_array, expected_object_array.size)
60
+ expected_object_array.each_with_index do |json_object, index|
61
+ assert_json_with_object(json_object, expected_object_array[index])
62
+ end
63
+ end
64
+
65
+ def assert_error_response(code)
66
+ expect(response_json['gem']).to eq('activity_notification')
67
+ expect(response_json['error']['code']).to eq(code)
68
+ end
69
+ end
70
+
71
+ module CommitteeUtility
72
+ extend ActiveSupport::Concern
73
+ included do
74
+ include Committee::Rails::Test::Methods
75
+
76
+ def api_path
77
+ "/#{root_path}/#{target_type}/#{test_target.id}"
78
+ end
79
+
80
+ def schema_path
81
+ Rails.root.join('..', 'openapi.json')
82
+ end
83
+
84
+ def write_schema_file(schema_json)
85
+ File.open(schema_path, "w") { |file| file.write(schema_json) }
86
+ end
87
+
88
+ def read_schema_file
89
+ JSON.parse(File.read(schema_path))
90
+ end
91
+
92
+ def committee_options
93
+ @committee_options ||= { schema: Committee::Drivers::load_from_file(schema_path), prefix: root_path, validate_success_only: true }
94
+ end
95
+
96
+ def get_with_compatibility path, options = {}
97
+ if Rails::VERSION::MAJOR <= 4
98
+ get path, options[:params], options[:headers]
99
+ else
100
+ get path, options
101
+ end
102
+ end
103
+
104
+ def post_with_compatibility path, options = {}
105
+ if Rails::VERSION::MAJOR <= 4
106
+ post path, options[:params], options[:headers]
107
+ else
108
+ post path, options
109
+ end
110
+ end
111
+
112
+ def put_with_compatibility path, options = {}
113
+ if Rails::VERSION::MAJOR <= 4
114
+ put path, options[:params], options[:headers]
115
+ else
116
+ put path, options
117
+ end
118
+ end
119
+
120
+ def delete_with_compatibility path, options = {}
121
+ if Rails::VERSION::MAJOR <= 4
122
+ delete path, options[:params], options[:headers]
123
+ else
124
+ delete path, options
125
+ end
126
+ end
127
+
128
+ def assert_all_schema_confirm(response, status)
129
+ expect(response).to have_http_status(status)
130
+ assert_request_schema_confirm
131
+ assert_response_schema_confirm
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,506 @@
1
+ require_relative 'controller_spec_utility'
2
+
3
+ shared_examples_for :notifications_api_controller do
4
+ include ActivityNotification::ControllerSpec::RequestUtility
5
+ include ActivityNotification::ControllerSpec::ApiResponseUtility
6
+
7
+ let(:target_params) { { target_type: target_type }.merge(extra_params || {}) }
8
+
9
+ describe "GET #index" do
10
+ context "with target_type and target_id parameters" do
11
+ before do
12
+ @notification = create(:notification, target: test_target)
13
+ get_with_compatibility :index, target_params.merge({ target_id: test_target, typed_target_param => 'dummy' }), valid_session
14
+ end
15
+
16
+ it "returns 200 as http status code" do
17
+ expect(response.status).to eq(200)
18
+ end
19
+
20
+ it "returns JSON response" do
21
+ expect(response_json["count"]).to eq(1)
22
+ assert_json_with_object_array(response_json["notifications"], [@notification])
23
+ end
24
+ end
25
+
26
+ context "with target_type and (typed_target)_id parameters" do
27
+ before do
28
+ @notification = create(:notification, target: test_target)
29
+ get_with_compatibility :index, target_params.merge({ typed_target_param => test_target }), valid_session
30
+ end
31
+
32
+ it "returns 200 as http status code" do
33
+ expect(response.status).to eq(200)
34
+ end
35
+ end
36
+
37
+ context "without target_type parameters" do
38
+ before do
39
+ @notification = create(:notification, target: test_target)
40
+ get_with_compatibility :index, { typed_target_param => test_target }, valid_session
41
+ end
42
+
43
+ it "returns 400 as http status code" do
44
+ expect(response.status).to eq(400)
45
+ end
46
+
47
+ it "returns error JSON response" do
48
+ assert_error_response(400)
49
+ end
50
+ end
51
+
52
+ context "with not found (typed_target)_id parameter" do
53
+ before do
54
+ @notification = create(:notification, target: test_target)
55
+ get_with_compatibility :index, target_params.merge({ typed_target_param => 0 }), valid_session
56
+ end
57
+
58
+ it "returns 404 as http status code" do
59
+ expect(response.status).to eq(404)
60
+ end
61
+
62
+ it "returns error JSON response" do
63
+ assert_error_response(404)
64
+ end
65
+ end
66
+
67
+ context "with filter parameter" do
68
+ context "with unopened as filter" do
69
+ before do
70
+ @notification = create(:notification, target: test_target)
71
+ get_with_compatibility :index, target_params.merge({ typed_target_param => test_target, filter: 'unopened' }), valid_session
72
+ end
73
+
74
+ it "returns unopened notification index as JSON" do
75
+ assert_json_with_object_array(response_json["notifications"], [@notification])
76
+ end
77
+ end
78
+
79
+ context "with opened as filter" do
80
+ before do
81
+ @notification = create(:notification, target: test_target)
82
+ get_with_compatibility :index, target_params.merge({ typed_target_param => test_target, filter: 'opened' }), valid_session
83
+ end
84
+
85
+ it "returns unopened notification index as JSON" do
86
+ assert_json_with_object_array(response_json["notifications"], [])
87
+ end
88
+ end
89
+ end
90
+
91
+ context "with limit parameter" do
92
+ before do
93
+ create(:notification, target: test_target)
94
+ create(:notification, target: test_target)
95
+ end
96
+ context "with 2 as limit" do
97
+ before do
98
+ get_with_compatibility :index, target_params.merge({ typed_target_param => test_target, limit: 2 }), valid_session
99
+ end
100
+
101
+ it "returns notification index of size 2 as JSON" do
102
+ assert_json_with_array_size(response_json["notifications"], 2)
103
+ end
104
+ end
105
+
106
+ context "with 1 as limit" do
107
+ before do
108
+ get_with_compatibility :index, target_params.merge({ typed_target_param => test_target, limit: 1 }), valid_session
109
+ end
110
+
111
+ it "returns notification index of size 1 as JSON" do
112
+ assert_json_with_array_size(response_json["notifications"], 1)
113
+ end
114
+ end
115
+ end
116
+
117
+ context "with reverse parameter" do
118
+ before do
119
+ @notifiable = create(:article)
120
+ @group = create(:article)
121
+ @key = 'test.key.1'
122
+ notification = create(:notification, target: test_target, notifiable: @notifiable)
123
+ create(:notification, target: test_target, notifiable: create(:comment), group: @group, created_at: notification.created_at + 10.second)
124
+ create(:notification, target: test_target, notifiable: create(:article), key: @key, created_at: notification.created_at + 20.second).open!
125
+ @notification1 = test_target.notification_index[0]
126
+ @notification2 = test_target.notification_index[1]
127
+ @notification3 = test_target.notification_index[2]
128
+ end
129
+
130
+ context "as default" do
131
+ before do
132
+ get_with_compatibility :index, target_params.merge({ typed_target_param => test_target }), valid_session
133
+ end
134
+
135
+ it "returns the latest order" do
136
+ assert_json_with_object_array(response_json["notifications"], [@notification1, @notification2, @notification3])
137
+ end
138
+ end
139
+
140
+ context "with true as reverse" do
141
+ before do
142
+ get_with_compatibility :index, target_params.merge({ typed_target_param => test_target, reverse: true }), valid_session
143
+ end
144
+
145
+ it "returns the earliest order" do
146
+ assert_json_with_object_array(response_json["notifications"], [@notification2, @notification1, @notification3])
147
+ end
148
+ end
149
+ end
150
+
151
+ context "with options filter parameters" do
152
+ before do
153
+ @notifiable = create(:article)
154
+ @group = create(:article)
155
+ @key = 'test.key.1'
156
+ @notification2 = create(:notification, target: test_target, notifiable: @notifiable)
157
+ @notification1 = create(:notification, target: test_target, notifiable: create(:comment), group: @group, created_at: @notification2.created_at + 10.second)
158
+ @notification3 = create(:notification, target: test_target, notifiable: create(:article), key: @key, created_at: @notification2.created_at + 20.second)
159
+ @notification3.open!
160
+ end
161
+
162
+ context 'with filtered_by_type parameter' do
163
+ it "returns filtered notifications only" do
164
+ get_with_compatibility :index, target_params.merge({ typed_target_param => test_target, filtered_by_type: 'Article' }), valid_session
165
+ assert_json_with_object_array(response_json["notifications"], [@notification2, @notification3])
166
+ end
167
+ end
168
+
169
+ context 'with filtered_by_group_type and filtered_by_group_id parameters' do
170
+ it "returns filtered notifications only" do
171
+ get_with_compatibility :index, target_params.merge({ typed_target_param => test_target, filtered_by_group_type: 'Article', filtered_by_group_id: @group.id.to_s }), valid_session
172
+ assert_json_with_object_array(response_json["notifications"], [@notification1])
173
+ end
174
+ end
175
+
176
+ context 'with filtered_by_key parameter' do
177
+ it "returns filtered notifications only" do
178
+ get_with_compatibility :index, target_params.merge({ typed_target_param => test_target, filtered_by_key: @key }), valid_session
179
+ assert_json_with_object_array(response_json["notifications"], [@notification3])
180
+ end
181
+ end
182
+
183
+ context 'with later_than parameter' do
184
+ it "returns filtered notifications only" do
185
+ get_with_compatibility :index, target_params.merge({ typed_target_param => test_target, later_than: (@notification1.created_at.in_time_zone + 0.001).iso8601(3) }), valid_session
186
+ assert_json_with_object_array(response_json["notifications"], [@notification3])
187
+ end
188
+ end
189
+
190
+ context 'with earlier_than parameter' do
191
+ it "returns filtered notifications only" do
192
+ get_with_compatibility :index, target_params.merge({ typed_target_param => test_target, earlier_than: @notification1.created_at.iso8601(3) }), valid_session
193
+ assert_json_with_object_array(response_json["notifications"], [@notification2])
194
+ end
195
+ end
196
+ end
197
+ end
198
+
199
+ describe "POST #open_all" do
200
+ context "http POST request" do
201
+ before do
202
+ @notification = create(:notification, target: test_target)
203
+ expect(@notification.opened?).to be_falsey
204
+ post_with_compatibility :open_all, target_params.merge({ typed_target_param => test_target }), valid_session
205
+ end
206
+
207
+ it "returns 200 as http status code" do
208
+ expect(response.status).to eq(200)
209
+ end
210
+
211
+ it "opens all notifications of the target" do
212
+ expect(@notification.reload.opened?).to be_truthy
213
+ end
214
+
215
+ it "returns JSON response" do
216
+ expect(response_json["count"]).to eq(1)
217
+ assert_json_with_object_array(response_json["notifications"], [@notification])
218
+ end
219
+ end
220
+
221
+ context "with filter request parameters" do
222
+ before do
223
+ @target_1, @notifiable_1, @group_1, @key_1 = create(:confirmed_user), create(:article), nil, "key.1"
224
+ @target_2, @notifiable_2, @group_2, @key_2 = create(:confirmed_user), create(:comment), @notifiable_1, "key.2"
225
+ @notification_1 = create(:notification, target: test_target, notifiable: @notifiable_1, group: @group_1, key: @key_1)
226
+ @notification_2 = create(:notification, target: test_target, notifiable: @notifiable_2, group: @group_2, key: @key_2, created_at: @notification_1.created_at + 10.second)
227
+ expect(@notification_1.opened?).to be_falsey
228
+ expect(@notification_2.opened?).to be_falsey
229
+ end
230
+
231
+ context "with filtered_by_type request parameters" do
232
+ it "opens filtered notifications only" do
233
+ post_with_compatibility :open_all, target_params.merge({ typed_target_param => test_target, 'filtered_by_type' => @notifiable_2.to_class_name }), valid_session
234
+ expect(@notification_1.reload.opened?).to be_falsey
235
+ expect(@notification_2.reload.opened?).to be_truthy
236
+ end
237
+ end
238
+
239
+ context 'with filtered_by_group_type and :filtered_by_group_id request parameters' do
240
+ it "opens filtered notifications only" do
241
+ post_with_compatibility :open_all, target_params.merge({ typed_target_param => test_target, 'filtered_by_group_type' => 'Article', 'filtered_by_group_id' => @group_2.id.to_s }), valid_session
242
+ expect(@notification_1.reload.opened?).to be_falsey
243
+ expect(@notification_2.reload.opened?).to be_truthy
244
+ end
245
+ end
246
+
247
+ context 'with filtered_by_key request parameters' do
248
+ it "opens filtered notifications only" do
249
+ post_with_compatibility :open_all, target_params.merge({ typed_target_param => test_target, 'filtered_by_key' => 'key.2' }), valid_session
250
+ expect(@notification_1.reload.opened?).to be_falsey
251
+ expect(@notification_2.reload.opened?).to be_truthy
252
+ end
253
+ end
254
+
255
+ context 'with later_than parameter' do
256
+ it "opens filtered notifications only" do
257
+ post_with_compatibility :open_all, target_params.merge({ typed_target_param => test_target, later_than: (@notification_1.created_at.in_time_zone + 0.001).iso8601(3) }), valid_session
258
+ expect(@notification_1.reload.opened?).to be_falsey
259
+ expect(@notification_2.reload.opened?).to be_truthy
260
+ end
261
+ end
262
+
263
+ context 'with earlier_than parameter' do
264
+ it "opens filtered notifications only" do
265
+ post_with_compatibility :open_all, target_params.merge({ typed_target_param => test_target, earlier_than: @notification_2.created_at.iso8601(3) }), valid_session
266
+ expect(@notification_1.reload.opened?).to be_truthy
267
+ expect(@notification_2.reload.opened?).to be_falsey
268
+ end
269
+ end
270
+
271
+ context "with no filter request parameters" do
272
+ it "opens all notifications of the target" do
273
+ post_with_compatibility :open_all, target_params.merge({ typed_target_param => test_target}), valid_session
274
+ expect(@notification_1.reload.opened?).to be_truthy
275
+ expect(@notification_2.reload.opened?).to be_truthy
276
+ end
277
+ end
278
+ end
279
+ end
280
+
281
+ describe "GET #show" do
282
+ context "with id, target_type and (typed_target)_id parameters" do
283
+ before do
284
+ @notification = create(:notification, target: test_target)
285
+ get_with_compatibility :show, target_params.merge({ id: @notification, typed_target_param => test_target }), valid_session
286
+ end
287
+
288
+ it "returns 200 as http status code" do
289
+ expect(response.status).to eq(200)
290
+ end
291
+
292
+ it "returns the requested notification as JSON" do
293
+ assert_json_with_object(response_json, @notification)
294
+ end
295
+ end
296
+
297
+ context "with wrong id and (typed_target)_id parameters" do
298
+ before do
299
+ @notification = create(:notification, target: create(:user))
300
+ get_with_compatibility :show, target_params.merge({ id: @notification, typed_target_param => test_target }), valid_session
301
+ end
302
+
303
+ it "returns 403 as http status code" do
304
+ expect(response.status).to eq(403)
305
+ end
306
+
307
+ it "returns error JSON response" do
308
+ assert_error_response(403)
309
+ end
310
+ end
311
+
312
+ context "when associated notifiable record was not found" do
313
+ before do
314
+ @notification = create(:notification, target: test_target)
315
+ @notification.notifiable.delete
316
+ get_with_compatibility :show, target_params.merge({ id: @notification, typed_target_param => test_target }), valid_session
317
+ end
318
+
319
+ it "returns 500 as http status code" do
320
+ expect(response.status).to eq(500)
321
+ end
322
+
323
+ it "returns error JSON response" do
324
+ assert_error_response(500)
325
+ end
326
+ end
327
+ end
328
+
329
+ describe "DELETE #destroy" do
330
+ context "http DELETE request" do
331
+ before do
332
+ @notification = create(:notification, target: test_target)
333
+ delete_with_compatibility :destroy, target_params.merge({ id: @notification, typed_target_param => test_target }), valid_session
334
+ end
335
+
336
+ it "returns 204 as http status code" do
337
+ expect(response.status).to eq(204)
338
+ end
339
+
340
+ it "deletes the notification" do
341
+ expect(test_target.notifications.where(id: @notification.id).exists?).to be_falsey
342
+ end
343
+ end
344
+ end
345
+
346
+ describe "PUT #open" do
347
+ context "without move parameter" do
348
+ context "http PUT request" do
349
+ before do
350
+ @notification = create(:notification, target: test_target)
351
+ expect(@notification.opened?).to be_falsey
352
+ put_with_compatibility :open, target_params.merge({ id: @notification, typed_target_param => test_target }), valid_session
353
+ end
354
+
355
+ it "returns 200 as http status code" do
356
+ expect(response.status).to eq(200)
357
+ end
358
+
359
+ it "opens the notification" do
360
+ expect(@notification.reload.opened?).to be_truthy
361
+ end
362
+
363
+ it "returns JSON response" do
364
+ expect(response_json["count"]).to eq(1)
365
+ assert_json_with_object(response_json["notification"], @notification)
366
+ end
367
+ end
368
+ end
369
+
370
+ context "with true as move parameter" do
371
+ context "http PUT request" do
372
+ before do
373
+ @notification = create(:notification, target: test_target)
374
+ expect(@notification.opened?).to be_falsey
375
+ put_with_compatibility :open, target_params.merge({ id: @notification, typed_target_param => test_target, move: true }), valid_session
376
+ end
377
+
378
+ it "returns 302 as http status code" do
379
+ expect(response.status).to eq(302)
380
+ end
381
+
382
+ it "opens the notification" do
383
+ expect(@notification.reload.opened?).to be_truthy
384
+ end
385
+
386
+ it "redirects to notifiable_path" do
387
+ expect(response).to redirect_to @notification.notifiable_path
388
+ end
389
+ end
390
+ end
391
+ end
392
+
393
+ describe "GET #move" do
394
+ context "without open parameter" do
395
+ context "http GET request" do
396
+ before do
397
+ @notification = create(:notification, target: test_target)
398
+ get_with_compatibility :move, target_params.merge({ id: @notification, typed_target_param => test_target }), valid_session
399
+ end
400
+
401
+ it "returns 302 as http status code" do
402
+ expect(response.status).to eq(302)
403
+ end
404
+
405
+ it "redirects to notifiable_path" do
406
+ expect(response).to redirect_to @notification.notifiable_path
407
+ end
408
+ end
409
+ end
410
+
411
+ context "with true as open parameter" do
412
+ context "http GET request" do
413
+ before do
414
+ @notification = create(:notification, target: test_target)
415
+ expect(@notification.opened?).to be_falsey
416
+ get_with_compatibility :move, target_params.merge({ id: @notification, typed_target_param => test_target, open: true }), valid_session
417
+ end
418
+
419
+ it "returns 302 as http status code" do
420
+ expect(response.status).to eq(302)
421
+ end
422
+
423
+ it "opens the notification" do
424
+ expect(@notification.reload.opened?).to be_truthy
425
+ end
426
+
427
+ it "redirects to notifiable_path" do
428
+ expect(response).to redirect_to @notification.notifiable_path
429
+ end
430
+ end
431
+ end
432
+ end
433
+ end
434
+
435
+ shared_examples_for :notifications_api_request do
436
+ include ActivityNotification::ControllerSpec::CommitteeUtility
437
+
438
+ before do
439
+ group = create(:article)
440
+ notifier = create(:user)
441
+ create(:notification, target: test_target)
442
+ group_owner = create(:notification, target: test_target, group: group, notifier: notifier, parameters: { "test_default_param": "1" })
443
+ @notification = create(:notification, target: test_target, group: group, group_owner: group_owner, notifier: notifier, parameters: { "test_default_param": "1" })
444
+ group_owner.open!
445
+ end
446
+
447
+ describe "GET /apidocs" do
448
+ it "returns API references as OpenAPI Specification JSON schema" do
449
+ get "#{root_path}/apidocs"
450
+ write_schema_file(response.body)
451
+ expect(read_schema_file["openapi"]).to eq("3.0.0")
452
+ end
453
+ end
454
+
455
+ describe "GET /{target_type}/{target_id}/notifications", type: :request do
456
+ it "returns response as API references" do
457
+ get_with_compatibility "#{api_path}/notifications", headers: @headers
458
+ assert_all_schema_confirm(response, 200)
459
+ end
460
+ end
461
+
462
+ describe "POST /{target_type}/{target_id}/notifications/open_all", type: :request do
463
+ it "returns response as API references" do
464
+ post_with_compatibility "#{api_path}/notifications/open_all", headers: @headers
465
+ assert_all_schema_confirm(response, 200)
466
+ end
467
+ end
468
+
469
+ describe "GET /{target_type}/{target_id}/notifications/{id}", type: :request do
470
+ it "returns response as API references" do
471
+ get_with_compatibility "#{api_path}/notifications/#{@notification.id}", headers: @headers
472
+ assert_all_schema_confirm(response, 200)
473
+ end
474
+
475
+ it "returns error response as API references" do
476
+ get_with_compatibility "#{api_path}/notifications/0", headers: @headers
477
+ assert_all_schema_confirm(response, 404)
478
+ end
479
+ end
480
+
481
+ describe "DELETE /{target_type}/{target_id}/notifications/{id}", type: :request do
482
+ it "returns response as API references" do
483
+ delete_with_compatibility "#{api_path}/notifications/#{@notification.id}", headers: @headers
484
+ assert_all_schema_confirm(response, 204)
485
+ end
486
+ end
487
+
488
+ describe "PUT /{target_type}/{target_id}/notifications/{id}/open", type: :request do
489
+ it "returns response as API references" do
490
+ put_with_compatibility "#{api_path}/notifications/#{@notification.id}/open", headers: @headers
491
+ assert_all_schema_confirm(response, 200)
492
+ end
493
+
494
+ it "returns response as API references when request parameters have move=true" do
495
+ put_with_compatibility "#{api_path}/notifications/#{@notification.id}/open?move=true", headers: @headers
496
+ assert_all_schema_confirm(response, 302)
497
+ end
498
+ end
499
+
500
+ describe "GET /{target_type}/{target_id}/notifications/{id}/move", type: :request do
501
+ it "returns response as API references" do
502
+ get_with_compatibility "#{api_path}/notifications/#{@notification.id}/move", headers: @headers
503
+ assert_all_schema_confirm(response, 302)
504
+ end
505
+ end
506
+ end