mirror of
https://github.com/langgenius/dify.git
synced 2026-09-24 23:22:26 +08:00
fix(plugin): preserve credentials during local replacement (#39764)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
autofix-ci[bot]
parent
81fd1da3ff
commit
72c20daa61
+4
-2
@@ -372,7 +372,7 @@ describe('Install', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should uninstall existing plugin before installing new version', async () => {
|
||||
it('should preserve credentials when replacing an installed plugin', async () => {
|
||||
mockUseCheckInstalled.mockReturnValue({
|
||||
installedInfo: {
|
||||
'test-author/Test Plugin': {
|
||||
@@ -394,7 +394,9 @@ describe('Install', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: 'plugin.installModal.install' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUninstallPlugin).toHaveBeenCalledWith('installed-id-to-uninstall')
|
||||
expect(mockUninstallPlugin).toHaveBeenCalledWith('installed-id-to-uninstall', {
|
||||
preserveCredentials: true,
|
||||
})
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
+3
-1
@@ -69,7 +69,9 @@ const Installed: FC<Props> = ({
|
||||
onStartToInstall?.()
|
||||
|
||||
try {
|
||||
if (hasInstalled) await uninstallPlugin(installedInfoPayload.installedId)
|
||||
if (hasInstalled) {
|
||||
await uninstallPlugin(installedInfoPayload.installedId, { preserveCredentials: true })
|
||||
}
|
||||
|
||||
const response = await installPackageFromLocal(uniqueIdentifier)
|
||||
const { all_installed, task_id } = response
|
||||
|
||||
@@ -452,10 +452,6 @@ export type InstalledPluginCategoryListResponse = {
|
||||
has_more: boolean
|
||||
}
|
||||
|
||||
export type UninstallPluginResponse = {
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export type GitHubItemAndMarketPlaceDependency = {
|
||||
type: 'github' | 'marketplace' | 'package'
|
||||
value: {
|
||||
|
||||
@@ -19,15 +19,17 @@ import { renderHook } from '@/test/console/render'
|
||||
import {
|
||||
normalizeInstalledPluginDetail,
|
||||
useInstalledPluginList,
|
||||
useInstallOrUpdate,
|
||||
useMutationPluginAutoUpgradeSettings,
|
||||
useMutationPluginPermissionSettings,
|
||||
usePluginAutoUpgradeSettings,
|
||||
usePluginTaskList,
|
||||
} from '../use-plugins'
|
||||
|
||||
const { mockGet, mockPost } = vi.hoisted(() => ({
|
||||
const { mockGet, mockPost, mockUninstallPlugin } = vi.hoisted(() => ({
|
||||
mockGet: vi.fn(),
|
||||
mockPost: vi.fn(),
|
||||
mockUninstallPlugin: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/base', () => ({
|
||||
@@ -37,6 +39,11 @@ vi.mock('@/service/base', () => ({
|
||||
postMarketplace: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/plugins', () => ({
|
||||
fetchPluginInfoFromMarketPlace: vi.fn(),
|
||||
uninstallPlugin: mockUninstallPlugin,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list', () => ({
|
||||
default: () => ({
|
||||
refreshPluginList: vi.fn(),
|
||||
@@ -303,6 +310,54 @@ describe('use-plugins mutations', () => {
|
||||
resolvePost({})
|
||||
})
|
||||
|
||||
it('preserves credentials when replacing an installed bundle package', async () => {
|
||||
const queryClient = createQueryClient()
|
||||
mockUninstallPlugin.mockResolvedValue({ success: true })
|
||||
mockPost.mockResolvedValue({ all_installed: true, task_id: '' })
|
||||
const payload = [
|
||||
{
|
||||
type: 'package' as const,
|
||||
value: {
|
||||
unique_identifier: 'langgenius/openai:0.0.2@new',
|
||||
manifest: {},
|
||||
},
|
||||
},
|
||||
]
|
||||
const plugin = [
|
||||
{
|
||||
org: 'langgenius',
|
||||
name: 'openai',
|
||||
},
|
||||
]
|
||||
const installedInfo = {
|
||||
'langgenius/openai': {
|
||||
installedId: 'installation-id',
|
||||
installedVersion: '0.0.1',
|
||||
uniqueIdentifier: 'langgenius/openai:0.0.1@old',
|
||||
},
|
||||
}
|
||||
const { result } = renderHook(() => useInstallOrUpdate({}), {
|
||||
wrapper: createWrapper(queryClient),
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({
|
||||
payload: payload as Parameters<typeof result.current.mutateAsync>[0]['payload'],
|
||||
plugin: plugin as Parameters<typeof result.current.mutateAsync>[0]['plugin'],
|
||||
installedInfo,
|
||||
})
|
||||
})
|
||||
|
||||
expect(mockUninstallPlugin).toHaveBeenCalledWith('installation-id', {
|
||||
preserveCredentials: true,
|
||||
})
|
||||
expect(mockPost).toHaveBeenCalledWith('/workspaces/current/plugin/install/pkg', {
|
||||
body: {
|
||||
plugin_unique_identifiers: ['langgenius/openai:0.0.2@new'],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('optimistically updates plugin permission cache before the request finishes', async () => {
|
||||
const queryClient = createQueryClient()
|
||||
const queryKey = ['plugins', 'referenceSettings', 'permission']
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { uninstallPlugin } from './plugins'
|
||||
|
||||
const { mockUninstallPost } = vi.hoisted(() => ({
|
||||
mockUninstallPost: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('./base', () => ({
|
||||
get: vi.fn(),
|
||||
getMarketplace: vi.fn(),
|
||||
post: vi.fn(),
|
||||
upload: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('./client', () => ({
|
||||
consoleClient: {
|
||||
workspaces: {
|
||||
current: {
|
||||
plugin: {
|
||||
uninstall: {
|
||||
post: mockUninstallPost,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
describe('uninstallPlugin', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockUninstallPost.mockResolvedValue({ success: true })
|
||||
})
|
||||
|
||||
it('should delete credentials for an ordinary uninstall by default', async () => {
|
||||
await uninstallPlugin('installation-id')
|
||||
|
||||
expect(mockUninstallPost).toHaveBeenCalledWith({
|
||||
body: {
|
||||
plugin_installation_id: 'installation-id',
|
||||
preserve_credentials: false,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should preserve credentials when requested for a replacement', async () => {
|
||||
await uninstallPlugin('installation-id', { preserveCredentials: true })
|
||||
|
||||
expect(mockUninstallPost).toHaveBeenCalledWith({
|
||||
body: {
|
||||
plugin_installation_id: 'installation-id',
|
||||
preserve_credentials: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
+10
-4
@@ -4,11 +4,11 @@ import type {
|
||||
InstallPackageResponse,
|
||||
PluginManifestInMarket,
|
||||
TaskStatusResponse,
|
||||
UninstallPluginResponse,
|
||||
updatePackageResponse,
|
||||
uploadGitHubResponse,
|
||||
} from '@/app/components/plugins/types'
|
||||
import { get, getMarketplace, post, upload } from './base'
|
||||
import { consoleClient } from './client'
|
||||
|
||||
export const uploadFile = async (file: File, isBundle: boolean) => {
|
||||
const formData = new FormData()
|
||||
@@ -87,8 +87,14 @@ export const checkTaskStatus = async (taskId: string) => {
|
||||
return get<TaskStatusResponse>(`/workspaces/current/plugin/tasks/${taskId}`)
|
||||
}
|
||||
|
||||
export const uninstallPlugin = async (pluginId: string) => {
|
||||
return post<UninstallPluginResponse>('/workspaces/current/plugin/uninstall', {
|
||||
body: { plugin_installation_id: pluginId },
|
||||
export const uninstallPlugin = async (
|
||||
pluginId: string,
|
||||
options: { preserveCredentials?: boolean } = {},
|
||||
) => {
|
||||
return consoleClient.workspaces.current.plugin.uninstall.post({
|
||||
body: {
|
||||
plugin_installation_id: pluginId,
|
||||
preserve_credentials: options.preserveCredentials ?? false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -919,7 +919,7 @@ export const useInstallOrUpdate = ({
|
||||
}
|
||||
if (isInstalled) {
|
||||
if (item.type === 'package') {
|
||||
await uninstallPlugin(installedPayload.installedId)
|
||||
await uninstallPlugin(installedPayload.installedId, { preserveCredentials: true })
|
||||
const response = await post<InstallPackageResponse>(
|
||||
'/workspaces/current/plugin/install/pkg',
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user