require 'rails_helper'

RSpec.describe Rs::OauthController, type: :controller do
  let(:user) { create :user }

  before do
    allow_any_instance_of(AppCatalog::WebApp).to receive(:update_metadata).and_return(true)
  end

  describe "GET /rs/oauth/:username" do
    context "when user is signed in" do
      before do
        sign_in user
      end

      context "when username is different than current user" do
        let(:other_user) { create :user, id: 23, cn: "jomokenyatta", email: "jomo@hotmail.com" }

        before do
          get :new, params: {
            username: other_user.cn,
            redirect_uri: "https://example.com",
            client_id: "example.com",
            scope: "examples"
          }
        end

        it "logs out the users and repeats the request" do
          url = new_rs_oauth_url other_user.cn,
            redirect_uri: "https://example.com",
            client_id: "example.com",
            scope: "examples"

          expect(response).to redirect_to(url)
        end
      end

      context "when no valid token exists" do
        before do
          get :new, params: {
            username: user.cn,
            redirect_uri: "https://example.com",
            client_id: "example.com",
            scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
            state: "foobar123"
          }
        end

        it "returns a 200" do
          expect(response.response_code).to eq(200)
        end

        it "sets the instance variables" do
          expected_scopes = %w(documents photos contacts:rw videos:r tasks/work:r)

          expect(assigns["user"]).to eq(user)
          expect(assigns["redirect_uri"]).to eq("https://example.com")
          expect(assigns["scopes"]).to eq(expected_scopes)
          expect(assigns["client_id"]).to eq("example.com")
          expect(assigns["root_access_requested"]).to eq(false)
          expect(assigns["state"]).to eq("foobar123")
          expect(assigns["denial_url"]).to eq("https://example.com#error=access_denied&state=foobar123")
        end

        context "no redirect_uri" do
          before do
            get :new, params: {
              username: user.cn,
              scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
              client_id: "https://example.com"
            }
          end

          it "returns a 400" do
            expect(response.response_code).to eq(400)
          end
        end

        context "no client_id" do
          before do
            get :new, params: {
              username: user.cn,
              scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
              redirect_uri: "https://example.com"
            }
          end

          it "redirects with invalid_request error" do
            expect(response).to redirect_to("https://example.com#error=invalid_request")
          end
        end

        context "different host for client_id and redirect_uri" do
          before do
            get :new, params: {
              username: user.cn,
              scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
              redirect_uri: "https://example.com/foobar",
              client_id: "https://google.com"
            }
          end

          it "redirects with invalid_client error" do
            expect(response).to redirect_to("https://example.com/foobar#error=invalid_client")
          end
        end
      end

      context "when valid token already exists" do
        before do
          @auth = 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",
            expire_at: 1.day.from_now
          )
        end

        after { @auth.destroy }

        context "with same host for client_id and redirect_uri" do
          before do
            get :new, params: {
              username: user.cn,
              scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
              redirect_uri: "https://example.com",
              client_id: "https://example.com"
            }
          end

          it "redirects to the redirect_uri with the existing token" do
            expect(response).to redirect_to("https://example.com#access_token=#{@auth.token}")
          end
        end

        context "with different host for client_id and redirect_uri" do
          before do
            get :new, params: {
              username: user.cn,
              scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
              redirect_uri: "https://app.example.com",
              client_id: "https://example.com"
            }
          end

          it "redirects with invalid_client error" do
            expect(response).to redirect_to("https://app.example.com#error=invalid_client")
          end
        end

        context "with different redirect_uri" do
          before do
            get :new, params: {
              username: user.cn,
              scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
              redirect_uri: "https://example.com/a_new_route",
              client_id: "https://example.com"
            }
          end

          it "redirects to the new redirect_uri" do
            expect(response).to redirect_to("https://example.com/a_new_route#access_token=#{@auth.token}")
          end
        end

        context "with state param given" do
          before do
            get :new, params: {
              username: user.cn,
              scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
              redirect_uri: "https://example.com",
              client_id: "https://example.com",
              state: "foobar123"
            }
          end

          it "redirects to the redirect_uri with token and state" do
            expect(response).to redirect_to("https://example.com#access_token=#{@auth.token}&state=foobar123")
          end
        end
      end

      context "no scope" do
        before do
          get :new, params: {
            username: user.cn,
            redirect_uri: "https://example.com",
            client_id: "https://example.com",
            state: "foobar123"
          }
        end

        it "redirects to the redirect_uri with an error code" do
          expect(response).to redirect_to("https://example.com#error=invalid_scope&state=foobar123")
        end
      end

      context "empty scope" do
        before do
          get :new, params: {
            username: user.cn,
            scope: "",
            redirect_uri: "https://example.com",
            client_id: "https://example.com",
            state: "foobar123"
          }
        end

        it "redirects to the redirect_uri with an error code" do
          expect(response).to redirect_to("https://example.com#error=invalid_scope&state=foobar123")
        end
      end
    end

    context "when user is not signed in" do
      it "redirects to the signin page with username pre-filled" do
        get :new, params: {
          username: user.cn,
          scope: "documents,photos",
          redirect_uri: "https://example.com"
        }

        expect(response).to redirect_to(new_user_session_path(cn: user.cn, ou: user.ou))
      end
    end

    describe "root access" do
      before do
        sign_in user
      end

      describe "full" do
        before do
          get :new, params: {
            username: user.cn,
            scope: "*:rw",
            redirect_uri: "https://example.com",
            client_id: "example.com"
          }
        end

        it "sets the instance variables" do
          expect(assigns["scopes"]).to eq([":rw"])
          expect(assigns["root_access_requested"]).to be(true)
        end
      end

      describe "read-only" do
        before do
          get :new, params: {
            username: user.cn,
            scope: "*:r",
            redirect_uri: "https://example.com",
            client_id: "example.com"
          }
        end

        it "sets the instance variables" do
          expect(assigns["scopes"]).to eq([":r"])
          expect(assigns["root_access_requested"]).to be(true)
        end
      end
    end
  end

  describe "POST /rs/oauth/:username" do
    context "when user is signed in" do
      before do
        sign_in user
      end

      after do
        user.remote_storage_authorizations.destroy_all
      end

      context "when no valid token exists" do
        before do
          post :create, params: {
            user_id: user.id,
            scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
            redirect_uri: "https://example.com",
            client_id: "example.com",
            state: "foobar123",
            expire_at: 1.day.from_now
          }
          @auth = user.reload.remote_storage_authorizations.first
        end

        it "creates a new token" do
          expect(@auth.permissions).to eq(%w(documents photos contacts:rw videos:r tasks/work:r))
        end

        it "redirects to the redirect_uri" do
          expect(response).to redirect_to("https://example.com#access_token=#{@auth.token}&state=foobar123")
        end
      end

      context "when token is expired" do
        before do
          @auth = user.remote_storage_authorizations.create!(
            permissions: %w(documents),
            client_id: "example.com",
            redirect_uri: "https://example.com",
            expire_at: 1.day.ago,
            token: nil
          )
          post :create, params: {
            user_id: user.id,
            scope: "documents",
            redirect_uri: "https://example.com",
            client_id: "example.com",
            state: "foobar123",
            expire_at: 1.month.from_now
          }
        end

        it "updates the token" do
          expect(@auth.reload.token).not_to be_nil
        end
      end

      context "root access with several scopes" do
        before do
          post :create, params: {
            user_id: user.id,
            scope: "*:rw contacts:r",
            redirect_uri: "https://example.com",
            client_id: "example.com",
            expire_at: 1.month.from_now
          }
          @auth = user.reload.remote_storage_authorizations.first
        end

        it "removes all scopes except for the root permission" do
          expect(@auth.permissions).to eq(%w(:rw))
        end
      end

      context "no redirect_uri" do
        before do
          post :create, params: {
            user_id: user.id,
            scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
            client_id: "example.com",
            expire_at: 1.month.from_now
          }
        end

        it "returns a 400" do
          expect(response.response_code).to eq(400)
        end
      end

      context "no client_id" do
        before do
          post :create, params: {
            user_id: user.id,
            scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
            redirect_uri: "https://example.com",
            expire_at: 1.month.from_now
          }
        end

        it "redirects with invalid_request error" do
          expect(response).to redirect_to("https://example.com#error=invalid_request")
        end
      end

      context "hostnames of client_id and redirect_uri do not match" do
        before do
          post :create, params: {
            user_id: user.id,
            scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
            client_id: "fishing.com",
            redirect_uri: "https://example.com",
            expire_at: 1.month.from_now
          }
        end

        it "redirects with invalid_client error" do
          expect(response).to redirect_to("https://example.com#error=invalid_client")
        end
      end

      context "empty scope" do
        before do
          post :create, params: {
            user_id: user.id,
            scope: "",
            redirect_uri: "https://example.com",
            client_id: "example.com",
            state: "foobar123",
            expire_at: 1.month.from_now
          }
        end

        it "redirects to the redirect_uri with an error code" do
          expect(response).to redirect_to("https://example.com#error=invalid_scope&state=foobar123")
        end
      end

      context "when the user_id is different from the signed in user" do
        before do
          post :create, params: {
            user_id: user.id,
            scope: "documents,photos",
            redirect_uri: "https://example.com",
            client_id: "example.com",
            expire_at: 1.month.from_now
          }
        end

        it "returns a 403" do
          post :create, params: {
            user_id: "69",
            scope: "documents,photos",
            redirect_uri: "https://example.com",
            client_id: "example.com",
            expire_at: 1.month.from_now
          }

          expect(response.response_code).to eq(403)
        end
      end
    end

    context "when user is not signed in" do
      it "redirects to the signin page" do
        post :create, params: {
          user_id: user.id,
          scope: "documents,photos",
          redirect_uri: "https://example.com",
          client_id: "example.com",
          expire_at: 1.month.from_now
        }

        expect(response).to redirect_to(new_user_session_path)
      end
    end
  end
end
