Add [snapcraft] version badge (#9976)

* Add snapstore version badge

Fixes badges/shields#9103

* Add basic test for Snapcraft version

* Handle snapstore-version - package not found

* Add test for invalid snapcraft package

* Remove redundent SnapstoreBase class

not needed as there is no auth

* Remove logo from default

The project convention is using namedLogo by default only for social badges.
This commit removes the default logo usage as this badge is not in the social category.

* Rename snapstore to snapcraft

Keep 1 convention for the snapcraft badge, use only snapcraft and ditch snapstore.

* Fix SnapcraftVersion schema

* Use renderVersionBadge

Replace the costume render in SnapcraftVersion with renderVersionBadge

* Rename folder from snapstore to snapcraft

* Add track & risk path parameters for SnapcraftVersion

enhancing control and clarity

* Add architecture query parameter to SnapcraftVersion

Added architecture query parameter: The snapcraft-version.service.js file now accepts an optional arch query parameter to specify the desired architecture for the Snap package. This defaults to amd64 if not provided.

If an unsupported architecture is specified in the query parameter, a NotFound error is thrown with a specific message indicating that the requested architecture is not found.

The snapcraft-version.tester.js file is updated to include a new test case that verifies the behavior when using the arch query parameter and also includes a test case for handling an invalid architecture.

* move filter logic into a transform function

* Fix filter logic

The goal here was to filter by all conditions with logic and.
Before this fix the only the logic of the last filter is used.

* Add tests for SnapcraftVersion.transform
This commit is contained in:
jNullj
2024-03-10 12:12:58 +02:00
committed by GitHub
parent 84e29440ba
commit c6be456d9c
3 changed files with 246 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
import Joi from 'joi'
import { BaseJsonService, NotFound, pathParams, queryParam } from '../index.js'
import { renderVersionBadge } from '../version.js'
const queryParamSchema = Joi.object({
arch: Joi.string(),
})
const versionSchema = Joi.object({
'channel-map': Joi.array()
.items(
Joi.object({
channel: Joi.object({
architecture: Joi.string().required(),
risk: Joi.string().required(),
track: Joi.string().required(),
}),
version: Joi.string().required(),
}).required(),
)
.min(1)
.required(),
}).required()
export default class SnapcraftVersion extends BaseJsonService {
static category = 'version'
static route = {
base: 'snapcraft/v',
pattern: ':package/:track/:risk',
queryParamSchema,
}
static defaultBadgeData = { label: 'snapcraft' }
static openApi = {
'/snapcraft/v/{package}/{track}/{risk}': {
get: {
summary: 'Snapcraft version',
parameters: [
...pathParams(
{ name: 'package', example: 'chromium' },
{ name: 'track', example: 'latest' },
{ name: 'risk', example: 'stable' },
),
queryParam({
name: 'arch',
example: 'amd64',
description:
'Architecture, When not specified, this will default to `amd64`.',
}),
],
},
},
}
transform(apiData, track, risk, arch) {
const channelMap = apiData['channel-map']
let filteredChannelMap = channelMap.filter(
({ channel }) => channel.architecture === arch,
)
if (filteredChannelMap.length === 0) {
throw new NotFound({ prettyMessage: 'arch not found' })
}
filteredChannelMap = filteredChannelMap.filter(
({ channel }) => channel.track === track,
)
if (filteredChannelMap.length === 0) {
throw new NotFound({ prettyMessage: 'track not found' })
}
filteredChannelMap = filteredChannelMap.filter(
({ channel }) => channel.risk === risk,
)
if (filteredChannelMap.length === 0) {
throw new NotFound({ prettyMessage: 'risk not found' })
}
return filteredChannelMap[0]
}
async handle({ package: packageName, track, risk }, { arch = 'amd64' }) {
const parsedData = await this._requestJson({
schema: versionSchema,
options: {
headers: { 'Snap-Device-Series': 16 },
},
url: `https://api.snapcraft.io/v2/snaps/info/${packageName}`,
httpErrors: {
404: 'package not found',
},
})
// filter results by track, risk and arch
const { version } = this.transform(parsedData, track, risk, arch)
return renderVersionBadge({ version })
}
}

View File

@@ -0,0 +1,103 @@
import { expect } from 'chai'
import { test, given } from 'sazerac'
import _ from 'lodash'
import { NotFound } from '../index.js'
import SnapcraftVersion from './snapcraft-version.service.js'
describe('SnapcraftVersion', function () {
const exampleChannel = {
channel: {
architecture: 'amd64',
risk: 'stable',
track: 'latest',
},
version: '1.2.3',
}
const exampleArchChange = _.merge(_.cloneDeep(exampleChannel), {
channel: { architecture: 'arm64' },
version: '2.3.4',
})
const exampleTrackChange = _.merge(_.cloneDeep(exampleChannel), {
channel: { track: 'beta' },
version: '3.4.5',
})
const exampleRiskChange = _.merge(_.cloneDeep(exampleChannel), {
channel: { risk: 'edge' },
version: '5.4.6',
})
const testApiData = {
'channel-map': [
exampleChannel,
exampleArchChange,
exampleTrackChange,
exampleRiskChange,
],
}
test(SnapcraftVersion.prototype.transform, () => {
given(
testApiData,
exampleChannel.channel.track,
exampleChannel.channel.risk,
exampleChannel.channel.architecture,
).expect(exampleChannel)
// change arch
given(
testApiData,
exampleChannel.channel.track,
exampleChannel.channel.risk,
exampleArchChange.channel.architecture,
).expect(exampleArchChange)
// change track
given(
testApiData,
exampleTrackChange.channel.track,
exampleChannel.channel.risk,
exampleChannel.channel.architecture,
).expect(exampleTrackChange)
// change risk
given(
testApiData,
exampleChannel.channel.track,
exampleRiskChange.channel.risk,
exampleChannel.channel.architecture,
).expect(exampleRiskChange)
})
it('throws NotFound error with missing arch', function () {
expect(() => {
SnapcraftVersion.prototype.transform(
testApiData,
exampleChannel.channel.track,
exampleChannel.channel.risk,
'missing',
)
})
.to.throw(NotFound)
.with.property('prettyMessage', 'arch not found')
})
it('throws NotFound error with missing track', function () {
expect(() => {
SnapcraftVersion.prototype.transform(
testApiData,
'missing',
exampleChannel.channel.risk,
exampleChannel.channel.architecture,
)
})
.to.throw(NotFound)
.with.property('prettyMessage', 'track not found')
})
it('throws NotFound error with missing risk', function () {
expect(() => {
SnapcraftVersion.prototype.transform(
testApiData,
exampleChannel.channel.track,
'missing',
exampleChannel.channel.architecture,
)
})
.to.throw(NotFound)
.with.property('prettyMessage', 'risk not found')
})
})

View File

@@ -0,0 +1,45 @@
import { isSemver } from '../test-validators.js'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('Snapcraft Version for redis')
.get('/redis/latest/stable.json')
.expectBadge({
label: 'snapcraft',
message: isSemver,
})
t.create('Snapcraft Version for redis (query param arch=arm64)')
.get('/redis/latest/stable.json?arch=arm64')
.expectBadge({
label: 'snapcraft',
message: isSemver,
})
t.create('Snapcraft Version for redis (invalid package)')
.get('/this_package_doesnt_exist/fake/fake.json')
.expectBadge({
label: 'snapcraft',
message: 'package not found',
})
t.create('Snapcraft Version for redis (invalid track)')
.get('/redis/notfound/stable.json')
.expectBadge({
label: 'snapcraft',
message: 'track not found',
})
t.create('Snapcraft Version for redis (invalid risk)')
.get('/redis/latest/notfound.json')
.expectBadge({
label: 'snapcraft',
message: 'risk not found',
})
t.create('Snapcraft Version for redis (invalid arch)')
.get('/redis/latest/stable.json?arch=fake')
.expectBadge({
label: 'snapcraft',
message: 'arch not found',
})