Skip to content

Commit 6807f02

Browse files
feat: Add mutation to update bundle cache settings (#3649)
1 parent daaed4b commit 6807f02

File tree

2 files changed

+308
-0
lines changed

2 files changed

+308
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import {
2+
QueryClientProvider as QueryClientProviderV5,
3+
QueryClient as QueryClientV5,
4+
} from '@tanstack/react-queryV5'
5+
import { act, renderHook, waitFor } from '@testing-library/react'
6+
import { graphql, HttpResponse } from 'msw'
7+
import { setupServer } from 'msw/node'
8+
9+
import { useUpdateBundleCache } from './useUpdateBundleCache'
10+
11+
const mockSuccessfulResponse = {
12+
data: {
13+
updateBundleCacheConfig: {
14+
results: [{ bundleName: 'bundle-1', isCached: true }],
15+
error: null,
16+
},
17+
},
18+
}
19+
20+
const mockParsingError = {
21+
data: null,
22+
errors: [{ message: 'Parsing error' }],
23+
}
24+
25+
const mockUnauthenticatedError = {
26+
data: {
27+
updateBundleCacheConfig: {
28+
results: null,
29+
error: {
30+
__typename: 'UnauthenticatedError',
31+
message: 'Unauthenticated error',
32+
},
33+
},
34+
},
35+
}
36+
37+
const mockValidationError = {
38+
data: {
39+
updateBundleCacheConfig: {
40+
results: null,
41+
error: { __typename: 'ValidationError', message: 'Validation error' },
42+
},
43+
},
44+
}
45+
46+
const queryClient = new QueryClientV5({
47+
defaultOptions: { mutations: { retry: false } },
48+
})
49+
50+
const wrapper: React.FC<React.PropsWithChildren> = ({ children }) => (
51+
<QueryClientProviderV5 client={queryClient}>{children}</QueryClientProviderV5>
52+
)
53+
54+
const server = setupServer()
55+
56+
beforeAll(() => {
57+
server.listen()
58+
})
59+
60+
afterEach(() => {
61+
queryClient.clear()
62+
server.resetHandlers()
63+
})
64+
65+
afterAll(() => {
66+
server.close()
67+
})
68+
69+
interface SetupArgs {
70+
isParsingError?: boolean
71+
isUnauthenticatedError?: boolean
72+
isValidationError?: boolean
73+
}
74+
75+
describe('useUpdateBundleCache', () => {
76+
function setup({
77+
isParsingError = false,
78+
isUnauthenticatedError = false,
79+
isValidationError = false,
80+
}: SetupArgs) {
81+
server.use(
82+
graphql.mutation('UpdateBundleCacheConfig', () => {
83+
if (isParsingError) {
84+
return HttpResponse.json(mockParsingError)
85+
} else if (isUnauthenticatedError) {
86+
return HttpResponse.json(mockUnauthenticatedError)
87+
} else if (isValidationError) {
88+
return HttpResponse.json(mockValidationError)
89+
}
90+
return HttpResponse.json(mockSuccessfulResponse)
91+
})
92+
)
93+
}
94+
95+
describe('when the mutation is successful', () => {
96+
it('returns the updated results', async () => {
97+
setup({})
98+
const { result } = renderHook(
99+
() =>
100+
useUpdateBundleCache({
101+
provider: 'gh',
102+
owner: 'owner',
103+
repo: 'repo',
104+
}),
105+
{ wrapper }
106+
)
107+
108+
act(() =>
109+
result.current.mutate([{ bundleName: 'bundle-1', isCached: true }])
110+
)
111+
await waitFor(() => expect(result.current.isSuccess).toBe(true))
112+
113+
expect(result.current.data).toEqual([
114+
{ bundleName: 'bundle-1', isCached: true },
115+
])
116+
})
117+
})
118+
119+
describe('when the mutation fails', () => {
120+
describe('when the mutation fails with a parsing error', () => {
121+
it('returns a parsing error', async () => {
122+
setup({ isParsingError: true })
123+
const { result } = renderHook(
124+
() =>
125+
useUpdateBundleCache({
126+
provider: 'gh',
127+
owner: 'owner',
128+
repo: 'repo',
129+
}),
130+
{ wrapper }
131+
)
132+
133+
act(() =>
134+
result.current.mutate([{ bundleName: 'bundle-1', isCached: true }])
135+
)
136+
await waitFor(() => expect(result.current.isError).toBe(true))
137+
138+
expect(result.current.error).toEqual({
139+
data: {},
140+
dev: 'useUpdateBundleCache - 400 failed to parse data',
141+
status: 400,
142+
})
143+
})
144+
})
145+
146+
describe('when the mutation fails with an unauthenticated error', () => {
147+
it('returns an unauthenticated error', async () => {
148+
setup({ isUnauthenticatedError: true })
149+
const { result } = renderHook(
150+
() =>
151+
useUpdateBundleCache({
152+
provider: 'gh',
153+
owner: 'owner',
154+
repo: 'repo',
155+
}),
156+
{ wrapper }
157+
)
158+
159+
act(() =>
160+
result.current.mutate([{ bundleName: 'bundle-1', isCached: true }])
161+
)
162+
await waitFor(() => expect(result.current.isError).toBe(true))
163+
164+
expect(result.current.error).toEqual({
165+
error: 'UnauthenticatedError',
166+
message: 'Unauthenticated error',
167+
})
168+
})
169+
})
170+
171+
describe('when the mutation fails with a validation error', () => {
172+
it('returns a validation error', async () => {
173+
setup({ isValidationError: true })
174+
const { result } = renderHook(
175+
() =>
176+
useUpdateBundleCache({
177+
provider: 'gh',
178+
owner: 'owner',
179+
repo: 'repo',
180+
}),
181+
{ wrapper }
182+
)
183+
184+
act(() =>
185+
result.current.mutate([{ bundleName: 'bundle-1', isCached: true }])
186+
)
187+
188+
await waitFor(() => expect(result.current.isError).toBe(true))
189+
190+
expect(result.current.error).toEqual({
191+
error: 'ValidationError',
192+
message: 'Validation error',
193+
})
194+
})
195+
})
196+
})
197+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { useMutation as useMutationV5 } from '@tanstack/react-queryV5'
2+
import { z } from 'zod'
3+
4+
import Api from 'shared/api'
5+
import { rejectNetworkError } from 'shared/api/helpers'
6+
7+
const UpdateBundleCacheInputSchema = z.array(
8+
z.object({
9+
bundleName: z.string(),
10+
isCached: z.boolean(),
11+
})
12+
)
13+
14+
const MutationErrorSchema = z.discriminatedUnion('__typename', [
15+
z.object({
16+
__typename: z.literal('UnauthenticatedError'),
17+
message: z.string(),
18+
}),
19+
z.object({
20+
__typename: z.literal('ValidationError'),
21+
message: z.string(),
22+
}),
23+
])
24+
25+
const MutationRequestSchema = z.object({
26+
updateBundleCacheConfig: z
27+
.object({
28+
results: UpdateBundleCacheInputSchema.nullable(),
29+
error: MutationErrorSchema.nullable(),
30+
})
31+
.nullable(),
32+
})
33+
34+
const query = `
35+
mutation UpdateBundleCacheConfig(
36+
$owner: String!
37+
$repo: String!
38+
$bundles: [BundleCacheConfigInput!]!
39+
) {
40+
updateBundleCacheConfig(
41+
input: { owner: $owner, repoName: $repo, bundles: $bundles }
42+
) {
43+
results {
44+
bundleName
45+
isCached
46+
}
47+
error {
48+
__typename
49+
... on UnauthenticatedError {
50+
message
51+
}
52+
... on ValidationError {
53+
message
54+
}
55+
}
56+
}
57+
}`
58+
59+
interface UseUpdateBundleCacheArgs {
60+
provider: string
61+
owner: string
62+
repo: string
63+
}
64+
65+
export const useUpdateBundleCache = ({
66+
provider,
67+
owner,
68+
repo,
69+
}: UseUpdateBundleCacheArgs) => {
70+
return useMutationV5({
71+
throwOnError: false,
72+
mutationFn: (input: z.infer<typeof UpdateBundleCacheInputSchema>) => {
73+
return Api.graphqlMutation({
74+
provider,
75+
query,
76+
variables: { owner, repo, bundles: input },
77+
mutationPath: 'updateBundleCache',
78+
}).then((res) => {
79+
const parsedData = MutationRequestSchema.safeParse(res.data)
80+
81+
if (!parsedData.success) {
82+
return rejectNetworkError({
83+
status: 400,
84+
error: parsedData.error,
85+
data: {},
86+
dev: 'useUpdateBundleCache - 400 failed to parse data',
87+
})
88+
}
89+
90+
const updateBundleCacheConfig = parsedData.data.updateBundleCacheConfig
91+
if (
92+
updateBundleCacheConfig?.error?.__typename === 'UnauthenticatedError'
93+
) {
94+
return Promise.reject({
95+
error: 'UnauthenticatedError',
96+
message: updateBundleCacheConfig?.error?.message,
97+
})
98+
}
99+
100+
if (updateBundleCacheConfig?.error?.__typename === 'ValidationError') {
101+
return Promise.reject({
102+
error: 'ValidationError',
103+
message: updateBundleCacheConfig?.error?.message,
104+
})
105+
}
106+
107+
return updateBundleCacheConfig?.results ?? []
108+
})
109+
},
110+
})
111+
}

0 commit comments

Comments
 (0)