Add versioned save slot/settings schemas with strict version checks
Splits saveslot_t/savesettings_t into a stable, version-independent in-memory struct (save/slot/, save/settings/) and a separate frozen per-schema-version struct (saveslotver1_t/savesettingsver1_t) used only for JSON serialization, converted between manually. Every write now stamps a "version" field (SAVE_SLOT_CURRENT/SAVE_SETTINGS_CURRENT), and every read requires it to match exactly, rejecting anything else - laying the groundwork for a real migration path instead of silently misreading old/incompatible save data.
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
#include "dusktest.h"
|
||||
#include "save/saveslot.h"
|
||||
#include "save/slot/saveslot.h"
|
||||
#include "save/savejson.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
@@ -174,7 +174,9 @@ static void test_saveSlotReadJSON_missingFieldsUseDefaults(void **state) {
|
||||
slot.cachedData.playerLevel = 999;
|
||||
|
||||
errorret_t ret = slotFromJSON(
|
||||
&slot, "{\"time\":{\"time\":1.0,\"timeZone\":0.0,\"offsetTime\":0.0}}"
|
||||
&slot,
|
||||
"{\"version\":1,"
|
||||
"\"time\":{\"time\":1.0,\"timeZone\":0.0,\"offsetTime\":0.0}}"
|
||||
);
|
||||
assert_true(errorIsOk(ret));
|
||||
|
||||
@@ -186,7 +188,9 @@ static void test_saveSlotReadJSON_missingTimeKey_errors(void **state) {
|
||||
saveslot_t slot;
|
||||
saveSlotInit(&slot);
|
||||
|
||||
errorret_t ret = slotFromJSON(&slot, "{\"name\":\"Hero\",\"playerLevel\":5}");
|
||||
errorret_t ret = slotFromJSON(
|
||||
&slot, "{\"version\":1,\"name\":\"Hero\",\"playerLevel\":5}"
|
||||
);
|
||||
assert_true(errorIsNotOk(ret));
|
||||
errorCatch(ret);
|
||||
}
|
||||
@@ -197,7 +201,7 @@ static void test_saveSlotReadJSON_nameTooLong_errors(void **state) {
|
||||
|
||||
errorret_t ret = slotFromJSON(
|
||||
&slot,
|
||||
"{\"name\":\"WayTooLongAName\","
|
||||
"{\"version\":1,\"name\":\"WayTooLongAName\","
|
||||
"\"time\":{\"time\":1.0,\"timeZone\":0.0,\"offsetTime\":0.0}}"
|
||||
);
|
||||
assert_true(errorIsNotOk(ret));
|
||||
@@ -209,12 +213,43 @@ static void test_saveSlotReadJSON_nonObjectRoot_errors(void **state) {
|
||||
saveSlotInit(&slot);
|
||||
|
||||
// A non-object root has no keys, so every field falls back to its
|
||||
// default - except "time", which is not optional and errors instead.
|
||||
// default - except "version" and "time", which are required and error
|
||||
// instead.
|
||||
errorret_t ret = slotFromJSON(&slot, "[]");
|
||||
assert_true(errorIsNotOk(ret));
|
||||
errorCatch(ret);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// saveSlotReadJSON - version enforcement
|
||||
// ============================================================
|
||||
|
||||
static void test_saveSlotReadJSON_versionMissing_errors(void **state) {
|
||||
saveslot_t slot;
|
||||
saveSlotInit(&slot);
|
||||
|
||||
errorret_t ret = slotFromJSON(
|
||||
&slot,
|
||||
"{\"name\":\"Hero\","
|
||||
"\"time\":{\"time\":1.0,\"timeZone\":0.0,\"offsetTime\":0.0}}"
|
||||
);
|
||||
assert_true(errorIsNotOk(ret));
|
||||
errorCatch(ret);
|
||||
}
|
||||
|
||||
static void test_saveSlotReadJSON_versionMismatch_errors(void **state) {
|
||||
saveslot_t slot;
|
||||
saveSlotInit(&slot);
|
||||
|
||||
errorret_t ret = slotFromJSON(
|
||||
&slot,
|
||||
"{\"version\":999,\"name\":\"Hero\","
|
||||
"\"time\":{\"time\":1.0,\"timeZone\":0.0,\"offsetTime\":0.0}}"
|
||||
);
|
||||
assert_true(errorIsNotOk(ret));
|
||||
errorCatch(ret);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
assertInit();
|
||||
const struct CMUnitTest tests[] = {
|
||||
@@ -234,6 +269,8 @@ int main(void) {
|
||||
cmocka_unit_test(test_saveSlotReadJSON_missingTimeKey_errors),
|
||||
cmocka_unit_test(test_saveSlotReadJSON_nameTooLong_errors),
|
||||
cmocka_unit_test(test_saveSlotReadJSON_nonObjectRoot_errors),
|
||||
cmocka_unit_test(test_saveSlotReadJSON_versionMissing_errors),
|
||||
cmocka_unit_test(test_saveSlotReadJSON_versionMismatch_errors),
|
||||
};
|
||||
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
|
||||
Reference in New Issue
Block a user