Skip to content

Add secure route to reseed data in test env only #570

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jul 29, 2025
35 changes: 35 additions & 0 deletions app/controllers/test_utilities_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

class TestUtilitiesController < ApplicationController
skip_before_action :verify_authenticity_token
ALLOWED_HOSTS = ['test-editor-api.raspberrypi.org', 'localhost'].freeze

def reseed
if reseed_allowed?
Rails.application.load_tasks
Rake::Task['test_seeds:destroy'].invoke
Rake::Task['test_seeds:create'].invoke
render json: { message: 'Database reseeded successfully.' }, status: :ok
else
head :not_found
end
end

private

def reseed_allowed?
api_key_valid? && environment_allowed? && host_allowed?
end

def api_key_valid?
ENV['RESEED_API_KEY'].present? && request.headers['X-RESEED-API-KEY'] == ENV['RESEED_API_KEY']
end

def environment_allowed?
Rails.env.development? || Rails.env.test?
end

def host_allowed?
ALLOWED_HOSTS.include?(request.host)
end
end
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
root to: 'projects#index'
end

post '/test/reseed', to: 'test_utilities#reseed' if Rails.env.development? || Rails.env.test?

post '/graphql', to: 'graphql#execute'
mount GraphiQL::Rails::Engine, at: '/graphql', graphql_path: '/graphql#execute' unless Rails.env.production?

Expand Down
132 changes: 132 additions & 0 deletions spec/requests/test_utilities_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'POST /test/reseed' do
subject(:request) { post('/test/reseed', headers:) }

let(:headers) { { 'X-RESEED-API-KEY' => ENV.fetch('RESEED_API_KEY', nil) } }

before do
allow(Rake::Task['test_seeds:destroy']).to receive(:invoke)
allow(Rake::Task['test_seeds:create']).to receive(:invoke)

host! 'test-editor-api.raspberrypi.org'
ENV['RESEED_API_KEY'] = 'my_test_api_key'
end

around do |example|
DatabaseCleaner.clean_with(:truncation)
DatabaseCleaner.strategy = :transaction

DatabaseCleaner.cleaning do
Rails.application.load_tasks
example.run
Rake::Task.clear
end
end

after do
ENV.delete('RESEED_API_KEY')
end

it 'returns OK' do
request
expect(response).to be_ok
end

it 'destroys the test seeds' do
request
expect(Rake::Task['test_seeds:destroy']).to have_received(:invoke)
end

it 'recreates the test seeds' do
request
expect(Rake::Task['test_seeds:create']).to have_received(:invoke)
end

context 'when the host is not allowed' do
before do
host! 'editor-api.raspberrypi.org'
end

it 'returns not found' do
request
expect(response).to be_not_found
end

it 'does not destroy test seeds' do
request
expect(Rake::Task['test_seeds:destroy']).not_to have_received(:invoke)
end

it 'does not recreate test seeds' do
request
expect(Rake::Task['test_seeds:create']).not_to have_received(:invoke)
end
end

context 'when the RESEED_API_KEY is not set in the environment' do
let(:headers) { { 'X-RESEED-API-KEY' => '' } }

before do
ENV.delete('RESEED_API_KEY')
end

it 'returns not found' do
request
expect(response).to be_not_found
end

it 'does not destroy test seeds' do
request
expect(Rake::Task['test_seeds:destroy']).not_to have_received(:invoke)
end

it 'does not recreate test seeds' do
request
expect(Rake::Task['test_seeds:create']).not_to have_received(:invoke)
end
end

context 'when the X-RESEED_API_KEY is incorrect' do
let(:headers) { { 'X-RESEED-API-KEY' => 'my_dodgy_api_key' } }

it 'returns not found' do
request
expect(response).to be_not_found
end

it 'does not destroy test seeds' do
request
expect(Rake::Task['test_seeds:destroy']).not_to have_received(:invoke)
end

it 'does not recreate test seeds' do
request
expect(Rake::Task['test_seeds:create']).not_to have_received(:invoke)
end
end

context 'when requested in production' do
before do
allow(Rails.env).to receive(:test?).and_return(false)
allow(Rails.env).to receive(:production?).and_return(true)
end

it 'returns not found' do
request
expect(response).to be_not_found
end

it 'does not destroy test seeds' do
request
expect(Rake::Task['test_seeds:destroy']).not_to have_received(:invoke)
end

it 'does not recreate test seeds' do
request
expect(Rake::Task['test_seeds:create']).not_to have_received(:invoke)
end
end
end