# frozen_string_literal: true

require_relative '../application_system_test_case'
require 'oauth2'
require 'rack'
require 'puma'

class OauthProviderSystemTest < ApplicationSystemTestCase
  fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
           :trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues,
           :enumerations, :custom_fields, :custom_values, :custom_fields_trackers,
           :watchers, :journals, :journal_details, :versions,
           :workflows

  test 'application creation and authorization' do
    #
    # admin creates the application, granting permissions and generating a uuid
    # and secret.
    #
    log_user 'admin', 'admin'
    with_settings rest_api_enabled: 1 do
      visit '/admin'
      within 'div#admin-menu ul' do
        click_link 'Applications'
      end
      click_link 'New Application'
      fill_in 'Name', with: 'Oauth Test'

      # as per https://tools.ietf.org/html/rfc8252#section-7.3, the port can be
      # anything when the redirect URI's host is 127.0.0.1.
      fill_in 'Redirect URI', with: 'http://127.0.0.1'

      check 'View Issues'
      click_button 'Create'

      assert_text "Application created."
    end

    assert app = Doorkeeper::Application.find_by_name('Oauth Test')

    find 'h2', visible: true, text: /Oauth Test/
    find 'p code', visible: true, text: app.uid
    find 'p strong', visible: true, text: /will not be shown again/
    find 'p code', visible: true, text: /View Issues/

    # scrape the clear text secret from the page
    app_secret = all(:css, 'p code')[1].text

    click_link 'Sign out'

    #
    # regular user authorizes the application
    #
    client = OAuth2::Client.new(app.uid, app_secret, site: "http://127.0.0.1:#{test_port}/")

    # set up a dummy http listener to handle the redirect
    port = rand 10000..20000
    redirect_uri = "http://127.0.0.1:#{port}"
    # the request handler below will set this to the auth token
    token = nil

    # launches webrick, listening for the redirect with the auth code.
    launch_client_app(port: port) do |req, res|
      # get access code from code url param
      if code = req.params['code'].presence
        # exchange it for token
        token = client.auth_code.get_token(code, redirect_uri: redirect_uri)
        res.body = ["<html><body><p>Authorization succeeded, you may close this window now.</p></body></html>"]
      end
    end

    log_user 'jsmith', 'jsmith'
    with_settings rest_api_enabled: 1 do
      visit '/my/account'
      click_link 'Authorized applications'
      find 'p.nodata', visible: true

      # an oauth client would send the user to this url to request permission
      url = client.auth_code.authorize_url redirect_uri: redirect_uri, scope: 'view_issues view_project'
      uri = URI.parse url
      visit uri.path + '?' + uri.query

      find 'h2', visible: true, text: 'Authorization required'
      find 'p', visible: true, text: /Authorize Oauth Test/
      find '.oauth-permissions', visible: true, text: /View Issues/
      find '.oauth-permissions', visible: true, text: /View project/

      click_button 'Authorize'

      assert grant = app.access_grants.last
      assert_equal 'view_issues view_project', grant.scopes.to_s

      # check for output defined above in the request handler
      find 'p', visible: true, text: /Authorization succeeded/
      assert token.present?

      visit '/my/account'
      click_link 'Authorized applications'
      find 'td', visible: true, text: /Oauth Test/
      click_link 'Sign out'

      # Now, use the token for some API requests
      assert_raise(RestClient::Unauthorized) do
        RestClient.get "http://localhost:#{test_port}/projects/onlinestore/issues.json"
      end

      headers = { 'Authorization' => "Bearer #{token.token}" }
      r = RestClient.get "http://localhost:#{test_port}/projects/onlinestore/issues.json", headers
      issues = JSON.parse(r.body)['issues']
      assert issues.any?

      # time entries access is not part of the granted scopes
      assert_raise(RestClient::Forbidden) do
        RestClient.get "http://localhost:#{test_port}/projects/onlinestore/time_entries.json", headers
      end
    end
  end

  private

  def launch_client_app(port: 12345, path: '/', &block)
    app = ->(env) do
      req = Rack::Request.new(env)
      res = Rack::Response.new
      yield(req, res)
      res.finish
    end

    server = Puma::Server.new app
    server.add_tcp_listener '127.0.0.1', port
    Thread.new { server.run }
  end

  def test_port
    Capybara.current_session.server.port
  end
end
