mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-16 19:24:31 +08:00
feat(settings): add AcoustID API key setting
This commit is contained in:
@@ -2142,6 +2142,10 @@ export default {
|
||||
tmdbApiKeyPlaceholder: 'Please enter TMDB API Key',
|
||||
tmdbApiKeyHint: 'Set TheMovieDb API Key',
|
||||
tmdbApiKeyRequired: 'Please enter TMDB API Key',
|
||||
acoustIdApiKey: 'AcoustID API Key',
|
||||
acoustIdApiKeyPlaceholder: 'Please enter AcoustID API Key',
|
||||
acoustIdApiKeyHint: 'Used to identify MusicBrainz recording IDs from audio fingerprints',
|
||||
acoustIdApiKeyRequired: 'Please enter AcoustID API Key',
|
||||
tmdbImageDomain: 'TMDB Image Service Address',
|
||||
tmdbImageDomainPlaceholder: 'image.tmdb.org',
|
||||
tmdbImageDomainHint: 'Customize TheMovieDb image service domain or proxy address',
|
||||
|
||||
@@ -2117,6 +2117,10 @@ export default {
|
||||
tmdbApiKeyPlaceholder: '请输入 TMDB API Key',
|
||||
tmdbApiKeyHint: '设置 TheMovieDb API Key',
|
||||
tmdbApiKeyRequired: '请输入TMDB API Key',
|
||||
acoustIdApiKey: 'AcoustID API Key',
|
||||
acoustIdApiKeyPlaceholder: '请输入 AcoustID API Key',
|
||||
acoustIdApiKeyHint: '用于通过音频指纹识别 MusicBrainz 录音 ID',
|
||||
acoustIdApiKeyRequired: '请输入 AcoustID API Key',
|
||||
tmdbImageDomain: 'TMDB 图片服务地址',
|
||||
tmdbImageDomainPlaceholder: 'image.tmdb.org',
|
||||
tmdbImageDomainHint: '自定义 TheMovieDb 图片服务域名或代理地址',
|
||||
|
||||
@@ -2116,6 +2116,10 @@ export default {
|
||||
tmdbApiKeyPlaceholder: '請輸入 TMDB API Key',
|
||||
tmdbApiKeyHint: '設定 TheMovieDb API Key',
|
||||
tmdbApiKeyRequired: '請輸入TMDB API Key',
|
||||
acoustIdApiKey: 'AcoustID API Key',
|
||||
acoustIdApiKeyPlaceholder: '請輸入 AcoustID API Key',
|
||||
acoustIdApiKeyHint: '用於通過音訊指紋識別 MusicBrainz 錄音 ID',
|
||||
acoustIdApiKeyRequired: '請輸入 AcoustID API Key',
|
||||
tmdbImageDomain: 'TMDB 圖片服務地址',
|
||||
tmdbImageDomainPlaceholder: 'image.tmdb.org',
|
||||
tmdbImageDomainHint: '自定義 TheMovieDb 圖片服務域名或代理地址',
|
||||
|
||||
@@ -104,6 +104,7 @@ const SystemSettings = ref<any>({
|
||||
MEDIA_RECOGNIZE_SHARE: true,
|
||||
TMDB_API_DOMAIN: null,
|
||||
TMDB_API_KEY: null,
|
||||
ACOUSTID_API_KEY: null,
|
||||
TMDB_IMAGE_DOMAIN: null,
|
||||
MUSIC_COVER_PROXY: null,
|
||||
TMDB_LOCALE: null,
|
||||
@@ -2045,6 +2046,17 @@ watch(currentLlmSnapshotKey, (snapshotKey, previousSnapshotKey) => {
|
||||
prepend-inner-icon="mdi-key-variant"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="SystemSettings.Advanced.ACOUSTID_API_KEY"
|
||||
:label="t('setting.system.acoustIdApiKey')"
|
||||
:hint="t('setting.system.acoustIdApiKeyHint')"
|
||||
persistent-hint
|
||||
:placeholder="t('setting.system.acoustIdApiKeyPlaceholder')"
|
||||
:rules="[(v: string) => !!v || t('setting.system.acoustIdApiKeyRequired')]"
|
||||
prepend-inner-icon="mdi-music-box-multiple-outline"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VCombobox
|
||||
v-model="SystemSettings.Advanced.TMDB_IMAGE_DOMAIN"
|
||||
|
||||
134
src/views/setting/__tests__/AccountSettingSystem.spec.ts
Normal file
134
src/views/setting/__tests__/AccountSettingSystem.spec.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import AccountSettingSystem from '@/views/setting/AccountSettingSystem.vue'
|
||||
import { fireEvent, screen, waitFor, within } from '@testing-library/vue'
|
||||
import { renderWithProviders } from '@tests/support/render'
|
||||
import { ref } from 'vue'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
apiGet: vi.fn(),
|
||||
apiPost: vi.fn(),
|
||||
toastError: vi.fn(),
|
||||
toastInfo: vi.fn(),
|
||||
toastSuccess: vi.fn(),
|
||||
useLlmProviderDirectory: vi.fn(),
|
||||
useSilentSettingRefresh: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('colorthief', () => ({
|
||||
default: class ColorThief {
|
||||
/** 返回稳定测试色,避免设置页子组件加载原生图像依赖。 */
|
||||
getColor() {
|
||||
return [40, 169, 225]
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/api', () => ({
|
||||
default: {
|
||||
get: mocks.apiGet,
|
||||
post: mocks.apiPost,
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('vue-toastification', () => ({
|
||||
useToast: () => ({
|
||||
error: mocks.toastError,
|
||||
info: mocks.toastInfo,
|
||||
success: mocks.toastSuccess,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useLlmProviderDirectory', () => ({
|
||||
useLlmProviderDirectory: mocks.useLlmProviderDirectory,
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useSilentSettingRefresh', () => ({
|
||||
useSilentSettingRefresh: mocks.useSilentSettingRefresh,
|
||||
}))
|
||||
|
||||
/** 构造系统设置页所需的最小 LLM 目录状态。 */
|
||||
function createLlmDirectoryState() {
|
||||
return {
|
||||
providerItems: ref([]),
|
||||
baseUrlPresetItems: ref([]),
|
||||
models: ref([]),
|
||||
selectedProvider: ref(null),
|
||||
selectedModel: ref(null),
|
||||
loadingProviders: ref(false),
|
||||
loadingModels: ref(false),
|
||||
providerConnected: ref(false),
|
||||
showBaseUrlField: ref(false),
|
||||
showApiKeyField: ref(false),
|
||||
showApiProtocolField: ref(false),
|
||||
supportsBuiltinWebSearch: ref(false),
|
||||
canRefreshModels: ref(false),
|
||||
setBaseUrlPreset: vi.fn(),
|
||||
authDialogVisible: ref(false),
|
||||
authPolling: ref(false),
|
||||
authPopupBlocked: ref(false),
|
||||
authSession: ref(null),
|
||||
handleProviderSelection: vi.fn(),
|
||||
applyModelMetadata: vi.fn(),
|
||||
loadProviders: vi.fn().mockResolvedValue(undefined),
|
||||
loadModels: vi.fn(),
|
||||
openAuthPage: vi.fn(),
|
||||
startAuth: vi.fn(),
|
||||
pollAuthSession: vi.fn(),
|
||||
disconnectAuth: vi.fn(),
|
||||
closeAuthDialog: vi.fn(),
|
||||
}
|
||||
}
|
||||
|
||||
describe('AccountSettingSystem', () => {
|
||||
beforeEach(() => {
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
mocks.apiGet.mockReset()
|
||||
mocks.apiPost.mockReset()
|
||||
mocks.toastError.mockReset()
|
||||
mocks.toastInfo.mockReset()
|
||||
mocks.toastSuccess.mockReset()
|
||||
mocks.useLlmProviderDirectory.mockReset()
|
||||
mocks.useSilentSettingRefresh.mockReset()
|
||||
mocks.useLlmProviderDirectory.mockReturnValue(createLlmDirectoryState())
|
||||
mocks.apiPost.mockResolvedValue({ success: true })
|
||||
mocks.apiGet.mockImplementation((endpoint: string) => {
|
||||
if (endpoint === 'system/env') {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
ACOUSTID_API_KEY: 'b1auxfOzAg',
|
||||
DB_TYPE: 'sqlite',
|
||||
RUST_ACCEL_AVAILABLE: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
if (endpoint === 'message/agent/mcp/servers') {
|
||||
return { success: true, data: { servers: [] } }
|
||||
}
|
||||
return { success: true, data: { value: [] } }
|
||||
})
|
||||
})
|
||||
|
||||
it('loads and saves the AcoustID key from advanced media settings', async () => {
|
||||
await renderWithProviders(AccountSettingSystem, {
|
||||
global: { stubs: { VDialogCloseBtn: true } },
|
||||
})
|
||||
await waitFor(() => expect(mocks.apiGet).toHaveBeenCalledWith('system/env'))
|
||||
|
||||
await fireEvent.click(screen.getByRole('button', { name: /高级设置/ }))
|
||||
await fireEvent.click(await screen.findByRole('tab', { name: '媒体' }))
|
||||
|
||||
const dialog = within(screen.getByRole('dialog'))
|
||||
const input = await dialog.findByLabelText('AcoustID API Key')
|
||||
expect(input).toHaveValue('b1auxfOzAg')
|
||||
await fireEvent.update(input, 'custom-acoustid-key')
|
||||
await fireEvent.click(dialog.getByRole('button', { name: '保存' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.apiPost).toHaveBeenCalledWith(
|
||||
'system/env',
|
||||
expect.objectContaining({ ACOUSTID_API_KEY: 'custom-acoustid-key' }),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user