Refactor tests and improve NPC handling

- Updated NPC ink loading tests to ensure proper handling of missing story files.
- Adjusted lazy loading tests for rooms to enhance clarity and maintainability.
- Enhanced unlock system tests by adding inventory checks for keys.
- Refined filtered scenario tests to ensure accurate preservation of game state.
- Improved game model tests to validate unlock functionality with various inventory scenarios.
This commit is contained in:
Z. Cliffe Schreuders
2025-11-25 16:28:18 +00:00
parent b317103c83
commit 26fc297ad8
16 changed files with 19110 additions and 108 deletions

View File

@@ -92,6 +92,11 @@ module BreakEscape
return render_error('Scenario data not available', :internal_server_error)
end
# Check if room exists in scenario FIRST (before accessibility check)
unless @game.scenario_data['rooms']&.key?(room_id)
return render_error("Room not found: #{room_id}", :not_found)
end
# Check if room is accessible (starting room OR in unlockedRooms)
is_start_room = @game.scenario_data['startRoom'] == room_id
is_unlocked = @game.player_state['unlockedRooms']&.include?(room_id)
@@ -628,14 +633,14 @@ module BreakEscape
return npc if npc['id'] == npc_id
end
end
# Log available NPCs for debugging
if available_npcs.any?
Rails.logger.debug "[BreakEscape] Available NPCs: #{available_npcs.join(', ')}"
else
Rails.logger.warn "[BreakEscape] No NPCs found in scenario data"
end
nil
end

View File

@@ -3,9 +3,9 @@ module BreakEscape
def index
@missions = if defined?(Pundit)
policy_scope(Mission)
else
else
Mission.published
end
end
# Filter by collection if specified
if params[:collection].present?

View File

@@ -1,33 +1,33 @@
module BreakEscape
class StaticFilesController < BreakEscape::ApplicationController
skip_before_action :verify_authenticity_token
def serve
# Use the BreakEscape engine's root, not Rails.root
engine_root = BreakEscape::Engine.root
# Determine the actual file path based on the request URL
request_path = request.path
# Map different URL patterns to their file locations
# Remember: request_path will be /break_escape/css/... when mounted at /break_escape
file_path = case request_path
when %r{^/break_escape/css/}
when %r{^/break_escape/css/}
engine_root.join('public', 'break_escape', 'css', params[:path])
when %r{^/break_escape/js/}
when %r{^/break_escape/js/}
engine_root.join('public', 'break_escape', 'js', params[:path])
when %r{^/break_escape/assets/}
when %r{^/break_escape/assets/}
engine_root.join('public', 'break_escape', 'assets', params[:path])
when %r{^/break_escape/stylesheets/}
when %r{^/break_escape/stylesheets/}
engine_root.join('public', 'break_escape', 'css', params[:path])
when %r{^/break_escape/.*\.html$}
when %r{^/break_escape/.*\.html$}
# HTML test files like /break_escape/test-assets.html
engine_root.join('public', 'break_escape', "#{params[:filename]}.html")
else
else
# Fallback for any other pattern
engine_root.join('public', 'break_escape', params[:path])
end
end
# Security: prevent directory traversal
base_path = engine_root.join('public', 'break_escape').to_s
unless file_path.to_s.start_with?(base_path)
@@ -40,7 +40,7 @@ module BreakEscape
# Determine content type
content_type = determine_content_type(file_path.to_s)
send_file file_path, type: content_type, disposition: 'inline'
rescue Errno::ENOENT
render_not_found
@@ -90,4 +90,3 @@ module BreakEscape
end
end
end

View File

@@ -61,22 +61,22 @@ module BreakEscape
# Check if player has a specific key in inventory
def has_key_in_inventory?(key_id)
inventory = player_state['inventory'] || []
Rails.logger.info "[BreakEscape] Checking for key #{key_id} in inventory (#{inventory.length} items)"
# Check for key with matching key_id
found = inventory.any? do |item|
is_match = item['scenarioData']&.dig('key_id') == key_id ||
is_match = item['scenarioData']&.dig('key_id') == key_id ||
item['scenarioData']&.dig('id') == key_id ||
item['key_id'] == key_id ||
item['id'] == key_id
item_key_id = item['scenarioData']&.dig('key_id') || item['key_id']
item_name = item['scenarioData']&.dig('name') || item['name']
Rails.logger.debug "[BreakEscape] Inventory item: name=#{item_name}, key_id=#{item_key_id}, is_match=#{is_match}"
is_match
end
Rails.logger.info "[BreakEscape] Key #{key_id} found in inventory: #{found}"
found
end
@@ -84,9 +84,9 @@ module BreakEscape
# Check if player has a lockpick in inventory
def has_lockpick_in_inventory?
inventory = player_state['inventory'] || []
Rails.logger.info "[BreakEscape] Checking for lockpick in inventory (#{inventory.length} items)"
# Check for lockpick item in scenarioData or at top level
found = inventory.any? do |item|
is_lockpick = item['scenarioData']&.dig('type') == 'lockpick' ||
@@ -94,7 +94,7 @@ module BreakEscape
Rails.logger.debug "[BreakEscape] Inventory item: type=#{item['type']}, scenarioData.type=#{item['scenarioData']&.dig('type')}, is_lockpick=#{is_lockpick}"
is_lockpick
end
Rails.logger.info "[BreakEscape] Lockpick found in inventory: #{found}"
found
end
@@ -149,7 +149,7 @@ module BreakEscape
# Returns scenario data without room contents for lazy-loading
# This significantly reduces initial payload by only sending metadata
filtered = scenario_data.deep_dup
# Remove all room contents - they'll be lazy-loaded via /room/:room_id endpoint
if filtered['rooms'].present?
filtered['rooms'].each do |room_id, room_data|
@@ -160,12 +160,12 @@ module BreakEscape
%w[type connections locked lockType requires difficulty door_sign keyPins].each do |field|
kept_fields[field] = room_data[field] if room_data.key?(field)
end
# Replace room data with filtered version
filtered['rooms'][room_id] = kept_fields
end
end
filtered
end
@@ -199,7 +199,7 @@ module BreakEscape
# If room is LOCKED, it requires validation
if room['locked']
Rails.logger.info "[BreakEscape] Room is LOCKED, method must be valid: #{method}"
# Handle method='unlocked' - REJECT for locked doors
if method == 'unlocked'
Rails.logger.warn "[BreakEscape] SECURITY VIOLATION: Client sent method='unlocked' for LOCKED door: #{target_id}"
@@ -240,15 +240,15 @@ module BreakEscape
end
Rails.logger.info "[BreakEscape] validate_unlock returning: #{result}"
return result
result
else
# Room is unlocked
if method == 'unlocked'
Rails.logger.info "[BreakEscape] Door is unlocked in scenario data, granting access"
return true
true
else
Rails.logger.warn "[BreakEscape] Client sent method='#{method}' for UNLOCKED door: #{target_id}, but room has no lock"
return true # Still allow access since room is unlocked
true # Still allow access since room is unlocked
end
end
else
@@ -373,11 +373,11 @@ module BreakEscape
def initialize_player_state
# Ensure player_state is always a hash
self.player_state = {} unless self.player_state.is_a?(Hash)
self.player_state['currentRoom'] ||= scenario_data['startRoom']
self.player_state['unlockedRooms'] ||= [scenario_data['startRoom']]
self.player_state['unlockedObjects'] ||= []
# Ensure inventory is always an array, even if it was corrupted
unless self.player_state['inventory'].is_a?(Array)
self.player_state['inventory'] = []