128 lines
6 KiB
JavaScript
128 lines
6 KiB
JavaScript
// 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);
|
||
});
|