-
Notifications
You must be signed in to change notification settings - Fork 126
/
Copy pathdata_dir.spec.ts
351 lines (277 loc) · 15.4 KB
/
data_dir.spec.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
import { describe, it, expect, beforeEach, vi } from "vitest";
import type { getTriliumDataDir as getTriliumDataDirType, getDataDirs as getDataDirsType, getPlatformAppDataDir as getPlatformAppDataDirType } from "./data_dir.js";
describe("data_dir.ts unit tests", async () => {
let getTriliumDataDir: typeof getTriliumDataDirType;
let getPlatformAppDataDir: typeof getPlatformAppDataDirType;
let getDataDirs: typeof getDataDirsType;
const mockFn = {
existsSyncMock: vi.fn(),
mkdirSyncMock: vi.fn(),
osHomedirMock: vi.fn(),
osPlatformMock: vi.fn(),
pathJoinMock: vi.fn()
};
// using doMock, to avoid hoisting, so that we can use the mockFn object
// to collect all mocked Fns
vi.doMock("node:fs", () => {
return {
default: {
existsSync: mockFn.existsSyncMock,
mkdirSync: mockFn.mkdirSyncMock
}
};
});
vi.doMock("node:os", () => {
return {
default: {
homedir: mockFn.osHomedirMock,
platform: mockFn.osPlatformMock
}
};
});
vi.doMock("path", () => {
return {
join: mockFn.pathJoinMock
};
});
// import function to test now, after creating the mocks
({ getTriliumDataDir } = await import("./data_dir.js"));
({ getPlatformAppDataDir } = await import("./data_dir.js"));
({ getDataDirs } = await import("./data_dir.js"));
// helper to reset call counts
const resetAllMocks = () => {
Object.values(mockFn).forEach((mockedFn) => {
mockedFn.mockReset();
});
};
// helper to set mocked Platform
const setMockPlatform = (osPlatform: string, homedir: string, pathJoin: string) => {
mockFn.osPlatformMock.mockImplementation(() => osPlatform);
mockFn.osHomedirMock.mockImplementation(() => homedir);
mockFn.pathJoinMock.mockImplementation(() => pathJoin);
};
describe("#getPlatformAppDataDir()", () => {
type TestCaseGetPlatformAppDataDir = [description: string, fnValue: Parameters<typeof getPlatformAppDataDir>, expectedValue: string | null, osHomedirMockValue: string | null];
const testCases: TestCaseGetPlatformAppDataDir[] = [
[ "w/ unsupported OS it should return 'null'", [ "aix", undefined ], null, null ],
[ "w/ win32 and no APPDATA set it should return 'null'", [ "win32", undefined ], null, null ],
[ "w/ win32 and set APPDATA it should return set 'APPDATA'", [ "win32", "AppData" ], "AppData", null ],
[ "w/ linux it should return '~/.local/share'", [ "linux", undefined ], "/home/mock/.local/share", "/home/mock" ],
[ "w/ linux and wrongly set APPDATA it should ignore APPDATA and return '~/.local/share'", [ "linux", "FakeAppData" ], "/home/mock/.local/share", "/home/mock" ],
[ "w/ darwin it should return '~/Library/Application Support'", [ "darwin", undefined ], "/Users/mock/Library/Application Support", "/Users/mock" ]
];
beforeEach(() => {
// make sure OS does not set its own process.env.APPDATA, so that we can use our own supplied value
delete process.env.APPDATA;
});
testCases.forEach((testCase) => {
const [ testDescription, fnValues, expected, osHomedirMockValue ] = testCase;
return it(testDescription, () => {
mockFn.osHomedirMock.mockReturnValue(osHomedirMockValue);
const actual = getPlatformAppDataDir(...fnValues);
expect(actual).toEqual(expected);
});
});
});
describe("#getTriliumDataDir", async () => {
beforeEach(() => {
// make sure these are not set
delete process.env.TRILIUM_DATA_DIR;
delete process.env.APPDATA;
resetAllMocks();
});
/**
* case A – process.env.TRILIUM_DATA_DIR is set
* case B – process.env.TRILIUM_DATA_DIR is not set and Trilium folder is existing in platform
* case C – process.env.TRILIUM_DATA_DIR is not set and Trilium folder is not existing in platform's home dir
* case D – fallback to creating Trilium folder in home dir
*/
describe("case A", () => {
it("when folder exists – it should return the path, without attempting to create the folder", async () => {
const mockTriliumDataPath = "/home/mock/trilium-data-ENV-A1";
process.env.TRILIUM_DATA_DIR = mockTriliumDataPath;
// set fs.existsSync to true, i.e. the folder does exist
mockFn.existsSyncMock.mockImplementation(() => true);
const result = getTriliumDataDir("trilium-data");
// createDirIfNotExisting should call existsync 1 time and mkdirSync 0 times -> as it does not need to create the folder
// and return value should be TRILIUM_DATA_DIR value from process.env
expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(1);
expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(0);
expect(result).toEqual(process.env.TRILIUM_DATA_DIR);
});
it("when folder does not exist – it should attempt to create the folder and return the path", async () => {
const mockTriliumDataPath = "/home/mock/trilium-data-ENV-A2";
process.env.TRILIUM_DATA_DIR = mockTriliumDataPath;
// set fs.existsSync mock to return false, i.e. the folder does not exist
mockFn.existsSyncMock.mockImplementation(() => false);
const result = getTriliumDataDir("trilium-data");
// createDirIfNotExisting should call existsync 1 time and mkdirSync 1 times -> as it has to create the folder
// and return value should be TRILIUM_DATA_DIR value from process.env
expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(1);
expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1);
expect(result).toEqual(process.env.TRILIUM_DATA_DIR);
});
});
describe("case B", () => {
it("it should check if folder exists and return it", async () => {
const homedir = "/home/mock";
const dataDirName = "trilium-data";
const mockTriliumDataPath = `${homedir}/${dataDirName}`;
mockFn.pathJoinMock.mockImplementation(() => mockTriliumDataPath);
// set fs.existsSync to true, i.e. the folder does exist
mockFn.existsSyncMock.mockImplementation(() => true);
const result = getTriliumDataDir(dataDirName);
expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(1);
expect(result).toEqual(mockTriliumDataPath);
});
});
describe("case C", () => {
it("w/ Platform 'Linux', an existing App Data Folder (~/.local/share) but non-existing Trilium dir (~/.local/share/trilium-data) – it should attempt to create the dir", async () => {
const homedir = "/home/mock";
const dataDirName = "trilium-data";
const mockPlatformDataPath = `${homedir}/.local/share/${dataDirName}`;
// mock set: os.platform, os.homedir and pathJoin return values
setMockPlatform("linux", homedir, mockPlatformDataPath);
// use Generator to precisely control order of fs.existSync return values
const existsSyncMockGen = (function* () {
// 1) fs.existSync -> case B
yield false;
// 2) fs.existSync -> case C -> checking if default OS PlatformAppDataDir exists
yield true;
// 3) fs.existSync -> case C -> checking if Trilium Data folder exists
yield false;
})();
mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value);
const result = getTriliumDataDir(dataDirName);
expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(3);
expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1);
expect(result).toEqual(mockPlatformDataPath);
});
it("w/ Platform Linux, an existing App Data Folder (~/.local/share) AND an existing Trilium Data dir – it should return path to the dir", async () => {
const homedir = "/home/mock";
const dataDirName = "trilium-data";
const mockPlatformDataPath = `${homedir}/.local/share/${dataDirName}`;
// mock set: os.platform, os.homedir and pathJoin return values
setMockPlatform("linux", homedir, mockPlatformDataPath);
// use Generator to precisely control order of fs.existSync return values
const existsSyncMockGen = (function* () {
// 1) fs.existSync -> case B
yield false;
// 2) fs.existSync -> case C -> checking if default OS PlatformAppDataDir exists
yield true;
// 3) fs.existSync -> case C -> checking if Trilium Data folder exists
yield true;
})();
mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value);
const result = getTriliumDataDir(dataDirName);
expect(result).toEqual(mockPlatformDataPath);
expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(3);
expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(0);
});
it("w/ Platform 'win32' and set process.env.APPDATA behaviour", async () => {
const homedir = "C:\\Users\\mock";
const dataDirName = "trilium-data";
const appDataDir = `${homedir}\\AppData\\Roaming`;
const mockPlatformDataPath = `${appDataDir}\\${dataDirName}`;
process.env.APPDATA = `${appDataDir}`;
// mock set: os.platform, os.homedir and pathJoin return values
setMockPlatform("win32", homedir, mockPlatformDataPath);
// use Generator to precisely control order of fs.existSync return values
const existsSyncMockGen = (function* () {
// 1) fs.existSync -> case B
yield false;
// 2) fs.existSync -> case C -> checking if default OS PlatformAppDataDir exists
yield true;
// 3) fs.existSync -> case C -> checking if Trilium Data folder exists
yield false;
})();
mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value);
const result = getTriliumDataDir(dataDirName);
expect(result).toEqual(mockPlatformDataPath);
expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(3);
expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1);
});
});
describe("case D", () => {
it("w/ unknown PlatformAppDataDir it should attempt to create the folder in the homefolder", async () => {
const homedir = "/home/mock";
const dataDirName = "trilium-data";
const mockPlatformDataPath = `${homedir}/${dataDirName}`;
setMockPlatform("aix", homedir, mockPlatformDataPath);
const existsSyncMockGen = (function* () {
// first fs.existSync -> case B -> checking if folder exists in home folder
yield false;
// second fs.existSync -> case D -> triggered by createDirIfNotExisting
yield false;
})();
mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value);
const result = getTriliumDataDir(dataDirName);
expect(result).toEqual(mockPlatformDataPath);
expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(2);
expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1);
});
});
});
describe("#getDataDirs()", () => {
const envKeys: Omit<keyof ReturnType<typeof getDataDirs>, "TRILIUM_DATA_DIR">[] = [ "DOCUMENT_PATH", "BACKUP_DIR", "LOG_DIR", "ANONYMIZED_DB_DIR", "CONFIG_INI_PATH", "TMP_DIR" ];
const setMockedEnv = (prefix: string | null) => {
envKeys.forEach((key) => {
if (prefix) {
process.env[`TRILIUM_${key}`] = `${prefix}_${key}`;
} else {
delete process.env[`TRILIUM_${key}`];
}
});
};
it("w/ process.env values present, it should return an object using values from process.env", () => {
// set mocked values
const mockValuePrefix = "MOCK";
setMockedEnv(mockValuePrefix);
// get result
const result = getDataDirs(`${mockValuePrefix}_TRILIUM_DATA_DIR`);
for (const key in result) {
expect(result[key as keyof typeof result]).toEqual(`${mockValuePrefix}_${key}`);
}
});
it("w/ NO process.env values present, it should return an object using supplied TRILIUM_DATA_DIR as base", () => {
// make sure values are undefined
setMockedEnv(null);
// mock pathJoin implementation to just return mockDataDir
const mockDataDir = "/home/test/MOCK_TRILIUM_DATA_DIR";
mockFn.pathJoinMock.mockImplementation(() => mockDataDir);
const result = getDataDirs(mockDataDir);
for (const key in result) {
expect(result[key as keyof typeof result].startsWith(mockDataDir)).toBeTruthy();
}
mockFn.pathJoinMock.mockReset();
});
it("should ignore attempts to change a property on the returned object", () => {
// make sure values are undefined
setMockedEnv(null);
const mockDataDirBase = "/home/test/MOCK_TRILIUM_DATA_DIR";
const result = getDataDirs(mockDataDirBase);
// as per MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#description
// Any attempt to change a frozen object will, either silently be ignored or
// throw a TypeError exception (most commonly, but not exclusively, when in strict mode).
// so be safe and check for both, even though it looks weird
const getChangeAttemptResult = () => {
try {
//@ts-expect-error - attempt to change value of readonly property
result.BACKUP_DIR = "attempt to change";
return result.BACKUP_DIR;
} catch (error) {
return error;
}
};
const changeAttemptResult = getChangeAttemptResult();
if (typeof changeAttemptResult === "string") {
// if it didn't throw above: assert that it did not change the value of it or any other keys of the object
for (const key in result) {
expect(result[key as keyof typeof result].startsWith(mockDataDirBase)).toBeTruthy();
}
} else {
expect(changeAttemptResult).toBeInstanceOf(TypeError);
}
});
});
});