-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add mutation to update bundle cache settings (#3649)
- Loading branch information
1 parent
daaed4b
commit 6807f02
Showing
2 changed files
with
308 additions
and
0 deletions.
There are no files selected for viewing
197 changes: 197 additions & 0 deletions
197
src/services/bundleAnalysis/useUpdateBundleCache.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
import { | ||
QueryClientProvider as QueryClientProviderV5, | ||
QueryClient as QueryClientV5, | ||
} from '@tanstack/react-queryV5' | ||
import { act, renderHook, waitFor } from '@testing-library/react' | ||
import { graphql, HttpResponse } from 'msw' | ||
import { setupServer } from 'msw/node' | ||
|
||
import { useUpdateBundleCache } from './useUpdateBundleCache' | ||
|
||
const mockSuccessfulResponse = { | ||
data: { | ||
updateBundleCacheConfig: { | ||
results: [{ bundleName: 'bundle-1', isCached: true }], | ||
error: null, | ||
}, | ||
}, | ||
} | ||
|
||
const mockParsingError = { | ||
data: null, | ||
errors: [{ message: 'Parsing error' }], | ||
} | ||
|
||
const mockUnauthenticatedError = { | ||
data: { | ||
updateBundleCacheConfig: { | ||
results: null, | ||
error: { | ||
__typename: 'UnauthenticatedError', | ||
message: 'Unauthenticated error', | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
const mockValidationError = { | ||
data: { | ||
updateBundleCacheConfig: { | ||
results: null, | ||
error: { __typename: 'ValidationError', message: 'Validation error' }, | ||
}, | ||
}, | ||
} | ||
|
||
const queryClient = new QueryClientV5({ | ||
defaultOptions: { mutations: { retry: false } }, | ||
}) | ||
|
||
const wrapper: React.FC<React.PropsWithChildren> = ({ children }) => ( | ||
<QueryClientProviderV5 client={queryClient}>{children}</QueryClientProviderV5> | ||
) | ||
|
||
const server = setupServer() | ||
|
||
beforeAll(() => { | ||
server.listen() | ||
}) | ||
|
||
afterEach(() => { | ||
queryClient.clear() | ||
server.resetHandlers() | ||
}) | ||
|
||
afterAll(() => { | ||
server.close() | ||
}) | ||
|
||
interface SetupArgs { | ||
isParsingError?: boolean | ||
isUnauthenticatedError?: boolean | ||
isValidationError?: boolean | ||
} | ||
|
||
describe('useUpdateBundleCache', () => { | ||
function setup({ | ||
isParsingError = false, | ||
isUnauthenticatedError = false, | ||
isValidationError = false, | ||
}: SetupArgs) { | ||
server.use( | ||
graphql.mutation('UpdateBundleCacheConfig', () => { | ||
if (isParsingError) { | ||
return HttpResponse.json(mockParsingError) | ||
} else if (isUnauthenticatedError) { | ||
return HttpResponse.json(mockUnauthenticatedError) | ||
} else if (isValidationError) { | ||
return HttpResponse.json(mockValidationError) | ||
} | ||
return HttpResponse.json(mockSuccessfulResponse) | ||
}) | ||
) | ||
} | ||
|
||
describe('when the mutation is successful', () => { | ||
it('returns the updated results', async () => { | ||
setup({}) | ||
const { result } = renderHook( | ||
() => | ||
useUpdateBundleCache({ | ||
provider: 'gh', | ||
owner: 'owner', | ||
repo: 'repo', | ||
}), | ||
{ wrapper } | ||
) | ||
|
||
act(() => | ||
result.current.mutate([{ bundleName: 'bundle-1', isCached: true }]) | ||
) | ||
await waitFor(() => expect(result.current.isSuccess).toBe(true)) | ||
|
||
expect(result.current.data).toEqual([ | ||
{ bundleName: 'bundle-1', isCached: true }, | ||
]) | ||
}) | ||
}) | ||
|
||
describe('when the mutation fails', () => { | ||
describe('when the mutation fails with a parsing error', () => { | ||
it('returns a parsing error', async () => { | ||
setup({ isParsingError: true }) | ||
const { result } = renderHook( | ||
() => | ||
useUpdateBundleCache({ | ||
provider: 'gh', | ||
owner: 'owner', | ||
repo: 'repo', | ||
}), | ||
{ wrapper } | ||
) | ||
|
||
act(() => | ||
result.current.mutate([{ bundleName: 'bundle-1', isCached: true }]) | ||
) | ||
await waitFor(() => expect(result.current.isError).toBe(true)) | ||
|
||
expect(result.current.error).toEqual({ | ||
data: {}, | ||
dev: 'useUpdateBundleCache - 400 failed to parse data', | ||
status: 400, | ||
}) | ||
}) | ||
}) | ||
|
||
describe('when the mutation fails with an unauthenticated error', () => { | ||
it('returns an unauthenticated error', async () => { | ||
setup({ isUnauthenticatedError: true }) | ||
const { result } = renderHook( | ||
() => | ||
useUpdateBundleCache({ | ||
provider: 'gh', | ||
owner: 'owner', | ||
repo: 'repo', | ||
}), | ||
{ wrapper } | ||
) | ||
|
||
act(() => | ||
result.current.mutate([{ bundleName: 'bundle-1', isCached: true }]) | ||
) | ||
await waitFor(() => expect(result.current.isError).toBe(true)) | ||
|
||
expect(result.current.error).toEqual({ | ||
error: 'UnauthenticatedError', | ||
message: 'Unauthenticated error', | ||
}) | ||
}) | ||
}) | ||
|
||
describe('when the mutation fails with a validation error', () => { | ||
it('returns a validation error', async () => { | ||
setup({ isValidationError: true }) | ||
const { result } = renderHook( | ||
() => | ||
useUpdateBundleCache({ | ||
provider: 'gh', | ||
owner: 'owner', | ||
repo: 'repo', | ||
}), | ||
{ wrapper } | ||
) | ||
|
||
act(() => | ||
result.current.mutate([{ bundleName: 'bundle-1', isCached: true }]) | ||
) | ||
|
||
await waitFor(() => expect(result.current.isError).toBe(true)) | ||
|
||
expect(result.current.error).toEqual({ | ||
error: 'ValidationError', | ||
message: 'Validation error', | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import { useMutation as useMutationV5 } from '@tanstack/react-queryV5' | ||
import { z } from 'zod' | ||
|
||
import Api from 'shared/api' | ||
import { rejectNetworkError } from 'shared/api/helpers' | ||
|
||
const UpdateBundleCacheInputSchema = z.array( | ||
z.object({ | ||
bundleName: z.string(), | ||
isCached: z.boolean(), | ||
}) | ||
) | ||
|
||
const MutationErrorSchema = z.discriminatedUnion('__typename', [ | ||
z.object({ | ||
__typename: z.literal('UnauthenticatedError'), | ||
message: z.string(), | ||
}), | ||
z.object({ | ||
__typename: z.literal('ValidationError'), | ||
message: z.string(), | ||
}), | ||
]) | ||
|
||
const MutationRequestSchema = z.object({ | ||
updateBundleCacheConfig: z | ||
.object({ | ||
results: UpdateBundleCacheInputSchema.nullable(), | ||
error: MutationErrorSchema.nullable(), | ||
}) | ||
.nullable(), | ||
}) | ||
|
||
const query = ` | ||
mutation UpdateBundleCacheConfig( | ||
$owner: String! | ||
$repo: String! | ||
$bundles: [BundleCacheConfigInput!]! | ||
) { | ||
updateBundleCacheConfig( | ||
input: { owner: $owner, repoName: $repo, bundles: $bundles } | ||
) { | ||
results { | ||
bundleName | ||
isCached | ||
} | ||
error { | ||
__typename | ||
... on UnauthenticatedError { | ||
message | ||
} | ||
... on ValidationError { | ||
message | ||
} | ||
} | ||
} | ||
}` | ||
|
||
interface UseUpdateBundleCacheArgs { | ||
provider: string | ||
owner: string | ||
repo: string | ||
} | ||
|
||
export const useUpdateBundleCache = ({ | ||
provider, | ||
owner, | ||
repo, | ||
}: UseUpdateBundleCacheArgs) => { | ||
return useMutationV5({ | ||
throwOnError: false, | ||
mutationFn: (input: z.infer<typeof UpdateBundleCacheInputSchema>) => { | ||
return Api.graphqlMutation({ | ||
provider, | ||
query, | ||
variables: { owner, repo, bundles: input }, | ||
mutationPath: 'updateBundleCache', | ||
}).then((res) => { | ||
const parsedData = MutationRequestSchema.safeParse(res.data) | ||
|
||
if (!parsedData.success) { | ||
return rejectNetworkError({ | ||
status: 400, | ||
error: parsedData.error, | ||
data: {}, | ||
dev: 'useUpdateBundleCache - 400 failed to parse data', | ||
}) | ||
} | ||
|
||
const updateBundleCacheConfig = parsedData.data.updateBundleCacheConfig | ||
if ( | ||
updateBundleCacheConfig?.error?.__typename === 'UnauthenticatedError' | ||
) { | ||
return Promise.reject({ | ||
error: 'UnauthenticatedError', | ||
message: updateBundleCacheConfig?.error?.message, | ||
}) | ||
} | ||
|
||
if (updateBundleCacheConfig?.error?.__typename === 'ValidationError') { | ||
return Promise.reject({ | ||
error: 'ValidationError', | ||
message: updateBundleCacheConfig?.error?.message, | ||
}) | ||
} | ||
|
||
return updateBundleCacheConfig?.results ?? [] | ||
}) | ||
}, | ||
}) | ||
} |