Skip to content

Commit 9823365

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

File tree

1 file changed

+238
-0
lines changed

1 file changed

+238
-0
lines changed
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
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+
headers: expect.objectContaining({
138+
"cache-control": "s-maxage=31536000, stale-while-revalidate=2592000",
139+
"content-type": "text/html; charset=utf-8",
140+
etag: expect.any(String),
141+
"x-opennext-cache": "HIT",
142+
}),
143+
}),
144+
);
145+
});
146+
147+
it("should take no action when tagCache lasModified is -1", async () => {
148+
const event = createEvent({
149+
url: "/albums",
150+
});
151+
incrementalCache.get.mockResolvedValueOnce({
152+
value: {
153+
type: "app",
154+
html: "Hello, world!",
155+
},
156+
});
157+
tagCache.getLastModified.mockResolvedValueOnce(-1);
158+
159+
const result = await cacheInterceptor(event);
160+
161+
expect(result).toEqual(event);
162+
});
163+
164+
it("should retrieve page router content from cache", async () => {
165+
const event = createEvent({
166+
url: "/revalidate",
167+
});
168+
incrementalCache.get.mockResolvedValueOnce({
169+
value: {
170+
type: "page",
171+
html: "Hello, world!",
172+
},
173+
});
174+
175+
const result = await cacheInterceptor(event);
176+
177+
const body = await fromReadableStream(result.body);
178+
expect(body).toEqual("Hello, world!");
179+
expect(result).toEqual(
180+
expect.objectContaining({
181+
type: "core",
182+
statusCode: 200,
183+
isBase64Encoded: false,
184+
headers: expect.objectContaining({
185+
"cache-control": "s-maxage=1, stale-while-revalidate=2592000",
186+
"content-type": "text/html; charset=utf-8",
187+
etag: expect.any(String),
188+
"x-opennext-cache": "STALE",
189+
}),
190+
}),
191+
);
192+
});
193+
194+
it("should retrieve redirect content from cache", async () => {
195+
const event = createEvent({
196+
url: "/albums",
197+
});
198+
incrementalCache.get.mockResolvedValueOnce({
199+
value: {
200+
type: "redirect",
201+
meta: {
202+
status: 302,
203+
},
204+
},
205+
});
206+
207+
const result = await cacheInterceptor(event);
208+
209+
expect(result).toEqual(
210+
expect.objectContaining({
211+
type: "core",
212+
statusCode: 302,
213+
isBase64Encoded: false,
214+
headers: expect.objectContaining({
215+
"cache-control": "s-maxage=31536000, stale-while-revalidate=2592000",
216+
etag: expect.any(String),
217+
"x-opennext-cache": "HIT",
218+
}),
219+
}),
220+
);
221+
});
222+
223+
it("should take no action when cache returns unrecoginsed type", async () => {
224+
const event = createEvent({
225+
url: "/albums",
226+
});
227+
incrementalCache.get.mockResolvedValueOnce({
228+
value: {
229+
type: "?",
230+
html: "Hello, world!",
231+
},
232+
});
233+
234+
const result = await cacheInterceptor(event);
235+
236+
expect(result).toEqual(event);
237+
});
238+
});

0 commit comments

Comments
 (0)