mirror of
https://github.com/langgenius/dify.git
synced 2026-03-31 02:48:49 +08:00
Signed-off-by: majiayu000 <1835304752@qq.com> Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com> Signed-off-by: -LAN- <laipz8200@outlook.com> Signed-off-by: yihong0618 <zouzou0208@gmail.com> Co-authored-by: QuantumGhost <obelisk.reg+git@gmail.com> Co-authored-by: 盐粒 Yanli <yanli@dify.ai> Co-authored-by: wangxiaolei <fatelei@gmail.com> Co-authored-by: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Cursx <33718736+Cursx@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: lif <1835304752@qq.com> Co-authored-by: 非法操作 <hjlarry@163.com> Co-authored-by: Asuka Minato <i@asukaminato.eu.org> Co-authored-by: fenglin <790872612@qq.com> Co-authored-by: qiaofenglin <qiaofenglin@baidu.com> Co-authored-by: -LAN- <laipz8200@outlook.com> Co-authored-by: TomoOkuyama <49631611+TomoOkuyama@users.noreply.github.com> Co-authored-by: Tomo Okuyama <tomo.okuyama@intersystems.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zyssyz123 <916125788@qq.com> Co-authored-by: hj24 <mambahj24@gmail.com> Co-authored-by: Coding On Star <447357187@qq.com> Co-authored-by: CodingOnStar <hanxujiang@dify.ai> Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com> Co-authored-by: Xiangxuan Qu <fghpdf@outlook.com> Co-authored-by: fghpdf <fghpdf@users.noreply.github.com> Co-authored-by: coopercoder <whitetiger0127@163.com> Co-authored-by: zhaiguangpeng <zhaiguangpeng@didiglobal.com> Co-authored-by: Junyan Qin (Chin) <rockchinq@gmail.com> Co-authored-by: E.G <146701565+GlobalStar117@users.noreply.github.com> Co-authored-by: GlobalStar117 <GlobalStar117@users.noreply.github.com> Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com> Co-authored-by: CodingOnStar <hanxujiang@dify.com> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: heyszt <270985384@qq.com> Co-authored-by: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com> Co-authored-by: Yeuoly <45712896+Yeuoly@users.noreply.github.com> Co-authored-by: zxhlyh <jasonapring2015@outlook.com> Co-authored-by: moonpanda <chuanzegao@163.com> Co-authored-by: warlocgao <warlocgao@tencent.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: KVOJJJin <jzongcode@gmail.com> Co-authored-by: eux <euxx@users.noreply.github.com> Co-authored-by: bangjiehan <bangjiehan@gmail.com> Co-authored-by: FFXN <31929997+FFXN@users.noreply.github.com> Co-authored-by: Jyong <76649700+JohnJyong@users.noreply.github.com> Co-authored-by: Nie Ronghua <nieronghua@sf-express.com> Co-authored-by: JQSevenMiao <141806521+JQSevenMiao@users.noreply.github.com> Co-authored-by: jiasiqi <jiasiqi3@tal.com> Co-authored-by: Seokrin Taron Sung <sungsjade@gmail.com> Co-authored-by: CrabSAMA <40541269+CrabSAMA@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: yihong <zouzou0208@gmail.com> Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Co-authored-by: yessenia <yessenia.contact@gmail.com> Co-authored-by: Jax <anobaka@qq.com> Co-authored-by: niveshdandyan <155956228+niveshdandyan@users.noreply.github.com> Co-authored-by: OSS Contributor <oss-contributor@example.com> Co-authored-by: niveshdandyan <niveshdandyan@users.noreply.github.com> Co-authored-by: Sean Kenneth Doherty <Smaster7772@gmail.com>
503 lines
16 KiB
TypeScript
503 lines
16 KiB
TypeScript
import type { PluginDeclaration, PluginManifestInMarket } from '../types'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
import { PluginCategoryEnum } from '../types'
|
|
import {
|
|
convertRepoToUrl,
|
|
parseGitHubUrl,
|
|
pluginManifestInMarketToPluginProps,
|
|
pluginManifestToCardPluginProps,
|
|
} from './utils'
|
|
|
|
// Mock es-toolkit/compat
|
|
vi.mock('es-toolkit/compat', () => ({
|
|
isEmpty: (obj: unknown) => {
|
|
if (obj === null || obj === undefined)
|
|
return true
|
|
if (typeof obj === 'object')
|
|
return Object.keys(obj).length === 0
|
|
return false
|
|
},
|
|
}))
|
|
|
|
describe('pluginManifestToCardPluginProps', () => {
|
|
const createMockPluginDeclaration = (overrides?: Partial<PluginDeclaration>): PluginDeclaration => ({
|
|
plugin_unique_identifier: 'test-plugin-123',
|
|
version: '1.0.0',
|
|
author: 'test-author',
|
|
icon: '/test-icon.png',
|
|
name: 'test-plugin',
|
|
category: PluginCategoryEnum.tool,
|
|
label: { 'en-US': 'Test Plugin' } as Record<string, string>,
|
|
description: { 'en-US': 'Test description' } as Record<string, string>,
|
|
created_at: '2024-01-01',
|
|
resource: {},
|
|
plugins: {},
|
|
verified: true,
|
|
endpoint: { settings: [], endpoints: [] },
|
|
model: {},
|
|
tags: ['search', 'api'],
|
|
agent_strategy: {},
|
|
meta: { version: '1.0.0' },
|
|
trigger: {} as PluginDeclaration['trigger'],
|
|
...overrides,
|
|
})
|
|
|
|
describe('Basic Conversion', () => {
|
|
it('should convert plugin_unique_identifier to plugin_id', () => {
|
|
const manifest = createMockPluginDeclaration()
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.plugin_id).toBe('test-plugin-123')
|
|
})
|
|
|
|
it('should convert category to type', () => {
|
|
const manifest = createMockPluginDeclaration({ category: PluginCategoryEnum.model })
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.type).toBe(PluginCategoryEnum.model)
|
|
expect(result.category).toBe(PluginCategoryEnum.model)
|
|
})
|
|
|
|
it('should map author to org', () => {
|
|
const manifest = createMockPluginDeclaration({ author: 'my-org' })
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.org).toBe('my-org')
|
|
expect(result.author).toBe('my-org')
|
|
})
|
|
|
|
it('should map label correctly', () => {
|
|
const manifest = createMockPluginDeclaration({
|
|
label: { 'en-US': 'My Plugin', 'zh-Hans': '我的插件' } as Record<string, string>,
|
|
})
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.label).toEqual({ 'en-US': 'My Plugin', 'zh-Hans': '我的插件' })
|
|
})
|
|
|
|
it('should map description to brief and description', () => {
|
|
const manifest = createMockPluginDeclaration({
|
|
description: { 'en-US': 'Plugin description' } as Record<string, string>,
|
|
})
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.brief).toEqual({ 'en-US': 'Plugin description' })
|
|
expect(result.description).toEqual({ 'en-US': 'Plugin description' })
|
|
})
|
|
})
|
|
|
|
describe('Tags Conversion', () => {
|
|
it('should convert tags array to objects with name property', () => {
|
|
const manifest = createMockPluginDeclaration({
|
|
tags: ['search', 'image', 'api'],
|
|
})
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.tags).toEqual([
|
|
{ name: 'search' },
|
|
{ name: 'image' },
|
|
{ name: 'api' },
|
|
])
|
|
})
|
|
|
|
it('should handle empty tags array', () => {
|
|
const manifest = createMockPluginDeclaration({ tags: [] })
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.tags).toEqual([])
|
|
})
|
|
|
|
it('should handle single tag', () => {
|
|
const manifest = createMockPluginDeclaration({ tags: ['single'] })
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.tags).toEqual([{ name: 'single' }])
|
|
})
|
|
})
|
|
|
|
describe('Default Values', () => {
|
|
it('should set latest_version to empty string', () => {
|
|
const manifest = createMockPluginDeclaration()
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.latest_version).toBe('')
|
|
})
|
|
|
|
it('should set latest_package_identifier to empty string', () => {
|
|
const manifest = createMockPluginDeclaration()
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.latest_package_identifier).toBe('')
|
|
})
|
|
|
|
it('should set introduction to empty string', () => {
|
|
const manifest = createMockPluginDeclaration()
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.introduction).toBe('')
|
|
})
|
|
|
|
it('should set repository to empty string', () => {
|
|
const manifest = createMockPluginDeclaration()
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.repository).toBe('')
|
|
})
|
|
|
|
it('should set install_count to 0', () => {
|
|
const manifest = createMockPluginDeclaration()
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.install_count).toBe(0)
|
|
})
|
|
|
|
it('should set empty badges array', () => {
|
|
const manifest = createMockPluginDeclaration()
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.badges).toEqual([])
|
|
})
|
|
|
|
it('should set verification with langgenius category', () => {
|
|
const manifest = createMockPluginDeclaration()
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.verification).toEqual({ authorized_category: 'langgenius' })
|
|
})
|
|
|
|
it('should set from to package', () => {
|
|
const manifest = createMockPluginDeclaration()
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.from).toBe('package')
|
|
})
|
|
})
|
|
|
|
describe('Icon Handling', () => {
|
|
it('should map icon correctly', () => {
|
|
const manifest = createMockPluginDeclaration({ icon: '/custom-icon.png' })
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.icon).toBe('/custom-icon.png')
|
|
})
|
|
|
|
it('should map icon_dark when provided', () => {
|
|
const manifest = createMockPluginDeclaration({
|
|
icon: '/light-icon.png',
|
|
icon_dark: '/dark-icon.png',
|
|
})
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.icon).toBe('/light-icon.png')
|
|
expect(result.icon_dark).toBe('/dark-icon.png')
|
|
})
|
|
})
|
|
|
|
describe('Endpoint Settings', () => {
|
|
it('should set endpoint with empty settings array', () => {
|
|
const manifest = createMockPluginDeclaration()
|
|
const result = pluginManifestToCardPluginProps(manifest)
|
|
|
|
expect(result.endpoint).toEqual({ settings: [] })
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('pluginManifestInMarketToPluginProps', () => {
|
|
const createMockPluginManifestInMarket = (overrides?: Partial<PluginManifestInMarket>): PluginManifestInMarket => ({
|
|
plugin_unique_identifier: 'market-plugin-123',
|
|
name: 'market-plugin',
|
|
org: 'market-org',
|
|
icon: '/market-icon.png',
|
|
label: { 'en-US': 'Market Plugin' } as Record<string, string>,
|
|
category: PluginCategoryEnum.tool,
|
|
version: '1.0.0',
|
|
latest_version: '1.2.0',
|
|
brief: { 'en-US': 'Market plugin description' } as Record<string, string>,
|
|
introduction: 'Full introduction text',
|
|
verified: true,
|
|
install_count: 5000,
|
|
badges: ['partner', 'verified'],
|
|
verification: { authorized_category: 'langgenius' },
|
|
from: 'marketplace',
|
|
...overrides,
|
|
})
|
|
|
|
describe('Basic Conversion', () => {
|
|
it('should convert plugin_unique_identifier to plugin_id', () => {
|
|
const manifest = createMockPluginManifestInMarket()
|
|
const result = pluginManifestInMarketToPluginProps(manifest)
|
|
|
|
expect(result.plugin_id).toBe('market-plugin-123')
|
|
})
|
|
|
|
it('should convert category to type', () => {
|
|
const manifest = createMockPluginManifestInMarket({ category: PluginCategoryEnum.model })
|
|
const result = pluginManifestInMarketToPluginProps(manifest)
|
|
|
|
expect(result.type).toBe(PluginCategoryEnum.model)
|
|
expect(result.category).toBe(PluginCategoryEnum.model)
|
|
})
|
|
|
|
it('should use latest_version for version', () => {
|
|
const manifest = createMockPluginManifestInMarket({
|
|
version: '1.0.0',
|
|
latest_version: '2.0.0',
|
|
})
|
|
const result = pluginManifestInMarketToPluginProps(manifest)
|
|
|
|
expect(result.version).toBe('2.0.0')
|
|
expect(result.latest_version).toBe('2.0.0')
|
|
})
|
|
|
|
it('should map org correctly', () => {
|
|
const manifest = createMockPluginManifestInMarket({ org: 'my-organization' })
|
|
const result = pluginManifestInMarketToPluginProps(manifest)
|
|
|
|
expect(result.org).toBe('my-organization')
|
|
})
|
|
})
|
|
|
|
describe('Brief and Description', () => {
|
|
it('should map brief to both brief and description', () => {
|
|
const manifest = createMockPluginManifestInMarket({
|
|
brief: { 'en-US': 'Brief description' } as Record<string, string>,
|
|
})
|
|
const result = pluginManifestInMarketToPluginProps(manifest)
|
|
|
|
expect(result.brief).toEqual({ 'en-US': 'Brief description' })
|
|
expect(result.description).toEqual({ 'en-US': 'Brief description' })
|
|
})
|
|
})
|
|
|
|
describe('Badges and Verification', () => {
|
|
it('should map badges array', () => {
|
|
const manifest = createMockPluginManifestInMarket({
|
|
badges: ['partner', 'premium'],
|
|
})
|
|
const result = pluginManifestInMarketToPluginProps(manifest)
|
|
|
|
expect(result.badges).toEqual(['partner', 'premium'])
|
|
})
|
|
|
|
it('should map verification when provided', () => {
|
|
const manifest = createMockPluginManifestInMarket({
|
|
verification: { authorized_category: 'partner' },
|
|
})
|
|
const result = pluginManifestInMarketToPluginProps(manifest)
|
|
|
|
expect(result.verification).toEqual({ authorized_category: 'partner' })
|
|
})
|
|
|
|
it('should use default verification when empty', () => {
|
|
const manifest = createMockPluginManifestInMarket({
|
|
verification: {} as PluginManifestInMarket['verification'],
|
|
})
|
|
const result = pluginManifestInMarketToPluginProps(manifest)
|
|
|
|
expect(result.verification).toEqual({ authorized_category: 'langgenius' })
|
|
})
|
|
})
|
|
|
|
describe('Default Values', () => {
|
|
it('should set verified to true', () => {
|
|
const manifest = createMockPluginManifestInMarket()
|
|
const result = pluginManifestInMarketToPluginProps(manifest)
|
|
|
|
expect(result.verified).toBe(true)
|
|
})
|
|
|
|
it('should set latest_package_identifier to empty string', () => {
|
|
const manifest = createMockPluginManifestInMarket()
|
|
const result = pluginManifestInMarketToPluginProps(manifest)
|
|
|
|
expect(result.latest_package_identifier).toBe('')
|
|
})
|
|
|
|
it('should set repository to empty string', () => {
|
|
const manifest = createMockPluginManifestInMarket()
|
|
const result = pluginManifestInMarketToPluginProps(manifest)
|
|
|
|
expect(result.repository).toBe('')
|
|
})
|
|
|
|
it('should set install_count to 0', () => {
|
|
const manifest = createMockPluginManifestInMarket()
|
|
const result = pluginManifestInMarketToPluginProps(manifest)
|
|
|
|
expect(result.install_count).toBe(0)
|
|
})
|
|
|
|
it('should set empty tags array', () => {
|
|
const manifest = createMockPluginManifestInMarket()
|
|
const result = pluginManifestInMarketToPluginProps(manifest)
|
|
|
|
expect(result.tags).toEqual([])
|
|
})
|
|
|
|
it('should set endpoint with empty settings', () => {
|
|
const manifest = createMockPluginManifestInMarket()
|
|
const result = pluginManifestInMarketToPluginProps(manifest)
|
|
|
|
expect(result.endpoint).toEqual({ settings: [] })
|
|
})
|
|
})
|
|
|
|
describe('From Property', () => {
|
|
it('should map from property correctly', () => {
|
|
const manifest = createMockPluginManifestInMarket({ from: 'marketplace' })
|
|
const result = pluginManifestInMarketToPluginProps(manifest)
|
|
|
|
expect(result.from).toBe('marketplace')
|
|
})
|
|
|
|
it('should handle github from type', () => {
|
|
const manifest = createMockPluginManifestInMarket({ from: 'github' })
|
|
const result = pluginManifestInMarketToPluginProps(manifest)
|
|
|
|
expect(result.from).toBe('github')
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('parseGitHubUrl', () => {
|
|
describe('Valid URLs', () => {
|
|
it('should parse valid GitHub URL', () => {
|
|
const result = parseGitHubUrl('https://github.com/owner/repo')
|
|
|
|
expect(result.isValid).toBe(true)
|
|
expect(result.owner).toBe('owner')
|
|
expect(result.repo).toBe('repo')
|
|
})
|
|
|
|
it('should parse URL with trailing slash', () => {
|
|
const result = parseGitHubUrl('https://github.com/owner/repo/')
|
|
|
|
expect(result.isValid).toBe(true)
|
|
expect(result.owner).toBe('owner')
|
|
expect(result.repo).toBe('repo')
|
|
})
|
|
|
|
it('should handle hyphenated owner and repo names', () => {
|
|
const result = parseGitHubUrl('https://github.com/my-org/my-repo')
|
|
|
|
expect(result.isValid).toBe(true)
|
|
expect(result.owner).toBe('my-org')
|
|
expect(result.repo).toBe('my-repo')
|
|
})
|
|
|
|
it('should handle underscored names', () => {
|
|
const result = parseGitHubUrl('https://github.com/my_org/my_repo')
|
|
|
|
expect(result.isValid).toBe(true)
|
|
expect(result.owner).toBe('my_org')
|
|
expect(result.repo).toBe('my_repo')
|
|
})
|
|
|
|
it('should handle numeric characters in names', () => {
|
|
const result = parseGitHubUrl('https://github.com/org123/repo456')
|
|
|
|
expect(result.isValid).toBe(true)
|
|
expect(result.owner).toBe('org123')
|
|
expect(result.repo).toBe('repo456')
|
|
})
|
|
})
|
|
|
|
describe('Invalid URLs', () => {
|
|
it('should return invalid for non-GitHub URL', () => {
|
|
const result = parseGitHubUrl('https://gitlab.com/owner/repo')
|
|
|
|
expect(result.isValid).toBe(false)
|
|
expect(result.owner).toBeUndefined()
|
|
expect(result.repo).toBeUndefined()
|
|
})
|
|
|
|
it('should return invalid for URL with extra path segments', () => {
|
|
const result = parseGitHubUrl('https://github.com/owner/repo/tree/main')
|
|
|
|
expect(result.isValid).toBe(false)
|
|
})
|
|
|
|
it('should return invalid for URL without repo', () => {
|
|
const result = parseGitHubUrl('https://github.com/owner')
|
|
|
|
expect(result.isValid).toBe(false)
|
|
})
|
|
|
|
it('should return invalid for empty string', () => {
|
|
const result = parseGitHubUrl('')
|
|
|
|
expect(result.isValid).toBe(false)
|
|
})
|
|
|
|
it('should return invalid for malformed URL', () => {
|
|
const result = parseGitHubUrl('not-a-url')
|
|
|
|
expect(result.isValid).toBe(false)
|
|
})
|
|
|
|
it('should return invalid for http URL', () => {
|
|
// Testing invalid http protocol - construct URL dynamically to avoid lint error
|
|
const httpUrl = `${'http'}://github.com/owner/repo`
|
|
const result = parseGitHubUrl(httpUrl)
|
|
|
|
expect(result.isValid).toBe(false)
|
|
})
|
|
|
|
it('should return invalid for URL with www', () => {
|
|
const result = parseGitHubUrl('https://www.github.com/owner/repo')
|
|
|
|
expect(result.isValid).toBe(false)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('convertRepoToUrl', () => {
|
|
describe('Valid Repos', () => {
|
|
it('should convert repo to GitHub URL', () => {
|
|
const result = convertRepoToUrl('owner/repo')
|
|
|
|
expect(result).toBe('https://github.com/owner/repo')
|
|
})
|
|
|
|
it('should handle hyphenated names', () => {
|
|
const result = convertRepoToUrl('my-org/my-repo')
|
|
|
|
expect(result).toBe('https://github.com/my-org/my-repo')
|
|
})
|
|
|
|
it('should handle complex repo strings', () => {
|
|
const result = convertRepoToUrl('organization_name/repository-name')
|
|
|
|
expect(result).toBe('https://github.com/organization_name/repository-name')
|
|
})
|
|
})
|
|
|
|
describe('Edge Cases', () => {
|
|
it('should return empty string for empty repo', () => {
|
|
const result = convertRepoToUrl('')
|
|
|
|
expect(result).toBe('')
|
|
})
|
|
|
|
it('should return empty string for undefined-like values', () => {
|
|
// TypeScript would normally prevent this, but testing runtime behavior
|
|
const result = convertRepoToUrl(undefined as unknown as string)
|
|
|
|
expect(result).toBe('')
|
|
})
|
|
|
|
it('should return empty string for null-like values', () => {
|
|
const result = convertRepoToUrl(null as unknown as string)
|
|
|
|
expect(result).toBe('')
|
|
})
|
|
|
|
it('should handle repo with special characters', () => {
|
|
const result = convertRepoToUrl('org/repo.js')
|
|
|
|
expect(result).toBe('https://github.com/org/repo.js')
|
|
})
|
|
})
|
|
})
|