mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-01 22:14:39 +08:00
## Summary - Unifies the two parallel push notification delivery paths (web push via VAPID and hub/native app relay) into a single `DeliverPushNotification` job - Extracts hub push delivery into `HubPushNotificationPusher` service, symmetric with the existing `PushNotificationPusher` for web push - Adds `UserApiKey.push_clients_for(user)` to encapsulate hub client lookup previously inlined in PostAlerter - Simplifies `PostAlerter.push_notification()` from two parallel enqueue blocks to a single call - Converts old jobs (`SendPushNotification`, `PushNotification`) to thin delegators for Sidekiq backward compatibility with in-flight jobs ## Test plan - [ ] `bin/rspec spec/jobs/deliver_push_notification_spec.rb` — new unified job tests - [ ] `bin/rspec spec/services/hub_push_notification_pusher_spec.rb` — new hub pusher tests - [ ] `bin/rspec spec/jobs/send_push_notification_spec.rb spec/jobs/push_notification_spec.rb` — backward compat delegation - [ ] `bin/rspec spec/services/post_alerter_spec.rb` — PostAlerter integration - [ ] `bin/rspec spec/services/push_notification_pusher_spec.rb` — web push unchanged - [ ] `bin/rspec plugins/chat/spec/jobs/regular/chat/notify_watching_spec.rb plugins/chat/spec/jobs/regular/chat/notify_mentioned_spec.rb` — chat plugin callers
190 lines
6.3 KiB
Ruby
190 lines
6.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe Jobs::DeliverPushNotification do
|
|
fab!(:user)
|
|
fab!(:post)
|
|
|
|
let(:payload) do
|
|
{
|
|
"notification_type" => 1,
|
|
"post_url" => "/t/#{post.topic_id}/#{post.post_number}",
|
|
"excerpt" => "Hello you",
|
|
}
|
|
end
|
|
|
|
before do
|
|
freeze_time
|
|
SiteSetting.push_notification_time_window_mins = 5
|
|
end
|
|
|
|
describe "time window gate" do
|
|
before { Fabricate(:push_subscription, user: user) }
|
|
|
|
it "does not deliver when user is missing" do
|
|
PushNotificationPusher.expects(:push).never
|
|
HubPushNotificationPusher.expects(:push).never
|
|
Jobs::DeliverPushNotification.new.execute(user_id: -999, payload: payload)
|
|
end
|
|
|
|
it "does not deliver when user was recently seen" do
|
|
user.update!(last_seen_at: 1.minute.ago)
|
|
PushNotificationPusher.expects(:push).never
|
|
HubPushNotificationPusher.expects(:push).never
|
|
Jobs::DeliverPushNotification.new.execute(user_id: user.id, payload: payload)
|
|
end
|
|
|
|
it "delivers when user is offline" do
|
|
user.update!(last_seen_at: 10.minutes.ago)
|
|
PushNotificationPusher.expects(:push).with(user, payload)
|
|
HubPushNotificationPusher.expects(:push).with(user, payload)
|
|
Jobs::DeliverPushNotification.new.execute(user_id: user.id, payload: payload)
|
|
end
|
|
|
|
it "delivers when bypass_time_window is true despite user being active" do
|
|
user.update!(last_seen_at: 1.minute.ago)
|
|
PushNotificationPusher.expects(:push).with(user, payload)
|
|
HubPushNotificationPusher.expects(:push).with(user, payload)
|
|
|
|
Jobs::DeliverPushNotification.new.execute(
|
|
user_id: user.id,
|
|
bypass_time_window: true,
|
|
payload: payload,
|
|
)
|
|
end
|
|
end
|
|
|
|
describe "web push delivery" do
|
|
before { user.update!(last_seen_at: 10.minutes.ago) }
|
|
|
|
it "calls PushNotificationPusher when user has push subscriptions" do
|
|
Fabricate(:push_subscription, user: user)
|
|
PushNotificationPusher.expects(:push).with(user, payload).once
|
|
Jobs::DeliverPushNotification.new.execute(user_id: user.id, payload: payload)
|
|
end
|
|
|
|
it "does not call PushNotificationPusher when user has no push subscriptions" do
|
|
PushNotificationPusher.expects(:push).never
|
|
Jobs::DeliverPushNotification.new.execute(user_id: user.id, payload: payload)
|
|
end
|
|
end
|
|
|
|
describe "hub push delivery" do
|
|
before { user.update!(last_seen_at: 10.minutes.ago) }
|
|
|
|
it "calls HubPushNotificationPusher" do
|
|
HubPushNotificationPusher.expects(:push).with(user, payload).once
|
|
Jobs::DeliverPushNotification.new.execute(user_id: user.id, payload: payload)
|
|
end
|
|
end
|
|
|
|
describe "both mechanisms" do
|
|
it "delivers via both pushers for the same user" do
|
|
user.update!(last_seen_at: 10.minutes.ago)
|
|
Fabricate(:push_subscription, user: user)
|
|
|
|
PushNotificationPusher.expects(:push).with(user, payload).once
|
|
HubPushNotificationPusher.expects(:push).with(user, payload).once
|
|
|
|
Jobs::DeliverPushNotification.new.execute(user_id: user.id, payload: payload)
|
|
end
|
|
end
|
|
|
|
describe "content localization" do
|
|
fab!(:topic) { Fabricate(:topic, title: "Original topic title") }
|
|
fab!(:localized_post) { Fabricate(:post, topic: topic, raw: "Original post content") }
|
|
|
|
let(:localizable_payload) do
|
|
{
|
|
"notification_type" => 1,
|
|
"post_url" => "/t/#{topic.id}/#{localized_post.post_number}",
|
|
"topic_title" => topic.title,
|
|
"topic_id" => topic.id,
|
|
"post_id" => localized_post.id,
|
|
"excerpt" => "Original post content",
|
|
"username" => "system",
|
|
"post_number" => localized_post.post_number,
|
|
}
|
|
end
|
|
|
|
before do
|
|
SiteSetting.content_localization_enabled = true
|
|
SiteSetting.allow_user_locale = true
|
|
user.update!(last_seen_at: 10.minutes.ago, locale: "ja")
|
|
end
|
|
|
|
it "localizes payload before delivering to web push" do
|
|
Fabricate(:push_subscription, user: user)
|
|
Fabricate(
|
|
:topic_localization,
|
|
topic: topic,
|
|
locale: "ja",
|
|
title: "ローカライズされたトピック",
|
|
fancy_title: "ローカライズされたトピック",
|
|
)
|
|
Fabricate(
|
|
:post_localization,
|
|
post: localized_post,
|
|
locale: "ja",
|
|
raw: "ローカライズされた投稿",
|
|
cooked: "<p>ローカライズされた投稿</p>",
|
|
)
|
|
|
|
PushNotificationPusher
|
|
.expects(:push)
|
|
.with { |u, p| p[:topic_title] == "ローカライズされたトピック" && p[:excerpt] == "ローカライズされた投稿" }
|
|
.once
|
|
|
|
Jobs::DeliverPushNotification.new.execute(user_id: user.id, payload: localizable_payload)
|
|
end
|
|
|
|
it "localizes payload before delivering to hub push" do
|
|
SiteSetting.allowed_user_api_push_urls = "https://hub.example.com/push"
|
|
client = Fabricate(:user_api_key_client)
|
|
Fabricate(
|
|
:user_api_key,
|
|
user: user,
|
|
scopes: ["notifications"].map { |name| UserApiKeyScope.new(name: name) },
|
|
push_url: "https://hub.example.com/push",
|
|
user_api_key_client_id: client.id,
|
|
)
|
|
Fabricate(
|
|
:topic_localization,
|
|
topic: topic,
|
|
locale: "ja",
|
|
title: "ローカライズされたトピック",
|
|
fancy_title: "ローカライズされたトピック",
|
|
)
|
|
|
|
body = nil
|
|
stub_request(:post, "https://hub.example.com/push").to_return do |request|
|
|
body = JSON.parse(request.body)
|
|
{ status: 200 }
|
|
end
|
|
|
|
Jobs::DeliverPushNotification.new.execute(user_id: user.id, payload: localizable_payload)
|
|
|
|
expect(body["notifications"].first["topic_title"]).to eq("ローカライズされたトピック")
|
|
end
|
|
|
|
it "does not localize when content_localization_enabled is false" do
|
|
SiteSetting.content_localization_enabled = false
|
|
Fabricate(:push_subscription, user: user)
|
|
Fabricate(
|
|
:topic_localization,
|
|
topic: topic,
|
|
locale: "ja",
|
|
title: "ローカライズされたトピック",
|
|
fancy_title: "ローカライズされたトピック",
|
|
)
|
|
|
|
PushNotificationPusher
|
|
.expects(:push)
|
|
.with do |u, p|
|
|
p[:topic_title] == "Original topic title" || p["topic_title"] == "Original topic title"
|
|
end
|
|
.once
|
|
|
|
Jobs::DeliverPushNotification.new.execute(user_id: user.id, payload: localizable_payload)
|
|
end
|
|
end
|
|
end
|