# frozen_string_literal: true

require "spec_helper"

describe Doorkeeper::OAuth::PreAuthorization do
  let(:server) do
    server = Doorkeeper.configuration
    allow(server).to receive(:default_scopes).and_return(Doorkeeper::OAuth::Scopes.from_string("default"))
    allow(server).to receive(:optional_scopes).and_return(Doorkeeper::OAuth::Scopes.from_string("public profile"))
    server
  end

  let(:application) { FactoryBot.create(:application, redirect_uri: "https://app.com/callback") }
  let(:client) { Doorkeeper::OAuth::Client.find(application.uid) }

  let :attributes do
    {
      client_id: client.uid,
      response_type: "code",
      redirect_uri: "https://app.com/callback",
      state: "save-this",
    }
  end

  subject do
    described_class.new(server, attributes)
  end

  it "is authorizable when request is valid" do
    expect(subject).to be_authorizable
  end

  it "accepts code as response type" do
    attributes[:response_type] = "code"
    expect(subject).to be_authorizable
  end

  it "accepts token as response type" do
    allow(server).to receive(:grant_flows).and_return(["implicit"])
    attributes[:response_type] = "token"
    expect(subject).to be_authorizable
  end

  context "when using default grant flows" do
    it 'accepts "code" as response type' do
      attributes[:response_type] = "code"
      expect(subject).to be_authorizable
    end

    it 'accepts "token" as response type' do
      allow(server).to receive(:grant_flows).and_return(["implicit"])
      attributes[:response_type] = "token"
      expect(subject).to be_authorizable
    end
  end

  context "when authorization code grant flow is disabled" do
    before do
      allow(server).to receive(:grant_flows).and_return(["implicit"])
    end

    it 'does not accept "code" as response type' do
      attributes[:response_type] = "code"
      expect(subject).not_to be_authorizable
    end
  end

  context "when implicit grant flow is disabled" do
    before do
      allow(server).to receive(:grant_flows).and_return(["authorization_code"])
    end

    it 'does not accept "token" as response type' do
      attributes[:response_type] = "token"
      expect(subject).not_to be_authorizable
    end
  end

  context "client application does not restrict valid scopes" do
    it "accepts valid scopes" do
      attributes[:scope] = "public"
      expect(subject).to be_authorizable
    end

    it "rejects (globally) non-valid scopes" do
      attributes[:scope] = "invalid"
      expect(subject).not_to be_authorizable
    end

    it "accepts scopes which are permitted for grant_type" do
      allow(server).to receive(:scopes_by_grant_type).and_return(authorization_code: [:public])
      attributes[:scope] = "public"
      expect(subject).to be_authorizable
    end

    it "rejects scopes which are not permitted for grant_type" do
      allow(server).to receive(:scopes_by_grant_type).and_return(authorization_code: [:profile])
      attributes[:scope] = "public"
      expect(subject).not_to be_authorizable
    end
  end

  context "client application restricts valid scopes" do
    let(:application) do
      FactoryBot.create(:application, scopes: Doorkeeper::OAuth::Scopes.from_string("public nonsense"))
    end

    it "accepts valid scopes" do
      attributes[:scope] = "public"
      expect(subject).to be_authorizable
    end

    it "rejects (globally) non-valid scopes" do
      attributes[:scope] = "invalid"
      expect(subject).not_to be_authorizable
    end

    it "rejects (application level) non-valid scopes" do
      attributes[:scope] = "profile"
      expect(subject).to_not be_authorizable
    end

    it "accepts scopes which are permitted for grant_type" do
      allow(server).to receive(:scopes_by_grant_type).and_return(authorization_code: [:public])
      attributes[:scope] = "public"
      expect(subject).to be_authorizable
    end

    it "rejects scopes which are not permitted for grant_type" do
      allow(server).to receive(:scopes_by_grant_type).and_return(authorization_code: [:profile])
      attributes[:scope] = "public"
      expect(subject).not_to be_authorizable
    end
  end

  context "when scope is not provided to pre_authorization" do
    before { attributes[:scope] = nil }

    context "when default scopes is provided" do
      it "uses default scopes" do
        allow(server).to receive(:default_scopes).and_return(Doorkeeper::OAuth::Scopes.from_string("default_scope"))
        expect(subject).to be_authorizable
        expect(subject.scope).to eq("default_scope")
        expect(subject.scopes).to eq(Doorkeeper::OAuth::Scopes.from_string("default_scope"))
      end
    end

    context "when default scopes is none" do
      it "not be authorizable when none default scope" do
        allow(server).to receive(:default_scopes).and_return(Doorkeeper::OAuth::Scopes.new)
        expect(subject).not_to be_authorizable
      end
    end
  end

  it "matches the redirect uri against client's one" do
    attributes[:redirect_uri] = "http://nothesame.com"
    expect(subject).not_to be_authorizable
  end

  it "stores the state" do
    expect(subject.state).to eq("save-this")
  end

  it "rejects if response type is not allowed" do
    attributes[:response_type] = "whops"
    expect(subject).not_to be_authorizable
  end

  it "requires an existing client" do
    attributes[:client_id] = nil
    expect(subject).not_to be_authorizable
  end

  it "requires a redirect uri" do
    attributes[:redirect_uri] = nil
    expect(subject).not_to be_authorizable
  end

  describe "as_json" do
    before { subject.authorizable? }

    it { is_expected.to respond_to :as_json }

    shared_examples "returns the pre authorization" do
      it "returns the pre authorization" do
        expect(json[:client_id]).to eq client.uid
        expect(json[:redirect_uri]).to eq subject.redirect_uri
        expect(json[:state]).to eq subject.state
        expect(json[:response_type]).to eq subject.response_type
        expect(json[:scope]).to eq subject.scope
        expect(json[:client_name]).to eq client.name
        expect(json[:status]).to eq I18n.t("doorkeeper.pre_authorization.status")
      end
    end

    context "when attributes param is not passed" do
      let(:json) { subject.as_json }

      include_examples "returns the pre authorization"
    end

    context "when attributes param is passed" do
      context "when attributes is a hash" do
        let(:custom_attributes) { { custom_id: "1234", custom_name: "a pretty good name" } }
        let(:json) { subject.as_json(custom_attributes) }

        include_examples "returns the pre authorization"

        it "merges the attributes in params" do
          expect(json[:custom_id]).to eq custom_attributes[:custom_id]
          expect(json[:custom_name]).to eq custom_attributes[:custom_name]
        end
      end

      context "when attributes is not a hash" do
        let(:json) { subject.as_json(nil) }

        include_examples "returns the pre authorization"
      end
    end
  end
end