Skip to content

Commit 82b3024

Browse files
authored
Protect site cache updates on persistence errors (#94)
1 parent 8f8022c commit 82b3024

2 files changed

Lines changed: 59 additions & 4 deletions

File tree

src/popup/site-storage.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,15 @@
3636

3737
async function persist(nextLevels) {
3838
const normalized = normalizeLevels(nextLevels);
39-
await storage.setSiteLevels(normalized);
40-
cache = normalized;
41-
return getCache();
39+
const previousCache = cache;
40+
try {
41+
await storage.setSiteLevels(normalized);
42+
cache = normalized;
43+
return getCache();
44+
} catch (error) {
45+
cache = previousCache;
46+
throw error;
47+
}
4248
}
4349

4450
async function upsert(host, level) {

tests/storage.test.js

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ let storageGet;
66
let nextResult;
77
let nextError;
88
let windowStub;
9+
let siteStorage;
10+
const noopSetSiteLevels = async () => {};
911

1012
function clone(obj) {
1113
if (typeof obj === 'undefined') {
@@ -23,7 +25,8 @@ before(async () => {
2325
SITE_KEY: globalThis.SITE_KEY,
2426
SCHEDULE_KEY: globalThis.SCHEDULE_KEY,
2527
chrome: globalThis.chrome,
26-
ScreenDimmerStorage: globalThis.ScreenDimmerStorage
28+
ScreenDimmerStorage: globalThis.ScreenDimmerStorage,
29+
ScreenDimmerSiteStorage: globalThis.ScreenDimmerSiteStorage
2730
};
2831

2932
windowStub = {
@@ -78,11 +81,21 @@ before(async () => {
7881
await import('../src/shared/storage.js');
7982
storageGet = globalThis.window.ScreenDimmerStorage.storageGet;
8083
globalThis.ScreenDimmerStorage = globalThis.window.ScreenDimmerStorage;
84+
85+
await import('../src/popup/site-storage.js');
86+
siteStorage = globalThis.window.ScreenDimmerSiteStorage;
87+
globalThis.ScreenDimmerSiteStorage = siteStorage;
8188
});
8289

8390
beforeEach(() => {
8491
nextResult = { sync: undefined, local: undefined };
8592
nextError = { sync: null, local: null };
93+
if (siteStorage) {
94+
siteStorage.setCache({});
95+
}
96+
if (globalThis.ScreenDimmerStorage) {
97+
globalThis.ScreenDimmerStorage.setSiteLevels = noopSetSiteLevels;
98+
}
8699
});
87100

88101
after(() => {
@@ -149,3 +162,39 @@ test('getSchedule falls back to local copy when sync is missing', async () => {
149162
assert.equal('enabled' in result.rules[0], false);
150163
assert.equal('location' in result, false);
151164
});
165+
166+
test('site storage cache updates only after persistence succeeds', async () => {
167+
siteStorage.setCache({ 'example.com': 0.2 });
168+
169+
let resolvePersist;
170+
const pending = new Promise((resolve) => {
171+
resolvePersist = resolve;
172+
});
173+
const capturedLevels = [];
174+
175+
globalThis.ScreenDimmerStorage.setSiteLevels = async (levels) => {
176+
capturedLevels.push(levels);
177+
return pending;
178+
};
179+
180+
const persistPromise = siteStorage.upsert('new.example.com', 0.6);
181+
182+
assert.deepEqual(siteStorage.getCache(), { 'example.com': 0.2 });
183+
resolvePersist();
184+
await persistPromise;
185+
186+
assert.deepEqual(capturedLevels, [{ 'example.com': 0.2, 'new.example.com': 0.6 }]);
187+
assert.deepEqual(siteStorage.getCache(), { 'example.com': 0.2, 'new.example.com': 0.6 });
188+
});
189+
190+
test('site storage restores previous cache when persistence fails', async () => {
191+
siteStorage.setCache({ 'existing.com': 0.4 });
192+
const error = new Error('persist failed');
193+
194+
globalThis.ScreenDimmerStorage.setSiteLevels = async () => {
195+
throw error;
196+
};
197+
198+
await assert.rejects(siteStorage.remove('existing.com'), /persist failed/);
199+
assert.deepEqual(siteStorage.getCache(), { 'existing.com': 0.4 });
200+
});

0 commit comments

Comments
 (0)