dify/web/features/deployments/create-release/state/__tests__/dsl-enabled.spec.ts
Stephen Zhou a84c2d36a3
style: format with vp fmt (#38803)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-12 15:57:46 +00:00

271 lines
7.7 KiB
TypeScript

import type { Getter } from 'jotai'
import { QueryClient, skipToken } from '@tanstack/react-query'
import { atom, createStore } from 'jotai'
import { queryClientAtom } from 'jotai-tanstack-query'
import { beforeEach, describe, expect, it, vi } from 'vitest'
type QueryResult = {
data?: unknown
isError?: boolean
isFetching?: boolean
isLoading?: boolean
isSuccess?: boolean
}
type QueryOptions = {
enabled?: boolean
input?: unknown
queryFn?: () => unknown
queryKey?: readonly unknown[]
retry?: boolean
}
type InfiniteQueryOptions = QueryOptions & {
input?: (pageParam: number) => unknown
}
type MutationResult = {
isPending: boolean
mutateAsync: ReturnType<typeof vi.fn>
}
const mockQueryResults = vi.hoisted(() => ({
current: new Map<string, QueryResult>(),
}))
const mockQueryOptions = vi.hoisted(() => ({
current: new Map<string, QueryOptions | InfiniteQueryOptions>(),
}))
const mockCreateReleaseMutation = vi.hoisted<{ current: MutationResult }>(() => ({
current: {
isPending: false,
mutateAsync: vi.fn(),
},
}))
vi.mock('../../../shared/domain/feature-flags', () => ({
isDeploymentDslImportEnabled: true,
}))
vi.mock('jotai-tanstack-query', async (importOriginal) => {
const actual = await importOriginal<typeof import('jotai-tanstack-query')>()
return {
...actual,
atomWithQuery: (createOptions: (get: Getter) => QueryOptions) =>
atom((get) => {
const options = createOptions(get)
const queryKey = Array.isArray(options.queryKey) ? options.queryKey[0] : undefined
const queryName = typeof queryKey === 'string' ? queryKey : 'unknown'
const queryResult =
options.enabled === false ? undefined : mockQueryResults.current.get(queryName)
mockQueryOptions.current.set(queryName, options)
return {
...options,
data: undefined,
isError: false,
isFetching: false,
isLoading: false,
isSuccess: false,
...queryResult,
}
}),
atomWithInfiniteQuery: (createOptions: (get: Getter) => InfiniteQueryOptions) =>
atom((get) => {
const options = createOptions(get)
const queryKey = Array.isArray(options.queryKey) ? options.queryKey[0] : undefined
const queryName = typeof queryKey === 'string' ? queryKey : 'unknown'
const queryResult =
options.enabled === false ? undefined : mockQueryResults.current.get(queryName)
mockQueryOptions.current.set(queryName, options)
return {
...options,
data: undefined,
hasNextPage: false,
isError: false,
isFetching: false,
isFetchingNextPage: false,
isLoading: false,
isPlaceholderData: false,
isSuccess: false,
...queryResult,
}
}),
atomWithMutation: () => atom(() => mockCreateReleaseMutation.current),
}
})
vi.mock('@/service/client', () => ({
consoleQuery: {
apps: {
get: {
infiniteOptions: (options: InfiniteQueryOptions) => ({
...options,
queryKey: ['sourceApps', options.input],
}),
},
},
enterprise: {
releaseService: {
listReleaseSummaries: {
key: ({ input }: { input?: unknown } = {}) =>
input === undefined ? ['listReleaseSummaries'] : ['listReleaseSummaries', input],
},
listReleases: {
key: ({ input }: { input?: unknown } = {}) =>
input === undefined ? ['listReleases'] : ['listReleases', input],
},
precheckRelease: {
queryOptions: (options: QueryOptions) => ({
...options,
queryKey: ['precheckRelease', options.input],
}),
},
createRelease: {
mutationOptions: () => ({ mutationKey: ['createRelease'] }),
},
},
},
},
}))
async function loadState() {
return await import('../index')
}
async function mountedStore() {
const state = await loadState()
const store = createStore()
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
})
store.set(queryClientAtom, queryClient)
const unsubscribe = store.sub(state.createReleaseFormIsSubmittingAtom, () => undefined)
return {
state,
store,
unsubscribe,
}
}
function workflowDsl() {
return ['app:', ' mode: workflow', ' name: Release source'].join('\n')
}
function setDslFileContentResult(overrides: QueryResult = {}) {
mockQueryResults.current.set('createReleaseDslFileContent', {
data: workflowDsl(),
isSuccess: true,
...overrides,
})
}
function setPrecheckReleaseResult(overrides: QueryResult = {}) {
mockQueryResults.current.set('precheckRelease', {
data: {
gateCommitId: 'gate-commit-1',
canCreate: true,
unsupportedNodes: [],
},
isSuccess: true,
...overrides,
})
}
describe('create release state with DSL import enabled', () => {
beforeEach(() => {
vi.clearAllMocks()
mockQueryResults.current.clear()
mockQueryOptions.current.clear()
mockCreateReleaseMutation.current = {
isPending: false,
mutateAsync: vi.fn(),
}
})
it('should enable source app search with the current search text while creating from an app', async () => {
const { state, store, unsubscribe } = await mountedStore()
store.set(state.createReleaseAppInstanceIdAtom, 'app-instance-1')
store.set(state.openCreateReleaseDialogAtom)
store.set(state.createReleaseSourceAppSearchTextAtom, 'customer')
const sourceAppsQuery = store.get(
state.createReleaseSourceAppsQueryAtom,
) as unknown as InfiniteQueryOptions
expect(sourceAppsQuery.enabled).toBe(true)
expect(sourceAppsQuery.input?.(2)).toEqual({
query: {
page: 2,
limit: 20,
name: 'customer',
mode: 'workflow',
},
})
unsubscribe()
})
it('should skip DSL release precheck input until DSL content is ready', async () => {
const { state, store, unsubscribe } = await mountedStore()
store.set(state.createReleaseAppInstanceIdAtom, 'app-instance-1')
store.set(state.openCreateReleaseDialogAtom)
store.set(state.selectCreateReleaseSourceModeAtom, 'dsl')
store.get(state.isCheckingCreateReleaseContentAtom)
expect(mockQueryOptions.current.get('precheckRelease')).toMatchObject({
enabled: false,
input: skipToken,
})
unsubscribe()
})
it('should submit a DSL release with encoded workflow content', async () => {
const { state, store, unsubscribe } = await mountedStore()
const response = {
release: {
displayName: 'Release from DSL',
},
}
const file = new File([workflowDsl()], 'workflow.yml', { type: 'text/yaml' })
mockCreateReleaseMutation.current.mutateAsync.mockResolvedValue(response)
store.set(state.createReleaseAppInstanceIdAtom, 'app-instance-1')
store.set(state.openCreateReleaseDialogAtom)
store.set(state.selectCreateReleaseSourceModeAtom, 'dsl')
store.set(state.updateCreateReleaseDslFileAtom, file)
setDslFileContentResult()
setPrecheckReleaseResult()
store.set(state.createReleaseNameFieldAtom, ' Release from DSL ')
store.set(state.createReleaseDescriptionFieldAtom, ' Imported workflow ')
const result = await store.set(state.submitCreateReleaseFormAtom)
const encodedDslContent = store.get(state.createReleaseEncodedDslContentAtom)
expect(result).toBe(response)
expect(encodedDslContent).not.toBe('')
expect(mockCreateReleaseMutation.current.mutateAsync).toHaveBeenCalledWith({
body: {
appInstanceId: 'app-instance-1',
displayName: 'Release from DSL',
description: 'Imported workflow',
createAppInstance: false,
dsl: encodedDslContent,
},
})
unsubscribe()
})
})