add unit tests

saveload fixes
This commit is contained in:
2025-09-27 12:17:14 +04:00
parent 3e960a955c
commit dd0c1a123c
31 changed files with 3400 additions and 282 deletions

View File

@@ -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