Omniauth / OAuth integration test with webmock

Took me a while to figure out all the stubs … maybe this is useful later :)
I’m using this to test our facebook / twitter signup flows.

def external_redirect(url)
  yield
rescue ActionController::RoutingError # goes to twitter.com/oauth/authenticate
  current_url.must_equal url
else
  raise "Missing external redirect"
end

it "signs up" do
  twitter_id = 12345
  visit "/"
  
  stub_request(:post, "https://api.twitter.com/oauth/request_token").
    to_return(body: "oauth_token=TOKEN&oauth_token_secret=SECRET&oauth_callback_confirmed=true")

  external_redirect "https://api.twitter.com/oauth/authenticate?x_auth_access_type=read&oauth_token=TOKEN" do
    click_link "Continue with Twitter"
  end

  # https://dev.twitter.com/oauth/reference/post/oauth/access_token
  stub_request(:post, "https://api.twitter.com/oauth/access_token").
    to_return(body: "oauth_token=TOKEN&oauth_token_secret=SECRET&user_id=#{twitter_id}&screen_name=twitterapi")

  # https://dev.twitter.com/rest/reference/get/account/verify_credentials
  stub_request(:get, "https://api.twitter.com/1.1/account/verify_credentials.json?include_entities=false&skip_status=true").
    to_return(body: %{{"profile_image_url_https": "image_normal.png"}})

  visit "/auth/twitter/callback"
end

Improving Autocomplete Cache Hit Rates by Looking up Parent Caches

When a user asks for all the words starting with “foobar” let’s check the cache for fooba,foob,foo,fo,f and see if any of these results already include a full set of words.
This saved us ~30% of our db queries and makes the ui more responsive.

# given a start word of foobar check caches for ["fooba", "foob", "foo", "fo", "f"]
# to see if any of the simpler queries already had a complete response
def cached_autocomplete(start)
  limit = 1000
  short = start.dup
  shorter = Array.new(start.size - 1){ short.chop!.dup }

  cached = Rails.cache.read_multi(*shorter.map { |s| "autocomplete-#{s}" })
  cached.each_value do |tags|
    if tags.size < limit # complete response / nothing is missing
      tags.select! { |t| t.start_with?(start) } # filter non-matches
      return tags
    end
  end

  Rails.cache.fetch("autocomplete-#{start}", expires_in: 15.minutes, race_condition_ttl: 1.minute) do
    ... hit the db ... limit(limit) ...
    ... prevent multiple cache refreshes with race_condition_ttl ...
  end
end

ActionMailer / Rails: No paths in my mails please

Always paying attention that mails only use urls is a bit annoying/dangerous and also means we cannot reuse partials and cannot use nice resource routes like `link_to user.name, user`

Make ActionMailer always use full urls:

# we only want urls in our emails, never paths
module OnlyAbsoluteUrls
  def url_for(*args)
    url = super
    if url.include?("://")
      url
    else
      "#{ActionMailer::Base.default_url_options.fetch(:host)}#{url}"
    end
  end
end

class ApplicationMailer < ActionMailer::Base
  helper OnlyAbsoluteUrls
end

and to make sure it works let’s verify in all mailer tests that we did not actually generated paths:

after { deliveries.map(&:body).map(&:to_s).join.wont_include '"/' }

Running karma js with rails asset pipeline / sprockets

# test/karma.conf.js
...
    basePath: '<%= Bundler.root %>',
...
    files: [
      '<%= resolve_asset('vis.js') %>',
      'app/assets/javascripts/app.js',
      'test/**/*_spec.js'
    ],

# Rakefile
namespace :test do
  task js: :environment do
    with_tmp_karma_config do |config|
      sh "./node_modules/karma/bin/karma start #{config} --single-run"
    end
  end

  private

  def with_tmp_karma_config
    Tempfile.open('karma.js') do |f|
      f.write ERB.new(File.read('test/karma.conf.js')).result(binding)
      f.flush
      yield f.path
    end
  end

  def resolve_asset(file)
    Rails.application.assets.find_asset(file).to_a.first.pathname.to_s
  end
end