Skip to content

Commit 0d51971

Browse files
committed
added some tests
1 parent 954e162 commit 0d51971

File tree

3 files changed

+263
-6
lines changed

3 files changed

+263
-6
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2+
3+
import doShardedTagCache from "./do-sharded-tag-cache";
4+
5+
const hasBeenRevalidatedMock = vi.fn();
6+
const writeTagsMock = vi.fn();
7+
const idFromNameMock = vi.fn();
8+
const getMock = vi
9+
.fn()
10+
.mockReturnValue({ hasBeenRevalidated: hasBeenRevalidatedMock, writeTags: writeTagsMock });
11+
const waitUntilMock = vi.fn().mockImplementation(async (fn) => fn());
12+
vi.mock("./cloudflare-context", () => ({
13+
getCloudflareContext: () => ({
14+
env: { NEXT_CACHE_D1_SHARDED: { idFromName: idFromNameMock, get: getMock } },
15+
ctx: { waitUntil: waitUntilMock },
16+
}),
17+
}));
18+
19+
describe("DOShardedTagCache", () => {
20+
afterEach(() => vi.clearAllMocks());
21+
22+
describe("generateShardId", () => {
23+
it("should generate a shardId", () => {
24+
const cache = doShardedTagCache();
25+
const expectedResult = new Map();
26+
expectedResult.set("shard-1", ["tag1"]);
27+
expectedResult.set("shard-2", ["tag2"]);
28+
expect(cache.generateShards(["tag1", "tag2"])).toEqual(expectedResult);
29+
});
30+
31+
it("should group tags by shard", () => {
32+
const cache = doShardedTagCache();
33+
const expectedResult = new Map();
34+
expectedResult.set("shard-1", ["tag1", "tag6"]);
35+
expect(cache.generateShards(["tag1", "tag6"])).toEqual(expectedResult);
36+
});
37+
38+
it("should generate the same shardId for the same tag", () => {
39+
const cache = doShardedTagCache();
40+
const firstResult = cache.generateShards(["tag1"]);
41+
const secondResult = cache.generateShards(["tag1", "tag3", "tag4"]);
42+
expect(firstResult.get("shard-1")).toEqual(secondResult.get("shard-1"));
43+
});
44+
});
45+
46+
describe("hasBeenRevalidated", () => {
47+
beforeEach(() => {
48+
globalThis.openNextConfig = {
49+
dangerous: { disableTagCache: false },
50+
};
51+
});
52+
it("should return false if the cache is disabled", async () => {
53+
globalThis.openNextConfig = {
54+
dangerous: { disableTagCache: true },
55+
};
56+
const cache = doShardedTagCache();
57+
const result = await cache.hasBeenRevalidated(["tag1"]);
58+
expect(result).toBe(false);
59+
expect(idFromNameMock).not.toHaveBeenCalled();
60+
});
61+
62+
it("should return false if stub return false", async () => {
63+
const cache = doShardedTagCache();
64+
cache.getFromRegionalCache = vi.fn();
65+
hasBeenRevalidatedMock.mockImplementationOnce(() => false);
66+
const result = await cache.hasBeenRevalidated(["tag1"], 123456);
67+
expect(cache.getFromRegionalCache).toHaveBeenCalled();
68+
expect(idFromNameMock).toHaveBeenCalled();
69+
expect(hasBeenRevalidatedMock).toHaveBeenCalled();
70+
expect(result).toBe(false);
71+
});
72+
73+
it("should return true if stub return true", async () => {
74+
const cache = doShardedTagCache();
75+
cache.getFromRegionalCache = vi.fn();
76+
hasBeenRevalidatedMock.mockImplementationOnce(() => true);
77+
const result = await cache.hasBeenRevalidated(["tag1"], 123456);
78+
expect(cache.getFromRegionalCache).toHaveBeenCalled();
79+
expect(idFromNameMock).toHaveBeenCalled();
80+
expect(hasBeenRevalidatedMock).toHaveBeenCalledWith(["tag1"], 123456);
81+
expect(result).toBe(true);
82+
});
83+
84+
it("should return false if it throws", async () => {
85+
const cache = doShardedTagCache();
86+
cache.getFromRegionalCache = vi.fn();
87+
hasBeenRevalidatedMock.mockImplementationOnce(() => {
88+
throw new Error("error");
89+
});
90+
const result = await cache.hasBeenRevalidated(["tag1"], 123456);
91+
expect(cache.getFromRegionalCache).toHaveBeenCalled();
92+
expect(idFromNameMock).toHaveBeenCalled();
93+
expect(hasBeenRevalidatedMock).toHaveBeenCalled();
94+
expect(result).toBe(false);
95+
});
96+
97+
it("Should return from the cache if it was found there", async () => {
98+
const cache = doShardedTagCache();
99+
cache.getFromRegionalCache = vi.fn().mockReturnValueOnce(new Response("true"));
100+
const result = await cache.hasBeenRevalidated(["tag1"], 123456);
101+
expect(result).toBe(true);
102+
expect(idFromNameMock).not.toHaveBeenCalled();
103+
expect(hasBeenRevalidatedMock).not.toHaveBeenCalled();
104+
});
105+
106+
it("should try to put the result in the cache if it was not revalidated", async () => {
107+
const cache = doShardedTagCache();
108+
cache.getFromRegionalCache = vi.fn();
109+
cache.putToRegionalCache = vi.fn();
110+
hasBeenRevalidatedMock.mockImplementationOnce(() => false);
111+
const result = await cache.hasBeenRevalidated(["tag1"], 123456);
112+
expect(result).toBe(false);
113+
114+
expect(waitUntilMock).toHaveBeenCalled();
115+
expect(cache.putToRegionalCache).toHaveBeenCalled();
116+
});
117+
118+
it("should call all the shards", async () => {
119+
const cache = doShardedTagCache();
120+
cache.getFromRegionalCache = vi.fn();
121+
const result = await cache.hasBeenRevalidated(["tag1", "tag2"], 123456);
122+
expect(result).toBe(false);
123+
expect(idFromNameMock).toHaveBeenCalledTimes(2);
124+
expect(hasBeenRevalidatedMock).toHaveBeenCalledTimes(2);
125+
});
126+
});
127+
128+
describe("writeTags", () => {
129+
beforeEach(() => {
130+
globalThis.openNextConfig = {
131+
dangerous: { disableTagCache: false },
132+
};
133+
});
134+
it("should return if the cache is disabled", async () => {
135+
globalThis.openNextConfig = {
136+
dangerous: { disableTagCache: true },
137+
};
138+
const cache = doShardedTagCache();
139+
await cache.writeTags(["tag1"]);
140+
expect(idFromNameMock).not.toHaveBeenCalled();
141+
expect(writeTagsMock).not.toHaveBeenCalled();
142+
});
143+
144+
it("should write the tags to the cache", async () => {
145+
const cache = doShardedTagCache();
146+
await cache.writeTags(["tag1"]);
147+
expect(idFromNameMock).toHaveBeenCalled();
148+
expect(writeTagsMock).toHaveBeenCalled();
149+
expect(writeTagsMock).toHaveBeenCalledWith(["tag1"]);
150+
});
151+
152+
it("should write the tags to the cache for multiple shards", async () => {
153+
const cache = doShardedTagCache();
154+
await cache.writeTags(["tag1", "tag2"]);
155+
expect(idFromNameMock).toHaveBeenCalledTimes(2);
156+
expect(writeTagsMock).toHaveBeenCalledTimes(2);
157+
expect(writeTagsMock).toHaveBeenCalledWith(["tag1"]);
158+
expect(writeTagsMock).toHaveBeenCalledWith(["tag2"]);
159+
});
160+
161+
it("should call deleteRegionalCache", async () => {
162+
const cache = doShardedTagCache();
163+
cache.deleteRegionalCache = vi.fn();
164+
await cache.writeTags(["tag1"]);
165+
expect(cache.deleteRegionalCache).toHaveBeenCalled();
166+
expect(cache.deleteRegionalCache).toHaveBeenCalledWith("shard-1", ["tag1"]);
167+
});
168+
});
169+
170+
describe("getCacheInstance", () => {
171+
it("should return undefined by default", async () => {
172+
const cache = doShardedTagCache();
173+
expect(await cache.getCacheInstance()).toBeUndefined();
174+
});
175+
176+
it("should try to return the cache instance if regional cache is enabled", async () => {
177+
// @ts-expect-error - Defined on cloudfare context
178+
globalThis.caches = {
179+
open: vi.fn().mockResolvedValue("cache"),
180+
};
181+
const cache = doShardedTagCache({ numberOfShards: 4, regionalCache: true });
182+
expect(cache.localCache).toBeUndefined();
183+
expect(await cache.getCacheInstance()).toBe("cache");
184+
expect(cache.localCache).toBe("cache");
185+
// @ts-expect-error - Defined on cloudfare context
186+
globalThis.caches = undefined;
187+
});
188+
});
189+
190+
describe("getFromRegionalCache", () => {
191+
it("should return undefined if regional cache is disabled", async () => {
192+
const cache = doShardedTagCache();
193+
expect(await cache.getFromRegionalCache("shard-1", ["tag1"])).toBeUndefined();
194+
});
195+
196+
it("should call .match on the cache", async () => {
197+
// @ts-expect-error - Defined on cloudfare context
198+
globalThis.caches = {
199+
open: vi.fn().mockResolvedValue({
200+
match: vi.fn().mockResolvedValue("response"),
201+
}),
202+
};
203+
const cache = doShardedTagCache({ numberOfShards: 4, regionalCache: true });
204+
expect(await cache.getFromRegionalCache("shard-1", ["tag1"])).toBe("response");
205+
// @ts-expect-error - Defined on cloudfare context
206+
globalThis.caches = undefined;
207+
});
208+
});
209+
});

