Skip to content

Commit

Permalink
feat: use a new master ref once a known-stale ref is used
Browse files Browse the repository at this point in the history
  • Loading branch information
angeloashmore committed Oct 30, 2024
1 parent 345ff2b commit ef49bdc
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 119 deletions.
8 changes: 8 additions & 0 deletions src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: (?<ref>.*)$/)
?.groups?.ref
if (!masterRef) {
Expand Down
268 changes: 149 additions & 119 deletions test/__testutils__/testInvalidRefRetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string | null>()

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<string | null>()

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<string | null>()

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<string | null>()

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)
})
}

0 comments on commit ef49bdc

Please sign in to comment.