tests: minitest + rack-test suite, bin/test with ephemeral Postgres
This commit is contained in:
parent
8468b3d099
commit
43bed98329
13 changed files with 456 additions and 2 deletions
|
|
@ -12,3 +12,6 @@ tmp
|
|||
flake.nix
|
||||
flake.lock
|
||||
CLAUDE.md
|
||||
test
|
||||
Rakefile
|
||||
bin
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ The hedgehog is the user's pre-IE personal mark; once the IE has its own logo, t
|
|||
- **Generate a new SESSION_SECRET:** `openssl rand -hex 64` (then update Forgejo secret).
|
||||
- **Generate a new ADMIN_PASSWORD:** `openssl rand -base64 24`. **Save it to a password manager** — Forgejo secrets are write-only.
|
||||
- **Local dev:** `nix develop`, then `bundle exec rerun -- rackup -p 3000`. Visit `http://localhost:3000`. Without `DATABASE_URL` the contact form still works; invoicing routes return 503.
|
||||
- **Run tests:** `nix develop -c bin/test` — full suite (minitest + rack-test) against an ephemeral Postgres it provisions and tears down itself. `bundle exec rake test` runs without a DB, skipping DB-backed tests. Mail uses `Mail::TestMailer`; S3 uses aws-sdk stubbed responses; tests never touch real services, and `.env` is deliberately not loaded when `RACK_ENV=test`.
|
||||
- **Local image build (sanity check):** `docker compose build`
|
||||
- **Production deploy:** automatic via `.forgejo/workflows/deploy.yaml` on push to `main`. Pipeline:
|
||||
1. Kaniko builds the image and pushes it to the Forgejo registry.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ ENV GEM_HOME=/app/bundle
|
|||
ENV PATH="${GEM_HOME}/bin:${PATH}"
|
||||
|
||||
COPY --chown=asxpio Gemfile Gemfile.lock* ./
|
||||
RUN bundle install --without development
|
||||
RUN bundle install --without development:test
|
||||
|
||||
COPY --chown=asxpio . .
|
||||
|
||||
|
|
|
|||
6
Gemfile
6
Gemfile
|
|
@ -26,3 +26,9 @@ gem 'rexml', '~> 3.4'
|
|||
group :development do
|
||||
gem 'rerun', '~> 0.14'
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'minitest', '~> 5.25'
|
||||
gem 'rack-test', '~> 2.2'
|
||||
gem 'rake', '~> 13.2'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ GEM
|
|||
net-smtp
|
||||
matrix (0.4.3)
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.27.0)
|
||||
multi_json (1.21.1)
|
||||
mustermann (3.1.1)
|
||||
net-imap (0.6.4)
|
||||
|
|
@ -87,8 +88,11 @@ GEM
|
|||
rack-session (2.1.2)
|
||||
base64 (>= 0.1.0)
|
||||
rack (>= 3.0.0)
|
||||
rack-test (2.2.0)
|
||||
rack (>= 1.3)
|
||||
rackup (2.3.1)
|
||||
rack (>= 3)
|
||||
rake (13.4.2)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.11.1)
|
||||
ffi (~> 1.0)
|
||||
|
|
@ -138,6 +142,7 @@ DEPENDENCIES
|
|||
dotenv (~> 3.1)
|
||||
erubi (~> 1.13)
|
||||
mail (~> 2.8)
|
||||
minitest (~> 5.25)
|
||||
pg (~> 1.5)
|
||||
prawn (~> 2.5)
|
||||
prawn-table (~> 0.2)
|
||||
|
|
@ -145,7 +150,9 @@ DEPENDENCIES
|
|||
rack (~> 3.2)
|
||||
rack-protection (~> 4.2)
|
||||
rack-session (~> 2.1)
|
||||
rack-test (~> 2.2)
|
||||
rackup (~> 2.3)
|
||||
rake (~> 13.2)
|
||||
rerun (~> 0.14)
|
||||
rexml (~> 3.4)
|
||||
rqrcode (~> 2.2)
|
||||
|
|
|
|||
9
Rakefile
Normal file
9
Rakefile
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
require 'rake/testtask'
|
||||
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'test'
|
||||
t.pattern = 'test/*_test.rb'
|
||||
t.warning = false
|
||||
end
|
||||
|
||||
task default: :test
|
||||
|
|
@ -7,7 +7,9 @@ require 'uri'
|
|||
|
||||
begin
|
||||
require 'dotenv'
|
||||
Dotenv.load
|
||||
# Tests control ENV themselves; a developer's .env must not leak in (it may
|
||||
# point DATABASE_URL at a real database).
|
||||
Dotenv.load unless ENV['RACK_ENV'] == 'test'
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
|
|
|
|||
19
bin/test
Executable file
19
bin/test
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env bash
|
||||
# Full test suite against an ephemeral Postgres. Run inside `nix develop`
|
||||
# (needs initdb/pg_ctl from the dev shell). Without a database you can still
|
||||
# run `bundle exec rake test` — DB-backed tests skip.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
PORT="${TEST_PG_PORT:-54329}"
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'pg_ctl -D "$TMP/pg" stop -m immediate >/dev/null 2>&1 || true; rm -rf "$TMP"' EXIT
|
||||
|
||||
initdb -D "$TMP/pg" -A trust -U postgres >/dev/null
|
||||
printf "unix_socket_directories = ''\nlisten_addresses = '127.0.0.1'\nport = %s\n" "$PORT" \
|
||||
>> "$TMP/pg/postgresql.conf"
|
||||
pg_ctl -D "$TMP/pg" -l "$TMP/pg.log" start >/dev/null
|
||||
createdb -h 127.0.0.1 -p "$PORT" -U postgres asxpio_test
|
||||
|
||||
TEST_DATABASE_URL="postgres://postgres@127.0.0.1:$PORT/asxpio_test" \
|
||||
bundle exec rake test "$@"
|
||||
40
test/admin_auth_test.rb
Normal file
40
test/admin_auth_test.rb
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
require_relative 'test_helper'
|
||||
|
||||
require 'base64'
|
||||
|
||||
class AdminAuthTest < Minitest::Test
|
||||
OK_APP = ->(_env) { [200, {}, ['ok']] }
|
||||
|
||||
def setup
|
||||
@auth = AdminAuth.new(OK_APP)
|
||||
end
|
||||
|
||||
def env_for(path, user: nil, pass: nil)
|
||||
env = { 'PATH_INFO' => path }
|
||||
if user
|
||||
env['HTTP_AUTHORIZATION'] = "Basic #{Base64.strict_encode64("#{user}:#{pass}")}"
|
||||
end
|
||||
env
|
||||
end
|
||||
|
||||
def test_correct_credentials_pass
|
||||
assert_equal 200, @auth.call(env_for('/admin/invoices', user: 'admin', pass: 'test-password'))[0]
|
||||
end
|
||||
|
||||
def test_wrong_password_rejected
|
||||
assert_equal 401, @auth.call(env_for('/admin/invoices', user: 'admin', pass: 'nope'))[0]
|
||||
end
|
||||
|
||||
def test_wrong_user_rejected
|
||||
assert_equal 401, @auth.call(env_for('/admin/invoices', user: 'other', pass: 'test-password'))[0]
|
||||
end
|
||||
|
||||
def test_missing_header_rejected
|
||||
assert_equal 401, @auth.call(env_for('/admin'))[0]
|
||||
end
|
||||
|
||||
def test_sibling_paths_not_gated
|
||||
assert_equal 200, @auth.call(env_for('/'))[0]
|
||||
assert_equal 200, @auth.call(env_for('/admin-invoice-form.js'))[0]
|
||||
end
|
||||
end
|
||||
174
test/app_test.rb
Normal file
174
test/app_test.rb
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
require_relative 'test_helper'
|
||||
|
||||
class AppTest < Minitest::Test
|
||||
include Rack::Test::Methods
|
||||
|
||||
def app
|
||||
AsxpioWeb
|
||||
end
|
||||
|
||||
def setup
|
||||
Mail::TestMailer.deliveries.clear
|
||||
TestDb.clean! if TestDb.available?
|
||||
end
|
||||
|
||||
# Each test that passes contact validation must use a fresh IP: the app-level
|
||||
# rate limiter (5/hour) is shared process state, so the counter must be
|
||||
# unique across the whole run, not per test.
|
||||
@@ip_counter = 0
|
||||
def fresh_ip
|
||||
@@ip_counter += 1
|
||||
"10.9.#{@@ip_counter / 250}.#{@@ip_counter % 250}"
|
||||
end
|
||||
|
||||
def csrf_token_from(path, env = {})
|
||||
get path, {}, env
|
||||
assert last_response.ok?, "GET #{path} failed: #{last_response.status}"
|
||||
last_response.body[/name="authenticity_token" value="([^"]+)"/, 1] ||
|
||||
flunk("no CSRF token found on #{path}")
|
||||
end
|
||||
|
||||
def admin_env(extra = {})
|
||||
{ 'HTTP_AUTHORIZATION' =>
|
||||
"Basic #{Base64.strict_encode64("#{ENV['ADMIN_USER']}:#{ENV['ADMIN_PASSWORD']}")}" }.merge(extra)
|
||||
end
|
||||
|
||||
# --- public pages ---------------------------------------------------------
|
||||
|
||||
def test_index_renders
|
||||
get '/'
|
||||
assert last_response.ok?
|
||||
end
|
||||
|
||||
# --- contact form ---------------------------------------------------------
|
||||
|
||||
def contact_params(over = {})
|
||||
{ name: 'Visitor', email: 'visitor@example.com', subject: 'Hello',
|
||||
message: 'A message.', website: '' }.merge(over)
|
||||
end
|
||||
|
||||
def test_contact_without_csrf_token_forbidden
|
||||
post '/contact', contact_params
|
||||
assert_equal 403, last_response.status
|
||||
end
|
||||
|
||||
def test_contact_honeypot_pretends_success_and_sends_nothing
|
||||
token = csrf_token_from('/')
|
||||
post '/contact', contact_params(website: 'spam', authenticity_token: token)
|
||||
assert_equal 302, last_response.status
|
||||
assert_match %r{/thanks}, last_response.location
|
||||
assert_empty Mail::TestMailer.deliveries
|
||||
end
|
||||
|
||||
def test_contact_invalid_email_rejected
|
||||
token = csrf_token_from('/')
|
||||
post '/contact', contact_params(email: 'not-an-email', authenticity_token: token)
|
||||
assert_equal 422, last_response.status
|
||||
assert_empty Mail::TestMailer.deliveries
|
||||
end
|
||||
|
||||
def test_contact_valid_submission_sends_two_mails
|
||||
token = csrf_token_from('/')
|
||||
post '/contact', contact_params(authenticity_token: token),
|
||||
'HTTP_X_FORWARDED_FOR' => fresh_ip
|
||||
assert_equal 302, last_response.status
|
||||
assert_equal 2, Mail::TestMailer.deliveries.size
|
||||
to_owner, to_visitor = Mail::TestMailer.deliveries
|
||||
assert_includes to_owner.to, ENV['MAIL_TO']
|
||||
assert_includes to_visitor.to, 'visitor@example.com'
|
||||
end
|
||||
|
||||
def test_contact_rate_limited_after_five
|
||||
ip = fresh_ip
|
||||
token = csrf_token_from('/')
|
||||
5.times do
|
||||
post '/contact', contact_params(authenticity_token: token), 'HTTP_X_FORWARDED_FOR' => ip
|
||||
assert_equal 302, last_response.status
|
||||
end
|
||||
post '/contact', contact_params(authenticity_token: token), 'HTTP_X_FORWARDED_FOR' => ip
|
||||
assert_equal 429, last_response.status
|
||||
end
|
||||
|
||||
# --- admin ------------------------------------------------------------
|
||||
|
||||
def test_admin_requires_auth
|
||||
get '/admin/invoices'
|
||||
assert_equal 401, last_response.status
|
||||
end
|
||||
|
||||
def test_admin_list_with_auth
|
||||
skip 'TEST_DATABASE_URL not set' unless TestDb.available?
|
||||
get '/admin/invoices', {}, admin_env
|
||||
assert last_response.ok?
|
||||
end
|
||||
|
||||
def invoice_form_params(token)
|
||||
{ authenticity_token: token,
|
||||
client_name: 'ACME', client_email: 'billing@example.com', client_address: '',
|
||||
currency: 'EUR', gel_rate: '3.05',
|
||||
issued_on: Date.today.to_s, due_on: (Date.today + 14).to_s, notes: '',
|
||||
ltc_address: '', ltc_rate: '', ltc_amount: '',
|
||||
items: { '0' => { 'description' => 'Engineering — test', 'qty' => '2', 'unit_price' => '100.50' } } }
|
||||
end
|
||||
|
||||
def test_create_invoice_end_to_end
|
||||
skip 'TEST_DATABASE_URL not set' unless TestDb.available?
|
||||
token = csrf_token_from('/admin/invoices/new', admin_env)
|
||||
|
||||
post '/admin/invoices', invoice_form_params(token), admin_env
|
||||
assert_equal 302, last_response.status, last_response.body
|
||||
|
||||
invoice = Invoice.first
|
||||
refute_nil invoice
|
||||
assert_equal BigDecimal('201'), invoice.total
|
||||
assert_equal "invoices/#{invoice.number}-#{invoice.uuid}.pdf", invoice.pdf_key
|
||||
assert_match %r{/admin/invoices/#{invoice.uuid}}, last_response.location
|
||||
end
|
||||
|
||||
def test_create_invoice_validation_failure_rerenders_form
|
||||
skip 'TEST_DATABASE_URL not set' unless TestDb.available?
|
||||
token = csrf_token_from('/admin/invoices/new', admin_env)
|
||||
|
||||
params = invoice_form_params(token)
|
||||
params[:items]['0']['qty'] = '1,5'
|
||||
post '/admin/invoices', params, admin_env
|
||||
assert_equal 422, last_response.status
|
||||
assert_nil Invoice.first
|
||||
end
|
||||
|
||||
# --- public invoice pages -----------------------------------------------
|
||||
|
||||
def create_invoice!
|
||||
inv = Invoice.build(
|
||||
client_name: 'ACME', client_email: 'billing@example.com',
|
||||
currency: 'EUR', gel_rate: '3.05',
|
||||
items: [{ 'description' => 'work', 'qty' => '1', 'unit_price' => '100' }]
|
||||
)
|
||||
inv.pdf_key = "invoices/#{inv.number}-#{inv.uuid}.pdf"
|
||||
inv.save_changes
|
||||
inv
|
||||
end
|
||||
|
||||
def test_public_landing_page
|
||||
skip 'TEST_DATABASE_URL not set' unless TestDb.available?
|
||||
inv = create_invoice!
|
||||
get "/i/#{inv.uuid}"
|
||||
assert last_response.ok?
|
||||
assert_includes last_response.body, inv.number
|
||||
assert_includes last_response.body, 'ACME'
|
||||
end
|
||||
|
||||
def test_public_pdf_redirects_to_presigned_url
|
||||
skip 'TEST_DATABASE_URL not set' unless TestDb.available?
|
||||
inv = create_invoice!
|
||||
get "/i/#{inv.uuid}/pdf"
|
||||
assert_equal 302, last_response.status
|
||||
assert_includes last_response.location, 'X-Amz-Signature'
|
||||
end
|
||||
|
||||
def test_unknown_invoice_404s
|
||||
skip 'TEST_DATABASE_URL not set' unless TestDb.available?
|
||||
get "/i/#{SecureRandom.uuid}"
|
||||
assert_equal 404, last_response.status
|
||||
end
|
||||
end
|
||||
115
test/invoice_test.rb
Normal file
115
test/invoice_test.rb
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
require_relative 'test_helper'
|
||||
|
||||
class InvoiceTest < Minitest::Test
|
||||
def setup
|
||||
skip 'TEST_DATABASE_URL not set' unless TestDb.available?
|
||||
TestDb.clean!
|
||||
@year = Date.today.year
|
||||
end
|
||||
|
||||
def insert_number(num)
|
||||
DB.connection[:invoices].insert(
|
||||
uuid: SecureRandom.uuid, number: num, client_name: 'c', client_email: 'c@example.com',
|
||||
currency: 'EUR', gel_rate: 3, subtotal: 1,
|
||||
items: [{ 'description' => 'x', 'qty' => '1', 'unit_price' => '1' }].to_json,
|
||||
issued_on: Date.today, due_on: Date.today + 14, pdf_key: 'k', created_at: Time.now.utc
|
||||
)
|
||||
end
|
||||
|
||||
def test_allocate_number_starts_at_one
|
||||
assert_equal "INV-#{@year}-0001", Invoice.allocate_number
|
||||
end
|
||||
|
||||
def test_allocate_number_increments
|
||||
insert_number("INV-#{@year}-0001")
|
||||
insert_number("INV-#{@year}-0002")
|
||||
assert_equal "INV-#{@year}-0003", Invoice.allocate_number
|
||||
end
|
||||
|
||||
def test_allocate_number_sorts_numerically_past_9999
|
||||
insert_number("INV-#{@year}-9999")
|
||||
insert_number("INV-#{@year}-10000")
|
||||
assert_equal "INV-#{@year}-10001", Invoice.allocate_number
|
||||
end
|
||||
|
||||
def test_allocate_number_ignores_other_years
|
||||
insert_number("INV-#{@year - 1}-0500")
|
||||
assert_equal "INV-#{@year}-0001", Invoice.allocate_number
|
||||
end
|
||||
|
||||
def test_duplicate_number_raises_unique_violation
|
||||
insert_number("INV-#{@year}-0001")
|
||||
assert_raises(Sequel::UniqueConstraintViolation) { insert_number("INV-#{@year}-0001") }
|
||||
end
|
||||
|
||||
def test_build_round_trip
|
||||
inv = Invoice.build(
|
||||
client_name: 'ACME', client_email: 'billing@example.com', client_address: '',
|
||||
currency: 'EUR', gel_rate: '3.05', issued_on: '', due_on: '', notes: '',
|
||||
items: [{ 'description' => 'work', 'qty' => '2', 'unit_price' => '100.50' }]
|
||||
)
|
||||
inv.pdf_key = "invoices/#{inv.number}-#{inv.uuid}.pdf"
|
||||
inv.save_changes
|
||||
|
||||
saved = Invoice[uuid: inv.uuid]
|
||||
assert_equal BigDecimal('201'), saved.total
|
||||
assert_equal BigDecimal('613.05'), saved.total_gel
|
||||
assert_equal 'pending', saved.status
|
||||
refute saved.ltc?
|
||||
end
|
||||
|
||||
def test_build_derives_ltc_amount_from_rate
|
||||
inv = Invoice.build(
|
||||
client_name: 'ACME', client_email: 'billing@example.com',
|
||||
currency: 'EUR', gel_rate: '3.05',
|
||||
items: [{ 'description' => 'work', 'qty' => '1', 'unit_price' => '100' }],
|
||||
ltc_address: 'ltc1qexampleexampleexampleexample', ltc_rate: '80', ltc_amount: ''
|
||||
)
|
||||
assert inv.ltc?
|
||||
assert_equal BigDecimal('1.25'), inv.ltc_amount_due
|
||||
end
|
||||
end
|
||||
|
||||
class InvoiceValidationTest < Minitest::Test
|
||||
BASE = {
|
||||
client_name: 'ACME', client_email: 'billing@example.com', currency: 'EUR',
|
||||
gel_rate: '3.05', ltc_address: '', ltc_rate: '', ltc_amount: '',
|
||||
items: [{ 'description' => 'work', 'qty' => '1', 'unit_price' => '100' }]
|
||||
}.freeze
|
||||
|
||||
def setup
|
||||
skip 'TEST_DATABASE_URL not set' unless TestDb.available?
|
||||
@app = AsxpioWeb.new!
|
||||
end
|
||||
|
||||
def validate(over = {})
|
||||
@app.send(:validate_invoice_params, BASE.merge(over).dup)
|
||||
end
|
||||
|
||||
def test_valid_params_pass
|
||||
assert_empty validate
|
||||
end
|
||||
|
||||
def test_gel_rate_must_be_positive
|
||||
assert_includes validate(gel_rate: '0'), :gel_rate
|
||||
assert_includes validate(gel_rate: '-2.9'), :gel_rate
|
||||
refute_includes validate(gel_rate: '2.95'), :gel_rate
|
||||
end
|
||||
|
||||
def test_item_numbers_must_parse
|
||||
assert_includes validate(items: [{ 'description' => 'w', 'qty' => '1,5', 'unit_price' => '100' }]), :items
|
||||
assert_includes validate(items: [{ 'description' => 'w', 'qty' => '1', 'unit_price' => 'abc' }]), :items
|
||||
end
|
||||
|
||||
def test_blank_item_numbers_allowed
|
||||
assert_empty validate(items: [{ 'description' => 'w', 'qty' => '', 'unit_price' => '' }])
|
||||
end
|
||||
|
||||
def test_at_least_one_item_required
|
||||
assert_includes validate(items: [{ 'description' => '', 'qty' => '1', 'unit_price' => '1' }]), :items
|
||||
end
|
||||
|
||||
def test_ltc_address_format
|
||||
assert_includes validate(ltc_address: 'not-an-address'), :ltc_address
|
||||
end
|
||||
end
|
||||
35
test/rate_limit_test.rb
Normal file
35
test/rate_limit_test.rb
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
require_relative 'test_helper'
|
||||
|
||||
class RateLimitTest < Minitest::Test
|
||||
def test_allows_up_to_limit_then_denies
|
||||
rl = RateLimit.new(limit: 5, window: 3600)
|
||||
assert (1..5).all? { rl.allow?('1.2.3.4') }
|
||||
refute rl.allow?('1.2.3.4')
|
||||
end
|
||||
|
||||
def test_keys_are_independent
|
||||
rl = RateLimit.new(limit: 1, window: 3600)
|
||||
assert rl.allow?('a')
|
||||
refute rl.allow?('a')
|
||||
assert rl.allow?('b')
|
||||
end
|
||||
|
||||
def test_sweep_removes_stale_keys
|
||||
rl = RateLimit.new(limit: 5, window: 3600)
|
||||
hits = rl.instance_variable_get(:@hits)
|
||||
old = Time.now.to_i - 7200
|
||||
50.times { |i| hits["stale-#{i}"] << old }
|
||||
rl.sweep!
|
||||
assert_empty hits.keys.grep(/\Astale-/)
|
||||
end
|
||||
|
||||
def test_allow_prunes_opportunistically
|
||||
rl = RateLimit.new(limit: 5, window: 3600)
|
||||
hits = rl.instance_variable_get(:@hits)
|
||||
old = Time.now.to_i - 7200
|
||||
50.times { |i| hits["stale-#{i}"] << old }
|
||||
RateLimit::SWEEP_EVERY.times { rl.allow?('fresh') }
|
||||
assert_empty hits.keys.grep(/\Astale-/)
|
||||
assert hits.key?('fresh')
|
||||
end
|
||||
end
|
||||
43
test/test_helper.rb
Normal file
43
test/test_helper.rb
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
ENV['RACK_ENV'] = 'test'
|
||||
|
||||
# Dummy env so the app class loads; nothing here reaches a real service.
|
||||
ENV['SESSION_SECRET'] ||= 'test-secret-0000000000000000000000000000000000000000000000000000'
|
||||
ENV['SMTP_ADDR'] ||= 'localhost'
|
||||
ENV['SMTP_PORT'] ||= '587'
|
||||
ENV['SMTP_USER'] ||= 'test'
|
||||
ENV['SMTP_PASSWORD'] ||= 'test'
|
||||
ENV['MAIL_FROM'] ||= 'Test <test@example.com>'
|
||||
ENV['MAIL_TO'] ||= 'owner@example.com'
|
||||
ENV['ADMIN_USER'] ||= 'admin'
|
||||
ENV['ADMIN_PASSWORD'] ||= 'test-password'
|
||||
|
||||
# DB-backed tests run only against an explicit test database (bin/test
|
||||
# provisions an ephemeral one). Never fall through to a developer DATABASE_URL.
|
||||
if ENV['TEST_DATABASE_URL']
|
||||
ENV['DATABASE_URL'] = ENV['TEST_DATABASE_URL']
|
||||
ENV['S3_ENDPOINT'] ||= 'http://127.0.0.1:1'
|
||||
ENV['S3_PUBLIC_ENDPOINT'] ||= 'http://127.0.0.1:1'
|
||||
ENV['S3_ACCESS_KEY'] ||= 'test'
|
||||
ENV['S3_SECRET_KEY'] ||= 'test'
|
||||
ENV['S3_BUCKET'] ||= 'test-bucket'
|
||||
require 'aws-sdk-s3'
|
||||
Aws.config[:s3] = { stub_responses: true } # no S3 call leaves the process
|
||||
else
|
||||
ENV.delete('DATABASE_URL')
|
||||
end
|
||||
|
||||
require_relative '../asxpio'
|
||||
require 'minitest/autorun'
|
||||
require 'rack/test'
|
||||
|
||||
Mail.defaults { delivery_method :test }
|
||||
|
||||
module TestDb
|
||||
def self.available?
|
||||
!ENV['DATABASE_URL'].nil?
|
||||
end
|
||||
|
||||
def self.clean!
|
||||
DB.connection[:invoices].delete
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue