require "helper"

describe "OmniAuth::Strategies::OAuth" do
  class MyOAuthProvider < OmniAuth::Strategies::OAuth
    uid { access_token.token }
    info { {"name" => access_token.token} }
  end

  def app
    Rack::Builder.new do
      use OmniAuth::Test::PhonySession
      use OmniAuth::Builder do
        provider MyOAuthProvider, "abc", "def", :client_options => {:site => "https://api.example.org"}, :name => "example.org"
        provider MyOAuthProvider, "abc", "def", :client_options => {:site => "https://api.example.org"}, :authorize_params => {:abc => "def"}, :name => "example.org_with_authorize_params"
        provider MyOAuthProvider, "abc", "def", :client_options => {:site => "https://api.example.org"}, :request_params => {:scope => "http://foobar.example.org"}, :name => "example.org_with_request_params"
      end
      run lambda { |env| [404, {"Content-Type" => "text/plain"}, [env.key?("omniauth.auth").to_s]] }
    end.to_app
  end

  def session
    last_request.env["rack.session"]
  end

  before do
    stub_request(:post, "https://api.example.org/oauth/request_token").
      to_return(:body => "oauth_token=yourtoken&oauth_token_secret=yoursecret&oauth_callback_confirmed=true")
  end

  it "should add a camelization for itself" do
    expect(OmniAuth::Utils.camelize("oauth")).to eq("OAuth")
  end

  describe "/auth/{name}" do
    context "successful" do
      before do
        post "/auth/example.org"
      end

      it "should redirect to authorize_url" do
        expect(last_response).to be_redirect
        expect(last_response.headers["Location"]).to eq("https://api.example.org/oauth/authorize?oauth_token=yourtoken")
      end

      it "should redirect to authorize_url with authorize_params when set" do
        post "/auth/example.org_with_authorize_params"
        expect(last_response).to be_redirect
        expect([
          "https://api.example.org/oauth/authorize?abc=def&oauth_token=yourtoken",
          "https://api.example.org/oauth/authorize?oauth_token=yourtoken&abc=def",
        ]).to be_include(last_response.headers["Location"])
      end

      it "should set appropriate session variables" do
        expect(session["oauth"]).to eq("example.org" => {"callback_confirmed" => true, "request_token" => "yourtoken", "request_secret" => "yoursecret"})
      end

      it "should pass request_params to get_request_token" do
        post "/auth/example.org_with_request_params"
        expect(WebMock).to have_requested(:post, "https://api.example.org/oauth/request_token").
          with { |req| req.body == "scope=http%3A%2F%2Ffoobar.example.org" }
      end
    end

    context "unsuccessful" do
      before do
        stub_request(:post, "https://api.example.org/oauth/request_token").
          to_raise(::Net::HTTPFatalError.new('502 "Bad Gateway"', nil))
        post "/auth/example.org"
      end

      it "should call fail! with :service_unavailable" do
        expect(last_request.env["omniauth.error"]).to be_kind_of(::Net::HTTPFatalError)
        last_request.env["omniauth.error.type"] = :service_unavailable
      end

      context "SSL failure" do
        before do
          stub_request(:post, "https://api.example.org/oauth/request_token").
            to_raise(::OpenSSL::SSL::SSLError.new("SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed"))
          post "/auth/example.org"
        end

        it "should call fail! with :service_unavailable" do
          expect(last_request.env["omniauth.error"]).to be_kind_of(::OpenSSL::SSL::SSLError)
          last_request.env["omniauth.error.type"] = :service_unavailable
        end
      end
    end
  end

  describe "/auth/{name}/callback" do
    before do
      stub_request(:post, "https://api.example.org/oauth/access_token").
        to_return(:body => "oauth_token=yourtoken&oauth_token_secret=yoursecret")
      get "/auth/example.org/callback", {:oauth_verifier => "dudeman"}, "rack.session" => {"oauth" => {"example.org" => {"callback_confirmed" => true, "request_token" => "yourtoken", "request_secret" => "yoursecret"}}}
    end

    it "should exchange the request token for an access token" do
      expect(last_request.env["omniauth.auth"]["provider"]).to eq("example.org")
      expect(last_request.env["omniauth.auth"]["extra"]["access_token"]).to be_kind_of(OAuth::AccessToken)
    end

    it "should call through to the master app" do
      expect(last_response.body).to eq("true")
    end

    context "bad gateway (or any 5xx) for access_token" do
      before do
        stub_request(:post, "https://api.example.org/oauth/access_token")  .
          to_raise(::Net::HTTPFatalError.new('502 "Bad Gateway"', nil))
        get "/auth/example.org/callback", {:oauth_verifier => "dudeman"}, "rack.session" => {"oauth" => {"example.org" => {"callback_confirmed" => true, "request_token" => "yourtoken", "request_secret" => "yoursecret"}}}
      end

      it "should call fail! with :service_unavailable" do
        expect(last_request.env["omniauth.error"]).to be_kind_of(::Net::HTTPFatalError)
        last_request.env["omniauth.error.type"] = :service_unavailable
      end
    end

    context "SSL failure" do
      before do
        stub_request(:post, "https://api.example.org/oauth/access_token")  .
          to_raise(::OpenSSL::SSL::SSLError.new("SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed"))
        get "/auth/example.org/callback", {:oauth_verifier => "dudeman"}, "rack.session" => {"oauth" => {"example.org" => {"callback_confirmed" => true, "request_token" => "yourtoken", "request_secret" => "yoursecret"}}}
      end

      it "should call fail! with :service_unavailable" do
        expect(last_request.env["omniauth.error"]).to be_kind_of(::OpenSSL::SSL::SSLError)
        last_request.env["omniauth.error.type"] = :service_unavailable
      end
    end
  end

  describe "/auth/{name}/callback with Rack 2.x and 3.x" do
    before do
      stub_request(:post, "https://api.example.org/oauth/access_token").
        to_return(:body => "oauth_token=yourtoken&oauth_token_secret=yoursecret")
    end

    context "Rack 2.x style request" do
      before do
        get "/auth/example.org/callback", {"oauth_verifier" => "dudeman"}, "rack.session" => {"oauth" => {"example.org" => {"callback_confirmed" => true, "request_token" => "yourtoken", "request_secret" => "yoursecret"}}}
      end

      it "should exchange the request token for an access token" do
        expect(last_request.env["omniauth.auth"]["provider"]).to eq("example.org")
        expect(last_request.env["omniauth.auth"]["extra"]["access_token"]).to be_kind_of(OAuth::AccessToken)
      end

      it "should call through to the master app" do
        expect(last_response.body).to eq("true")
      end
    end

    context "Rack 3.x style request" do
      before do
        # Simulate Rack 3.x behavior by putting oauth_verifier in the params
        allow_any_instance_of(Rack::Request).to receive(:params).and_return({"oauth_verifier" => "dudeman"})
        get "/auth/example.org/callback", {}, "rack.session" => {"oauth" => {"example.org" => {"callback_confirmed" => true, "request_token" => "yourtoken", "request_secret" => "yoursecret"}}}
      end

      it "should exchange the request token for an access token" do
        expect(last_request.env["omniauth.auth"]["provider"]).to eq("example.org")
        expect(last_request.env["omniauth.auth"]["extra"]["access_token"]).to be_kind_of(OAuth::AccessToken)
      end

      it "should call through to the master app" do
        expect(last_response.body).to eq("true")
      end
    end
  end

  describe "/auth/{name}/callback with expired session" do
    before do
      stub_request(:post, "https://api.example.org/oauth/access_token").
        to_return(:body => "oauth_token=yourtoken&oauth_token_secret=yoursecret")
      get "/auth/example.org/callback", {:oauth_verifier => "dudeman"}, "rack.session" => {}
    end

    it "should call fail! with :session_expired" do
      expect(last_request.env["omniauth.error"]).to be_kind_of(::OmniAuth::NoSessionError)
      last_request.env["omniauth.error.type"] = :session_expired
    end
  end
end
