# frozen_string_literal: true

require_relative 'spec_config'
require_relative 'client_registry'

class SpecSetup
  def run
    if SpecConfig.instance.external_user?
      warn 'Skipping user creation because the set of users is fixed'
      return
    end

    with_client do |client|
      # For historical reasons, the test suite always uses
      # password-authenticated users, even when authentication is not
      # requested in the configuration. When authentication is requested
      # and password authentication is used (i.e., not x509 and not kerberos),
      # a suitable user already exists (it's the one specified in the URI)
      # and no additional users are needed. In other cases, including x509
      # auth and kerberos, create the "root user".
      # TODO redo the test suite so that this password-authenticated root user
      # is not required and the test suite uses whichever user is specified
      # in the URI, which could be none.
      if !SpecConfig.instance.auth? || SpecConfig.instance.x509_auth?
        # Create the root user administrator as the first user to be added to the
        # database. This user will need to be authenticated in order to add any
        # more users to any other databases.
        begin
          create_user(client, SpecConfig.instance.root_user)
        rescue Mongo::Error::OperationFailure::Family => e
          # When testing a cluster that requires auth, root user is already set up
          # and it is not creatable without auth.
          raise unless /command createUser requires authentication/.match?(e.message)
          # However, if the cluster is configured to require auth but
          # test suite has wrong credentials, then admin_authorized_test_client
          # won't be authenticated and the following line will raise an
          # exception
          raise unless client.use('admin').database.users.info(SpecConfig.instance.root_user.name).any?

          warn 'Skipping root user creation, likely auth is enabled on cluster'
        end
      end

      # Adds the test user to the test database with permissions on all
      # databases that will be used in the test suite.
      create_user(client, SpecConfig.instance.test_user)
    end
  end

  def create_user(client, user)
    users = client.use('admin').database.users
    begin
      users.create(user)
    rescue Mongo::Error::OperationFailure::Family => e
      raise unless /User.*already exists/.match?(e.message)

      users.remove(user.name)
      users.create(user)
    end
  end

  def with_client(&block)
    Mongo::Client.new(
      SpecConfig.instance.addresses,
      SpecConfig.instance.all_test_options.merge(
        socket_timeout: 5, connect_timeout: 5
      ),
      &block
    )
  end
end
