Skip to content

Commit 24bbbfc

Browse files
committed
Add unit tests for cache interceptor
1 parent 6afd7ab commit 24bbbfc

File tree

1 file changed

+232
-0
lines changed

1 file changed

+232
-0
lines changed
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/* eslint-disable sonarjs/no-duplicate-string */
2+
import { cacheInterceptor } from "@opennextjs/aws/core/routing/cacheInterceptor.js";
3+
import { convertFromQueryString } from "@opennextjs/aws/core/routing/util.js";
4+
import { Queue } from "@opennextjs/aws/queue/types.js";
5+
import { InternalEvent } from "@opennextjs/aws/types/open-next.js";
6+
import { fromReadableStream } from "@opennextjs/aws/utils/stream.js";
7+
import { vi } from "vitest";
8+
9+
vi.mock("@opennextjs/aws/adapters/config/index.js", () => ({
10+
NextConfig: {},
11+
PrerenderManifest: {
12+
routes: {
13+
"/albums": {
14+
initialRevalidateSeconds: false,
15+
srcRoute: "/albums",
16+
dataRoute: "/albums.rsc",
17+
},
18+
"/revalidate": {
19+
initialRevalidateSeconds: 60,
20+
srcRoute: "/revalidate",
21+
dataRoute: "/revalidate.rsc",
22+
},
23+
},
24+
dynamicRoutes: {},
25+
},
26+
}));
27+
28+
vi.mock("@opennextjs/aws/core/routing/i18n/index.js", () => ({
29+
localizePath: (event: InternalEvent) => event.rawPath,
30+
}));
31+
32+
type PartialEvent = Partial<
33+
Omit<InternalEvent, "body" | "rawPath" | "query">
34+
> & { body?: string };
35+
36+
function createEvent(event: PartialEvent): InternalEvent {
37+
const [rawPath, qs] = (event.url ?? "/").split("?", 2);
38+
return {
39+
type: "core",
40+
method: event.method ?? "GET",
41+
rawPath,
42+
url: event.url ?? "/",
43+
body: Buffer.from(event.body ?? ""),
44+
headers: event.headers ?? {},
45+
query: convertFromQueryString(qs ?? ""),
46+
cookies: event.cookies ?? {},
47+
remoteAddress: event.remoteAddress ?? "::1",
48+
};
49+
}
50+
51+
const incrementalCache = {
52+
name: "mock",
53+
get: vi.fn(),
54+
set: vi.fn(),
55+
delete: vi.fn(),
56+
};
57+
58+
const tagCache = {
59+
name: "mock",
60+
getByTag: vi.fn(),
61+
getByPath: vi.fn(),
62+
getLastModified: vi.fn(),
63+
writeTags: vi.fn(),
64+
};
65+
66+
const queue = {
67+
name: "mock",
68+
send: vi.fn(),
69+
};
70+
71+
globalThis.incrementalCache = incrementalCache;
72+
globalThis.tagCache = tagCache;
73+
74+
declare global {
75+
var queue: Queue;
76+
}
77+
globalThis.queue = queue;
78+
79+
beforeEach(() => {
80+
vi.clearAllMocks();
81+
});
82+
83+
describe("cacheInterceptor", () => {
84+
it("should take no action when next-action header is present", async () => {
85+
const event = createEvent({
86+
headers: {
87+
"next-action": "something",
88+
},
89+
});
90+
const result = await cacheInterceptor(event);
91+
92+
expect(result).toEqual(event);
93+
});
94+
95+
it("should take no action when x-prerender-revalidate header is present", async () => {
96+
const event = createEvent({
97+
headers: {
98+
"x-prerender-revalidate": "1",
99+
},
100+
});
101+
const result = await cacheInterceptor(event);
102+
103+
expect(result).toEqual(event);
104+
});
105+
106+
it("should take no action when incremental cache throws", async () => {
107+
const event = createEvent({
108+
url: "/albums",
109+
});
110+
111+
incrementalCache.get.mockRejectedValueOnce(new Error("mock error"));
112+
const result = await cacheInterceptor(event);
113+
114+
expect(result).toEqual(event);
115+
});
116+
117+
it("should retrieve app router content from cache", async () => {
118+
const event = createEvent({
119+
url: "/albums",
120+
});
121+
incrementalCache.get.mockResolvedValueOnce({
122+
value: {
123+
type: "app",
124+
html: "Hello, world!",
125+
},
126+
});
127+
128+
const result = await cacheInterceptor(event);
129+
130+
const body = await fromReadableStream(result.body);
131+
expect(body).toEqual("Hello, world!");
132+
expect(result).toEqual(
133+
expect.objectContaining({
134+
type: "core",
135+
statusCode: 200,
136+
isBase64Encoded: false,
137+
}),
138+
);
139+
expect(result.headers).toHaveProperty("cache-control");
140+
expect(result.headers).toHaveProperty("content-type");
141+
expect(result.headers).toHaveProperty("etag");
142+
expect(result.headers).toHaveProperty("x-opennext-cache");
143+
});
144+
145+
it("should take no action when tagCache lasModified is -1", async () => {
146+
const event = createEvent({
147+
url: "/albums",
148+
});
149+
incrementalCache.get.mockResolvedValueOnce({
150+
value: {
151+
type: "app",
152+
html: "Hello, world!",
153+
},
154+
});
155+
tagCache.getLastModified.mockResolvedValueOnce(-1);
156+
157+
const result = await cacheInterceptor(event);
158+
159+
expect(result).toEqual(event);
160+
});
161+
162+
it("should retrieve page router content from cache", async () => {
163+
const event = createEvent({
164+
url: "/revalidate",
165+
});
166+
incrementalCache.get.mockResolvedValueOnce({
167+
value: {
168+
type: "page",
169+
html: "Hello, world!",
170+
},
171+
});
172+
173+
const result = await cacheInterceptor(event);
174+
175+
const body = await fromReadableStream(result.body);
176+
expect(body).toEqual("Hello, world!");
177+
expect(result).toEqual(
178+
expect.objectContaining({
179+
type: "core",
180+
statusCode: 200,
181+
isBase64Encoded: false,
182+
}),
183+
);
184+
expect(result.headers).toHaveProperty("cache-control");
185+
expect(result.headers).toHaveProperty("content-type");
186+
expect(result.headers).toHaveProperty("etag");
187+
expect(result.headers).toHaveProperty("x-opennext-cache");
188+
});
189+
190+
it("should retrieve redirect content from cache", async () => {
191+
const event = createEvent({
192+
url: "/albums",
193+
});
194+
incrementalCache.get.mockResolvedValueOnce({
195+
value: {
196+
type: "redirect",
197+
meta: {
198+
status: 302,
199+
},
200+
},
201+
});
202+
203+
const result = await cacheInterceptor(event);
204+
205+
expect(result).toEqual(
206+
expect.objectContaining({
207+
type: "core",
208+
statusCode: 302,
209+
isBase64Encoded: false,
210+
}),
211+
);
212+
expect(result.headers).toHaveProperty("cache-control");
213+
expect(result.headers).toHaveProperty("etag");
214+
expect(result.headers).toHaveProperty("x-opennext-cache");
215+
});
216+
217+
it("should take no action when cache returns unrecoginsed type", async () => {
218+
const event = createEvent({
219+
url: "/albums",
220+
});
221+
incrementalCache.get.mockResolvedValueOnce({
222+
value: {
223+
type: "?",
224+
html: "Hello, world!",
225+
},
226+
});
227+
228+
const result = await cacheInterceptor(event);
229+
230+
expect(result).toEqual(event);
231+
});
232+
});

0 commit comments

Comments
 (0)