ministryofjustice/peoplefinder

View on GitHub
smoke_test

Summary

Maintainability
Test Coverage
#!/usr/bin/env ruby

# Peoplefinder smoke test
# =======================
#
# Set the following environment variables:
#
# * HOST - the root of the server to be tested, e.g. http://example.com/
# * EMAIL_ADDRESS - the email address to sign in with
# * EMAIL_PASSWORD - the IMAP password
#
# Google Apps IMAP is assumed, and the account must have IMAP access
# configured. For the password, generate an app-specific password (this is
# necessary if two-factor authentication is enabled).
#
# Run the test:
#
#     $ bundle exec ./smoke_test
#
# Successful output will look like this:
#
#     Request token ... OK
#     Fetch token email ... wait 2s ... OK
#     Sign in ... OK
#     Edit profile ... OK
#     Search for profile ... wait 2s ... OK
#
# On success, the return code is zero. Failure will result in a non-zero return
# code.

require "bundler/setup"
require "capybara"
require "capybara/dsl"
require "capybara/poltergeist"
require "mail"
require "net/imap"
require "securerandom"

host = ENV.fetch("HOST")
email_address = ENV.fetch("EMAIL_ADDRESS")
email_password = ENV.fetch("EMAIL_PASSWORD")
search_name = ENV.fetch("SEARCH_NAME")

Capybara.register_driver :poltergeist do |app|
  Capybara::Poltergeist::Driver.new(app, phantomjs_options: ["--ignore-ssl-errors=yes"])
end
Capybara.run_server = false
Capybara.current_driver = :poltergeist
Capybara.app_host = host

class SmokeTest
  include Capybara::DSL

  TIME_DRIFT_ALLOWANCE = 5

  def initialize(email_address, imap, host, search_name)
    @email_address = email_address
    @imap = imap
    @host = host
    @search_name = search_name
    @start_time = Time.zone.now.utc
  end

  def run
    step "Request token" do
      # 300 secs is the TTL for the DNS based maintenance page
      with_retries(initial_delay: 15, max_delay: 60) { request_token }
      # request_token
    end

    signin_path = nil
    step "Fetch token email" do
      token_mail = with_retries(initial_delay: 5, max_delay: 120) { fetch_token_mail }
      raise "couldn't find token email" unless token_mail

      text_part = token_mail.text_part.decoded
      signin_path = text_part[%r{/tokens/[0-9a-f-]+}]
      raise "couldn't find sign-in token link" unless signin_path
    end

    my_name = nil
    step "Sign in" do
      sign_in signin_path
      my_name = find_own_name
    end

    unique_string = SecureRandom.uuid
    step "Edit profile" do
      update_profile my_name, unique_string
    end

    step "Search for own changed profile" do
      found = with_retries { search_profile my_name, unique_string }
      raise "couldn't find own profile via search" unless found
    end

    step "Search for profile: #{@search_name}" do
      found = with_retries { search_profile @search_name, @search_name }
      raise "couldn't find profile for #{@search_name} via search" unless found
    end
  rescue StandardError => e
    puts page.text
    raise e
  end

private

  attr_reader :imap, :email_address

  def find_own_name
    page.text[/Signed in as (.*?) Sign out/, 1].tap do |name|
      raise "couldn't find own name" unless name
    end
  end

  def search_profile(name, search_term)
    visit "/"
    fill_in "Enter the name of a person or role", with: search_term
    click_button "Submit search"
    result = nil
    within "#search_results" do
      result = page.has_text?(name)
    end
    result
  end

  def update_profile(name, uuid)
    desc = "Smoke test started at #{@start_time.iso8601}\n\nUnique identifier: #{uuid}"
    click_link name, match: :first
    click_edit_profile
    fill_in "Extra information", with: desc
    click_button "Save", match: :first
    raise "couldn't update profile" unless page.has_text?("Updated your profile")
  end

  def request_token
    visit "/"
    page_loaded = page.has_text?("Request link to access People Finder")
    if page_loaded
      fill_in "Email address", with: email_address
      click_button "Request link"
    end
    page_loaded
  end

  def fetch_token_mail
    imap.examine "INBOX"
    candidate_ids = imap.search(["SINCE", @start_time.strftime("%-d-%b-%Y")])
    mails = candidate_ids.map do |id|
      msg = imap.fetch(id, "RFC822")[0].attr["RFC822"]
      Mail.read_from_string(msg)
    end
    mails.reverse.find do |mail|
      mail.subject =~ /access request/i &&
        mail.date.to_time >= (@start_time - TIME_DRIFT_ALLOWANCE) &&
        mail.text_part.decoded.include?(@host)
    end
  end

  def sign_in(path)
    visit path
    raise "couldn't sign in via #{path}" unless page.body =~ /Signed in as/
  end

  def with_retries(tries: 5, initial_delay: 2, max_delay: 30)
    delay = initial_delay
    result = nil
    tries.times do
      result = yield
      break if result

      wait delay
      delay = [max_delay, delay * 2].min
    end
    result
  end

  def wait(delay)
    $stdout.print "wait #{delay} ... "
    $stdout.flush
    sleep delay
  end

  def step(string)
    $stdout.print string, " ... "
    $stdout.flush
    yield.tap { $stdout.puts "OK" }
  end
end

imap = Net::IMAP.new("imap.gmail.com", port: 993, ssl: true)
imap.login email_address, email_password
SmokeTest.new(email_address, imap, host, search_name).run
imap.logout
imap.disconnect