From ef49bdc4ee6638971c88442146d7ddc77f089bd7 Mon Sep 17 00:00:00 2001 From: Angelo Ashmore Date: Wed, 30 Oct 2024 09:53:00 -1000 Subject: [PATCH] feat: use a new master ref once a known-stale ref is used --- src/Client.ts | 8 + test/__testutils__/testInvalidRefRetry.ts | 268 ++++++++++++---------- 2 files changed, 157 insertions(+), 119 deletions(-) diff --git a/src/Client.ts b/src/Client.ts index b064ec2f..548493f6 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -1721,6 +1721,14 @@ export class Client< throw error } + // If no explicit ref is given (i.e. the master ref from + // /api/v2 is used), clear the cached repository value. + // Clearing the cached value prevents other methods from + // using a known-stale ref. + if (!params?.ref) { + this.cachedRepository = undefined + } + const masterRef = error.message.match(/Master ref is: (?.*)$/) ?.groups?.ref if (!masterRef) { diff --git a/test/__testutils__/testInvalidRefRetry.ts b/test/__testutils__/testInvalidRefRetry.ts index 8ef46c43..9406f1eb 100644 --- a/test/__testutils__/testInvalidRefRetry.ts +++ b/test/__testutils__/testInvalidRefRetry.ts @@ -15,126 +15,156 @@ type TestInvalidRefRetryArgs = { } export const testInvalidRefRetry = (args: TestInvalidRefRetryArgs): void => { - it.concurrent( - "retries with the master ref when an invalid ref is used", - async (ctx) => { - const client = createTestClient({ ctx }) - const badRef = ctx.mock.api.ref().ref - const masterRef = ctx.mock.api.ref().ref - const queryResponse = ctx.mock.api.query({ - documents: [ctx.mock.value.document()], - }) - - const triedRefs: (string | null)[] = [] - - mockPrismicRestAPIV2({ ctx, queryResponse }) - const endpoint = new URL( - "documents/search", - `${client.documentAPIEndpoint}/`, - ).toString() - ctx.server.use( - rest.get(endpoint, (req) => { - triedRefs.push(req.url.searchParams.get("ref")) - }), - rest.get(endpoint, (_req, res, ctx) => - res.once( - ctx.json({ - type: "api_notfound_error", - message: `Master ref is: ${masterRef}`, - }), - ctx.status(404), - ), + it("retries with the master ref when an invalid ref is used", async (ctx) => { + const client = createTestClient({ ctx }) + const badRef = ctx.mock.api.ref().ref + const masterRef = ctx.mock.api.ref().ref + const queryResponse = ctx.mock.api.query({ + documents: [ctx.mock.value.document()], + }) + + const triedRefs = new Set() + + mockPrismicRestAPIV2({ ctx, queryResponse }) + const endpoint = new URL( + "documents/search", + `${client.documentAPIEndpoint}/`, + ).toString() + ctx.server.use( + rest.get(endpoint, (req) => { + triedRefs.add(req.url.searchParams.get("ref")) + }), + rest.get(endpoint, (_req, res, ctx) => + res.once( + ctx.json({ + type: "api_notfound_error", + message: `Master ref is: ${masterRef}`, + }), + ctx.status(404), ), - ) - - const consoleWarnSpy = vi - .spyOn(console, "warn") - .mockImplementation(() => void 0) - await args.run(client, { ref: badRef }) - consoleWarnSpy.mockRestore() - - expect(triedRefs).toStrictEqual([badRef, masterRef]) - }, - ) - - it.concurrent( - "retries with the master ref when an expired ref is used", - async (ctx) => { - const client = createTestClient({ ctx }) - const badRef = ctx.mock.api.ref().ref - const masterRef = ctx.mock.api.ref().ref - const queryResponse = ctx.mock.api.query({ - documents: [ctx.mock.value.document()], - }) - - const triedRefs: (string | null)[] = [] - - mockPrismicRestAPIV2({ ctx, queryResponse }) - const endpoint = new URL( - "documents/search", - `${client.documentAPIEndpoint}/`, - ).toString() - ctx.server.use( - rest.get(endpoint, (req) => { - triedRefs.push(req.url.searchParams.get("ref")) - }), - rest.get(endpoint, (_req, res, ctx) => - res.once( - ctx.json({ message: `Master ref is: ${masterRef}` }), - ctx.status(410), - ), + ), + ) + + const consoleWarnSpy = vi + .spyOn(console, "warn") + .mockImplementation(() => void 0) + await args.run(client, { ref: badRef }) + consoleWarnSpy.mockRestore() + + expect([...triedRefs]).toStrictEqual([badRef, masterRef]) + }) + + it("retries with the master ref when an expired ref is used", async (ctx) => { + const client = createTestClient({ ctx }) + const badRef = ctx.mock.api.ref().ref + const masterRef = ctx.mock.api.ref().ref + const queryResponse = ctx.mock.api.query({ + documents: [ctx.mock.value.document()], + }) + + const triedRefs = new Set() + + mockPrismicRestAPIV2({ ctx, queryResponse }) + const endpoint = new URL( + "documents/search", + `${client.documentAPIEndpoint}/`, + ).toString() + ctx.server.use( + rest.get(endpoint, (req) => { + triedRefs.add(req.url.searchParams.get("ref")) + }), + rest.get(endpoint, (_req, res, ctx) => + res.once( + ctx.json({ message: `Master ref is: ${masterRef}` }), + ctx.status(410), ), - ) - - const consoleWarnSpy = vi - .spyOn(console, "warn") - .mockImplementation(() => void 0) - await args.run(client, { ref: badRef }) - consoleWarnSpy.mockRestore() - - expect(triedRefs).toStrictEqual([badRef, masterRef]) - }, - ) - - it.concurrent( - "throws if the maximum number of retries when an invalid ref is used is reached", - async (ctx) => { - const client = createTestClient({ ctx }) - const queryResponse = ctx.mock.api.query({ - documents: [ctx.mock.value.document()], - }) - - const triedRefs: (string | null)[] = [] - - mockPrismicRestAPIV2({ ctx, queryResponse }) - const endpoint = new URL( - "documents/search", - `${client.documentAPIEndpoint}/`, - ).toString() - ctx.server.use( - rest.get(endpoint, (req) => { - triedRefs.push(req.url.searchParams.get("ref")) - }), - rest.get(endpoint, (_req, res, requestCtx) => - res( - requestCtx.json({ - type: "api_notfound_error", - message: `Master ref is: ${ctx.mock.api.ref().ref}`, - }), - requestCtx.status(404), - ), + ), + ) + + const consoleWarnSpy = vi + .spyOn(console, "warn") + .mockImplementation(() => void 0) + await args.run(client, { ref: badRef }) + consoleWarnSpy.mockRestore() + + expect([...triedRefs]).toStrictEqual([badRef, masterRef]) + }) + + it("throws if the maximum number of retries when an invalid ref is used is reached", async (ctx) => { + const client = createTestClient({ ctx }) + const queryResponse = ctx.mock.api.query({ + documents: [ctx.mock.value.document()], + }) + + const triedRefs = new Set() + + mockPrismicRestAPIV2({ ctx, queryResponse }) + const endpoint = new URL( + "documents/search", + `${client.documentAPIEndpoint}/`, + ).toString() + ctx.server.use( + rest.get(endpoint, (req) => { + triedRefs.add(req.url.searchParams.get("ref")) + }), + rest.get(endpoint, (_req, res, requestCtx) => + res( + requestCtx.json({ + type: "api_notfound_error", + message: `Master ref is: ${ctx.mock.api.ref().ref}`, + }), + requestCtx.status(404), ), - ) - - const consoleWarnSpy = vi - .spyOn(console, "warn") - .mockImplementation(() => void 0) - await expect(async () => { - await args.run(client) - }).rejects.toThrow(prismic.RefNotFoundError) - consoleWarnSpy.mockRestore() - - expect(triedRefs.length).toBe(3) - }, - ) + ), + ) + + const consoleWarnSpy = vi + .spyOn(console, "warn") + .mockImplementation(() => void 0) + await expect(async () => { + await args.run(client) + }).rejects.toThrow(prismic.RefNotFoundError) + consoleWarnSpy.mockRestore() + + expect(triedRefs.size).toBe(3) + }) + + it("fetches a new master ref on subsequent queries if an invalid ref is used", async (ctx) => { + const client = createTestClient({ ctx }) + const queryResponse = ctx.mock.api.query({ + documents: [ctx.mock.value.document()], + }) + + const triedRefs = new Set() + + mockPrismicRestAPIV2({ ctx, queryResponse }) + const endpoint = new URL( + "documents/search", + `${client.documentAPIEndpoint}/`, + ).toString() + ctx.server.use( + rest.get(endpoint, (req) => { + triedRefs.add(req.url.searchParams.get("ref")) + }), + rest.get(endpoint, (_req, res, requestCtx) => + res.once( + requestCtx.json({ + type: "api_notfound_error", + message: `Master ref is: ${ctx.mock.api.ref().ref}`, + }), + requestCtx.status(404), + ), + ), + ) + + const consoleWarnSpy = vi + .spyOn(console, "warn") + .mockImplementation(() => void 0) + await args.run(client) + consoleWarnSpy.mockRestore() + + await args.run(client) + + expect(triedRefs.size).toBe(3) + }) }