diff --git a/services/copr/copr.service.js b/services/copr/copr.service.js new file mode 100644 index 0000000000000..12416b5eaa1cb --- /dev/null +++ b/services/copr/copr.service.js @@ -0,0 +1,131 @@ +import Joi from 'joi' +import { renderBuildStatusBadge } from '../build-status.js' +import { BaseJsonService, NotFound, pathParams, queryParam } from '../index.js' + +const stateStatusMap = { + succeeded: 'passing', + failed: 'failing', + canceled: 'cancelled', + skipped: 'skipped', + importing: 'building', + pending: 'building', + starting: 'building', + running: 'building', + waiting: 'building', + forked: 'passing', +} + +const schema = Joi.object({ + builds: Joi.object({ + latest: Joi.object({ + state: Joi.string().required(), + }).required(), + }).required(), +}).required() + +const queryParamSchema = Joi.object({ + server: Joi.string() + .uri({ scheme: ['http', 'https'] }) + .optional(), +}).required() + +const description = ` +[COPR](https://copr.fedorainfracloud.org/) is a build system for creating RPM packages. + +For group projects, prefix the owner with \`@\` (for example \`@copr\`). +` + +export default class Copr extends BaseJsonService { + static category = 'build' + + static route = { + base: 'copr/build', + pattern: ':owner/:project/:package', + queryParamSchema, + } + + static openApi = { + '/copr/build/{owner}/{project}/{package}': { + get: { + summary: 'Copr Build', + description, + parameters: [ + ...pathParams( + { + name: 'owner', + example: 'msuchy', + description: + 'Project owner. For group projects, prefix with `@`.', + }, + { + name: 'project', + example: 'nanoblogger', + }, + { + name: 'package', + example: 'nanoblogger', + }, + ), + queryParam({ + name: 'server', + example: 'https://copr.fedorainfracloud.org', + description: + 'Copr server URL. Defaults to `https://copr.fedorainfracloud.org`.', + }), + ], + }, + }, + } + + static defaultBadgeData = { label: 'build' } + + static render({ status }) { + return renderBuildStatusBadge({ status }) + } + + packageUrl({ server, owner, project, package: packageName }) { + const trimmedServer = server.replace(/\/$/, '') + return `${trimmedServer}/api_3/package` + } + + transform({ builds }) { + const { state } = builds.latest + const status = stateStatusMap[state] + if (!status) { + throw new NotFound({ prettyMessage: 'unknown build state' }) + } + return { status } + } + + async fetch({ server, owner, project, package: packageName }) { + return this._requestJson({ + schema, + url: this.packageUrl({ server, owner, project, package: packageName }), + options: { + searchParams: { + ownername: owner, + projectname: project, + packagename: packageName, + with_latest_build: 'True', + }, + }, + httpErrors: { + 404: 'project or package not found', + }, + }) + } + + async handle( + { owner, project, package: packageName }, + { server = 'https://copr.fedorainfracloud.org' }, + ) { + const json = await this.fetch({ + server, + owner, + project, + package: packageName, + }) + const { status } = this.transform(json) + return this.constructor.render({ status }) + } +} diff --git a/services/copr/copr.service.spec.js b/services/copr/copr.service.spec.js new file mode 100644 index 0000000000000..9fb263b84782c --- /dev/null +++ b/services/copr/copr.service.spec.js @@ -0,0 +1,53 @@ +import { test, given } from 'sazerac' +import { renderBuildStatusBadge } from '../build-status.js' +import Copr from './copr.service.js' + +describe('Copr service', function () { + test(Copr.prototype.packageUrl, () => { + given({ + server: 'https://copr.fedorainfracloud.org', + owner: 'msuchy', + project: 'nanoblogger', + package: 'nanoblogger', + }).expect('https://copr.fedorainfracloud.org/api_3/package') + given({ + server: 'https://copr.fedorainfracloud.org/', + owner: '@copr', + project: 'copr', + package: 'copr-backend', + }).expect('https://copr.fedorainfracloud.org/api_3/package') + }) + + test(Copr.prototype.transform, () => { + given({ builds: { latest: { state: 'succeeded' } } }).expect({ + status: 'passing', + }) + given({ builds: { latest: { state: 'failed' } } }).expect({ + status: 'failing', + }) + given({ builds: { latest: { state: 'running' } } }).expect({ + status: 'building', + }) + given({ builds: { latest: { state: 'canceled' } } }).expect({ + status: 'cancelled', + }) + given({ builds: { latest: { state: 'skipped' } } }).expect({ + status: 'skipped', + }) + given({ builds: { latest: { state: 'unknown-state' } } }).expectError( + 'Not Found: unknown build state', + ) + }) + + test(Copr.render, () => { + given({ status: 'passing' }).expect( + renderBuildStatusBadge({ status: 'passing' }), + ) + given({ status: 'failing' }).expect( + renderBuildStatusBadge({ status: 'failing' }), + ) + given({ status: 'building' }).expect( + renderBuildStatusBadge({ status: 'building' }), + ) + }) +}) diff --git a/services/copr/copr.tester.js b/services/copr/copr.tester.js new file mode 100644 index 0000000000000..0545c1d644c41 --- /dev/null +++ b/services/copr/copr.tester.js @@ -0,0 +1,42 @@ +import { isBuildStatus } from '../build-status.js' +import { createServiceTester } from '../tester.js' +export const t = await createServiceTester() + +t.create('build (valid user project)') + .get('/msuchy/nanoblogger/nanoblogger.json') + .expectBadge({ + label: 'build', + message: isBuildStatus, + }) + +t.create('build (valid group project)') + .get('/%40copr/copr/copr-backend.json') + .expectBadge({ + label: 'build', + message: isBuildStatus, + }) + +t.create('build (valid, mocked)') + .get('/owner/project/package.json') + .intercept(nock => + nock('https://copr.fedorainfracloud.org') + .get('/api_3/package') + .query({ + ownername: 'owner', + projectname: 'project', + packagename: 'package', + with_latest_build: 'True', + }) + .reply(200, { + builds: { + latest: { + state: 'succeeded', + }, + }, + }), + ) + .expectBadge({ label: 'build', message: 'passing' }) + +t.create('build (not found)') + .get('/not-a-user/not-a-project/not-a-package.json') + .expectBadge({ label: 'build', message: 'project or package not found' })