4 Commits

Author SHA1 Message Date
3a136865b0 Added some more tests; changed URLs for model list pages and added redirects
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2025-08-12 18:22:22 -04:00
Gregory Ballantine
f40d69a98d Using before hook for index route
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-12 16:26:48 -04:00
Gregory Ballantine
40cfdcc2a3 Changed naming from Routes to Controllers; fixed some Sinatra modular layout stuff; added RSpec for testing and some basic tests
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-12 16:15:43 -04:00
Gregory Ballantine
260d0d1268 Added rspec testing. It should work, but doesn't for unknown reasons 2025-08-12 15:35:46 -04:00
22 changed files with 306 additions and 67 deletions

View File

@@ -1,4 +1,6 @@
require: rubocop-sequel plugins:
- rubocop-rspec
- rubocop-sequel
AllCops: AllCops:
NewCops: enable NewCops: enable

View File

@@ -15,5 +15,11 @@ group :development, :test do
# rubocop and extensions for code style # rubocop and extensions for code style
gem 'rubocop' gem 'rubocop'
gem 'rubocop-rspec'
gem 'rubocop-sequel' gem 'rubocop-sequel'
end end
group :test do
gem 'rspec'
gem 'rack-test'
end

View File

@@ -4,6 +4,7 @@ GEM
ast (2.4.3) ast (2.4.3)
base64 (0.3.0) base64 (0.3.0)
bigdecimal (3.2.2) bigdecimal (3.2.2)
diff-lcs (1.6.2)
ffi (1.17.2-aarch64-linux-gnu) ffi (1.17.2-aarch64-linux-gnu)
ffi (1.17.2-aarch64-linux-musl) ffi (1.17.2-aarch64-linux-musl)
ffi (1.17.2-arm-linux-gnu) ffi (1.17.2-arm-linux-gnu)
@@ -41,6 +42,8 @@ GEM
rack-session (2.1.1) rack-session (2.1.1)
base64 (>= 0.1.0) base64 (>= 0.1.0)
rack (>= 3.0.0) rack (>= 3.0.0)
rack-test (2.2.0)
rack (>= 1.3)
rainbow (3.1.1) rainbow (3.1.1)
rb-fsevent (0.11.2) rb-fsevent (0.11.2)
rb-inotify (0.11.1) rb-inotify (0.11.1)
@@ -48,6 +51,19 @@ GEM
regexp_parser (2.10.0) regexp_parser (2.10.0)
rerun (0.14.0) rerun (0.14.0)
listen (~> 3.0) listen (~> 3.0)
rspec (3.13.1)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.5)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-support (3.13.4)
rubocop (1.76.1) rubocop (1.76.1)
json (~> 2.3) json (~> 2.3)
language_server-protocol (~> 3.17.0.2) language_server-protocol (~> 3.17.0.2)
@@ -62,6 +78,9 @@ GEM
rubocop-ast (1.45.1) rubocop-ast (1.45.1)
parser (>= 3.3.7.2) parser (>= 3.3.7.2)
prism (~> 1.4) prism (~> 1.4)
rubocop-rspec (3.6.0)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-sequel (0.4.1) rubocop-sequel (0.4.1)
lint_roller (~> 1.1) lint_roller (~> 1.1)
rubocop (>= 1.72.1, < 2) rubocop (>= 1.72.1, < 2)
@@ -112,8 +131,11 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
logger logger
puma (~> 6.6) puma (~> 6.6)
rack-test
rerun rerun
rspec
rubocop rubocop
rubocop-rspec
rubocop-sequel rubocop-sequel
sequel (~> 5.92) sequel (~> 5.92)
sinatra (~> 4.1) sinatra (~> 4.1)

View File

@@ -24,8 +24,12 @@ namespace :server do
end end
namespace :test do namespace :test do
task :unit do
system("rspec")
end
task :rubocop do task :rubocop do
system("rubocop src/") system("rubocop src/ spec/")
end end
end end

View File

