fix: Make tests database-agnostic and fix fixture loading

- Update migration to support both PostgreSQL (jsonb) and SQLite (json)
- Fix Rails 8 compatibility (remove config.assets)
- Configure Pundit to use current_player instead of current_user
- Add explicit fixture class mappings for engine fixtures
- Configure standalone mode for tests
- Update test fixtures with proper timestamps and structure
- Improve game test to create data programmatically

Tests now run with 10/12 assertions passing. Remaining errors are due to
missing test scenario files, which can be addressed separately.
This commit is contained in:
Claude
2025-11-20 17:25:34 +00:00
parent a9ea621333
commit 32d7150837
14 changed files with 1462 additions and 43 deletions

View File

@@ -17,6 +17,11 @@ module BreakEscape
end
helper_method :current_player
# Tell Pundit to use current_player as the user for authorization
def pundit_user
current_player
end
# Handle authorization errors
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

View File

@@ -1,5 +1,8 @@
class CreateBreakEscapeGames < ActiveRecord::Migration[7.0]
def change
# Detect database adapter
is_postgresql = ActiveRecord::Base.connection.adapter_name.downcase == 'postgresql'
create_table :break_escape_games do |t|
# Polymorphic player
t.references :player, polymorphic: true, null: false, index: true
@@ -8,22 +11,44 @@ class CreateBreakEscapeGames < ActiveRecord::Migration[7.0]
t.references :mission, null: false, foreign_key: { to_table: :break_escape_missions }
# Scenario snapshot (ERB-generated)
t.jsonb :scenario_data, null: false
# Use jsonb for PostgreSQL, json for SQLite
if is_postgresql
t.jsonb :scenario_data, null: false
else
t.json :scenario_data, null: false
end
# Player state
t.jsonb :player_state, null: false, default: {
currentRoom: nil,
unlockedRooms: [],
unlockedObjects: [],
inventory: [],
encounteredNPCs: [],
globalVariables: {},
biometricSamples: [],
biometricUnlocks: [],
bluetoothDevices: [],
notes: [],
health: 100
}
# Use jsonb for PostgreSQL, json for SQLite
if is_postgresql
t.jsonb :player_state, null: false, default: {
currentRoom: nil,
unlockedRooms: [],
unlockedObjects: [],
inventory: [],
encounteredNPCs: [],
globalVariables: {},
biometricSamples: [],
biometricUnlocks: [],
bluetoothDevices: [],
notes: [],
health: 100
}
else
t.json :player_state, null: false, default: {
currentRoom: nil,
unlockedRooms: [],
unlockedObjects: [],
inventory: [],
encounteredNPCs: [],
globalVariables: {},
biometricSamples: [],
biometricUnlocks: [],
bluetoothDevices: [],
notes: [],
health: 100
}.to_json
end
# Metadata
t.string :status, default: 'in_progress', null: false
@@ -38,8 +63,13 @@ class CreateBreakEscapeGames < ActiveRecord::Migration[7.0]
[:player_type, :player_id, :mission_id],
unique: true,
name: 'index_games_on_player_and_mission'
add_index :break_escape_games, :scenario_data, using: :gin
add_index :break_escape_games, :player_state, using: :gin
# GIN indexes only available in PostgreSQL
if is_postgresql
add_index :break_escape_games, :scenario_data, using: :gin
add_index :break_escape_games, :player_state, using: :gin
end
add_index :break_escape_games, :status
end
end

View File

@@ -1,12 +1,5 @@
# Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = "1.0"
# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
# Rails.application.config.assets.precompile += %w[ admin.js admin.css ]
# Asset pipeline configuration removed - Rails 8 uses Propshaft instead of Sprockets
# For Rails engine testing, asset configuration is not required
# The engine serves static assets from public/break_escape/

53
test/dummy/db/schema.rb Normal file
View File

@@ -0,0 +1,53 @@
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2025_11_20_160000) do
create_table "break_escape_demo_users", force: :cascade do |t|
t.datetime "created_at", null: false
t.string "handle", null: false
t.string "role", default: "user", null: false
t.datetime "updated_at", null: false
t.index ["handle"], name: "index_break_escape_demo_users_on_handle", unique: true
end
create_table "break_escape_games", force: :cascade do |t|
t.datetime "completed_at"
t.datetime "created_at", null: false
t.integer "mission_id", null: false
t.integer "player_id", null: false
t.json "player_state", default: "{\"currentRoom\":null,\"unlockedRooms\":[],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}", null: false
t.string "player_type", null: false
t.json "scenario_data", null: false
t.integer "score", default: 0, null: false
t.datetime "started_at"
t.string "status", default: "in_progress", null: false
t.datetime "updated_at", null: false
t.index ["mission_id"], name: "index_break_escape_games_on_mission_id"
t.index ["player_type", "player_id", "mission_id"], name: "index_games_on_player_and_mission", unique: true
t.index ["player_type", "player_id"], name: "index_break_escape_games_on_player"
t.index ["status"], name: "index_break_escape_games_on_status"
end
create_table "break_escape_missions", force: :cascade do |t|
t.datetime "created_at", null: false
t.text "description"
t.integer "difficulty_level", default: 1, null: false
t.string "display_name", null: false
t.string "name", null: false
t.boolean "published", default: false, null: false
t.datetime "updated_at", null: false
t.index ["name"], name: "index_break_escape_missions_on_name", unique: true
t.index ["published"], name: "index_break_escape_missions_on_published"
end
add_foreign_key "break_escape_games", "break_escape_missions", column: "mission_id"
end

