Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add mutation to update bundle cache settings #3649

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 197 additions & 0 deletions src/services/bundleAnalysis/useUpdateBundleCache.test.tsx
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',
})
})
})
})
})
111 changes: 111 additions & 0 deletions src/services/bundleAnalysis/useUpdateBundleCache.tsx
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 ?? []
})
},
})
}
Loading