@@ -1,7 +1,5 @@
# Load application config # Load application config
require_relative 'src/config.rb'
$conf = Config.new(File.join(__dir__, 'config/defaults.yaml'))
root = ::File.dirname(__FILE__) root = ::File.dirname(__FILE__)
require ::File.join( root, 'src', 'app' ) require ::File.join( root, 'src', 'server' )
run GameData.new run GameData.new

View File

@@ -0,0 +1,41 @@
# frozen_string_literal: true
require_relative '../spec_helper'
RSpec.describe(BenchmarkController) do
# /benchmark - redirects to /benchmark/list
describe 'GET /benchmark' do
before { get '/benchmark' }
it 'Benchmark base route redirects to /benchmark/list' do
expect(last_response).to(be_redirect)
expect(last_response['Location']).to(eq("#{BASE_URL}/benchmark/list"))
end
end
# /benchmark/list - displays a table of benchmarks
describe 'GET /benchmark/list' do
before { get '/benchmark/list' }
it 'Benchmark list page returns 200.' do
expect(last_response).to(be_ok)
end
it "Benchmark list page contains 'List of benchmarks' on page." do
expect(last_response.body).to(include('List of benchmarks'))
end
end
# /benchmark/add - form for adding benchmark
describe 'GET /benchmark/add' do
before { get '/benchmark/add' }
it 'Benchmark add page returns 200.' do
expect(last_response).to(be_ok)
end
it "Benchmark add page contains 'Add new benchmark' on page." do
expect(last_response.body).to(include('Add new benchmark'))
end
end
end

View File

@@ -0,0 +1,41 @@
# frozen_string_literal: true
require_relative '../spec_helper'
RSpec.describe(HardwareController) do
# /hardware - redirects to /hardware/list
describe 'GET /hardware' do
before { get '/hardware' }
it 'Hardware base route redirects to /hardware/list' do
expect(last_response).to(be_redirect)
expect(last_response['Location']).to(eq("#{BASE_URL}/hardware/list"))
end
end
# /hardware/list - displays a table of hardwares
describe 'GET /hardware/list' do
before { get '/hardware/list' }
it 'Hardware list page returns 200.' do
expect(last_response).to(be_ok)
end
it "Hardware list page contains 'List of hardware' on page." do
expect(last_response.body).to(include('List of hardware'))
end
end
# /hardware/add - form for adding hardware
describe 'GET /hardware/add' do
before { get '/hardware/add' }
it 'Hardware add page returns 200.' do
expect(last_response).to(be_ok)
end
it "Hardware add page contains 'Add new hardware' on page." do
expect(last_response.body).to(include('Add new hardware'))
end
end
end

View File

@@ -0,0 +1,21 @@
# frozen_string_literal: true
require_relative '../spec_helper'
RSpec.describe(IndexController) do
describe 'GET /' do
before { get '/' }
it 'Dashboard returns 200.' do
expect(last_response).to(be_ok)
end
it "Dashboard contains 'Game Data' on page (nav bar should be loaded)." do
expect(last_response.body).to(include('Game Data'))
end
it "Dashboard contains 'Ruby version' on page (footer should be loaded)." do
expect(last_response.body).to(include('Ruby version'))
end
end
end

View File

@@ -0,0 +1,17 @@
# frozen_string_literal: true
require_relative '../spec_helper'
RSpec.describe(ReportsController) do
describe 'GET /report' do
before { get '/report' }
it 'Reports page returns 200.' do
expect(last_response).to(be_ok)
end
it "Reports page contains 'Generate report' on page." do
expect(last_response.body).to(include('Generate report'))
end
end
end

View File

