feat(store): _saveDisabled latch and stronger corruption-wipe coverage
This commit is contained in:
+4
-1
@@ -11,6 +11,7 @@ export class Store {
|
|||||||
if (!storage) throw new Error('Store requires a storage object');
|
if (!storage) throw new Error('Store requires a storage object');
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.onWarn = onWarn;
|
this.onWarn = onWarn;
|
||||||
|
this._saveDisabled = false;
|
||||||
this.targets = this._loadJson(KEY_TARGETS, {});
|
this.targets = this._loadJson(KEY_TARGETS, {});
|
||||||
this.history = this._loadJson(KEY_HISTORY, {});
|
this.history = this._loadJson(KEY_HISTORY, {});
|
||||||
this.prefs = this._mergePrefs(this._loadJson(KEY_PREFS, null));
|
this.prefs = this._mergePrefs(this._loadJson(KEY_PREFS, null));
|
||||||
@@ -34,11 +35,13 @@ export class Store {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_saveJson(key, value) {
|
_saveJson(key, value) {
|
||||||
|
if (this._saveDisabled) return false;
|
||||||
try {
|
try {
|
||||||
this.storage.setItem(key, JSON.stringify(value));
|
this.storage.setItem(key, JSON.stringify(value));
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.onWarn(`[tat] failed to persist ${key}: ${e.message}`);
|
this.onWarn(`[tat] failed to persist ${key}: ${e.message}; further saves disabled for this session`);
|
||||||
|
this._saveDisabled = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,4 +48,25 @@ test('Store: corrupted JSON is wiped with a warning', () => {
|
|||||||
const s = new Store({ storage, onWarn: (m) => warnings.push(m) });
|
const s = new Store({ storage, onWarn: (m) => warnings.push(m) });
|
||||||
assert.equal(s.getTarget('strength'), null);
|
assert.equal(s.getTarget('strength'), null);
|
||||||
assert.equal(warnings.length, 1);
|
assert.equal(warnings.length, 1);
|
||||||
|
assert.equal(storage.getItem('tat.targets'), null);
|
||||||
|
const warningsBefore = warnings.length;
|
||||||
|
new Store({ storage, onWarn: (m) => warnings.push(m) });
|
||||||
|
assert.equal(warnings.length, warningsBefore);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Store: latches off further saves after a storage write failure', () => {
|
||||||
|
const data = new Map();
|
||||||
|
const throwingStorage = {
|
||||||
|
getItem(k) { return data.has(k) ? data.get(k) : null; },
|
||||||
|
setItem() { throw new Error('quota exceeded'); },
|
||||||
|
removeItem(k) { data.delete(k); },
|
||||||
|
clear() { data.clear(); },
|
||||||
|
};
|
||||||
|
const warnings = [];
|
||||||
|
const s = new Store({ storage: throwingStorage, onWarn: (m) => warnings.push(m) });
|
||||||
|
assert.equal(s.setTarget('strength', 25_000_000), false);
|
||||||
|
assert.equal(s.setTarget('speed', 50_000_000), false);
|
||||||
|
assert.equal(warnings.length, 1);
|
||||||
|
assert.equal(s.getTarget('strength'), 25_000_000);
|
||||||
|
assert.equal(s.getTarget('speed'), 50_000_000);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user