Skip to content

Commit a9551bc

Browse files
committed
test(rsc-mf): cover stale hashed expose fallback paths
1 parent 2d115bf commit a9551bc

3 files changed

Lines changed: 167 additions & 0 deletions

File tree

tests/integration/rsc-mf/tests/manifestFallback.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,36 @@ describe('manifest fallback shared helpers', () => {
124124
).toBeUndefined();
125125
});
126126

127+
it('resolves stale hashed expose requests to current hashed assets', () => {
128+
const manifest: RemoteManifestShape = {
129+
exposes: [
130+
{
131+
assets: {
132+
js: {
133+
sync: [
134+
'static/js/async/__federation_expose_RemoteClientCounter.7745fe5f0a.js',
135+
],
136+
async: [],
137+
},
138+
css: {
139+
sync: [],
140+
async: [],
141+
},
142+
},
143+
},
144+
],
145+
};
146+
147+
expect(
148+
resolveManifestFallbackAssetPath(
149+
'/static/js/async/__federation_expose_RemoteClientCounter.deadbeef12.js',
150+
manifest,
151+
),
152+
).toBe(
153+
'static/js/async/__federation_expose_RemoteClientCounter.7745fe5f0a.js',
154+
);
155+
});
156+
127157
it('builds safe fallback URL and merges request query params', () => {
128158
expect(
129159
createManifestFallbackAssetUrl({

tests/integration/rsc-mf/tests/modernServerConfig.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,76 @@ describe('rsc-mf host modern.server middleware contracts', () => {
394394
await expect(context.res?.text()).resolves.toBe('fallback-hit');
395395
});
396396

397+
it('recovers stale hashed expose chunk path via manifest-driven fallback', async () => {
398+
const handler = getProxyMiddlewareHandler();
399+
const next = jest.fn(async (): Promise<void> => undefined);
400+
const fetchMock = installFetchMock(
401+
jest
402+
.fn()
403+
.mockResolvedValueOnce(new Response('not-found', { status: 404 }))
404+
.mockResolvedValueOnce(
405+
new Response(
406+
JSON.stringify({
407+
exposes: [
408+
{
409+
assets: {
410+
js: {
411+
sync: [
412+
'static/js/async/__federation_expose_RemoteClientCounter.7745fe5f0a.js',
413+
],
414+
async: [],
415+
},
416+
css: {
417+
sync: [],
418+
async: [],
419+
},
420+
},
421+
},
422+
],
423+
}),
424+
{
425+
status: 200,
426+
headers: {
427+
'content-type': 'application/json',
428+
},
429+
},
430+
),
431+
)
432+
.mockResolvedValueOnce(
433+
new Response('hashed-fallback-hit', {
434+
status: 200,
435+
headers: {
436+
'content-type': 'application/javascript',
437+
},
438+
}),
439+
),
440+
);
441+
const context: { req: { url: string }; res?: Response } = {
442+
req: {
443+
url: 'http://127.0.0.1:3007/static/js/async/__federation_expose_RemoteClientCounter.deadbeef12.js',
444+
},
445+
};
446+
447+
await withRemotePort('3999', () => handler(context, next));
448+
449+
expect(fetchMock).toHaveBeenNthCalledWith(
450+
1,
451+
'http://127.0.0.1:3999/static/js/async/__federation_expose_RemoteClientCounter.deadbeef12.js',
452+
);
453+
expectInternalFallbackFetchCall(
454+
fetchMock,
455+
2,
456+
'http://127.0.0.1:3999/static/mf-manifest.json',
457+
);
458+
expectInternalFallbackFetchCall(
459+
fetchMock,
460+
3,
461+
'http://127.0.0.1:3999/static/js/async/__federation_expose_RemoteClientCounter.7745fe5f0a.js',
462+
);
463+
expect(next).not.toHaveBeenCalled();
464+
await expect(context.res?.text()).resolves.toBe('hashed-fallback-hit');
465+
});
466+
397467
it('recovers stale expose path when manifest match is under shared assets', async () => {
398468
const handler = getProxyMiddlewareHandler();
399469
const next = jest.fn(async (): Promise<void> => undefined);

tests/integration/rsc-mf/tests/remoteModernServerConfig.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,73 @@ describe('rsc-mf remote modern.server middleware contracts', () => {
146146
await expect(context.res?.text()).resolves.toBe('fallback-asset');
147147
});
148148

149+
it('recovers stale hashed expose asset path via remote manifest fallback', async () => {
150+
const handler = getRecoverMiddlewareHandler();
151+
const next = jest.fn(async (): Promise<void> => undefined);
152+
const fetchMock = installFetchMock(
153+
jest
154+
.fn()
155+
.mockResolvedValueOnce(
156+
new Response(
157+
JSON.stringify({
158+
exposes: [
159+
{
160+
assets: {
161+
js: {
162+
sync: [
163+
'static/js/async/__federation_expose_RemoteClientCounter.7745fe5f0a.js',
164+
],
165+
async: [],
166+
},
167+
css: {
168+
sync: [],
169+
async: [],
170+
},
171+
},
172+
},
173+
],
174+
}),
175+
{
176+
status: 200,
177+
headers: {
178+
'content-type': 'application/json',
179+
},
180+
},
181+
),
182+
)
183+
.mockResolvedValueOnce(
184+
new Response('hashed-fallback-asset', {
185+
status: 200,
186+
headers: {
187+
'content-type': 'application/javascript',
188+
},
189+
}),
190+
),
191+
);
192+
const context: {
193+
req: { url: string; headers?: { get?: (name: string) => string | null } };
194+
res?: Response;
195+
} = {
196+
req: {
197+
url: 'http://127.0.0.1:3008/static/js/async/__federation_expose_RemoteClientCounter.deadbeef12.js',
198+
},
199+
};
200+
201+
await handler(context, next);
202+
203+
expect(fetchMock).toHaveBeenNthCalledWith(
204+
2,
205+
'http://127.0.0.1:3008/static/js/async/__federation_expose_RemoteClientCounter.7745fe5f0a.js',
206+
{
207+
headers: {
208+
[INTERNAL_FALLBACK_HEADER]: '1',
209+
},
210+
},
211+
);
212+
expect(next).not.toHaveBeenCalled();
213+
await expect(context.res?.text()).resolves.toBe('hashed-fallback-asset');
214+
});
215+
149216
it('falls through when request path is not a federated expose asset', async () => {
150217
const handler = getRecoverMiddlewareHandler();
151218
const next = jest.fn(async (): Promise<void> => undefined);

0 commit comments

Comments
 (0)