Add local automatic backups and Aniimo regions lists
All checks were successful
Deploy Sokko G / deploy (push) Successful in 18s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 18s
This commit is contained in:
parent
2255de94a7
commit
0a3bb4dffd
24 changed files with 908 additions and 16 deletions
128
tests/auto-backup.test.mjs
Normal file
128
tests/auto-backup.test.mjs
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// Rôle : vérifie les règles de nommage, d’écriture et de rétention des sauvegardes locales.
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { backupContentsEqual, getBackupFileName, getLocalDateKey, isFileSystemAccessSupported, saveBackup } from "../website/src/features/toolboxes/storage/autoBackup.js";
|
||||
|
||||
class FakeDirectory {
|
||||
constructor(names = []) {
|
||||
this.files = new Map(names.map((name) => [name, "old"]));
|
||||
this.directories = new Set();
|
||||
this.permission = "granted";
|
||||
this.failCleanup = false;
|
||||
this.failWrite = false;
|
||||
}
|
||||
|
||||
async queryPermission() { return this.permission; }
|
||||
|
||||
async *values() {
|
||||
for (const name of this.files.keys()) yield { kind: "file", name };
|
||||
for (const name of this.directories) yield { kind: "directory", name };
|
||||
}
|
||||
|
||||
async getFileHandle(name, { create = false } = {}) {
|
||||
if (create && !this.files.has(name)) this.files.set(name, "");
|
||||
const directory = this;
|
||||
return {
|
||||
async getFile() {
|
||||
return { text: async () => directory.files.get(name) || "" };
|
||||
},
|
||||
async createWritable() {
|
||||
if (directory.failWrite) throw new Error("write failed");
|
||||
return {
|
||||
async write(contents) { directory.files.set(name, contents); },
|
||||
async close() {},
|
||||
async abort() {}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async removeEntry(name) {
|
||||
if (this.failCleanup) throw new Error("cleanup failed");
|
||||
this.files.delete(name);
|
||||
}
|
||||
}
|
||||
|
||||
test("feature detection does not rely on the user agent", () => {
|
||||
assert.equal(isFileSystemAccessSupported({ showDirectoryPicker() {} }), true);
|
||||
assert.equal(isFileSystemAccessSupported({}), false);
|
||||
});
|
||||
|
||||
test("backup names use the browser local date", () => {
|
||||
const date = new Date(2026, 8, 24, 0, 5);
|
||||
assert.equal(getLocalDateKey(date), "2026-09-24");
|
||||
assert.equal(getBackupFileName(date), "sokkog-global-2026-09-24.json");
|
||||
});
|
||||
|
||||
test("automatic backup skips an existing day and creates the next day", async () => {
|
||||
const directory = new FakeDirectory(["sokkog-global-2026-09-23.json"]);
|
||||
let generated = 0;
|
||||
const generateJson = async () => { generated += 1; return "{}"; };
|
||||
const first = await saveBackup({ directoryHandle: directory, generateJson, date: new Date(2026, 8, 23) });
|
||||
const second = await saveBackup({ directoryHandle: directory, generateJson, date: new Date(2026, 8, 24) });
|
||||
assert.equal(first.created, false);
|
||||
assert.equal(second.created, true);
|
||||
assert.equal(generated, 1);
|
||||
assert.ok(directory.files.has("sokkog-global-2026-09-24.json"));
|
||||
});
|
||||
|
||||
test("automatic backup skips a new day when the export is unchanged", async () => {
|
||||
const previous = JSON.stringify({ version: 1, exportedAt: "2026-09-23T08:00:00.000Z", toolboxes: [], modules: {}, links: {} });
|
||||
const directory = new FakeDirectory(["sokkog-global-2026-09-23.json"]);
|
||||
directory.files.set("sokkog-global-2026-09-23.json", previous);
|
||||
const current = JSON.stringify({ version: 1, exportedAt: "2026-09-24T08:00:00.000Z", toolboxes: [], modules: {}, links: {} });
|
||||
const result = await saveBackup({ directoryHandle: directory, generateJson: async () => current, date: new Date(2026, 8, 24) });
|
||||
assert.equal(backupContentsEqual(previous, current), true);
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.created, false);
|
||||
assert.equal(result.unchanged, true);
|
||||
assert.equal(directory.files.has("sokkog-global-2026-09-24.json"), false);
|
||||
});
|
||||
|
||||
test("manual backup replaces the day and retains ten non-consecutive versions", async () => {
|
||||
const names = [
|
||||
"sokkog-global-2024-01-01.json", "sokkog-global-2024-06-01.json", "sokkog-global-2025-01-01.json",
|
||||
"sokkog-global-2025-03-01.json", "sokkog-global-2025-09-01.json", "sokkog-global-2026-01-01.json",
|
||||
"sokkog-global-2026-03-01.json", "sokkog-global-2026-06-01.json", "sokkog-global-2026-07-01.json",
|
||||
"sokkog-global-2026-08-01.json", "sokkog-global-2026-09-24.json", "notes.json"
|
||||
];
|
||||
const directory = new FakeDirectory(names);
|
||||
const result = await saveBackup({ directoryHandle: directory, force: true, date: new Date(2026, 8, 24), generateJson: async () => "new" });
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(directory.files.get("sokkog-global-2026-09-24.json"), "new");
|
||||
assert.equal([...directory.files.keys()].filter((name) => /^sokkog-global-\d{4}-\d{2}-\d{2}\.json$/.test(name)).length, 10);
|
||||
assert.equal(directory.files.get("notes.json"), "old");
|
||||
});
|
||||
|
||||
test("permission failure prevents export and cleanup", async () => {
|
||||
const directory = new FakeDirectory();
|
||||
directory.permission = "prompt";
|
||||
let generated = false;
|
||||
const result = await saveBackup({ directoryHandle: directory, generateJson: async () => { generated = true; return "{}"; } });
|
||||
assert.equal(result.reason, "permission");
|
||||
assert.equal(generated, false);
|
||||
});
|
||||
|
||||
test("cleanup failure does not invalidate a successfully written backup", async () => {
|
||||
const directory = new FakeDirectory(Array.from({ length: 10 }, (_, index) => `sokkog-global-2020-01-${String(index + 1).padStart(2, "0")}.json`));
|
||||
directory.failCleanup = true;
|
||||
const result = await saveBackup({ directoryHandle: directory, generateJson: async () => "{}", date: new Date(2026, 8, 24) });
|
||||
assert.equal(result.ok, true);
|
||||
assert.match(result.cleanupError, /cleanup failed/);
|
||||
assert.equal(directory.files.get("sokkog-global-2026-09-24.json"), "{}");
|
||||
});
|
||||
|
||||
test("failed write does not leave a newly created backup", async () => {
|
||||
const directory = new FakeDirectory();
|
||||
directory.failWrite = true;
|
||||
const result = await saveBackup({ directoryHandle: directory, generateJson: async () => "{}", date: new Date(2026, 8, 24) });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(directory.files.has("sokkog-global-2026-09-24.json"), false);
|
||||
});
|
||||
|
||||
test("export failure does not create a partial backup", async () => {
|
||||
const directory = new FakeDirectory();
|
||||
const result = await saveBackup({ directoryHandle: directory, generateJson: async () => { throw new Error("export failed"); }, date: new Date(2026, 8, 24) });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(directory.files.has("sokkog-global-2026-09-24.json"), false);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue