-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathdisk-cache.test.js
More file actions
146 lines (124 loc) · 4.62 KB
/
Copy pathdisk-cache.test.js
File metadata and controls
146 lines (124 loc) · 4.62 KB
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
import { DiskCache } from "../../build/utils/disk-cache.js";
import { mkdtemp, rm } from "fs/promises";
import { join } from "path";
import { tmpdir } from "os";
describe("utils/DiskCache", () => {
let tempDir;
let originalDataDir;
beforeEach(async () => {
tempDir = undefined;
originalDataDir = process.env.DATA_DIR;
tempDir = await mkdtemp(join(tmpdir(), "disk-cache-test-"));
process.env.DATA_DIR = tempDir;
});
afterEach(async () => {
try {
if (originalDataDir !== undefined) {
process.env.DATA_DIR = originalDataDir;
} else {
delete process.env.DATA_DIR;
}
} finally {
if (tempDir) {
await rm(tempDir, { recursive: true, force: true });
}
}
});
it("round-trips set and get", async () => {
const cache = new DiskCache({ ttl: 60_000, path: "test-cache" });
await cache.set("greeting", "hello");
const result = await cache.get("greeting");
expect(result).toBe("hello");
});
it("returns undefined for missing key", async () => {
const cache = new DiskCache({ ttl: 60_000, path: "test-cache" });
const result = await cache.get("nonexistent");
expect(result).toBeUndefined();
});
it("stores complex objects", async () => {
const cache = new DiskCache({ ttl: 60_000, path: "test-cache" });
const data = { name: "test", items: [1, 2, 3], nested: { a: true } };
await cache.set("complex", data);
const result = await cache.get("complex");
expect(result).toEqual(data);
});
it("overwrites existing values", async () => {
const cache = new DiskCache({ ttl: 60_000, path: "test-cache" });
await cache.set("key", "old");
await cache.set("key", "new");
expect(await cache.get("key")).toBe("new");
});
it("persists to disk and reads back in a fresh instance", async () => {
const opts = { ttl: 60_000, path: "test-cache" };
const cache1 = new DiskCache(opts);
await cache1.set("persist", "data");
// A new instance should read from disk
const cache2 = new DiskCache(opts);
expect(await cache2.get("persist")).toBe("data");
});
describe("time-dependent expiry", () => {
let now;
beforeEach(() => {
now = 1_000;
spyOn(Date, "now").and.callFake(() => now);
});
describe("TTL expiry", () => {
it("returns undefined for expired entries", async () => {
// Use a very short TTL
const cache = new DiskCache({ ttl: 1, path: "test-cache" });
await cache.set("key", "value");
now += 10;
expect(await cache.get("key")).toBeUndefined();
});
it("returns stale value when allowStale is true", async () => {
const cache = new DiskCache({ ttl: 1, path: "test-cache" });
await cache.set("key", "value");
now += 10;
expect(await cache.get("key", true)).toBe("value");
});
});
describe("invalidate()", () => {
it("removes expired entries from memory and disk", async () => {
const cache = new DiskCache({ ttl: 1, path: "test-cache" });
await cache.set("a", 1);
await cache.set("b", 2);
now += 10;
await cache.invalidate();
expect(await cache.get("a", true)).toBeUndefined();
expect(await cache.get("b", true)).toBeUndefined();
});
});
});
describe("path traversal prevention", () => {
it("rejects keys with path traversal (..)", async () => {
const cache = new DiskCache({ ttl: 60_000, path: "test-cache" });
await expectAsync(cache.set("foo/../evil", "data")).toBeRejectedWithError(
Error,
/Invalid (key|path)/i
);
});
it("rejects keys with double slashes", async () => {
const cache = new DiskCache({ ttl: 60_000, path: "test-cache" });
await expectAsync(cache.set("foo//bar", "data")).toBeRejectedWithError(
Error,
/Invalid (key|path)/i
);
});
it("rejects keys with dot segments", async () => {
const cache = new DiskCache({ ttl: 60_000, path: "test-cache" });
await expectAsync(cache.set("foo/./bar", "data")).toBeRejectedWithError(
Error,
/Invalid (key|path)/i
);
});
it("rejects prefix-bypass traversal (../test-cache-evil)", async () => {
// A key like ../test-cache-evil/pwn resolves to a path that *starts with*
// the base directory string but escapes it (e.g. /tmp/test-cache-evil).
// The check must use baseDir + sep to prevent this bypass.
const cache = new DiskCache({ ttl: 60_000, path: "test-cache" });
await expectAsync(
cache.set("../test-cache-evil/pwn", "data")
).toBeRejectedWithError(Error, /Invalid (key|path)/i);
});
});
});