mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
137 lines
5.1 KiB
Lua
137 lines
5.1 KiB
Lua
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local T = require("tests.harness")
|
|
love = love or require("tests.love_stub")
|
|
|
|
local SaveData = require("src.core.SaveData")
|
|
local SyncState = require("src.sync.SyncState")
|
|
|
|
local realFS = love.filesystem
|
|
|
|
local function memfs(files)
|
|
return {
|
|
files = files,
|
|
write = function(path, content) files[path] = content return true end,
|
|
read = function(path) return files[path] end,
|
|
remove = function(path) files[path] = nil return true end,
|
|
getInfo = function(path)
|
|
if files[path] then return { type = "file" } end
|
|
return nil
|
|
end,
|
|
}
|
|
end
|
|
|
|
local function fresh()
|
|
local files = {}
|
|
love.filesystem = memfs(files)
|
|
SaveData.resetSlotState()
|
|
return files
|
|
end
|
|
|
|
do
|
|
local opts = SaveData.defaultOptions()
|
|
T.check(type(opts.saveSync) == "table", "defaultOptions carries saveSync")
|
|
T.eq(opts.saveSync.enabled, false, "sync is off until the player sets it up")
|
|
T.check(type(opts.saveSync.revs) == "table", "and starts with no synced revs")
|
|
T.eq(opts.saveSync.account, nil, "and no account")
|
|
|
|
local state = SyncState.defaults()
|
|
T.eq(SyncState.linked(state), false, "a default state is not linked")
|
|
T.eq(state.lastSyncAt, 0, "and has never synced")
|
|
end
|
|
|
|
do
|
|
local dirty = SyncState.sanitize({
|
|
enabled = "yes",
|
|
account = "aa11bb22cc33dd44",
|
|
deviceToken = "tok",
|
|
deviceLabel = "",
|
|
lastSyncAt = 0 / 0,
|
|
code1 = "12345678",
|
|
code2 = "87654321",
|
|
revs = { ["red/aaa"] = 4, [7] = 9, ["red/bad"] = "no" },
|
|
stamps = { ["red/aaa"] = 1700 },
|
|
pendingConflicts = { { key = "red/aaa", version = "red", overlap = true },
|
|
{ nope = true } },
|
|
})
|
|
T.eq(dirty.enabled, false, "a non-boolean enabled reads as off")
|
|
T.eq(dirty.account, "aa11bb22cc33dd44", "the account id survives")
|
|
T.eq(dirty.deviceLabel, nil, "an empty device label is dropped")
|
|
T.eq(dirty.lastSyncAt, 0, "a NaN lastSyncAt is refused")
|
|
T.eq(dirty.code1, "12345678", "the first account code is kept")
|
|
T.eq(dirty.code2, "87654321", "and the second")
|
|
T.eq(SyncState.sanitize({ code1 = "1234", code2 = "8765-4321" }).code1, nil,
|
|
"a code that is not 8 digits is dropped")
|
|
T.eq(SyncState.sanitize({ code1 = "1234", code2 = "8765-4321" }).code2,
|
|
"87654321", "and a grouped code is stored as bare digits")
|
|
T.eq(dirty.revs["red/aaa"], 4, "numeric revs survive")
|
|
T.eq(dirty.revs["red/bad"], nil, "a non-numeric rev is dropped")
|
|
T.eq(dirty.revs[7], nil, "a non-string rev key is dropped")
|
|
T.eq(dirty.stamps["red/aaa"], 1700, "the savedAt stamp survives")
|
|
T.eq(#dirty.pendingConflicts, 1, "only well-formed conflicts are kept")
|
|
T.eq(dirty.pendingConflicts[1].overlap, true, "with their overlap flag")
|
|
end
|
|
|
|
do
|
|
local files = fresh()
|
|
local state = SyncState.load()
|
|
T.eq(SyncState.linked(state), false, "a first boot has no linked account")
|
|
|
|
state.account = "aa11bb22cc33dd44"
|
|
state.deviceToken = "feedface"
|
|
state.deviceId = "0a1b2c3d"
|
|
state.deviceLabel = "laptop"
|
|
state.enabled = true
|
|
state.code1 = "12345678"
|
|
SyncState.setRev(state, SyncState.key("red", "abc"), 3, 1700000000)
|
|
SyncState.save(state)
|
|
|
|
T.check(files["options.lua"] ~= nil, "the state lands in options.lua")
|
|
T.check(files["options.lua"]:find("12345678", 1, true) ~= nil,
|
|
"the account codes are written with the state so the popup can show them")
|
|
|
|
local back = SyncState.load()
|
|
T.eq(SyncState.linked(back), true, "the linked account survives a reload")
|
|
T.eq(back.deviceLabel, "laptop", "and the device label")
|
|
T.eq(back.deviceId, "0a1b2c3d",
|
|
"and the device id the server revokes tokens by")
|
|
T.eq(SyncState.rev(back, "red/abc"), 3, "and the last synced rev")
|
|
T.eq(SyncState.stamp(back, "red/abc"), 1700000000, "and the savedAt stamp")
|
|
T.eq(back.code1, "12345678", "the code survives the reload")
|
|
|
|
local opts = SaveData.loadOptions()
|
|
T.eq(opts.textSpeed, 3, "writing sync state leaves other options alone")
|
|
|
|
SyncState.forget(back, "red/abc")
|
|
T.eq(SyncState.rev(back, "red/abc"), nil, "forget drops the rev")
|
|
T.eq(SyncState.stamp(back, "red/abc"), nil, "and the stamp")
|
|
|
|
SyncState.clear()
|
|
T.eq(SyncState.linked(SyncState.load()), false, "clear unlinks the device")
|
|
end
|
|
|
|
do
|
|
T.eq(SyncState.key("red", "abc"), "red/abc", "keys join version and id")
|
|
T.eq(SyncState.key("red", ""), nil, "an empty playthrough id has no key")
|
|
T.eq(SyncState.key(nil, "abc"), nil, "and neither does a missing version")
|
|
local version, id = SyncState.splitKey("gold/deadbeef")
|
|
T.eq(version, "gold", "splitKey reads the version back")
|
|
T.eq(id, "deadbeef", "and the playthrough id")
|
|
end
|
|
|
|
love.filesystem = realFS
|
|
|
|
do
|
|
local state = SyncState.defaults()
|
|
SyncState.markDeleted(state, "red/abc", 3, 900)
|
|
local back = SyncState.sanitize(state)
|
|
T.eq(back.pendingDeletes["red/abc"].rev, 3, "a pending delete survives sanitize")
|
|
T.eq(back.pendingDeletes["red/abc"].deletedAt, 900, "with its time")
|
|
SyncState.clearDeleted(back, "red/abc")
|
|
T.eq(next(back.pendingDeletes), nil, "and can be cleared")
|
|
T.eq(next(SyncState.sanitize({ pendingDeletes = { [7] = {}, x = "no" } })
|
|
.pendingDeletes), nil, "malformed pending deletes are dropped")
|
|
end
|
|
|
|
T.finish("sync_state")
|