1303
test/dummy/log/test.log Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1 @@
022d8111afae5d7b817e22ced18d14e9fbbfc47836a0997a9df1dd5463dec5baddf264a995609fc7474d10805f47cf8860d3653f4baf680b5ae7415a9a92415b

View File

@@ -1,5 +0,0 @@
test_user:
handle: test_user
other_user:
handle: other_user

View File

@@ -1,7 +0,0 @@
active_game:
player: test_user (BreakEscape::DemoUser)
mission: ceo_exfil
scenario_data: { "startRoom": "reception", "rooms": {} }
player_state: { "currentRoom": "reception", "unlockedRooms": ["reception"] }
status: in_progress
score: 0

View File

@@ -0,0 +1,9 @@
test_user:
handle: test_user
created_at: <%= Time.now %>
updated_at: <%= Time.now %>
other_user:
handle: other_user
created_at: <%= Time.now %>
updated_at: <%= Time.now %>

View File

@@ -4,6 +4,8 @@ ceo_exfil:
description: Test scenario
published: true
difficulty_level: 3
created_at: <%= Time.now %>
updated_at: <%= Time.now %>
unpublished:
name: test_unpublished
@@ -11,3 +13,5 @@ unpublished:
description: Not visible
published: false
difficulty_level: 1
created_at: <%= Time.now %>
updated_at: <%= Time.now %>

View File

@@ -3,7 +3,26 @@ require 'test_helper'
module BreakEscape
class GameTest < ActiveSupport::TestCase
setup do
@game = games(:active_game)
@mission = break_escape_missions(:ceo_exfil)
@player = break_escape_demo_users(:test_user)
@game = Game.create!(
mission: @mission,
player: @player,
scenario_data: { "startRoom" => "reception", "rooms" => {} },
player_state: {
"currentRoom" => "reception",
"unlockedRooms" => ["reception"],
"unlockedObjects" => [],
"inventory" => [],
"encounteredNPCs" => [],
"globalVariables" => {},
"biometricSamples" => [],
"biometricUnlocks" => [],
"bluetoothDevices" => [],
"notes" => [],
"health" => 100
}
)
end
test "should belong to player and mission" do

View File

@@ -15,12 +15,12 @@ module BreakEscape
end
test "published scope returns only published missions" do
assert_includes Mission.published, missions(:ceo_exfil)
assert_not_includes Mission.published, missions(:unpublished)
assert_includes Mission.published, break_escape_missions(:ceo_exfil)
assert_not_includes Mission.published, break_escape_missions(:unpublished)
end
test "scenario_path returns correct path" do
mission = missions(:ceo_exfil)
mission = break_escape_missions(:ceo_exfil)
expected = Rails.root.join('app', 'assets', 'scenarios', 'ceo_exfil')
assert_equal expected, mission.scenario_path
end

View File

@@ -1,5 +1,6 @@
# Configure Rails Environment
ENV["RAILS_ENV"] = "test"
ENV["BREAK_ESCAPE_STANDALONE"] = "true" # Use standalone mode for tests
require_relative "../test/dummy/config/environment"
ActiveRecord::Migrator.migrations_paths = [ File.expand_path("../test/dummy/db/migrate", __dir__) ]
@@ -11,5 +12,18 @@ if ActiveSupport::TestCase.respond_to?(:fixture_paths=)
ActiveSupport::TestCase.fixture_paths = [ File.expand_path("fixtures", __dir__) ]
ActionDispatch::IntegrationTest.fixture_paths = ActiveSupport::TestCase.fixture_paths
ActiveSupport::TestCase.file_fixture_path = File.expand_path("fixtures", __dir__) + "/files"
ActiveSupport::TestCase.fixtures :all
# Map fixture names to model classes
ActiveSupport::TestCase.set_fixture_class(
break_escape_missions: BreakEscape::Mission,
break_escape_demo_users: BreakEscape::DemoUser
)
ActiveSupport::TestCase.fixtures :break_escape_missions, :break_escape_demo_users
end
# Reload configuration after setting ENV variable
BreakEscape.configure do |config|
config.standalone_mode = true
config.demo_user_handle = 'test_user'
end