Files
akkounts/spec/models/remote_storage_authorization_spec.rb
T
raucao 2419982ef5
CI / Test (pull_request) Successful in 42s
Release Drafter / Update release notes draft (pull_request) Successful in 2s
Stabilize flaky remoteStorage Redis spec
"stores a token in redis" asserted an exact count of Redis keys for the
user, but database cleanup only covers SQL and Redis keys accumulate
across examples and spec files. It also relied on a previously-run
example having created the authorization, so it failed depending on test
order and leftover state.

Clear the authorizations:* namespace before each example in the #create
group, create the authorization under test, and assert on its specific
token key instead of a global count.
2026-10-06 15:33:04 +02:00

315 lines
10 KiB
Ruby

require 'rails_helper'
RSpec.describe RemoteStorageAuthorization, type: :model do
include ActiveJob::TestHelper
let(:user) { create :user }
before do
allow_any_instance_of(AppCatalog::WebApp).to receive(:update_metadata).and_return(true)
allow_any_instance_of(RemoteStorageAuthorization).to receive(:remove_token_expiry_job).and_return(nil)
end
describe "#create" do
before(:each) { redis_rs_delete_keys("authorizations:*") }
after(:each) { clear_enqueued_jobs }
after(:all) { redis_rs_delete_keys("authorizations:*") }
let(:auth) do
user.remote_storage_authorizations.create!(
permissions: %w(documents photos contacts:rw videos:r tasks/work:r),
client_id: "example.com",
redirect_uri: "https://example.com"
)
end
it "generates a token" do
expect(auth.token).to match(/[a-zA-Z0-9]+/)
end
it "stores a token in redis" do
auth
authorizations = redis_rs.smembers("authorizations:#{user.cn}:#{auth.token}")
expect(authorizations.sort).to eq(%w(documents photos contacts:rw videos:r tasks/work:r).sort)
end
context "with expiry set" do
it "enqueues an expiration job" do
auth_with_expiry = user.remote_storage_authorizations.create!(
permissions: %w(documents:rw), client_id: "example.com",
redirect_uri: "https://example.com",
expire_at: 1.month.from_now
)
job = enqueued_jobs.select{|j| j['job_class'] == "RemoteStorageExpireAuthorizationJob"}.first
expect(job['arguments'][0]).to eq(auth_with_expiry.id)
end
end
end
describe "#destroy" do
after(:each) { clear_enqueued_jobs }
after(:all) { redis_rs_delete_keys("authorizations:*") }
it "removes the token from redis" do
auth = user.remote_storage_authorizations.create!(
permissions: %w(shares:rw documents pictures:r),
client_id: "sharesome.5apps.com",
redirect_uri: "https://sharesome.5apps.com"
)
auth.destroy!
expect(redis_rs.keys("authorizations:#{user.address}:*")).to be_empty
end
context "with expiry set" do
it "removes the expiration job" do
auth_with_expiry = user.remote_storage_authorizations.create!(
permissions: %w(documents:rw), client_id: "example.com",
redirect_uri: "https://example.com",
expire_at: 1.month.from_now
)
# Cannot test for removal from the actual Sidekiq::Queue, because it is
# not used in specs, but the method directly removes jobs from there
expect(auth_with_expiry).to receive(:remove_token_expiry_job)
auth_with_expiry.destroy!
end
end
end
describe "#find_or_create_web_app" do
context "with origin that looks hosted" do
after(:all) { redis_rs_delete_keys("authorizations:*") }
let(:auth) do
user.remote_storage_authorizations.create!(
permissions: %w(documents:rw),
client_id: "example.com",
redirect_uri: "https://example.com"
)
end
it "generates a web_app" do
expect(auth.web_app).to be_a(AppCatalog::WebApp)
end
end
context "when creating two authorizations for the same app" do
let(:user_2) { create :user, id: 23, cn: "michiel", email: "michiel@example.com" }
let(:auth_1) do
user.remote_storage_authorizations.create!(
permissions: %w(documents photos contacts:rw videos:r tasks/work:r),
client_id: "example.com",
redirect_uri: "https://example.com"
)
end
let(:auth_2) do
user_2.remote_storage_authorizations.create!(
permissions: %w(documents photos contacts:rw videos:r tasks/work:r),
client_id: "example.com",
redirect_uri: "https://example.com"
)
end
after do
auth_1.destroy
auth_2.destroy
user_2.destroy
end
it "uses the same web app for both authorizations" do
expect(auth_1.web_app).to eq(auth_2.web_app)
end
end
describe "non-production app origins" do
context "when host is not an FQDN" do
let(:auth) do
user.remote_storage_authorizations.create!(
permissions: %w(recipes),
client_id: "localhost:4200",
redirect_uri: "http://localhost:4200"
)
end
it "does not create a web app" do
expect(auth.web_app).to be_nil
expect(auth.app_name).to eq("localhost:4200")
end
end
context "when host is an IP address" do
let(:auth) do
user.remote_storage_authorizations.create!(
permissions: %w(recipes),
client_id: "192.168.0.23:3000",
redirect_uri: "http://192.168.0.23:3000"
)
end
it "does not create a web app" do
expect(auth.web_app).to be_nil
expect(auth.app_name).to eq("192.168.0.23:3000")
end
end
context "when host is an extension URL" do
let(:auth) do
user.remote_storage_authorizations.create!(
permissions: %w(bookmarks),
client_id: "123.addons.allizom.org",
redirect_uri: "123.addons.allizom.org/foo"
)
end
it "does not create a web app" do
expect(auth.web_app).to be_nil
expect(auth.app_name).to eq("123.addons.allizom.org")
end
end
end
end
describe "#launch_url" do
after(:all) { redis_rs_delete_keys("authorizations:*") }
context "without start URL" do
before do
AppCatalog::WebApp.create!(
url: "https://webmarks.5apps.com", name: "Webmarks",
metadata: { name: "Webmarks", start_url: nil, scope: nil }
)
end
let(:auth) do
user.remote_storage_authorizations.create!(
permissions: %w(bookmarks:rw), client_id: "webmarks.5apps.com",
redirect_uri: "https://webmarks.5apps.com/connect"
)
end
it "uses the base URL (from client ID)" do
expect(auth.launch_url).to eq("https://webmarks.5apps.com")
end
end
context "with start URL" do
before do
AppCatalog::WebApp.create!(
url: "https://hyperdraft.rosano.ca", name: "Hyperdraft",
metadata: {
name: "Hyperdraft", scope: nil,
start_url: "https://hyperdraft.rosano.ca/start"
}
)
end
let(:auth) do
user.remote_storage_authorizations.create!(
permissions: %w(notes:rw), client_id: "hyperdraft.rosano.ca",
redirect_uri: "https://hyperdraft.rosano.ca/write/foo"
)
end
describe "full URL" do
it "respects the start URL" do
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca/start")
end
it "does not respect URLs outside of the client ID scope" do
auth.web_app.metadata[:start_url] = "https://uberdraft.rosano.ca/write"
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca")
end
end
describe "relative paths" do
it "includes the path relative from the base URL" do
auth.web_app.metadata[:start_url] = "start.html"
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca/start.html")
auth.web_app.metadata[:start_url] = "./start.html"
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca/start.html")
auth.web_app.metadata[:start_url] = "../start.html"
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca/start.html")
end
end
describe "absolute path" do
it "includes the path relative from the base URL" do
auth.web_app.metadata[:start_url] = "/write"
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca/write")
end
end
end
end
describe "notifications" do
include ActiveJob::TestHelper
after(:each) { clear_enqueued_jobs }
after(:all) { redis_rs_delete_keys("authorizations:*") }
before { allow(user).to receive(:display_name).and_return("Jimmy") }
context "with notifications disabled" do
before do
user.preferences.merge!({ remotestorage_notify_auth_created: "off" })
user.save!
user.remote_storage_authorizations.create!(
:permissions => %w(photos), :client_id => "app.example.com",
:redirect_uri => "https://app.example.com"
)
end
it "does not notify the user via email about new RS app" do
expect(enqueued_jobs.size).to eq(0)
end
end
context "with email notifications enabled" do
before do
user.preferences.merge!({ remotestorage_notify_auth_created: "email" })
user.save!
user.remote_storage_authorizations.create!(
:permissions => %w(photos), :client_id => "app.example.com",
:redirect_uri => "https://app.example.com"
)
end
it "notifies the user via email" do
expect(enqueued_jobs.size).to eq(1)
job = enqueued_jobs.select{|j| j['job_class'] == "ActionMailer::MailDeliveryJob"}.first
expect(job['arguments'][0]).to eq('NotificationMailer')
expect(job['arguments'][1]).to eq('remotestorage_auth_created')
expect(job['arguments'][3]['params']['user']['_aj_globalid']).to eq('gid://akkounts/User/1')
expect(job['arguments'][3]['params']['auth']['_aj_globalid']).to eq('gid://akkounts/RemoteStorageAuthorization/1')
end
end
context "with XMPP notifications enabled" do
before do
Setting.xmpp_notifications_from_address = "botka@kosmos.org"
user.preferences.merge!({ remotestorage_notify_auth_created: "xmpp" })
user.save!
user.remote_storage_authorizations.create!(
:permissions => %w(photos), :client_id => "app.example.com",
:redirect_uri => "https://app.example.com"
)
end
it "sends an XMPP message to the account owner's JID" do
expect(enqueued_jobs.size).to eq(1)
expect(enqueued_jobs.first["job_class"]).to eq("XmppSendMessageJob")
msg = enqueued_jobs.first["arguments"].first
expect(msg["type"]).to eq("normal")
expect(msg["from"]).to eq("botka@kosmos.org")
expect(msg["to"]).to eq(user.address)
expect(msg["body"]).to match(/granted 'app\.example\.com' access to your Kosmos Storage/)
end
end
end
end