diff --git a/app/models/break_escape/game.rb b/app/models/break_escape/game.rb index 467bb6e..c24a955 100644 --- a/app/models/break_escape/game.rb +++ b/app/models/break_escape/game.rb @@ -159,6 +159,12 @@ module BreakEscape return false end + # NPC unlock: Validate NPC has been encountered and has permission to unlock this door + if method == 'npc' + npc_id = attempt # NPC id is passed as 'attempt' + return validate_npc_unlock(npc_id, target_id) + end + case method when 'key', 'lockpick', 'biometric', 'bluetooth', 'rfid' # Client validated the unlock - trust it @@ -193,6 +199,12 @@ module BreakEscape return false end + # NPC unlock: Validate NPC has been encountered and has permission to unlock this object + if method == 'npc' + npc_id = attempt # NPC id is passed as 'attempt' + return validate_npc_unlock(npc_id, target_id) + end + case method when 'key', 'lockpick', 'biometric', 'bluetooth', 'rfid' # Client validated the unlock - trust it @@ -209,6 +221,44 @@ module BreakEscape end end + # Validate NPC unlock permission + def validate_npc_unlock(npc_id, target_id) + Rails.logger.info "[BreakEscape] Validating NPC unlock: npc=#{npc_id}, target=#{target_id}" + + # Find NPC in scenario data + npc = find_npc_in_scenario(npc_id) + unless npc + Rails.logger.warn "[BreakEscape] NPC not found: #{npc_id}" + return false + end + + # Check if player has encountered this NPC + unless player_state['encounteredNPCs']&.include?(npc_id) + Rails.logger.warn "[BreakEscape] Player has not encountered NPC: #{npc_id}" + return false + end + + # Check if NPC has permission to unlock this target + unlockable = npc['unlockable'] + unless unlockable.is_a?(Array) && unlockable.include?(target_id) + Rails.logger.warn "[BreakEscape] NPC #{npc_id} does not have permission to unlock #{target_id}" + return false + end + + Rails.logger.info "[BreakEscape] NPC unlock validated: #{npc_id} can unlock #{target_id}" + true + end + + # Find NPC in scenario data + def find_npc_in_scenario(npc_id) + scenario_data['rooms']&.each do |_room_id, room_data| + room_data['npcs']&.each do |npc| + return npc if npc['id'] == npc_id + end + end + nil + end + private def filter_requires_and_contents_recursive(obj) diff --git a/public/break_escape/js/minigames/person-chat/person-chat-conversation.js b/public/break_escape/js/minigames/person-chat/person-chat-conversation.js index 90fddd1..1e23fdb 100644 --- a/public/break_escape/js/minigames/person-chat/person-chat-conversation.js +++ b/public/break_escape/js/minigames/person-chat/person-chat-conversation.js @@ -442,19 +442,58 @@ export default class PersonChatConversation { * Handle unlock_door tag * @param {string} doorId - Door to unlock */ - handleUnlockDoor(doorId) { + async handleUnlockDoor(doorId) { if (!doorId) return; - - console.log(`🔓 Unlocking door: ${doorId}`); - - // Dispatch event for interactions system to handle - const event = new CustomEvent('ink-action', { - detail: { - action: 'unlock_door', - doorId: doorId + + console.log(`🔓 NPC unlocking door: ${doorId}`); + + // SECURITY: Call server API with NPC unlock method + // Server will validate NPC has been encountered and has permission + const apiClient = window.ApiClient || window.APIClient; + const gameId = window.breakEscapeConfig?.gameId; + + if (!apiClient || !gameId) { + console.error('ApiClient or gameId not available for NPC unlock'); + window.gameAlert('Failed to unlock door', 'error', 'Error', 3000); + return; + } + + try { + const response = await apiClient.unlock('door', doorId, this.npc.id, 'npc'); + + if (response.success) { + console.log(`✅ NPC ${this.npc.id} successfully unlocked door ${doorId}`); + window.gameAlert(`Door unlocked!`, 'success', 'Access Granted', 3000); + + // Trigger door unlock visual update if door sprite exists + const doorSprite = this.findDoorSprite(doorId); + if (doorSprite && window.unlockDoor) { + window.unlockDoor(doorSprite, response.roomData); + } + } else { + console.error('NPC unlock failed:', response); + window.gameAlert('Failed to unlock door', 'error', 'Error', 3000); } - }); - window.dispatchEvent(event); + } catch (error) { + console.error('NPC unlock error:', error); + window.gameAlert('Failed to unlock door', 'error', 'Error', 3000); + } + } + + /** + * Find door sprite by room ID + * @param {string} roomId - Room ID to find door for + */ + findDoorSprite(roomId) { + // Search through all door sprites in the game + if (!window.game || !window.game.children) return null; + + const doors = window.game.children.list.filter(child => + child.doorProperties && + child.doorProperties.connectedRoom === roomId + ); + + return doors.length > 0 ? doors[0] : null; } /** diff --git a/test/dummy/log/test.log b/test/dummy/log/test.log index 97ba2b9..47b2694 100644 --- a/test/dummy/log/test.log +++ b/test/dummy/log/test.log @@ -21060,3 +21060,989 @@ Processing by BreakEscape::GamesController#unlock as HTML TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 Completed 200 OK in 4ms (Views: 0.1ms | ActiveRecord: 0.6ms (5 queries, 0 cached) | GC: 0.0ms) TRANSACTION (0.1ms) rollback transaction + ActiveRecord::InternalMetadata Load (0.4ms) SELECT * FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = ? ORDER BY "ar_internal_metadata"."key" ASC LIMIT 1 [[nil, "schema_sha1"]] + ActiveRecord::SchemaMigration Load (0.2ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC + TRANSACTION (0.3ms) begin transaction +  (0.6ms) PRAGMA foreign_keys +  (0.0ms) PRAGMA defer_foreign_keys +  (0.1ms) PRAGMA defer_foreign_keys = ON +  (0.0ms) PRAGMA foreign_keys = OFF + Fixtures Load (0.2ms) DELETE FROM "break_escape_demo_users"; +DELETE FROM "break_escape_missions"; +INSERT INTO "break_escape_demo_users" ("id", "handle", "created_at", "updated_at") VALUES (149617800, 'test_user', '2025-11-21 23:40:56', '2025-11-21 23:40:56'); +INSERT INTO "break_escape_demo_users" ("id", "handle", "created_at", "updated_at") VALUES (618102942, 'other_user', '2025-11-21 23:40:56', '2025-11-21 23:40:56'); +INSERT INTO "break_escape_missions" ("id", "name", "display_name", "description", "published", "difficulty_level", "created_at", "updated_at") VALUES (418560898, 'ceo_exfil', 'CEO Exfiltration', 'Test scenario', 1, 3, '2025-11-21 23:40:56', '2025-11-21 23:40:56'); +INSERT INTO "break_escape_missions" ("id", "name", "display_name", "description", "published", "difficulty_level", "created_at", "updated_at") VALUES (636030761, 'test_unpublished', 'Unpublished Test', 'Not visible', 0, 1, '2025-11-21 23:40:56', '2025-11-21 23:40:56') +  (0.0ms) PRAGMA defer_foreign_keys = 0 +  (0.0ms) PRAGMA foreign_keys = 1 + TRANSACTION (7.1ms) commit transaction +  (0.1ms) PRAGMA foreign_key_check + TRANSACTION (0.1ms) begin transaction +-------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_SECURITY:_NPC_unlock_fails_if_unlockable_is_not_an_array +-------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.2ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.7ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:40:56.331119"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:40:56.330405"], ["updated_at", "2025-11-21 23:40:56.330405"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Update (0.3ms) UPDATE "break_escape_games" SET "scenario_data" = ?, "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}],\"npcs\":[{\"id\":\"helper_npc\",\"displayName\":\"Helpful Contact\",\"unlockable\":\"office_pin\"}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[\"helper_npc\"],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:40:56.334523"], ["id", 1]] + TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:40:56 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>"helper_npc", "method"=>"npc", "id"=>"1"} + BreakEscape::Game Load (0.2ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=helper_npc, method=npc +[BreakEscape] Validating NPC unlock: npc=helper_npc, target=office_pin +[BreakEscape] NPC helper_npc does not have permission to unlock office_pin +Completed 422 Unprocessable Content in 33ms (Views: 0.4ms | ActiveRecord: 0.4ms (3 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +------------------------------------------------------------------------------------------------ +BreakEscape::UnlockSystemTest: test_SECURITY:_NPC_unlock_fails_if_player_has_not_encountered_NPC +------------------------------------------------------------------------------------------------ + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.5ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:40:56.467243"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:40:56.467011"], ["updated_at", "2025-11-21 23:40:56.467011"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Update (0.2ms) UPDATE "break_escape_games" SET "scenario_data" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}],\"npcs\":[{\"id\":\"helper_npc\",\"displayName\":\"Helpful Contact\",\"unlockable\":[\"office_pin\"]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["updated_at", "2025-11-21 23:40:56.469132"], ["id", 1]] + TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:40:56 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>"helper_npc", "method"=>"npc", "id"=>"1"} + BreakEscape::Game Load (0.2ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=helper_npc, method=npc +[BreakEscape] Validating NPC unlock: npc=helper_npc, target=office_pin +[BreakEscape] Player has not encountered NPC: helper_npc +Completed 422 Unprocessable Content in 2ms (Views: 0.1ms | ActiveRecord: 0.3ms (3 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +------------------------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_NPC_can_unlock_door_if_player_has_encountered_them_and_NPC_has_permission +------------------------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:40:56.477393"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:40:56.477211"], ["updated_at", "2025-11-21 23:40:56.477211"]] + TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1 + TRANSACTION (0.0ms) SAVEPOINT active_record_1 + BreakEscape::Game Update (0.2ms) UPDATE "break_escape_games" SET "scenario_data" = ?, "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}],\"npcs\":[{\"id\":\"helper_npc\",\"displayName\":\"Helpful Contact\",\"unlockable\":[\"office_pin\",\"office_password\"]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[\"helper_npc\"],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:40:56.478824"], ["id", 1]] + TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:40:56 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>"helper_npc", "method"=>"npc", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=helper_npc, method=npc +[BreakEscape] Validating NPC unlock: npc=helper_npc, target=office_pin +[BreakEscape] NPC unlock validated: helper_npc can unlock office_pin + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.3ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\",\"office_pin\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[\"helper_npc\"],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:40:56.484470"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 5ms (Views: 0.1ms | ActiveRecord: 0.7ms (5 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.0ms) begin transaction +----------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_SECURITY:_NPC_unlock_fails_for_non-existent_NPC +----------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.4ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:40:56.489275"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:40:56.489092"], ["updated_at", "2025-11-21 23:40:56.489092"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Update (0.3ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[\"helper_npc\"],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:40:56.490777"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:40:56 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>"fake_npc", "method"=>"npc", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=fake_npc, method=npc +[BreakEscape] Validating NPC unlock: npc=fake_npc, target=office_pin +[BreakEscape] NPC not found: fake_npc +Completed 422 Unprocessable Content in 2ms (Views: 0.1ms | ActiveRecord: 0.2ms (3 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +----------------------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_SECURITY:_NPC_unlock_fails_if_NPC_doesn't_have_permission_for_that_door +----------------------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.4ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:40:56.497351"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:40:56.497172"], ["updated_at", "2025-11-21 23:40:56.497172"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 + TRANSACTION (0.0ms) SAVEPOINT active_record_1 + BreakEscape::Game Update (0.2ms) UPDATE "break_escape_games" SET "scenario_data" = ?, "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}],\"npcs\":[{\"id\":\"helper_npc\",\"displayName\":\"Helpful Contact\",\"unlockable\":[\"office_password\"]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[\"helper_npc\"],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:40:56.498946"], ["id", 1]] + TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:40:56 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>"helper_npc", "method"=>"npc", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=helper_npc, method=npc +[BreakEscape] Validating NPC unlock: npc=helper_npc, target=office_pin +[BreakEscape] NPC helper_npc does not have permission to unlock office_pin +Completed 422 Unprocessable Content in 2ms (Views: 0.1ms | ActiveRecord: 0.2ms (3 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +------------------------------------------------------------------------------------------------------------------ +BreakEscape::UnlockSystemTest: test_NPC_can_unlock_container_if_player_has_encountered_them_and_NPC_has_permission +------------------------------------------------------------------------------------------------------------------ + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.4ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:40:56.506227"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:40:56.506047"], ["updated_at", "2025-11-21 23:40:56.506047"]] + TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1 + TRANSACTION (0.0ms) SAVEPOINT active_record_1 + BreakEscape::Game Update (0.3ms) UPDATE "break_escape_games" SET "scenario_data" = ?, "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}],\"npcs\":[{\"id\":\"helper_npc\",\"displayName\":\"Helpful Contact\",\"unlockable\":[\"safe_pin\",\"cabinet_password\"]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[\"helper_npc\"],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:40:56.508108"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:40:56 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"object", "targetId"=>"safe_pin", "attempt"=>"helper_npc", "method"=>"npc", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=object, id=safe_pin, attempt=helper_npc, method=npc +[BreakEscape] Found object: id=safe_pin, name=PIN Safe, locked=true, requires=1234 +[BreakEscape] Validating NPC unlock: npc=helper_npc, target=safe_pin +[BreakEscape] NPC unlock validated: helper_npc can unlock safe_pin + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.3ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[\"safe_pin\"],\"inventory\":[],\"encounteredNPCs\":[\"helper_npc\"],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:40:56.524379"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 15ms (Views: 0.1ms | ActiveRecord: 0.7ms (5 queries, 0 cached) | GC: 10.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + ActiveRecord::InternalMetadata Load (0.4ms) SELECT * FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = ? ORDER BY "ar_internal_metadata"."key" ASC LIMIT 1 [[nil, "schema_sha1"]] + ActiveRecord::SchemaMigration Load (0.2ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC + TRANSACTION (0.1ms) begin transaction +  (0.4ms) PRAGMA foreign_keys +  (0.0ms) PRAGMA defer_foreign_keys +  (0.0ms) PRAGMA defer_foreign_keys = ON +  (0.0ms) PRAGMA foreign_keys = OFF + Fixtures Load (0.2ms) DELETE FROM "break_escape_demo_users"; +DELETE FROM "break_escape_missions"; +INSERT INTO "break_escape_demo_users" ("id", "handle", "created_at", "updated_at") VALUES (149617800, 'test_user', '2025-11-21 23:41:20', '2025-11-21 23:41:20'); +INSERT INTO "break_escape_demo_users" ("id", "handle", "created_at", "updated_at") VALUES (618102942, 'other_user', '2025-11-21 23:41:20', '2025-11-21 23:41:20'); +INSERT INTO "break_escape_missions" ("id", "name", "display_name", "description", "published", "difficulty_level", "created_at", "updated_at") VALUES (418560898, 'ceo_exfil', 'CEO Exfiltration', 'Test scenario', 1, 3, '2025-11-21 23:41:20', '2025-11-21 23:41:20'); +INSERT INTO "break_escape_missions" ("id", "name", "display_name", "description", "published", "difficulty_level", "created_at", "updated_at") VALUES (636030761, 'test_unpublished', 'Unpublished Test', 'Not visible', 0, 1, '2025-11-21 23:41:20', '2025-11-21 23:41:20') +  (0.1ms) PRAGMA defer_foreign_keys = 0 +  (0.0ms) PRAGMA foreign_keys = 1 + TRANSACTION (5.2ms) commit transaction +  (0.1ms) PRAGMA foreign_key_check + TRANSACTION (0.1ms) begin transaction +------------------------------------------------------------------------------------------------------------------ +BreakEscape::UnlockSystemTest: test_NPC_can_unlock_container_if_player_has_encountered_them_and_NPC_has_permission +------------------------------------------------------------------------------------------------------------------ + BreakEscape::Mission Load (0.3ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.2ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.6ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.031490"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.030707"], ["updated_at", "2025-11-21 23:41:21.030707"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Update (0.5ms) UPDATE "break_escape_games" SET "scenario_data" = ?, "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}],\"npcs\":[{\"id\":\"helper_npc\",\"displayName\":\"Helpful Contact\",\"unlockable\":[\"safe_pin\",\"cabinet_password\"]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[\"helper_npc\"],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.034592"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"object", "targetId"=>"safe_pin", "attempt"=>"helper_npc", "method"=>"npc", "id"=>"1"} + BreakEscape::Game Load (0.3ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.2ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=object, id=safe_pin, attempt=helper_npc, method=npc +[BreakEscape] Found object: id=safe_pin, name=PIN Safe, locked=true, requires=1234 +[BreakEscape] Validating NPC unlock: npc=helper_npc, target=safe_pin +[BreakEscape] NPC unlock validated: helper_npc can unlock safe_pin + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.3ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[\"safe_pin\"],\"inventory\":[],\"encounteredNPCs\":[\"helper_npc\"],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.150118"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 38ms (Views: 0.5ms | ActiveRecord: 1.0ms (5 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.2ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +-------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_container_with_RFID_lock:_should_trust_client_validation +-------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.4ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.176406"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.176190"], ["updated_at", "2025-11-21 23:41:21.176190"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"object", "targetId"=>"door_rfid", "attempt"=>nil, "method"=>"rfid", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=object, id=door_rfid, attempt=, method=rfid +[BreakEscape] Found object: id=door_rfid, name=RFID Door, locked=true, requires=admin_badge + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[\"door_rfid\"],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.182607"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 4ms (Views: 0.1ms | ActiveRecord: 0.6ms (5 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +--------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_door_with_PIN_lock:_correct_PIN_should_unlock +--------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.186527"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.186348"], ["updated_at", "2025-11-21 23:41:21.186348"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>"9876", "method"=>"pin", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=9876, method=pin + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\",\"office_pin\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.191396"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 4ms (Views: 0.1ms | ActiveRecord: 0.6ms (5 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +----------------------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_SECURITY:_method='unlocked'_only_works_for_actually_unlocked_containers +----------------------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.4ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.195618"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.195431"], ["updated_at", "2025-11-21 23:41:21.195431"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"object", "targetId"=>"chest_unlocked", "attempt"=>nil, "method"=>"unlocked", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=object, id=chest_unlocked, attempt=, method=unlocked +[BreakEscape] Found object: id=chest_unlocked, name=Open Chest, locked=false, requires= +[BreakEscape] Object is unlocked in server data, granting access + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[\"chest_unlocked\"],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.200607"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 3ms (Views: 0.1ms | ActiveRecord: 0.6ms (5 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.0ms) begin transaction +----------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_SECURITY:_NPC_unlock_fails_for_non-existent_NPC +----------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.4ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.204400"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.204209"], ["updated_at", "2025-11-21 23:41:21.204209"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Update (0.2ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[\"helper_npc\"],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.205928"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>"fake_npc", "method"=>"npc", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=fake_npc, method=npc +[BreakEscape] Validating NPC unlock: npc=fake_npc, target=office_pin +[BreakEscape] NPC not found: fake_npc +Completed 422 Unprocessable Content in 2ms (Views: 0.1ms | ActiveRecord: 0.3ms (3 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +-------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_container_with_PIN_lock:_incorrect_PIN_should_fail +-------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.213008"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.212836"], ["updated_at", "2025-11-21 23:41:21.212836"]] + TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"object", "targetId"=>"safe_pin", "attempt"=>"0000", "method"=>"pin", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=object, id=safe_pin, attempt=0000, method=pin +[BreakEscape] Found object: id=safe_pin, name=PIN Safe, locked=true, requires=1234 +[BreakEscape] Password validation: required='1234', attempt='0000', result=false +Completed 422 Unprocessable Content in 2ms (Views: 0.1ms | ActiveRecord: 0.2ms (3 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_container_with_password_lock:_empty_attempt_should_fail +------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.5ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.233413"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.219937"], ["updated_at", "2025-11-21 23:41:21.219937"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"object", "targetId"=>"cabinet_password", "attempt"=>"", "method"=>"password", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=object, id=cabinet_password, attempt=, method=password +[BreakEscape] Found object: id=cabinet_password, name=Password Cabinet, locked=true, requires=secret123 +[BreakEscape] Password validation: required='secret123', attempt='', result=false +Completed 422 Unprocessable Content in 2ms (Views: 0.1ms | ActiveRecord: 0.2ms (3 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +------------------------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_NPC_can_unlock_door_if_player_has_encountered_them_and_NPC_has_permission +------------------------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (3.0ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.4ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.245201"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.245006"], ["updated_at", "2025-11-21 23:41:21.245006"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 + TRANSACTION (0.0ms) SAVEPOINT active_record_1 + BreakEscape::Game Update (0.2ms) UPDATE "break_escape_games" SET "scenario_data" = ?, "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}],\"npcs\":[{\"id\":\"helper_npc\",\"displayName\":\"Helpful Contact\",\"unlockable\":[\"office_pin\",\"office_password\"]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[\"helper_npc\"],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.246804"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>"helper_npc", "method"=>"npc", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=helper_npc, method=npc +[BreakEscape] Validating NPC unlock: npc=helper_npc, target=office_pin +[BreakEscape] NPC unlock validated: helper_npc can unlock office_pin + TRANSACTION (0.0ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\",\"office_pin\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[\"helper_npc\"],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.251901"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 4ms (Views: 0.1ms | ActiveRecord: 0.6ms (5 queries, 0 cached) | GC: 10.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +----------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_door_with_password_lock:_case_sensitivity +----------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.0ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.5ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.256123"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.255945"], ["updated_at", "2025-11-21 23:41:21.255945"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_password", "attempt"=>"OpenSesame", "method"=>"password", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_password, attempt=OpenSesame, method=password +Completed 422 Unprocessable Content in 2ms (Views: 0.1ms | ActiveRecord: 0.2ms (3 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +--------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_door_with_PIN_lock:_incorrect_PIN_should_fail +--------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.264398"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.264196"], ["updated_at", "2025-11-21 23:41:21.264196"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>"0000", "method"=>"pin", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=0000, method=pin +Completed 422 Unprocessable Content in 2ms (Views: 0.1ms | ActiveRecord: 0.2ms (3 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.2ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +------------------------------------------------------------------------------------------------------ +BreakEscape::UnlockSystemTest: test_SECURITY:_method='unlocked'_only_works_for_actually_unlocked_doors +------------------------------------------------------------------------------------------------------ + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.4ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.273841"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.273599"], ["updated_at", "2025-11-21 23:41:21.273599"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_unlocked", "attempt"=>nil, "method"=>"unlocked", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_unlocked, attempt=, method=unlocked +[BreakEscape] Door is unlocked in server data, granting access + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\",\"office_unlocked\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.279539"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 4ms (Views: 0.1ms | ActiveRecord: 0.6ms (5 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.2ms) begin transaction +-------------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_container_unlock_response_should_filter_requires_from_contents +-------------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.5ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.284501"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.284155"], ["updated_at", "2025-11-21 23:41:21.284155"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"object", "targetId"=>"safe_pin", "attempt"=>"1234", "method"=>"pin", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=object, id=safe_pin, attempt=1234, method=pin +[BreakEscape] Found object: id=safe_pin, name=PIN Safe, locked=true, requires=1234 +[BreakEscape] Password validation: required='1234', attempt='1234', result=true + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.3ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[\"safe_pin\"],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.291090"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 5ms (Views: 0.1ms | ActiveRecord: 0.7ms (5 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +----------------------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_SECURITY:_NPC_unlock_fails_if_NPC_doesn't_have_permission_for_that_door +----------------------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.295096"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.294919"], ["updated_at", "2025-11-21 23:41:21.294919"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Update (0.4ms) UPDATE "break_escape_games" SET "scenario_data" = ?, "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}],\"npcs\":[{\"id\":\"helper_npc\",\"displayName\":\"Helpful Contact\",\"unlockable\":[\"office_password\"]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[\"helper_npc\"],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.296652"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>"helper_npc", "method"=>"npc", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=helper_npc, method=npc +[BreakEscape] Validating NPC unlock: npc=helper_npc, target=office_pin +[BreakEscape] NPC helper_npc does not have permission to unlock office_pin +Completed 422 Unprocessable Content in 2ms (Views: 0.1ms | ActiveRecord: 0.2ms (3 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_door_with_password_lock:_incorrect_password_should_fail +------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.4ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.304994"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.304741"], ["updated_at", "2025-11-21 23:41:21.304741"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_password", "attempt"=>"wrongpassword", "method"=>"password", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_password, attempt=wrongpassword, method=password +Completed 422 Unprocessable Content in 2ms (Views: 0.1ms | ActiveRecord: 0.2ms (3 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_container_with_lockpick:_should_trust_client_validation +------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.4ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.323432"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.323171"], ["updated_at", "2025-11-21 23:41:21.323171"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"object", "targetId"=>"box_lockpick", "attempt"=>nil, "method"=>"lockpick", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=object, id=box_lockpick, attempt=, method=lockpick +[BreakEscape] Found object: id=box_lockpick, name=Lockpickable Box, locked=true, requires= + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.3ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.2ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[\"box_lockpick\"],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.328815"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 5ms (Views: 0.1ms | ActiveRecord: 0.7ms (5 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +----------------------------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_door_unlock_response_should_not_expose_'requires'_field_for_exploitable_locks +----------------------------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.334489"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.334303"], ["updated_at", "2025-11-21 23:41:21.334303"]] + TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>"9876", "method"=>"pin", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=9876, method=pin + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.3ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\",\"office_pin\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.340672"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 5ms (Views: 0.1ms | ActiveRecord: 0.8ms (5 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.0ms) begin transaction +-------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_unlock_non-existent_object_should_fail +-------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.345306"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.344700"], ["updated_at", "2025-11-21 23:41:21.344700"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"object", "targetId"=>"non_existent_object", "attempt"=>"1234", "method"=>"pin", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=object, id=non_existent_object, attempt=1234, method=pin +[BreakEscape] Object not found: non_existent_object +Completed 422 Unprocessable Content in 2ms (Views: 0.1ms | ActiveRecord: 0.2ms (3 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +-------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_SECURITY:_NPC_unlock_fails_if_unlockable_is_not_an_array +-------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.352910"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.352694"], ["updated_at", "2025-11-21 23:41:21.352694"]] + TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1 + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Update (0.2ms) UPDATE "break_escape_games" SET "scenario_data" = ?, "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}],\"npcs\":[{\"id\":\"helper_npc\",\"displayName\":\"Helpful Contact\",\"unlockable\":\"office_pin\"}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[\"helper_npc\"],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.354396"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>"helper_npc", "method"=>"npc", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=helper_npc, method=npc +[BreakEscape] Validating NPC unlock: npc=helper_npc, target=office_pin +[BreakEscape] NPC helper_npc does not have permission to unlock office_pin +Completed 422 Unprocessable Content in 2ms (Views: 0.1ms | ActiveRecord: 0.2ms (3 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.0ms) begin transaction +--------------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_SECURITY:_locked_door_cannot_be_bypassed_with_method='unlocked' +--------------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.361204"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.361041"], ["updated_at", "2025-11-21 23:41:21.361041"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>nil, "method"=>"unlocked", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=, method=unlocked +[BreakEscape] SECURITY VIOLATION: Client sent method='unlocked' for LOCKED door office_pin +Completed 422 Unprocessable Content in 2ms (Views: 0.1ms | ActiveRecord: 0.3ms (3 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_door_with_password_lock:_correct_password_should_unlock +------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.368967"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.368791"], ["updated_at", "2025-11-21 23:41:21.368791"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_password", "attempt"=>"opensesame", "method"=>"password", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_password, attempt=opensesame, method=password + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.5ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\",\"office_password\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.374399"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 4ms (Views: 0.1ms | ActiveRecord: 0.9ms (5 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_unlock_same_door_twice_should_be_idempotent +------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.4ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.379071"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.378899"], ["updated_at", "2025-11-21 23:41:21.378899"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>"9876", "method"=>"pin", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=9876, method=pin + TRANSACTION (0.0ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\",\"office_pin\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.384841"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 4ms (Views: 0.1ms | ActiveRecord: 0.6ms (5 queries, 0 cached) | GC: 0.0ms) +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>"9876", "method"=>"pin", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=9876, method=pin + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 3ms (Views: 0.1ms | ActiveRecord: 0.5ms (4 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +-------------------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_SECURITY:_locked_container_cannot_be_bypassed_with_method='unlocked' +-------------------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.4ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.400753"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.400540"], ["updated_at", "2025-11-21 23:41:21.400540"]] + TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"object", "targetId"=>"safe_pin", "attempt"=>nil, "method"=>"unlocked", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=object, id=safe_pin, attempt=, method=unlocked +[BreakEscape] Found object: id=safe_pin, name=PIN Safe, locked=true, requires=1234 +[BreakEscape] SECURITY VIOLATION: Client sent method='unlocked' for LOCKED object safe_pin +Completed 422 Unprocessable Content in 2ms (Views: 0.1ms | ActiveRecord: 0.2ms (3 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +----------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_unlocked_door:_should_grant_access_without_validation +----------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.408538"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.408338"], ["updated_at", "2025-11-21 23:41:21.408338"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_unlocked", "attempt"=>nil, "method"=>"unlocked", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_unlocked, attempt=, method=unlocked +[BreakEscape] Door is unlocked in server data, granting access + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\",\"office_unlocked\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.413516"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 4ms (Views: 0.1ms | ActiveRecord: 0.6ms (5 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.0ms) begin transaction +-------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_unlock_with_invalid_method_should_fail +-------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.417612"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.417429"], ["updated_at", "2025-11-21 23:41:21.417429"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>"9876", "method"=>"invalid_method", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=9876, method=invalid_method +Completed 422 Unprocessable Content in 2ms (Views: 0.1ms | ActiveRecord: 0.2ms (3 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +------------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_container_with_biometric_lock:_should_trust_client_validation +------------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.424257"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.424056"], ["updated_at", "2025-11-21 23:41:21.424056"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"object", "targetId"=>"scanner_biometric", "attempt"=>nil, "method"=>"biometric", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=object, id=scanner_biometric, attempt=, method=biometric +[BreakEscape] Found object: id=scanner_biometric, name=Biometric Scanner, locked=true, requires=ceo_fingerprint + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.2ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[\"scanner_biometric\"],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.428900"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 4ms (Views: 0.1ms | ActiveRecord: 0.6ms (5 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +------------------------------------------------------------------------------------------ +BreakEscape::UnlockSystemTest: test_multiple_unlock_attempts_should_update_state_correctly +------------------------------------------------------------------------------------------ + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.433125"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.432924"], ["updated_at", "2025-11-21 23:41:21.432924"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>"9876", "method"=>"pin", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=9876, method=pin + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.3ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\",\"office_pin\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.437659"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 4ms (Views: 0.1ms | ActiveRecord: 0.7ms (5 queries, 0 cached) | GC: 0.0ms) +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_password", "attempt"=>"opensesame", "method"=>"password", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_password, attempt=opensesame, method=password + TRANSACTION (0.0ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\",\"office_pin\",\"office_password\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.442786"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 3ms (Views: 0.1ms | ActiveRecord: 0.7ms (5 queries, 0 cached) | GC: 0.0ms) +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"object", "targetId"=>"safe_pin", "attempt"=>"1234", "method"=>"pin", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=object, id=safe_pin, attempt=1234, method=pin +[BreakEscape] Found object: id=safe_pin, name=PIN Safe, locked=true, requires=1234 +[BreakEscape] Password validation: required='1234', attempt='1234', result=true + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\",\"office_pin\",\"office_password\"],\"unlockedObjects\":[\"safe_pin\"],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.447411"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 3ms (Views: 0.1ms | ActiveRecord: 0.6ms (5 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.0ms) begin transaction +------------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_container_with_bluetooth_lock:_should_trust_client_validation +------------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.451190"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.451026"], ["updated_at", "2025-11-21 23:41:21.451026"]] + TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"object", "targetId"=>"terminal_bluetooth", "attempt"=>nil, "method"=>"bluetooth", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=object, id=terminal_bluetooth, attempt=, method=bluetooth +[BreakEscape] Found object: id=terminal_bluetooth, name=Bluetooth Terminal, locked=true, requires=admin_device + TRANSACTION (0.0ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[\"terminal_bluetooth\"],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.455705"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 3ms (Views: 0.1ms | ActiveRecord: 0.6ms (5 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +------------------------------------------------------------------------ +BreakEscape::UnlockSystemTest: test_unlock_non-existent_door_should_fail +------------------------------------------------------------------------ + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.459101"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.458933"], ["updated_at", "2025-11-21 23:41:21.458933"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"non_existent_room", "attempt"=>"1234", "method"=>"pin", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=non_existent_room, attempt=1234, method=pin +Completed 422 Unprocessable Content in 2ms (Views: 0.1ms | ActiveRecord: 0.2ms (3 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +-------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_door_with_key_lock:_should_trust_client_validation +-------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.0ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.468819"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.468657"], ["updated_at", "2025-11-21 23:41:21.468657"]] + TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_key", "attempt"=>nil, "method"=>"key", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_key, attempt=, method=key + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\",\"office_key\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.473733"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 3ms (Views: 0.1ms | ActiveRecord: 0.6ms (5 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.0ms) begin transaction +------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_container_with_key_lock:_should_trust_client_validation +------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.477910"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.477722"], ["updated_at", "2025-11-21 23:41:21.477722"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"object", "targetId"=>"drawer_key", "attempt"=>nil, "method"=>"key", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=object, id=drawer_key, attempt=, method=key +[BreakEscape] Found object: id=drawer_key, name=Locked Drawer, locked=true, requires=drawer_key + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.3ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.2ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[\"drawer_key\"],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.482653"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 4ms (Views: 0.1ms | ActiveRecord: 0.7ms (5 queries, 0 cached) | GC: 0.0ms) + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.0ms) begin transaction +-------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_container_with_PIN_lock:_correct_PIN_should_unlock +-------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.486316"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.486148"], ["updated_at", "2025-11-21 23:41:21.486148"]] + TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"object", "targetId"=>"safe_pin", "attempt"=>"1234", "method"=>"pin", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=object, id=safe_pin, attempt=1234, method=pin +[BreakEscape] Found object: id=safe_pin, name=PIN Safe, locked=true, requires=1234 +[BreakEscape] Password validation: required='1234', attempt='1234', result=true + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[\"safe_pin\"],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.490833"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 3ms (Views: 0.1ms | ActiveRecord: 0.6ms (5 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.1ms) begin transaction +------------------------------------------------------------------------------------------------ +BreakEscape::UnlockSystemTest: test_SECURITY:_NPC_unlock_fails_if_player_has_not_encountered_NPC +------------------------------------------------------------------------------------------------ + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.495089"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.494909"], ["updated_at", "2025-11-21 23:41:21.494909"]] + TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1 + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Update (0.2ms) UPDATE "break_escape_games" SET "scenario_data" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}],\"npcs\":[{\"id\":\"helper_npc\",\"displayName\":\"Helpful Contact\",\"unlockable\":[\"office_pin\"]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["updated_at", "2025-11-21 23:41:21.496534"], ["id", 1]] + TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"door", "targetId"=>"office_pin", "attempt"=>"helper_npc", "method"=>"npc", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=door, id=office_pin, attempt=helper_npc, method=npc +[BreakEscape] Validating NPC unlock: npc=helper_npc, target=office_pin +[BreakEscape] Player has not encountered NPC: helper_npc +Completed 422 Unprocessable Content in 2ms (Views: 0.1ms | ActiveRecord: 0.2ms (3 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.0ms) begin transaction +------------------------------------------------------------------------------------------------ +BreakEscape::UnlockSystemTest: test_container_with_password_lock:_correct_password_should_unlock +------------------------------------------------------------------------------------------------ + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.503357"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.503144"], ["updated_at", "2025-11-21 23:41:21.503144"]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"object", "targetId"=>"cabinet_password", "attempt"=>"secret123", "method"=>"password", "id"=>"1"} + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=object, id=cabinet_password, attempt=secret123, method=password +[BreakEscape] Found object: id=cabinet_password, name=Password Cabinet, locked=true, requires=secret123 +[BreakEscape] Password validation: required='secret123', attempt='secret123', result=true + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.2ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[\"cabinet_password\"],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.508174"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 4ms (Views: 0.1ms | ActiveRecord: 0.6ms (5 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction + TRANSACTION (0.0ms) begin transaction +---------------------------------------------------------------------------------------------- +BreakEscape::UnlockSystemTest: test_unlocked_container:_should_grant_access_without_validation +---------------------------------------------------------------------------------------------- + BreakEscape::Mission Load (0.1ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Game Create (0.3ms) INSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id" [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"lobby\",\"rooms\":{\"lobby\":{\"type\":\"office_lobby\",\"locked\":false,\"connections\":{\"north\":\"office_pin\",\"south\":\"office_password\",\"east\":\"office_key\",\"west\":\"office_unlocked\"},\"objects\":[{\"id\":\"safe_pin\",\"name\":\"PIN Safe\",\"type\":\"safe\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"contents\":[{\"type\":\"document\",\"name\":\"Secret Document\"}]},{\"id\":\"cabinet_password\",\"name\":\"Password Cabinet\",\"type\":\"cabinet\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"secret123\",\"contents\":[{\"type\":\"key\",\"name\":\"Master Key\"}]},{\"id\":\"drawer_key\",\"name\":\"Locked Drawer\",\"type\":\"drawer\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"drawer_key\",\"contents\":[{\"type\":\"note\",\"name\":\"Important Note\"}]},{\"id\":\"box_lockpick\",\"name\":\"Lockpickable Box\",\"type\":\"box\",\"locked\":true,\"lockType\":\"lockpick\",\"difficulty\":3,\"contents\":[{\"type\":\"coin\",\"name\":\"Gold Coin\"}]},{\"id\":\"scanner_biometric\",\"name\":\"Biometric Scanner\",\"type\":\"scanner\",\"locked\":true,\"lockType\":\"biometric\",\"requires\":\"ceo_fingerprint\",\"contents\":[{\"type\":\"usb\",\"name\":\"Data USB\"}]},{\"id\":\"terminal_bluetooth\",\"name\":\"Bluetooth Terminal\",\"type\":\"terminal\",\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"admin_device\",\"contents\":[{\"type\":\"file\",\"name\":\"Access Codes\"}]},{\"id\":\"door_rfid\",\"name\":\"RFID Door\",\"type\":\"door\",\"locked\":true,\"lockType\":\"rfid\",\"requires\":\"admin_badge\",\"contents\":[{\"type\":\"keycard\",\"name\":\"Security Card\"}]},{\"id\":\"chest_unlocked\",\"name\":\"Open Chest\",\"type\":\"chest\",\"locked\":false,\"contents\":[{\"type\":\"tool\",\"name\":\"Wrench\"}]}]},\"office_pin\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9876\",\"connections\":{\"south\":\"lobby\"},\"objects\":[]},\"office_password\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"password\",\"requires\":\"opensesame\",\"connections\":{\"north\":\"lobby\"},\"objects\":[]},\"office_key\":{\"type\":\"office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office_key\",\"connections\":{\"west\":\"lobby\"},\"objects\":[]},\"office_unlocked\":{\"type\":\"office\",\"locked\":false,\"connections\":{\"east\":\"lobby\"},\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["status", "in_progress"], ["started_at", "2025-11-21 23:41:21.511990"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-21 23:41:21.511832"], ["updated_at", "2025-11-21 23:41:21.511832"]] + TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1 +Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-21 23:41:21 +0000 +Processing by BreakEscape::GamesController#unlock as HTML + Parameters: {"targetType"=>"object", "targetId"=>"chest_unlocked", "attempt"=>nil, "method"=>"unlocked", "id"=>"1"} + BreakEscape::Game Load (0.2ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + BreakEscape::DemoUser Load (0.1ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ? [["LIMIT", 1]] + BreakEscape::DemoUser Load (0.2ms) SELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ? [["id", 149617800], ["LIMIT", 1]] +[BreakEscape] validate_unlock: type=object, id=chest_unlocked, attempt=, method=unlocked +[BreakEscape] Found object: id=chest_unlocked, name=Open Chest, locked=false, requires= +[BreakEscape] Object is unlocked in server data, granting access + TRANSACTION (0.1ms) SAVEPOINT active_record_1 + BreakEscape::Mission Load (0.3ms) SELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ? [["id", 418560898], ["LIMIT", 1]] + BreakEscape::Game Update (0.1ms) UPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ? [["player_state", "{\"currentRoom\":\"lobby\",\"unlockedRooms\":[\"lobby\"],\"unlockedObjects\":[\"chest_unlocked\"],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100}"], ["updated_at", "2025-11-21 23:41:21.517299"], ["id", 1]] + TRANSACTION (0.1ms) RELEASE SAVEPOINT active_record_1 +Completed 200 OK in 4ms (Views: 0.1ms | ActiveRecord: 0.8ms (5 queries, 0 cached) | GC: 0.0ms) + BreakEscape::Game Load (0.1ms) SELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] + TRANSACTION (0.1ms) rollback transaction diff --git a/test/dummy/storage/test.sqlite3 b/test/dummy/storage/test.sqlite3 index 34d6b8e..4fae540 100644 Binary files a/test/dummy/storage/test.sqlite3 and b/test/dummy/storage/test.sqlite3 differ diff --git a/test/integration/unlock_system_test.rb b/test/integration/unlock_system_test.rb index ff1569b..d3884c0 100644 --- a/test/integration/unlock_system_test.rb +++ b/test/integration/unlock_system_test.rb @@ -491,6 +491,164 @@ module BreakEscape assert_equal false, json['success'] end + # ============================================================================= + # NPC UNLOCK TESTS + # ============================================================================= + + test "NPC can unlock door if player has encountered them and NPC has permission" do + # Add NPC with unlock permission to scenario + @game.scenario_data['rooms']['lobby']['npcs'] = [ + { + 'id' => 'helper_npc', + 'displayName' => 'Helpful Contact', + 'unlockable' => ['office_pin', 'office_password'] + } + ] + # Mark NPC as encountered + @game.player_state['encounteredNPCs'] = ['helper_npc'] + @game.save! + + post unlock_game_url(@game), params: { + targetType: 'door', + targetId: 'office_pin', + attempt: 'helper_npc', # NPC id + method: 'npc' + } + + assert_response :success + json = JSON.parse(@response.body) + assert json['success'], "NPC should be able to unlock door they have permission for" + + @game.reload + assert_includes @game.player_state['unlockedRooms'], 'office_pin' + end + + test "NPC can unlock container if player has encountered them and NPC has permission" do + # Add NPC with unlock permission to scenario + @game.scenario_data['rooms']['lobby']['npcs'] = [ + { + 'id' => 'helper_npc', + 'displayName' => 'Helpful Contact', + 'unlockable' => ['safe_pin', 'cabinet_password'] + } + ] + # Mark NPC as encountered + @game.player_state['encounteredNPCs'] = ['helper_npc'] + @game.save! + + post unlock_game_url(@game), params: { + targetType: 'object', + targetId: 'safe_pin', + attempt: 'helper_npc', # NPC id + method: 'npc' + } + + assert_response :success + json = JSON.parse(@response.body) + assert json['success'], "NPC should be able to unlock container they have permission for" + + @game.reload + assert_includes @game.player_state['unlockedObjects'], 'safe_pin' + end + + test "SECURITY: NPC unlock fails if player has not encountered NPC" do + # Add NPC with unlock permission to scenario but DON'T mark as encountered + @game.scenario_data['rooms']['lobby']['npcs'] = [ + { + 'id' => 'helper_npc', + 'displayName' => 'Helpful Contact', + 'unlockable' => ['office_pin'] + } + ] + @game.save! + + post unlock_game_url(@game), params: { + targetType: 'door', + targetId: 'office_pin', + attempt: 'helper_npc', + method: 'npc' + } + + assert_response :unprocessable_entity, + "SECURITY FAIL: Unlock succeeded without encountering NPC" + json = JSON.parse(@response.body) + assert_equal false, json['success'] + + @game.reload + assert_not_includes @game.player_state['unlockedRooms'], 'office_pin' + end + + test "SECURITY: NPC unlock fails if NPC doesn't have permission for that door" do + # Add NPC but without permission for this specific door + @game.scenario_data['rooms']['lobby']['npcs'] = [ + { + 'id' => 'helper_npc', + 'displayName' => 'Helpful Contact', + 'unlockable' => ['office_password'] # Has permission for different door + } + ] + @game.player_state['encounteredNPCs'] = ['helper_npc'] + @game.save! + + post unlock_game_url(@game), params: { + targetType: 'door', + targetId: 'office_pin', # Trying to unlock door not in NPC's unlockable list + attempt: 'helper_npc', + method: 'npc' + } + + assert_response :unprocessable_entity, + "SECURITY FAIL: NPC unlocked door they don't have permission for" + json = JSON.parse(@response.body) + assert_equal false, json['success'] + + @game.reload + assert_not_includes @game.player_state['unlockedRooms'], 'office_pin' + end + + test "SECURITY: NPC unlock fails for non-existent NPC" do + # Try to use non-existent NPC + @game.player_state['encounteredNPCs'] = ['helper_npc'] + @game.save! + + post unlock_game_url(@game), params: { + targetType: 'door', + targetId: 'office_pin', + attempt: 'fake_npc', # Non-existent NPC + method: 'npc' + } + + assert_response :unprocessable_entity, + "SECURITY FAIL: Unlock succeeded with non-existent NPC" + json = JSON.parse(@response.body) + assert_equal false, json['success'] + end + + test "SECURITY: NPC unlock fails if unlockable is not an array" do + # Malformed NPC data - unlockable is not an array + @game.scenario_data['rooms']['lobby']['npcs'] = [ + { + 'id' => 'helper_npc', + 'displayName' => 'Helpful Contact', + 'unlockable' => 'office_pin' # String instead of array + } + ] + @game.player_state['encounteredNPCs'] = ['helper_npc'] + @game.save! + + post unlock_game_url(@game), params: { + targetType: 'door', + targetId: 'office_pin', + attempt: 'helper_npc', + method: 'npc' + } + + assert_response :unprocessable_entity, + "SECURITY FAIL: Unlock succeeded with malformed unlockable data" + json = JSON.parse(@response.body) + assert_equal false, json['success'] + end + # ============================================================================= # SECURITY TESTS - CLIENT BYPASS ATTEMPTS # =============================================================================