@@ -0,0 +1,41 @@
# frozen_string_literal: true
require_relative '../spec_helper'
RSpec.describe(TestController) do
# /test - redirects to /test/list
describe 'GET /test' do
before { get '/test' }
it 'Test base route redirects to /test/list' do
expect(last_response).to(be_redirect)
expect(last_response['Location']).to(eq("#{BASE_URL}/test/list"))
end
end
# /test/list - displays a table of tests
describe 'GET /test/list' do
before { get '/test/list' }
it 'Test list page returns 200.' do
expect(last_response).to(be_ok)
end
it "Test list page contains 'List of tests' on page." do
expect(last_response.body).to(include('List of tests'))
end
end
# /test/add - form for adding test
describe 'GET /test/add' do
before { get '/test/add' }
it 'Test add page returns 200.' do
expect(last_response).to(be_ok)
end
it "Test add page contains 'Add new test' on page." do
expect(last_response.body).to(include('Add new test'))
end
end
end

24
spec/spec_helper.rb Normal file
View File

@@ -0,0 +1,24 @@
# frozen_string_literal: true
ENV['APP_ENV'] = 'test'
require_relative '../src/server'
require 'rspec'
require 'rack/test'
# setting this here so all redirect tests can reference the same base URL
BASE_URL = 'http://example.org'
module RSpecMixin
include Rack::Test::Methods
def app
GameData
end
end
RSpec.configure do |config|
config.include(RSpecMixin)
end

View File

@@ -1,41 +0,0 @@
# frozen_string_literal: true
require 'sinatra/base'
require 'sequel'
require 'sqlite3'
# Load the Sequel timestamps plugin
Sequel::Model.plugin(:timestamps)
# Initialize Sequel gem for database actions
DB = Sequel.connect(adapter: $conf.get('database.adapter'), database: $conf.get('database.database'))
# Load in routes (must happen after Sequel is loaded!)
require_relative 'routes/api1'
require_relative 'routes/benchmark'
require_relative 'routes/hardware'
require_relative 'routes/index'
require_relative 'routes/reports'
require_relative 'routes/result'
require_relative 'routes/test'
# GameData - main app that gets launched
# - inherits from Sinatra::Base to instantiate the server
# - sets up some base app configuration
# - registers route classes with the base app
class GameData < Sinatra::Base
enable :sessions
# Set up static file serving
enable :static
set :public_folder, File.join(__dir__, '/../public')
use IndexRoutes
use HardwareRoutes
use BenchmarkRoutes
use TestRoutes
use ResultRoutes
use ReportsRoutes
use APIv1Routes
end

View File

@@ -2,13 +2,13 @@
require 'sinatra/json' require 'sinatra/json'
require_relative '../server' require_relative 'base_controller'
require_relative '../models/benchmark' require_relative '../models/benchmark'
require_relative '../models/test' require_relative '../models/test'
require_relative '../models/result' require_relative '../models/result'
# /api/v1 routes # /api/v1 routes
class APIv1Routes < Server class APIv1Controller < BaseController
get '/api/v1/benchmark/details' do get '/api/v1/benchmark/details' do
benchmark_id = params[:benchmark_id] benchmark_id = params[:benchmark_id]

View File

@@ -0,0 +1,15 @@
# frozen_string_literal: true
require 'sinatra/base'
# BaseController - base modular Sinatra app class
class BaseController < Sinatra::Base
# Register view helpers
require_relative '../helpers'
helpers Helpers
# Set up our view engine
set :views, File.join(settings.root, '/../../views')
end

View File

