joplin-plugin-backup 0.5.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,838 @@
1
+ import { Backup } from "../src/Backup";
2
+ import * as fs from "fs-extra";
3
+ import * as path from "path";
4
+ import { when } from "jest-when";
5
+ import { sevenZip } from "../src/sevenZip";
6
+ import joplin from "api";
7
+
8
+ function getTestPaths(): any {
9
+ const testPath: any = {};
10
+ testPath.base = path.join(__dirname, "tests");
11
+ testPath.backupBasePath = path.join(testPath.base, "Backup");
12
+ testPath.activeBackupJob = path.join(
13
+ testPath.backupBasePath,
14
+ "activeBackupJob"
15
+ );
16
+ testPath.joplinProfile = path.join(testPath.base, "joplin-desktop");
17
+ testPath.templates = path.join(testPath.joplinProfile, "templates");
18
+ return testPath;
19
+ }
20
+
21
+ let backup = null;
22
+
23
+ let spyOnLogVerbose = null;
24
+ let spyOnLogInfo = null;
25
+ let spyOnLogWarn = null;
26
+ let spyOnLogError = null;
27
+ let spyOnSaveBackupInfo = null;
28
+
29
+ const spyOnsSettingsValue = jest.spyOn(joplin.settings, "value");
30
+ const spyOnGlobalValue = jest.spyOn(joplin.settings, "globalValue");
31
+ const spyOnSettingsSetValue = jest
32
+ .spyOn(joplin.settings, "setValue")
33
+ .mockImplementation();
34
+
35
+ async function createTestStructure() {
36
+ const test = await getTestPaths();
37
+ fs.emptyDirSync(test.base);
38
+ fs.emptyDirSync(test.backupBasePath);
39
+ fs.emptyDirSync(test.joplinProfile);
40
+ fs.emptyDirSync(test.templates);
41
+ }
42
+
43
+ const testPath = getTestPaths();
44
+
45
+ describe("Backup", function () {
46
+ beforeEach(async () => {
47
+ /* prettier-ignore */
48
+ when(spyOnsSettingsValue)
49
+ .mockImplementation(() => Promise.resolve("no mockImplementation"))
50
+ .calledWith("fileLogLevel").mockImplementation(() => Promise.resolve("error"))
51
+ .calledWith("path").mockImplementation(() => Promise.resolve(testPath.backupBasePath));
52
+
53
+ /* prettier-ignore */
54
+ when(spyOnGlobalValue)
55
+ .mockImplementation(() => Promise.resolve("no mockImplementation"))
56
+ .calledWith("profileDir").mockImplementation(() => Promise.resolve(testPath.joplinProfile))
57
+ .calledWith("templateDir").mockImplementation(() => Promise.resolve(testPath.templates));
58
+
59
+ await createTestStructure();
60
+ backup = new Backup() as any;
61
+ backup.backupStartTime = new Date();
62
+ backup.backupSetName = "{YYYYMMDDHHmm}";
63
+
64
+ spyOnSaveBackupInfo = jest
65
+ .spyOn(backup, "saveBackupInfo")
66
+ .mockImplementation(() => {});
67
+
68
+ spyOnLogVerbose = jest
69
+ .spyOn(backup.log, "verbose")
70
+ .mockImplementation(() => {});
71
+ spyOnLogInfo = jest.spyOn(backup.log, "info").mockImplementation(() => {});
72
+ spyOnLogWarn = jest.spyOn(backup.log, "warn").mockImplementation(() => {});
73
+ spyOnLogError = jest
74
+ .spyOn(backup.log, "error")
75
+ .mockImplementation(() => {});
76
+ });
77
+
78
+ afterEach(async () => {
79
+ spyOnLogVerbose.mockReset();
80
+ spyOnLogInfo.mockReset();
81
+ spyOnLogWarn.mockReset();
82
+ spyOnLogError.mockReset();
83
+ spyOnsSettingsValue.mockReset();
84
+ spyOnGlobalValue.mockReset();
85
+ spyOnSaveBackupInfo.mockReset();
86
+ });
87
+
88
+ afterAll(async () => {
89
+ fs.removeSync(testPath.base);
90
+ });
91
+
92
+ describe("Backup path", function () {
93
+ it(`Backup path != Profile`, async () => {
94
+ await backup.loadBackupPath();
95
+ expect(backup.backupBasePath).toBe(testPath.backupBasePath);
96
+ expect(backup.backupBasePath).not.toBe(testPath.joplinProfile);
97
+
98
+ /* prettier-ignore */
99
+ when(spyOnsSettingsValue)
100
+ .calledWith("path").mockImplementation(() => Promise.resolve(""));
101
+ await backup.loadBackupPath();
102
+ expect(backup.backupBasePath).not.toBe(testPath.joplinProfile);
103
+ expect(backup.backupBasePath).toBe(null);
104
+
105
+ /* prettier-ignore */
106
+ when(spyOnsSettingsValue)
107
+ .calledWith("path").mockImplementation(() => Promise.resolve(testPath.joplinProfile));
108
+ await backup.loadBackupPath();
109
+ expect(backup.backupBasePath).not.toBe(testPath.joplinProfile);
110
+ expect(backup.backupBasePath).toBe(null);
111
+
112
+ expect(backup.log.error).toHaveBeenCalledTimes(0);
113
+ expect(backup.log.warn).toHaveBeenCalledTimes(0);
114
+ });
115
+
116
+ it(`relative paths`, async () => {
117
+ const backupPath = "../";
118
+ /* prettier-ignore */
119
+ when(spyOnsSettingsValue)
120
+ .calledWith("path").mockImplementation(() => Promise.resolve(backupPath));
121
+ await backup.loadBackupPath();
122
+ const toBe = path.normalize(
123
+ path.join(testPath.backupBasePath, backupPath)
124
+ );
125
+ expect(backup.backupBasePath).toBe(toBe);
126
+ expect(backup.log.error).toHaveBeenCalledTimes(0);
127
+ expect(backup.log.warn).toHaveBeenCalledTimes(0);
128
+ });
129
+ });
130
+
131
+ describe("Div", function () {
132
+ it(`Create empty folder`, async () => {
133
+ const folder = await backup.createEmptyFolder(
134
+ testPath.backupBasePath,
135
+ "profile"
136
+ );
137
+ const check = path.join(testPath.backupBasePath, "profile");
138
+ expect(folder).toBe(check);
139
+ expect(fs.existsSync(check)).toBe(true);
140
+ expect(backup.log.error).toHaveBeenCalledTimes(0);
141
+ expect(backup.log.warn).toHaveBeenCalledTimes(0);
142
+ });
143
+
144
+ it(`Delete log`, async () => {
145
+ backup.logFile = path.join(testPath.backupBasePath, "test.log");
146
+ fs.writeFileSync(backup.logFile, "data");
147
+
148
+ expect(fs.existsSync(backup.logFile)).toBe(true);
149
+ await backup.deleteLogFile();
150
+ expect(fs.existsSync(backup.logFile)).toBe(false);
151
+ expect(backup.log.error).toHaveBeenCalledTimes(0);
152
+ expect(backup.log.warn).toHaveBeenCalledTimes(0);
153
+ });
154
+ });
155
+
156
+ describe("moveFinishedBackup", function () {
157
+ it(`no retention`, async () => {
158
+ const emptyFolder = path.join(testPath.activeBackupJob, "emptyFolder");
159
+ const emptyFolderCheck = path.join(
160
+ testPath.backupBasePath,
161
+ "emptyFolder"
162
+ );
163
+ const folder = path.join(testPath.activeBackupJob, "folder");
164
+ const folderCheck = path.join(testPath.backupBasePath, "folder");
165
+ const file1 = path.join(folder, "file.txt");
166
+ const file1Check = path.join(folderCheck, "file.txt");
167
+ const file2 = path.join(testPath.activeBackupJob, "file.txt");
168
+ const file2Check = path.join(testPath.backupBasePath, "file.txt");
169
+ backup.backupBasePath = testPath.backupBasePath;
170
+ backup.activeBackupPath = testPath.activeBackupJob;
171
+
172
+ fs.emptyDirSync(testPath.activeBackupJob);
173
+ fs.emptyDirSync(emptyFolder);
174
+ fs.emptyDirSync(folder);
175
+ fs.writeFileSync(file1, "file");
176
+ fs.writeFileSync(file2, "file");
177
+
178
+ backup.backupRetention = 1;
179
+
180
+ expect(await backup.moveFinishedBackup()).toBe(testPath.backupBasePath);
181
+ expect(fs.existsSync(folderCheck)).toBe(true);
182
+ expect(fs.existsSync(emptyFolderCheck)).toBe(true);
183
+ expect(fs.existsSync(file1Check)).toBe(true);
184
+ expect(fs.existsSync(file2Check)).toBe(true);
185
+ expect(backup.log.error).toHaveBeenCalledTimes(0);
186
+ expect(backup.log.warn).toHaveBeenCalledTimes(0);
187
+ });
188
+
189
+ it(`retention > 1`, async () => {
190
+ const backupDst = path.join(testPath.backupBasePath, "202101021630");
191
+ const testEpoch = new Date(2021, 0, 2, 16, 30, 45, 0).getTime();
192
+ const spyOnDateNow = jest
193
+ .spyOn(Date, "now")
194
+ .mockImplementation(() => testEpoch);
195
+
196
+ const emptyFolder = path.join(testPath.activeBackupJob, "emptyFolder");
197
+ const emptyFolderCheck = path.join(backupDst, "emptyFolder");
198
+ const folder = path.join(testPath.activeBackupJob, "folder");
199
+ const folderCheck = path.join(backupDst, "folder");
200
+ const file1 = path.join(folder, "file.txt");
201
+ const file1Check = path.join(folderCheck, "file.txt");
202
+ const file2 = path.join(testPath.activeBackupJob, "file.txt");
203
+ const file2Check = path.join(backupDst, "file.txt");
204
+ backup.backupBasePath = testPath.backupBasePath;
205
+ backup.activeBackupPath = testPath.activeBackupJob;
206
+
207
+ fs.emptyDirSync(testPath.activeBackupJob);
208
+ fs.emptyDirSync(emptyFolder);
209
+ fs.emptyDirSync(folder);
210
+ fs.writeFileSync(file1, "file");
211
+ fs.writeFileSync(file2, "file");
212
+
213
+ backup.backupRetention = 2;
214
+
215
+ expect(await backup.moveFinishedBackup()).toBe(backupDst);
216
+ expect(fs.existsSync(folderCheck)).toBe(true);
217
+ expect(fs.existsSync(emptyFolderCheck)).toBe(true);
218
+ expect(fs.existsSync(file1Check)).toBe(true);
219
+ expect(fs.existsSync(file2Check)).toBe(true);
220
+ expect(backup.log.error).toHaveBeenCalledTimes(0);
221
+ expect(backup.log.warn).toHaveBeenCalledTimes(0);
222
+
223
+ spyOnDateNow.mockRestore();
224
+ });
225
+
226
+ it(`retention > 1, folder exist`, async () => {
227
+ backup.backupBasePath = testPath.backupBasePath;
228
+ backup.activeBackupPath = testPath.activeBackupJob;
229
+ backup.backupSetName = "JoplinBackupSet";
230
+ backup.backupRetention = 2;
231
+
232
+ const expected = path.join(
233
+ testPath.backupBasePath,
234
+ backup.backupSetName + " (1)"
235
+ );
236
+
237
+ fs.emptyDirSync(testPath.activeBackupJob);
238
+ const existingBackupSet = path.join(
239
+ testPath.backupBasePath,
240
+ backup.backupSetName
241
+ );
242
+ fs.emptyDirSync(existingBackupSet);
243
+ expect(fs.existsSync(existingBackupSet)).toBe(true);
244
+
245
+ expect(fs.existsSync(expected)).toBe(false);
246
+ expect(await backup.moveFinishedBackup()).toBe(expected);
247
+ expect(fs.existsSync(expected)).toBe(true);
248
+ });
249
+
250
+ it(`retention > 1, file exist`, async () => {
251
+ backup.backupBasePath = testPath.backupBasePath;
252
+ backup.activeBackupPath = testPath.activeBackupJob;
253
+ backup.backupSetName = "JoplinBackupSet";
254
+ backup.backupRetention = 2;
255
+
256
+ const zipFile = path.join(testPath.backupBasePath, "test.7z");
257
+ fs.writeFileSync(zipFile, "backup set");
258
+ expect(fs.existsSync(zipFile)).toBe(true);
259
+
260
+ const expected = path.join(
261
+ testPath.backupBasePath,
262
+ backup.backupSetName + " (1).7z"
263
+ );
264
+
265
+ fs.emptyDirSync(testPath.activeBackupJob);
266
+ const existingBackupSet = path.join(
267
+ testPath.backupBasePath,
268
+ backup.backupSetName + ".7z"
269
+ );
270
+ fs.writeFileSync(existingBackupSet, "backup set");
271
+ expect(fs.existsSync(existingBackupSet)).toBe(true);
272
+
273
+ expect(fs.existsSync(expected)).toBe(false);
274
+ expect(await backup.moveFinishedBackup(zipFile)).toBe(expected);
275
+ expect(fs.existsSync(expected)).toBe(true);
276
+ });
277
+ });
278
+ describe("Backup set", function () {
279
+ it(`Name`, async () => {
280
+ const testEpoch = new Date(2021, 0, 2, 16, 30, 45, 0).getTime();
281
+ /* prettier-ignore */
282
+ const spyOnDateNow = jest.spyOn(Date, "now").mockImplementation(() => testEpoch);
283
+
284
+ const testCases = [
285
+ {
286
+ backupSetName: "{YYYYMMDDHHmm}",
287
+ expected: "202101021630",
288
+ },
289
+ {
290
+ backupSetName: "{YYYY-MM-DD HH:mm}",
291
+ expected: "2021-01-02 16:30",
292
+ },
293
+ {
294
+ backupSetName: "Joplinbackup_{YYYYMMDDHHmm}",
295
+ expected: "Joplinbackup_202101021630",
296
+ },
297
+ {
298
+ backupSetName: "A {YYYY} b {MMDDHHmm}",
299
+ expected: "A 2021 b 01021630",
300
+ },
301
+ {
302
+ backupSetName: "j{j}j",
303
+ expected: "jjj",
304
+ },
305
+ {
306
+ backupSetName: "No var",
307
+ expected: "No var",
308
+ },
309
+ ];
310
+
311
+ for (const testCase of testCases) {
312
+ backup.backupSetName = testCase.backupSetName;
313
+ expect(await backup.getBackupSetFolderName()).toBe(testCase.expected);
314
+ }
315
+
316
+ spyOnDateNow.mockRestore();
317
+ expect(backup.log.error).toHaveBeenCalledTimes(0);
318
+ expect(backup.log.warn).toHaveBeenCalledTimes(0);
319
+ });
320
+
321
+ it(`Creation`, async () => {
322
+ const testEpoch = new Date(2021, 0, 2, 16, 30, 45, 0);
323
+ const spyOnDateNow = jest
324
+ .spyOn(Date, "now")
325
+ .mockImplementation(() => testEpoch.getTime());
326
+
327
+ const testCases = [
328
+ {
329
+ zipArchive: "no",
330
+ backupRetention: 1,
331
+ password: null,
332
+ singleJex: false,
333
+ result: testPath.backupBasePath,
334
+ testFile: "testFile.txt",
335
+ checkFile: path.join(testPath.backupBasePath, "testFile.txt"),
336
+ saveBackupInfoCalled: 0,
337
+ },
338
+ {
339
+ zipArchive: "no",
340
+ backupRetention: 1,
341
+ password: "secret",
342
+ singleJex: false,
343
+ result: testPath.backupBasePath,
344
+ testFile: "testFile.txt",
345
+ checkFile: path.join(testPath.backupBasePath, "testFile.txt.7z"),
346
+ saveBackupInfoCalled: 0,
347
+ },
348
+ {
349
+ zipArchive: "no",
350
+ backupRetention: 2,
351
+ password: null,
352
+ singleJex: false,
353
+ result: path.join(testPath.backupBasePath, "202101021630"),
354
+ testFile: "testFile.txt",
355
+ checkFile: path.join(
356
+ testPath.backupBasePath,
357
+ "202101021630",
358
+ "testFile.txt"
359
+ ),
360
+ saveBackupInfoCalled: 1,
361
+ },
362
+ {
363
+ zipArchive: "yes",
364
+ backupRetention: 1,
365
+ password: null,
366
+ singleJex: false,
367
+ result: testPath.backupBasePath,
368
+ testFile: "testFile.txt",
369
+ checkFile: path.join(testPath.backupBasePath, "testFile.txt.7z"),
370
+ saveBackupInfoCalled: 0,
371
+ },
372
+ {
373
+ zipArchive: "yes",
374
+ backupRetention: 2,
375
+ password: null,
376
+ singleJex: false,
377
+ result: path.join(testPath.backupBasePath, "202101021630"),
378
+ testFile: "testFile.txt",
379
+ checkFile: path.join(
380
+ testPath.backupBasePath,
381
+ "202101021630",
382
+ "testFile.txt.7z"
383
+ ),
384
+ saveBackupInfoCalled: 1,
385
+ },
386
+ {
387
+ zipArchive: "yesone",
388
+ backupRetention: 1,
389
+ password: null,
390
+ singleJex: false,
391
+ result: path.join(testPath.backupBasePath, "JoplinBackup.7z"),
392
+ testFile: "testFile.txt",
393
+ checkFile: path.join(testPath.backupBasePath, "JoplinBackup.7z"),
394
+ saveBackupInfoCalled: 0,
395
+ },
396
+ {
397
+ zipArchive: "yesone",
398
+ backupRetention: 2,
399
+ password: null,
400
+ singleJex: false,
401
+ result: path.join(testPath.backupBasePath, "202101021630.7z"),
402
+ testFile: "testFile.txt",
403
+ checkFile: path.join(testPath.backupBasePath, "202101021630.7z"),
404
+ saveBackupInfoCalled: 1,
405
+ },
406
+ {
407
+ zipArchive: "no",
408
+ backupRetention: 1,
409
+ password: null,
410
+ singleJex: true,
411
+ result: testPath.backupBasePath,
412
+ testFile: "testFile.txt",
413
+ checkFile: path.join(testPath.backupBasePath, "testFile.txt"),
414
+ saveBackupInfoCalled: 0,
415
+ },
416
+ {
417
+ zipArchive: "no",
418
+ backupRetention: 2,
419
+ password: null,
420
+ singleJex: true,
421
+ result: path.join(testPath.backupBasePath, "202101021630"),
422
+ testFile: "testFile.txt",
423
+ checkFile: path.join(
424
+ testPath.backupBasePath,
425
+ "202101021630",
426
+ "testFile.txt"
427
+ ),
428
+ saveBackupInfoCalled: 1,
429
+ },
430
+ {
431
+ zipArchive: "yes",
432
+ backupRetention: 1,
433
+ password: null,
434
+ singleJex: true,
435
+ result: path.join(testPath.backupBasePath, "JoplinBackup.7z"),
436
+ testFile: "testFile.txt",
437
+ checkFile: path.join(testPath.backupBasePath, "JoplinBackup.7z"),
438
+ saveBackupInfoCalled: 0,
439
+ },
440
+ {
441
+ zipArchive: "yes",
442
+ backupRetention: 2,
443
+ password: null,
444
+ singleJex: true,
445
+ result: path.join(testPath.backupBasePath, "202101021630.7z"),
446
+ testFile: "testFile.txt",
447
+ checkFile: path.join(testPath.backupBasePath, "202101021630.7z"),
448
+ saveBackupInfoCalled: 1,
449
+ },
450
+ {
451
+ zipArchive: "yesone",
452
+ backupRetention: 1,
453
+ password: null,
454
+ singleJex: true,
455
+ result: path.join(testPath.backupBasePath, "JoplinBackup.7z"),
456
+ testFile: "testFile.txt",
457
+ checkFile: path.join(testPath.backupBasePath, "JoplinBackup.7z"),
458
+ saveBackupInfoCalled: 0,
459
+ },
460
+ {
461
+ zipArchive: "yesone",
462
+ backupRetention: 1,
463
+ password: "secret",
464
+ singleJex: true,
465
+ result: path.join(testPath.backupBasePath, "JoplinBackup.7z"),
466
+ testFile: "testFile.txt",
467
+ checkFile: path.join(testPath.backupBasePath, "JoplinBackup.7z"),
468
+ saveBackupInfoCalled: 0,
469
+ },
470
+ {
471
+ zipArchive: "yesone",
472
+ backupRetention: 2,
473
+ password: null,
474
+ singleJex: true,
475
+ result: path.join(testPath.backupBasePath, "202101021630.7z"),
476
+ testFile: "testFile.txt",
477
+ checkFile: path.join(testPath.backupBasePath, "202101021630.7z"),
478
+ saveBackupInfoCalled: 1,
479
+ },
480
+ ];
481
+
482
+ backup.backupBasePath = testPath.backupBasePath;
483
+ backup.activeBackupPath = testPath.activeBackupJob;
484
+ backup.backupStartTime = testEpoch;
485
+ backup.logFile = path.join(testPath.backupBasePath, "test.log");
486
+
487
+ /* prettier-ignore */
488
+ when(spyOnsSettingsValue)
489
+ .calledWith("backupInfo").mockImplementation(() => Promise.resolve(JSON.stringify([])));
490
+ jest.spyOn(backup, "saveBackupInfo").mockImplementation(() => {});
491
+
492
+ for (const testCase of testCases) {
493
+ await createTestStructure();
494
+ fs.emptyDirSync(testPath.activeBackupJob);
495
+ const fileName = testCase.testFile;
496
+ const file = path.join(testPath.activeBackupJob, fileName);
497
+ fs.writeFileSync(file, "testFile");
498
+ expect(fs.existsSync(file)).toBe(true);
499
+
500
+ backup.zipArchive = testCase.zipArchive;
501
+ backup.backupRetention = testCase.backupRetention;
502
+ backup.singleJex = testCase.singleJex;
503
+ backup.passwordEnabled = testCase.password === null ? false : true;
504
+ backup.password = testCase.password;
505
+
506
+ const result = await backup.makeBackupSet();
507
+ expect(result).toBe(testCase.result);
508
+ expect(fs.existsSync(testCase.checkFile)).toBe(true);
509
+ expect(backup.saveBackupInfo).toHaveBeenCalledTimes(
510
+ testCase.saveBackupInfoCalled
511
+ );
512
+ const pwCheck = await sevenZip.passwordProtected(testCase.checkFile);
513
+ if (backup.passwordEnabled === true || testCase.zipArchive !== "no") {
514
+ expect(pwCheck).toBe(backup.passwordEnabled);
515
+ }
516
+
517
+ backup.saveBackupInfo.mockReset();
518
+ fs.emptyDirSync(testPath.activeBackupJob);
519
+ expect(fs.existsSync(file)).toBe(false);
520
+ }
521
+
522
+ spyOnDateNow.mockRestore();
523
+ });
524
+ });
525
+
526
+ describe("Backup retention", function () {
527
+ it(`Backups < retention`, async () => {
528
+ const backupRetention = 3;
529
+ const folder1 = path.join(testPath.backupBasePath, "202101011630");
530
+ const folder2 = path.join(testPath.backupBasePath, "202101021630");
531
+
532
+ fs.emptyDirSync(folder1);
533
+ fs.emptyDirSync(folder2);
534
+
535
+ const backupInfo = [
536
+ { name: "202101011630", date: 1 },
537
+ { name: "202101021630", date: 2 },
538
+ ];
539
+ /* prettier-ignore */
540
+ when(spyOnsSettingsValue)
541
+ .calledWith("backupInfo").mockImplementation(() => Promise.resolve(JSON.stringify(backupInfo)));
542
+
543
+ backup.deleteOldBackupSets(testPath.backupBasePath, backupRetention);
544
+
545
+ const folderAnz = fs
546
+ .readdirSync(testPath.backupBasePath, { withFileTypes: true })
547
+ .filter((dirent) => dirent.isDirectory()).length;
548
+
549
+ expect(folderAnz).toBe(2);
550
+
551
+ expect(fs.existsSync(folder1)).toBe(true);
552
+ expect(fs.existsSync(folder2)).toBe(true);
553
+ });
554
+
555
+ it(`Backups = retention`, async () => {
556
+ const backupRetention = 3;
557
+ const folder1 = path.join(testPath.backupBasePath, "202101011630");
558
+ const folder2 = path.join(testPath.backupBasePath, "202101021630");
559
+ const folder3 = path.join(testPath.backupBasePath, "202101031630");
560
+
561
+ fs.emptyDirSync(folder1);
562
+ fs.emptyDirSync(folder2);
563
+ fs.emptyDirSync(folder3);
564
+
565
+ const backupInfo = [
566
+ { name: "202101011630", date: 1 },
567
+ { name: "202101021630", date: 2 },
568
+ { name: "202101031630", date: 3 },
569
+ ];
570
+ /* prettier-ignore */
571
+ when(spyOnsSettingsValue)
572
+ .calledWith("backupInfo").mockImplementation(() => Promise.resolve(JSON.stringify(backupInfo)));
573
+
574
+ backup.deleteOldBackupSets(testPath.backupBasePath, backupRetention);
575
+ const folderAnz = fs
576
+ .readdirSync(testPath.backupBasePath, { withFileTypes: true })
577
+ .filter((dirent) => dirent.isDirectory()).length;
578
+
579
+ expect(folderAnz).toBe(3);
580
+
581
+ expect(fs.existsSync(folder1)).toBe(true);
582
+ expect(fs.existsSync(folder2)).toBe(true);
583
+ expect(fs.existsSync(folder3)).toBe(true);
584
+ });
585
+
586
+ it(`Backups > retention`, async () => {
587
+ const backupRetention = 3;
588
+ const folder1 = path.join(testPath.backupBasePath, "202101011630");
589
+ const folder2 = path.join(testPath.backupBasePath, "202101021630");
590
+ const folder3 = path.join(testPath.backupBasePath, "202101031630");
591
+ const folder4 = path.join(testPath.backupBasePath, "202101041630");
592
+ const folder5 = path.join(testPath.backupBasePath, "202101051630");
593
+
594
+ fs.emptyDirSync(folder1);
595
+ fs.emptyDirSync(folder2);
596
+ fs.emptyDirSync(folder3);
597
+ fs.emptyDirSync(folder4);
598
+ fs.emptyDirSync(folder5);
599
+
600
+ const backupInfo = [
601
+ { name: "202101011630", date: 1 },
602
+ { name: "202101021630", date: 2 },
603
+ { name: "202101031630", date: 3 },
604
+ { name: "202101041630", date: 4 },
605
+ { name: "202101051630", date: 5 },
606
+ ];
607
+ /* prettier-ignore */
608
+ when(spyOnsSettingsValue)
609
+ .calledWith("backupInfo").mockImplementation(() => Promise.resolve(JSON.stringify(backupInfo)));
610
+
611
+ await backup.deleteOldBackupSets(
612
+ testPath.backupBasePath,
613
+ backupRetention
614
+ );
615
+
616
+ const folderAnz = fs
617
+ .readdirSync(testPath.backupBasePath, { withFileTypes: true })
618
+ .filter((dirent) => dirent.isDirectory()).length;
619
+
620
+ expect(folderAnz).toBe(3);
621
+ expect(fs.existsSync(folder1)).toBe(false);
622
+ expect(fs.existsSync(folder2)).toBe(false);
623
+ expect(fs.existsSync(folder3)).toBe(true);
624
+ expect(fs.existsSync(folder4)).toBe(true);
625
+ expect(fs.existsSync(folder5)).toBe(true);
626
+ });
627
+ });
628
+
629
+ describe("Logging", function () {
630
+ beforeEach(async () => {
631
+ backup.setupLog();
632
+ });
633
+
634
+ it(`Default`, async () => {
635
+ expect(backup.log.transports.console.level).toBe("verbose");
636
+ expect(backup.log.transports.file.level).toBe(false);
637
+ });
638
+
639
+ it(`Toggel file`, async () => {
640
+ await backup.fileLogging(false);
641
+ expect(backup.log.transports.file.level).toBe(false);
642
+
643
+ /* prettier-ignore */
644
+ when(spyOnsSettingsValue)
645
+ .calledWith("fileLogLevel").mockImplementation(() => Promise.resolve("verbose"));
646
+
647
+ backup.backupBasePath = "./";
648
+ await backup.fileLogging(true);
649
+ expect(backup.log.transports.file.level).toBe("verbose");
650
+
651
+ /* prettier-ignore */
652
+ when(spyOnsSettingsValue)
653
+ .calledWith("fileLogLevel").mockImplementation(() => Promise.resolve("error"));
654
+
655
+ backup.backupBasePath = "./";
656
+ await backup.fileLogging(true);
657
+ expect(backup.log.transports.file.level).toBe("error");
658
+ });
659
+
660
+ it(`move logfile`, async () => {
661
+ const testCases = [
662
+ {
663
+ zipArchive: "no",
664
+ password: null,
665
+ logDst: testPath.backupBasePath,
666
+ testLogFile: path.join(testPath.backupBasePath, "backup.log"),
667
+ },
668
+ {
669
+ zipArchive: "no",
670
+ password: null,
671
+ logDst: path.join(testPath.backupBasePath, "testDir"),
672
+ testLogFile: path.join(
673
+ testPath.backupBasePath,
674
+ "testDir",
675
+ "backup.log"
676
+ ),
677
+ },
678
+ {
679
+ zipArchive: "yes",
680
+ password: null,
681
+ logDst: path.join(testPath.backupBasePath, "testDir"),
682
+ testLogFile: path.join(
683
+ testPath.backupBasePath,
684
+ "testDir",
685
+ "backup.log"
686
+ ),
687
+ },
688
+ {
689
+ zipArchive: "yesone",
690
+ password: null,
691
+ logDst: path.join(testPath.backupBasePath, "Backup.7z"),
692
+ testLogFile: "backup.log",
693
+ },
694
+ {
695
+ zipArchive: "yesone",
696
+ password: "secret",
697
+ logDst: path.join(testPath.backupBasePath, "Backup.7z"),
698
+ testLogFile: "backup.log",
699
+ },
700
+ {
701
+ zipArchive: "no",
702
+ password: "secret",
703
+ logDst: testPath.backupBasePath,
704
+ testLogFile: "backup.log",
705
+ },
706
+ ];
707
+
708
+ backup.logFile = path.join(testPath.base, "test.log");
709
+ for (const testCase of testCases) {
710
+ await createTestStructure();
711
+ if (testCase.zipArchive !== "yesone") {
712
+ fs.emptyDirSync(testCase.logDst);
713
+ }
714
+ if (testCase.zipArchive === "yesone") {
715
+ const dummyFile = path.join(testPath.base, "dummy");
716
+ fs.writeFileSync(dummyFile, "dummy");
717
+ await sevenZip.add(testCase.logDst, dummyFile, testCase.password);
718
+ expect(fs.existsSync(dummyFile)).toBe(true);
719
+ expect(fs.existsSync(testCase.logDst)).toBe(true);
720
+ }
721
+
722
+ fs.writeFileSync(backup.logFile, "log");
723
+
724
+ backup.zipArchive = testCase.zipArchive;
725
+ backup.password = testCase.password;
726
+
727
+ expect(fs.existsSync(backup.logFile)).toBe(true);
728
+ expect(await backup.moveLogFile(testCase.logDst)).toBe(true);
729
+ expect(fs.existsSync(backup.logFile)).toBe(false);
730
+
731
+ if (testCase.password !== null || testCase.zipArchive === "yesone") {
732
+ const fileList = await sevenZip.list(
733
+ testCase.logDst,
734
+ testCase.password
735
+ );
736
+ expect(fileList.map((f) => f.file)).toContain(testCase.testLogFile);
737
+ } else {
738
+ expect(fs.existsSync(testCase.testLogFile)).toBe(true);
739
+ }
740
+ }
741
+ });
742
+ });
743
+
744
+ describe("Backup action", function () {
745
+ it(`File`, async () => {
746
+ const src1 = path.join(testPath.joplinProfile, "settings.json");
747
+ const src2 = path.join(testPath.joplinProfile, "doesNotExist.json");
748
+ const dst = path.join(testPath.backupBasePath, "settings.json");
749
+ fs.writeFileSync(src1, "data");
750
+
751
+ expect(await backup.backupFile(src1, dst)).toBe(true);
752
+ expect(fs.existsSync(dst)).toBe(true);
753
+
754
+ expect(await backup.backupFile(src2, dst)).toBe(false);
755
+
756
+ expect(backup.log.error).toHaveBeenCalledTimes(0);
757
+ expect(backup.log.warn).toHaveBeenCalledTimes(0);
758
+ });
759
+
760
+ it(`Folder`, async () => {
761
+ const file1 = path.join(testPath.templates, "template1.md");
762
+ const file2 = path.join(testPath.templates, "template2.md");
763
+
764
+ const doesNotExist = path.join(testPath.base, "doesNotExist");
765
+
766
+ const dst = path.join(testPath.backupBasePath, "templates");
767
+ const checkFile1 = path.join(dst, "template1.md");
768
+ const checkFile2 = path.join(dst, "template2.md");
769
+
770
+ fs.writeFileSync(file1, "template1");
771
+ fs.writeFileSync(file2, "template2");
772
+
773
+ expect(await backup.backupFolder(testPath.templates, dst)).toBe(true);
774
+ expect(fs.existsSync(checkFile1)).toBe(true);
775
+ expect(fs.existsSync(checkFile2)).toBe(true);
776
+
777
+ expect(await backup.backupFolder(doesNotExist, dst)).toBe(false);
778
+
779
+ expect(backup.log.error).toHaveBeenCalledTimes(0);
780
+ expect(backup.log.warn).toHaveBeenCalledTimes(0);
781
+ });
782
+
783
+ it(`Profile`, async () => {
784
+ const template = path.join(testPath.templates, "template1.md");
785
+ const settings = path.join(testPath.joplinProfile, "settings.json");
786
+ const userstyle = path.join(testPath.joplinProfile, "userstyle.css");
787
+ const userchrome = path.join(testPath.joplinProfile, "userchrome.css");
788
+ const keymap = path.join(testPath.joplinProfile, "keymap-desktop.json");
789
+
790
+ fs.writeFileSync(template, "template");
791
+ fs.writeFileSync(settings, "settings");
792
+ fs.writeFileSync(userstyle, "userstyle");
793
+ fs.writeFileSync(userchrome, "userchrome");
794
+ fs.writeFileSync(keymap, "keymap");
795
+
796
+ fs.emptyDirSync(testPath.activeBackupJob);
797
+
798
+ const backupTemplate = path.join(
799
+ testPath.activeBackupJob,
800
+ "profile",
801
+ "templates",
802
+ "template1.md"
803
+ );
804
+ const backupSettings = path.join(
805
+ testPath.activeBackupJob,
806
+ "profile",
807
+ "settings.json"
808
+ );
809
+ const backupUserstyle = path.join(
810
+ testPath.activeBackupJob,
811
+ "profile",
812
+ "userstyle.css"
813
+ );
814
+ const backupUserchrome = path.join(
815
+ testPath.activeBackupJob,
816
+ "profile",
817
+ "userchrome.css"
818
+ );
819
+ const backupKeymap = path.join(
820
+ testPath.activeBackupJob,
821
+ "profile",
822
+ "keymap-desktop.json"
823
+ );
824
+
825
+ backup.activeBackupPath = testPath.activeBackupJob;
826
+ await backup.backupProfileData();
827
+
828
+ expect(fs.existsSync(backupTemplate)).toBe(true);
829
+ expect(fs.existsSync(backupSettings)).toBe(true);
830
+ expect(fs.existsSync(backupUserstyle)).toBe(true);
831
+ expect(fs.existsSync(backupUserchrome)).toBe(true);
832
+ expect(fs.existsSync(backupKeymap)).toBe(true);
833
+
834
+ expect(backup.log.error).toHaveBeenCalledTimes(0);
835
+ expect(backup.log.warn).toHaveBeenCalledTimes(0);
836
+ });
837
+ });
838
+ });