add unit tests
saveload fixes
This commit is contained in:
@@ -2,6 +2,8 @@ extends Node
|
||||
|
||||
const LANGUAGES_JSON_PATH := "res://localization/languages.json"
|
||||
const SETTINGS_FILE = "user://settings.cfg"
|
||||
const MAX_JSON_FILE_SIZE = 65536 # 64KB limit for languages.json
|
||||
const MAX_SETTING_STRING_LENGTH = 10 # Max length for string settings like language code
|
||||
# dev `user://`=`%APPDATA%\Godot\app_userdata\Skelly`
|
||||
# prod `user://`=`%APPDATA%\Skelly\`
|
||||
|
||||
@@ -107,12 +109,32 @@ func set_setting(key: String, value) -> bool:
|
||||
func _validate_setting_value(key: String, value) -> bool:
|
||||
match key:
|
||||
"master_volume", "music_volume", "sfx_volume":
|
||||
return value is float and value >= 0.0 and value <= 1.0
|
||||
# Enhanced numeric validation with NaN/Infinity checks
|
||||
if not (value is float or value is int):
|
||||
return false
|
||||
# Convert to float for validation
|
||||
var float_value = float(value)
|
||||
# Check for NaN and infinity
|
||||
if is_nan(float_value) or is_inf(float_value):
|
||||
DebugManager.log_warn("Invalid float value for %s: %s" % [key, str(value)], "SettingsManager")
|
||||
return false
|
||||
# Range validation
|
||||
return float_value >= 0.0 and float_value <= 1.0
|
||||
"language":
|
||||
if not value is String:
|
||||
return false
|
||||
# Prevent extremely long strings
|
||||
if value.length() > MAX_SETTING_STRING_LENGTH:
|
||||
DebugManager.log_warn("Language code too long: %d characters" % value.length(), "SettingsManager")
|
||||
return false
|
||||
# Check for valid characters (alphanumeric and common separators only)
|
||||
var regex = RegEx.new()
|
||||
regex.compile("^[a-zA-Z0-9_-]+$")
|
||||
if not regex.search(value):
|
||||
DebugManager.log_warn("Language code contains invalid characters: %s" % value, "SettingsManager")
|
||||
return false
|
||||
# Check if language is supported
|
||||
if languages_data.has("languages"):
|
||||
if languages_data.has("languages") and languages_data.languages is Dictionary:
|
||||
return value in languages_data.languages
|
||||
else:
|
||||
# Fallback to basic validation if languages not loaded
|
||||
@@ -120,6 +142,9 @@ func _validate_setting_value(key: String, value) -> bool:
|
||||
|
||||
# Default validation: accept if type matches default setting type
|
||||
var default_value = default_settings.get(key)
|
||||
if default_value == null:
|
||||
DebugManager.log_warn("Unknown setting key in validation: %s" % key, "SettingsManager")
|
||||
return false
|
||||
return typeof(value) == typeof(default_value)
|
||||
|
||||
func _apply_setting_side_effect(key: String, value) -> void:
|
||||
@@ -143,6 +168,20 @@ func load_languages():
|
||||
_load_default_languages()
|
||||
return
|
||||
|
||||
# Check file size to prevent memory exhaustion
|
||||
var file_size = file.get_length()
|
||||
if file_size > MAX_JSON_FILE_SIZE:
|
||||
DebugManager.log_error("Languages.json file too large: %d bytes (max %d)" % [file_size, MAX_JSON_FILE_SIZE], "SettingsManager")
|
||||
file.close()
|
||||
_load_default_languages()
|
||||
return
|
||||
|
||||
if file_size == 0:
|
||||
DebugManager.log_error("Languages.json file is empty", "SettingsManager")
|
||||
file.close()
|
||||
_load_default_languages()
|
||||
return
|
||||
|
||||
var json_string = file.get_as_text()
|
||||
var file_error = file.get_error()
|
||||
file.close()
|
||||
@@ -152,6 +191,12 @@ func load_languages():
|
||||
_load_default_languages()
|
||||
return
|
||||
|
||||
# Validate the JSON string is not empty
|
||||
if json_string.is_empty():
|
||||
DebugManager.log_error("Languages.json contains empty content", "SettingsManager")
|
||||
_load_default_languages()
|
||||
return
|
||||
|
||||
var json = JSON.new()
|
||||
var parse_result = json.parse(json_string)
|
||||
if parse_result != OK:
|
||||
@@ -164,12 +209,14 @@ func load_languages():
|
||||
_load_default_languages()
|
||||
return
|
||||
|
||||
languages_data = json.data
|
||||
if languages_data.has("languages") and languages_data.languages is Dictionary:
|
||||
DebugManager.log_info("Languages loaded: " + str(languages_data.languages.keys()), "SettingsManager")
|
||||
else:
|
||||
DebugManager.log_warn("Languages.json missing 'languages' dictionary, using defaults", "SettingsManager")
|
||||
# Validate the structure of the JSON data
|
||||
if not _validate_languages_structure(json.data):
|
||||
DebugManager.log_error("Languages.json structure validation failed", "SettingsManager")
|
||||
_load_default_languages()
|
||||
return
|
||||
|
||||
languages_data = json.data
|
||||
DebugManager.log_info("Languages loaded successfully: " + str(languages_data.languages.keys()), "SettingsManager")
|
||||
|
||||
func _load_default_languages():
|
||||
# Fallback language data when JSON file fails to load
|
||||
@@ -185,7 +232,49 @@ func get_languages_data():
|
||||
return languages_data
|
||||
|
||||
func reset_settings_to_defaults() -> void:
|
||||
DebugManager.log_info("Resetting all settings to defaults", "SettingsManager")
|
||||
for key in default_settings.keys():
|
||||
settings[key] = default_settings[key]
|
||||
_apply_setting_side_effect(key, settings[key])
|
||||
save_settings()
|
||||
var save_success = save_settings()
|
||||
if save_success:
|
||||
DebugManager.log_info("Settings reset completed successfully", "SettingsManager")
|
||||
else:
|
||||
DebugManager.log_error("Failed to save reset settings", "SettingsManager")
|
||||
|
||||
func _validate_languages_structure(data: Dictionary) -> bool:
|
||||
"""Validate the structure and content of languages.json data"""
|
||||
if not data.has("languages"):
|
||||
DebugManager.log_error("Languages.json missing 'languages' key", "SettingsManager")
|
||||
return false
|
||||
|
||||
var languages = data["languages"]
|
||||
if not languages is Dictionary:
|
||||
DebugManager.log_error("'languages' is not a dictionary", "SettingsManager")
|
||||
return false
|
||||
|
||||
if languages.is_empty():
|
||||
DebugManager.log_error("Languages dictionary is empty", "SettingsManager")
|
||||
return false
|
||||
|
||||
# Validate each language entry
|
||||
for lang_code in languages.keys():
|
||||
if not lang_code is String:
|
||||
DebugManager.log_error("Language code is not a string: %s" % str(lang_code), "SettingsManager")
|
||||
return false
|
||||
|
||||
if lang_code.length() > MAX_SETTING_STRING_LENGTH:
|
||||
DebugManager.log_error("Language code too long: %s" % lang_code, "SettingsManager")
|
||||
return false
|
||||
|
||||
var lang_data = languages[lang_code]
|
||||
if not lang_data is Dictionary:
|
||||
DebugManager.log_error("Language data for '%s' is not a dictionary" % lang_code, "SettingsManager")
|
||||
return false
|
||||
|
||||
# Validate required fields in language data
|
||||
if not lang_data.has("name") or not lang_data["name"] is String:
|
||||
DebugManager.log_error("Language '%s' missing valid 'name' field" % lang_code, "SettingsManager")
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
Reference in New Issue
Block a user