@@ -1,12 +1,16 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative '../server' require_relative 'base_controller'
require_relative '../models/benchmark' require_relative '../models/benchmark'
# /benchmark routes # /benchmark routes
class BenchmarkRoutes < Server class BenchmarkController < BaseController
get '/benchmark' do get '/benchmark' do
redirect('/benchmark/list')
end
get '/benchmark/list' do
benchmarks = Benchmark.reverse(:updated_at).limit(10).all() benchmarks = Benchmark.reverse(:updated_at).limit(10).all()
erb :'benchmark/index', locals: { erb :'benchmark/index', locals: {

View File

@@ -1,13 +1,17 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative '../server' require_relative 'base_controller'
require_relative '../models/hardware' require_relative '../models/hardware'
require_relative '../models/benchmark' require_relative '../models/benchmark'
# /hardware routes # /hardware routes
class HardwareRoutes < Server class HardwareController < BaseController
get '/hardware' do get '/hardware' do
redirect('/hardware/list')
end
get '/hardware/list' do
hardware = Hardware.reverse(:updated_at).limit(10).all() hardware = Hardware.reverse(:updated_at).limit(10).all()
erb :'hardware/index', locals: { erb :'hardware/index', locals: {

View File

@@ -1,10 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative '../server' require_relative 'base_controller'
require_relative '../models/test' require_relative '../models/test'
# / (top-level) routes # / (top-level) routes
class IndexRoutes < Server class IndexController < BaseController
get '/' do get '/' do
tests = Test.reverse(:updated_at).limit(10).all() tests = Test.reverse(:updated_at).limit(10).all()

View File

@@ -2,13 +2,13 @@
require 'sinatra/json' require 'sinatra/json'
require_relative '../server' require_relative 'base_controller'
require_relative '../models/benchmark' require_relative '../models/benchmark'
require_relative '../models/result' require_relative '../models/result'
require_relative '../models/test' require_relative '../models/test'
# /reports routes # /reports routes
class ReportsRoutes < Server class ReportsController < BaseController
get '/report' do get '/report' do
benchmarks = Benchmark.order(:name).all() benchmarks = Benchmark.order(:name).all()

View File

@@ -1,10 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative '../server' require_relative 'base_controller'
require_relative '../models/result' require_relative '../models/result'
# /result routes # /result routes
class ResultRoutes < Server class ResultController < BaseController
post '/result/add' do post '/result/add' do
result_minimum = params[:result_minimum] if params.key?(:result_minimum) result_minimum = params[:result_minimum] if params.key?(:result_minimum)

View File

@@ -1,14 +1,18 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative '../server' require_relative 'base_controller'
require_relative '../models/benchmark' require_relative '../models/benchmark'
require_relative '../models/hardware' require_relative '../models/hardware'
require_relative '../models/test' require_relative '../models/test'
# /test routes # /test routes
class TestRoutes < Server class TestController < BaseController
get '/test' do get '/test' do
redirect('/test/list')
end
get '/test/list' do
tests = Test.reverse(:updated_at).limit(10).all() tests = Test.reverse(:updated_at).limit(10).all()
erb :'test/index', locals: { erb :'test/index', locals: {

44
src/server.rb Executable file → Normal file
View File

@@ -1,15 +1,45 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'sinatra/base' require 'sinatra/base'
require 'sequel'
require 'sqlite3'
# Server - base modular Sinatra app class require_relative 'config'
class Server < Sinatra::Base
# Register view helpers $conf = Config.new(File.join(__dir__, 'config/defaults.yaml'))
require_relative 'helpers'
helpers Helpers
# Set up our view engine # Load the Sequel timestamps plugin
set :views, File.join(settings.root, '/../views') Sequel::Model.plugin(:timestamps)
# Initialize Sequel gem for database actions
DB = Sequel.connect(adapter: $conf.get('database.adapter'), database: $conf.get('database.database'))
# Load in routes (must happen after Sequel is loaded!)
require_relative 'controllers/api1'
require_relative 'controllers/benchmark'
require_relative 'controllers/hardware'
require_relative 'controllers/index'
require_relative 'controllers/reports'
require_relative 'controllers/result'
require_relative 'controllers/test'
# GameData - main app that gets launched
# - inherits from Sinatra::Base to instantiate the server
# - sets up some base app configuration
# - registers route classes with the base app
class GameData < Sinatra::Base
enable :sessions
# Set up static file serving
enable :static
set :public_folder, File.join(__dir__, '/../public')
use IndexController
use HardwareController
use BenchmarkController
use TestController
use ResultController
use ReportsController
use APIv1Controller
end end

View File

@@ -1,3 +1,9 @@
<div class="row">
<div class="col-12">
<h1>Generate report</h1>
</div>
</div>
<div class="row"> <div class="row">
<form class="col-12" action="/reports" method="post"> <form class="col-12" action="/reports" method="post">
<div class="row mb-3"> <div class="row mb-3">