fix(settings): tolerate wrongly-typed values instead of quarantining
Several settings were read with json's value()/get<T>() helpers that assume the stored type. A hand-edited settings file, or one re-saved after an inf/NaN float was written back as JSON null, could therefore hold a value of the wrong type and throw during load. The whole load is wrapped so a throw quarantines the entire settings file to a .corrupt- copy, meaning one bad field reset every unrelated setting. - ui_opacity is accepted only when it is actually a number, and it is clamped to the same 0.3..1.0 range the setter enforces; the legacy reduced_transparency migration likewise runs only when that key is a boolean. - A non-string name in a blocked chat conversation, and a non-string scope in a portfolio entry, now fall back to an empty string instead of throwing. The file loads with the offending field defaulted, and the rest of the settings survive. Tests cover ui_opacity as a string/null/int/float/bool, a non-string blocked-conversation name and portfolio scope, and the legacy reduced_transparency migration, asserting no quarantine file is written. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7918,6 +7918,47 @@ void testPortfolioScopeVisibility()
|
||||
std::error_code ec; fs::remove_all(dir, ec);
|
||||
}
|
||||
|
||||
// Settings::load must tolerate wrongly-typed values without quarantining the whole file.
|
||||
void testSettingsLoadTypeTolerance()
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
const fs::path dir = fs::temp_directory_path() / "dragonx_settings_type_tolerance_test";
|
||||
std::error_code ec; fs::remove_all(dir, ec); fs::create_directories(dir, ec);
|
||||
const std::string p = (dir / "settings.json").string();
|
||||
auto writeAndLoad = [&](const std::string& json, dragonx::config::Settings& s) {
|
||||
{ std::ofstream o(p, std::ios::binary | std::ios::trunc); o << json; }
|
||||
return s.load(p);
|
||||
};
|
||||
auto noQuarantine = [&]() {
|
||||
for (const auto& de : fs::directory_iterator(dir, ec))
|
||||
if (de.path().filename().string().find(".corrupt-") != std::string::npos) return false;
|
||||
return true;
|
||||
};
|
||||
for (const char* bad : {"\"x\"", "null", "5", "0.01", "true"}) {
|
||||
dragonx::config::Settings s;
|
||||
const std::string js = std::string("{\"ui_opacity\":") + bad + ",\"chat_density\":1}";
|
||||
EXPECT_TRUE(writeAndLoad(js, s));
|
||||
EXPECT_TRUE(noQuarantine());
|
||||
EXPECT_TRUE(s.getUIOpacity() >= 0.3f && s.getUIOpacity() <= 1.0f);
|
||||
}
|
||||
{
|
||||
dragonx::config::Settings s;
|
||||
EXPECT_TRUE(writeAndLoad(
|
||||
"{\"blocked_chat_convs\":[{\"cid\":\"abc\",\"name\":7}],"
|
||||
"\"portfolio_entries\":[{\"label\":\"L\",\"scope\":7}]}", s));
|
||||
EXPECT_TRUE(noQuarantine());
|
||||
EXPECT_EQ(s.blockedChatConversations().size(), static_cast<size_t>(1));
|
||||
EXPECT_EQ(s.getPortfolioEntries().size(), static_cast<size_t>(1));
|
||||
EXPECT_EQ(s.getPortfolioEntries()[0].scope, std::string());
|
||||
}
|
||||
{
|
||||
dragonx::config::Settings s;
|
||||
EXPECT_TRUE(writeAndLoad("{\"reduced_transparency\":true}", s));
|
||||
EXPECT_NEAR(s.getUIOpacity(), 1.0f, 1e-6f);
|
||||
}
|
||||
fs::remove_all(dir, ec);
|
||||
}
|
||||
|
||||
// Market price-series math (data/market_series.h) — minute-resampling + time-window bucketing
|
||||
// that feed the price chart and the portfolio-group sparklines.
|
||||
void testMarketSeries()
|
||||
@@ -18669,6 +18710,7 @@ int main()
|
||||
testHushChatTransport();
|
||||
testHushChatShuffledReceive();
|
||||
testWalletFileProbe();
|
||||
testSettingsLoadTypeTolerance();
|
||||
testAddressChecksumValidation();
|
||||
testPrivateKeyImportRecognition();
|
||||
testSeedPhraseHelpers();
|
||||
|
||||
Reference in New Issue
Block a user