packages/cloudflare/src/api/do-sharded-tag-cache.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class ShardedD1TagCache implements NextModeTagCache {
4747
* @param tags
4848
* @returns A map of shardId to tags
4949
*/
50-
private generateShards(tags: string[]) {
50+
generateShards(tags: string[]) {
5151
// For each tag, we generate a message group id
5252
const messageGroupIds = tags.map((tag) => ({
5353
shardId: generateShardId(tag, this.opts.numberOfShards, "shard"),
@@ -156,11 +156,16 @@ class ShardedD1TagCache implements NextModeTagCache {
156156
}
157157

158158
async getFromRegionalCache(shardId: string, tags: string[]) {
159-
if (!this.opts.regionalCache) return;
160-
const cache = await this.getCacheInstance();
161-
if (!cache) return;
162-
const key = await this.getCacheKey(shardId, tags);
163-
return cache.match(key);
159+
try {
160+
if (!this.opts.regionalCache) return;
161+
const cache = await this.getCacheInstance();
162+
if (!cache) return;
163+
const key = await this.getCacheKey(shardId, tags);
164+
return cache.match(key);
165+
} catch (e) {
166+
error("Error while fetching from regional cache", e);
167+
return;
168+
}
164169
}
165170

166171
async putToRegionalCache(shardId: string, tags: string[], hasBeenRevalidated: boolean) {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { describe, expect, it, vi } from "vitest";
2+
3+
import { DOShardedTagCache } from "./sharded-tag-cache";
4+
5+
vi.mock("cloudflare:workers", () => ({
6+
DurableObject: class {
7+
ctx: DurableObjectState;
8+
env: CloudflareEnv;
9+
constructor(ctx: DurableObjectState, env: CloudflareEnv) {
10+
this.ctx = ctx;
11+
this.env = env;
12+
}
13+
},
14+
}));
15+
16+
const createDOShardedTagCache = () => {
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+
})),
27+
},
28+
},
29+
};
30+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
31+
return new DOShardedTagCache(mockState as any, {});
32+
};
33+
34+
describe("DOShardedTagCache class", () => {
35+
it("should block concurrency while creating the table", async () => {
36+
const cache = createDOShardedTagCache();
37+
// @ts-expect-error - testing private method
38+
expect(cache.ctx.blockConcurrencyWhile).toHaveBeenCalled();
39+
expect(cache.sql.exec).toHaveBeenCalledWith(
40+
`CREATE TABLE IF NOT EXISTS revalidations (tag TEXT PRIMARY KEY, revalidatedAt INTEGER)`
41+
);
42+
});
43+
});

0 commit comments

Comments
 (0)