Support multiple deploy template list URLs with cross-list dedupe
CI / test (push) Successful in 9m55s
CI / test (push) Successful in 9m55s
Add Settings UI to configure many Portainer-format template catalogs (not just one), merge them on fetch, drop duplicates by image/stack/title, and show source counts on the Deploy view.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import test from 'brittle'
|
||||
import {
|
||||
normalizeTemplateListUrls,
|
||||
templateDedupeKey,
|
||||
extractTemplatesArray,
|
||||
mergeAndDedupeTemplates,
|
||||
DEFAULT_TEMPLATE_LIST_URLS,
|
||||
} from '../client/templateLists.js'
|
||||
|
||||
test('normalizeTemplateListUrls unique https only', (t) => {
|
||||
const urls = normalizeTemplateListUrls([
|
||||
'https://example.com/a.json',
|
||||
'https://example.com/a.json',
|
||||
' https://example.com/b.json ',
|
||||
'ftp://bad.example/c.json',
|
||||
'not-a-url',
|
||||
'',
|
||||
])
|
||||
t.is(urls.length, 2)
|
||||
t.ok(urls[0].includes('a.json'))
|
||||
t.ok(urls[1].includes('b.json'))
|
||||
})
|
||||
|
||||
test('templateDedupeKey prefers image', (t) => {
|
||||
t.is(
|
||||
templateDedupeKey({ title: 'App', image: 'nginx:latest' }),
|
||||
templateDedupeKey({ title: 'Other', image: 'nginx' })
|
||||
)
|
||||
t.is(templateDedupeKey({ image: 'nginx:latest' }), 'image:nginx')
|
||||
})
|
||||
|
||||
test('templateDedupeKey for stacks uses repo', (t) => {
|
||||
const k = templateDedupeKey({
|
||||
title: 'Stack A',
|
||||
repository: { url: 'https://github.com/x/y', stackfile: 'docker-compose.yml' },
|
||||
})
|
||||
t.ok(k.startsWith('stack:'))
|
||||
t.ok(k.includes('github.com/x/y'))
|
||||
})
|
||||
|
||||
test('extractTemplatesArray handles shapes', (t) => {
|
||||
t.is(extractTemplatesArray({ templates: [{ title: 'a' }] }).length, 1)
|
||||
t.is(extractTemplatesArray([{ title: 'b' }]).length, 1)
|
||||
t.is(extractTemplatesArray(null).length, 0)
|
||||
})
|
||||
|
||||
test('mergeAndDedupeTemplates removes duplicates across sources', (t) => {
|
||||
const result = mergeAndDedupeTemplates([
|
||||
{
|
||||
url: 'https://a.example/t.json',
|
||||
templates: [
|
||||
{ title: 'Nginx', image: 'nginx:latest' },
|
||||
{ title: 'Redis', image: 'redis:7' },
|
||||
],
|
||||
},
|
||||
{
|
||||
url: 'https://b.example/t.json',
|
||||
templates: [
|
||||
{ title: 'Nginx Official', image: 'nginx' }, // dupe of first
|
||||
{ title: 'Postgres', image: 'postgres:16' },
|
||||
],
|
||||
},
|
||||
{
|
||||
url: 'https://bad.example/t.json',
|
||||
templates: [],
|
||||
error: 'HTTP 404',
|
||||
},
|
||||
])
|
||||
t.is(result.templates.length, 3)
|
||||
t.is(result.stats.duplicates, 1)
|
||||
t.is(result.stats.fetched, 4)
|
||||
t.is(result.stats.unique, 3)
|
||||
t.is(result.stats.errors.length, 1)
|
||||
t.is(result.templates[0]._sourceUrl, 'https://a.example/t.json')
|
||||
})
|
||||
|
||||
test('default catalog is non-empty', (t) => {
|
||||
t.ok(DEFAULT_TEMPLATE_LIST_URLS.length >= 1)
|
||||
t.ok(DEFAULT_TEMPLATE_LIST_URLS[0].startsWith('https://'))
|
||||
})
|
||||
Reference in New Issue
Block a user