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)
  end

  describe "#create" do
    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
      user_auth_keys = redis_rs.keys("authorizations:#{user.cn}:*")
      expect(user_auth_keys.length).to eq(1)

      authorizations = redis_rs.smembers(user_auth_keys.first)
      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
