Skip to content

Commit e4ee91d

Browse files
committed
added unit test
1 parent dfd1e33 commit e4ee91d

File tree

2 files changed

+160
-1
lines changed

2 files changed

+160
-1
lines changed
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { describe, expect, it, vi } from "vitest";
2+
3+
import * as internal from "../overrides/internal";
4+
import { BucketCachePurge } from "./bucket-cache-purge";
5+
6+
vi.mock("cloudflare:workers", () => ({
7+
DurableObject: class {
8+
constructor(
9+
public ctx: DurableObjectState,
10+
public env: CloudflareEnv
11+
) {}
12+
},
13+
}));
14+
15+
16+
const createBucketCachePurge = () => {
17+
const mockState = {
18+
waitUntil: vi.fn(),
19+
blockConcurrencyWhile: vi.fn().mockImplementation(async (fn) => fn()),
20+
storage: {
21+
setAlarm: vi.fn(),
22+
getAlarm: vi.fn(),
23+
sql: {
24+
exec: vi.fn().mockImplementation(() => ({
25+
one: vi.fn(),
26+
toArray: vi.fn().mockReturnValue([]),
27+
})),
28+
},
29+
},
30+
};
31+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
32+
return new BucketCachePurge(mockState as any, {});
33+
};
34+
35+
describe("BucketCachePurge", () => {
36+
it("should block concurrency while creating the table", async () => {
37+
const cache = createBucketCachePurge();
38+
// @ts-expect-error - testing private method
39+
expect(cache.ctx.blockConcurrencyWhile).toHaveBeenCalled();
40+
// @ts-expect-error - testing private method
41+
expect(cache.ctx.storage.sql.exec).toHaveBeenCalledWith(
42+
expect.stringContaining("CREATE TABLE IF NOT EXISTS cache_purge"),
43+
);
44+
});
45+
46+
describe("purgeCacheByTags", () => {
47+
it("should insert tags into the sql table", async () => {
48+
const cache = createBucketCachePurge();
49+
const tags = ["tag1", "tag2"];
50+
await cache.purgeCacheByTags(tags);
51+
// @ts-expect-error - testing private method
52+
expect(cache.ctx.storage.sql.exec).toHaveBeenCalledWith(
53+
expect.stringContaining("INSERT OR REPLACE INTO cache_purge"),
54+
[tags[0]],
55+
);
56+
// @ts-expect-error - testing private method
57+
expect(cache.ctx.storage.sql.exec).toHaveBeenCalledWith(
58+
expect.stringContaining("INSERT OR REPLACE INTO cache_purge"),
59+
[tags[1]],
60+
);
61+
});
62+
63+
it("should set an alarm if no alarm is set", async () => {
64+
const cache = createBucketCachePurge();
65+
// @ts-expect-error - testing private method
66+
cache.ctx.storage.getAlarm.mockResolvedValueOnce(null);
67+
await cache.purgeCacheByTags(["tag"]);
68+
// @ts-expect-error - testing private method
69+
expect(cache.ctx.storage.setAlarm).toHaveBeenCalled();
70+
});
71+
72+
it("should not set an alarm if one is already set", async () => {
73+
const cache = createBucketCachePurge();
74+
// @ts-expect-error - testing private method
75+
cache.ctx.storage.getAlarm.mockResolvedValueOnce(true);
76+
await cache.purgeCacheByTags(["tag"]);
77+
// @ts-expect-error - testing private method
78+
expect(cache.ctx.storage.setAlarm).not.toHaveBeenCalled();
79+
});
80+
})
81+
82+
describe("alarm", () => {
83+
it("should purge cache by tags and delete them from the sql table", async () => {
84+
const cache = createBucketCachePurge();
85+
// @ts-expect-error - testing private method
86+
cache.ctx.storage.sql.exec.mockReturnValueOnce({
87+
toArray: () => [{ tag: "tag1" }, { tag: "tag2" }],
88+
});
89+
await cache.alarm();
90+
// @ts-expect-error - testing private method
91+
expect(cache.ctx.storage.sql.exec).toHaveBeenCalledWith(
92+
expect.stringContaining("DELETE FROM cache_purge"),
93+
["tag1", "tag2"],
94+
);
95+
});
96+
it("should not purge cache if no tags are found", async () => {
97+
const cache = createBucketCachePurge();
98+
// @ts-expect-error - testing private method
99+
cache.ctx.storage.sql.exec.mockReturnValueOnce({
100+
toArray: () => [],
101+
});
102+
await cache.alarm();
103+
// @ts-expect-error - testing private method
104+
expect(cache.ctx.storage.sql.exec).not.toHaveBeenCalledWith(
105+
expect.stringContaining("DELETE FROM cache_purge"),
106+
[],
107+
);
108+
});
109+
110+
it("should call internalPurgeCacheByTags with the correct tags", async () => {
111+
const cache = createBucketCachePurge();
112+
const tags = ["tag1", "tag2"];
113+
// @ts-expect-error - testing private method
114+
cache.ctx.storage.sql.exec.mockReturnValueOnce({
115+
toArray: () => tags.map((tag) => ({ tag })),
116+
});
117+
const internalPurgeCacheByTagsSpy = vi.spyOn(internal, "internalPurgeCacheByTags");
118+
await cache.alarm();
119+
expect(internalPurgeCacheByTagsSpy).toHaveBeenCalledWith(
120+
// @ts-expect-error - testing private method
121+
cache.env,
122+
tags,
123+
);
124+
// @ts-expect-error - testing private method 1st is constructor, 2nd is to get the tags and 3rd is to delete them
125+
expect(cache.ctx.storage.sql.exec).toHaveBeenCalledTimes(3);
126+
});
127+
128+
it("should continue until all tags are purged", async () => {
129+
const cache = createBucketCachePurge();
130+
const tags = Array.from({ length: 100 }, (_, i) => `tag${i}`);
131+
// @ts-expect-error - testing private method
132+
cache.ctx.storage.sql.exec.mockReturnValueOnce({
133+
toArray: () => tags.map((tag) => ({ tag })),
134+
});
135+
const internalPurgeCacheByTagsSpy = vi.spyOn(internal, "internalPurgeCacheByTags");
136+
await cache.alarm();
137+
expect(internalPurgeCacheByTagsSpy).toHaveBeenCalledWith(
138+
// @ts-expect-error - testing private method
139+
cache.env,
140+
tags,
141+
);
142+
// @ts-expect-error - testing private method 1st is constructor, 2nd is to get the tags and 3rd is to delete them, 4th is to get the next 100 tags
143+
expect(cache.ctx.storage.sql.exec).toHaveBeenCalledTimes(4);
144+
// @ts-expect-error - testing private method
145+
expect(cache.ctx.storage.sql.exec).toHaveBeenLastCalledWith(
146+
expect.stringContaining("SELECT * FROM cache_purge LIMIT 100"),
147+
);
148+
});
149+
150+
151+
})
152+
});

packages/cloudflare/src/api/durable-objects/bucket-cache-purge.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ export class BucketCachePurge extends DurableObject<CloudflareEnv> {
1414
: DEFAULT_BUFFER_TIME; // Default buffer time
1515

1616
// Initialize the sql table if it doesn't exist
17-
state.storage.sql.exec(`
17+
state.blockConcurrencyWhile(async () => {
18+
state.storage.sql.exec(`
1819
CREATE TABLE IF NOT EXISTS cache_purge (
1920
tag TEXT NOT NULL
2021
);
2122
CREATE UNIQUE INDEX IF NOT EXISTS tag_index ON cache_purge (tag);
2223
`);
24+
})
2325
}
2426

2527
async purgeCacheByTags(tags: string[]) {
@@ -47,6 +49,11 @@ export class BucketCachePurge extends DurableObject<CloudflareEnv> {
4749
`
4850
)
4951
.toArray();
52+
if (tags.length === 0) {
53+
// No tags to purge, we can stop
54+
// It shouldn't happen, but just in case
55+
return;
56+
}
5057
do {
5158
await internalPurgeCacheByTags(
5259
this.env,

0 commit comments

Comments
 (0)