# frozen_string_literal: true require 'minitest/autorun' require 'tmpdir' require 'fileutils' require 'json' require 'rack' require 'rack/mock' # Point the app at a throwaway database *before* loading it — app.rb opens the # store at require time. TEST_DIR = Dir.mktmpdir('validboard-test') ENV['VALIDBOARD_DB'] = File.join(TEST_DIR, 'test.db') ENV.delete('VALIDBOARD_SECRET') ENV.delete('VALIDBOARD_SECURE_COOKIE') Minitest.after_run { FileUtils.remove_entry(TEST_DIR, true) } require_relative '../app' TEST_PASSWORD = 'correct-horse-battery' # A cookie-carrying client. Rack::MockRequest doesn't keep a jar of its own, so # without this every request would look like a fresh browser and nothing that # depends on a session could be tested. class Client COOKIE_NAME = 'validboard.session' def initialize(remote_addr: '127.0.0.1') @mock = Rack::MockRequest.new(ValidBoard::App) @cookies = {} @remote_addr = remote_addr end def get(path, env = {}) = request('GET', path, env) def post(path, env = {}) = request('POST', path, env) def delete(path, env = {}) = request('DELETE', path, env) def post_json(path, obj, env = {}) post(path, env.merge(input: JSON.generate(obj), 'CONTENT_TYPE' => 'application/json')) end def login(password = TEST_PASSWORD) post('/login', params: { 'password' => password }) end def session_cookie @cookies[COOKIE_NAME] end private def request(method, path, env) # Rack::MockRequest leaves HTTP_VERSION unset, which reads as HTTP/1.0 — # and Sinatra answers a POST redirect with 302 there but 303 over 1.1. Pin # it so the tests see what a browser will. env = { 'REMOTE_ADDR' => @remote_addr, 'HTTP_VERSION' => 'HTTP/1.1' }.merge(env) env['HTTP_COOKIE'] = @cookies.map { |k, v| "#{k}=#{v}" }.join('; ') unless @cookies.empty? response = @mock.request(method, path, env) absorb_cookies(response) response end def absorb_cookies(response) raw = response.headers['Set-Cookie'] return if raw.nil? Array(raw).flat_map { |header| header.split("\n") }.each do |line| name, value = line.split(';', 2).first.to_s.split('=', 2) next if name.nil? @cookies[name] = value.to_s end end end module TestHelpers # Puts the app's store back to a known state: one password, no boards. def reset_app_store! ValidBoard::STORE.wipe_items! ValidBoard::STORE.password = TEST_PASSWORD end def signed_in_client(**kwargs) client = Client.new(**kwargs) response = client.login raise "login failed: #{response.status}" unless response.status == 303 client end def json_body(response) JSON.parse(response.body) end end