Use url pattern for [nexus] + minor cleanup (#3326)

* Use url `pattern` for [nexus]

* chore: minor nexus example tweaks

* chore: fix prettier issue

* refactor(Nexus): minor refactor to fix tests and examples

* chore: increase timeout on nexus service tests

* chore: prettified for prettier

* tests(Nexus): increase timeout for recurringly slow test

* chore: update nexus schema with docs
This commit is contained in:
Paul Melnikow
2019-04-20 16:23:42 -05:00
committed by Caleb Cartwright
parent 049057c230
commit a291ba73a3
3 changed files with 126 additions and 40 deletions

View File

@@ -16,7 +16,12 @@ const searchApiSchema = Joi.object({
Joi.object({
latestRelease: optionalDottedVersionNClausesWithOptionalSuffix,
latestSnapshot: optionalDottedVersionNClausesWithOptionalSuffix,
version: optionalDottedVersionNClausesWithOptionalSuffix,
// `version` will almost always follow the same pattern as optionalDottedVersionNClausesWithOptionalSuffix.
// However, there are a couple exceptions where `version` may be a simple string (like `android-SNAPSHOT`)
// This schema is relaxed accordingly since for snapshot/release badges the schema has to validate
// the entire history of each published version for the artifact.
// Example artifact that includes such a historical version: https://oss.sonatype.org/service/local/lucene/search?g=com.google.guava&a=guava
version: Joi.string(),
})
)
.required(),
@@ -46,24 +51,23 @@ module.exports = class Nexus extends BaseJsonService {
base: 'nexus',
// API pattern:
// /nexus/(r|s|<repo-name>)/(http|https)/<nexus.host>[:port][/<entry-path>]/<group>/<artifact>[:k1=v1[:k2=v2[...]]]
format:
'(r|s|[^/]+)/(https?)/((?:[^/]+)(?:/[^/]+)?)/([^/]+)/([^/:]+)(:.+)?',
capture: ['repo', 'scheme', 'host', 'groupId', 'artifactId', 'queryOpt'],
pattern:
':repo(r|s|[^/]+)/:scheme(http|https)/:hostAndPath+/:groupId/:artifactId([^/:]+):queryOpt(:.+)?',
}
}
static get defaultBadgeData() {
return { color: 'blue', label: 'nexus' }
return { label: 'nexus' }
}
static get examples() {
return [
{
title: 'Sonatype Nexus (Releases)',
pattern: 'r/:scheme/:host/:groupId/:artifactId',
pattern: 'r/:scheme(http|https)/:hostAndPath/:groupId/:artifactId',
namedParams: {
scheme: 'https',
host: 'oss.sonatype.org',
hostAndPath: 'oss.sonatype.org',
groupId: 'com.google.guava',
artifactId: 'guava',
},
@@ -73,10 +77,10 @@ module.exports = class Nexus extends BaseJsonService {
},
{
title: 'Sonatype Nexus (Snapshots)',
pattern: 's/:scheme/:host/:groupId/:artifactId',
pattern: 's/:scheme(http|https)/:hostAndPath/:groupId/:artifactId',
namedParams: {
scheme: 'https',
host: 'oss.sonatype.org',
hostAndPath: 'oss.sonatype.org',
groupId: 'com.google.guava',
artifactId: 'guava',
},
@@ -86,11 +90,11 @@ module.exports = class Nexus extends BaseJsonService {
},
{
title: 'Sonatype Nexus (Repository)',
pattern: ':repo/:scheme/:host/:groupId/:artifactId',
pattern: ':repo/:scheme(http|https)/:hostAndPath/:groupId/:artifactId',
namedParams: {
repo: 'developer',
scheme: 'https',
host: 'repository.jboss.org/nexus',
hostAndPath: 'repository.jboss.org/nexus',
groupId: 'ai.h2o',
artifactId: 'h2o-automl',
},
@@ -100,11 +104,12 @@ module.exports = class Nexus extends BaseJsonService {
},
{
title: 'Sonatype Nexus (Query Options)',
pattern: ':repo/:scheme/:host/:groupId/:artifactId/:queryOpt',
pattern:
':repo/:scheme(http|https)/:hostAndPath/:groupId/:artifactId/:queryOpt',
namedParams: {
repo: 'fs-public-snapshots',
scheme: 'https',
host: 'repository.jboss.org/nexus',
hostAndPath: 'repository.jboss.org/nexus',
groupId: 'com.progress.fuse',
artifactId: 'fusehq',
queryOpt: ':c=agent-apple-osx:p=tar.gz',
@@ -114,10 +119,10 @@ module.exports = class Nexus extends BaseJsonService {
}),
documentation: `
<p>
Note that you can use query options with any Nexus badge type (Releases, Snapshots, or Repository)
Note that you can use query options with any Nexus badge type (Releases, Snapshots, or Repository).
</p>
<p>
Query options should be provided as key=value pairs separated by a semicolon
Query options should be provided as key=value pairs separated by a colon.
</p>
`,
},
@@ -125,8 +130,15 @@ module.exports = class Nexus extends BaseJsonService {
}
transform({ repo, json }) {
if (json.data.length === 0) {
throw new NotFound({ prettyMessage: 'artifact or version not found' })
}
if (repo === 'r') {
return { version: json.data[0].latestRelease }
const version = json.data[0].latestRelease
if (!version) {
throw new InvalidResponse({ prettyMessage: 'invalid artifact version' })
}
return { version }
} else if (repo === 's') {
// only want to match 1.2.3-SNAPSHOT style versions, which may not always be in
// 'latestSnapshot' so check 'version' as well before continuing to next entry
@@ -140,32 +152,31 @@ module.exports = class Nexus extends BaseJsonService {
}
throw new InvalidResponse({ prettyMessage: 'no snapshot versions found' })
} else {
return { version: json.data.baseVersion || json.data.version }
const version = json.data.baseVersion || json.data.version
if (!version) {
throw new InvalidResponse({ prettyMessage: 'invalid artifact version' })
}
return { version }
}
}
async handle({ repo, scheme, host, groupId, artifactId, queryOpt }) {
async handle({ repo, scheme, hostAndPath, groupId, artifactId, queryOpt }) {
const { json } = await this.fetch({
repo,
scheme,
host,
hostAndPath,
groupId,
artifactId,
queryOpt,
})
if (json.data.length === 0) {
throw new NotFound({ prettyMessage: 'artifact or version not found' })
}
const { version } = this.transform({ repo, json })
if (!version) {
throw new InvalidResponse({ prettyMessage: 'invalid artifact version' })
}
return this.constructor.render({ version })
}
addQueryParamsToQueryString({ qs, queryOpt }) {
// Users specify query options with 'key=value' pairs, using a
// semicolon delimiter between pairs ([:k1=v1[:k2=v2[...]]]).
// colon delimiter between pairs ([:k1=v1[:k2=v2[...]]]).
// queryOpt will be a string containing those key/value pairs,
// For example: :c=agent-apple-osx:p=tar.gz
const keyValuePairs = queryOpt.split(':')
@@ -177,13 +188,13 @@ module.exports = class Nexus extends BaseJsonService {
})
}
async fetch({ repo, scheme, host, groupId, artifactId, queryOpt }) {
async fetch({ repo, scheme, hostAndPath, groupId, artifactId, queryOpt }) {
const qs = {
g: groupId,
a: artifactId,
}
let schema
let url = `${scheme}://${host}/`
let url = `${scheme}://${hostAndPath}/`
// API pattern:
// for /nexus/[rs]/... pattern, use the search api of the nexus server, and
// for /nexus/<repo-name>/... pattern, use the resolve api of the nexus server.

View File

@@ -0,0 +1,71 @@
'use strict'
const { expect } = require('chai')
const { InvalidResponse, NotFound } = require('..')
const Nexus = require('./nexus.service')
describe('Nexus', function() {
context('transform()', function() {
it('throws NotFound error when no versions exist', function() {
try {
Nexus.prototype.transform({ json: { data: [] } })
expect.fail('Expected to throw')
} catch (e) {
expect(e).to.be.an.instanceof(NotFound)
expect(e.prettyMessage).to.equal('artifact or version not found')
}
})
it('throws InvalidResponse error when no there is no latestRelease version', function() {
try {
Nexus.prototype.transform({ repo: 'r', json: { data: [{}] } })
expect.fail('Expected to throw')
} catch (e) {
expect(e).to.be.an.instanceof(InvalidResponse)
expect(e.prettyMessage).to.equal('invalid artifact version')
}
})
it('returns latestSnapshot value', function() {
const latestSnapshot = '7.0.1-SNAPSHOT'
const { version } = Nexus.prototype.transform({
repo: 's',
json: {
data: [{ latestSnapshot }, { version: '1.2.3' }],
},
})
expect(version).to.equal(latestSnapshot)
})
it('returns version value when it is a snapshot', function() {
const latestSnapshot = '1.2.7-SNAPSHOT'
const { version } = Nexus.prototype.transform({
repo: 's',
json: {
data: [{ latestSnapshot: '1.2.3' }, { version: latestSnapshot }],
},
})
expect(version).to.equal(latestSnapshot)
})
it('throws InvalidResponse error when no snapshot versions exist', function() {
try {
Nexus.prototype.transform({ repo: 's', json: { data: [{}] } })
expect.fail('Expected to throw')
} catch (e) {
expect(e).to.be.an.instanceof(InvalidResponse)
expect(e.prettyMessage).to.equal('no snapshot versions found')
}
})
it('throws InvalidResponse error when repository has no version data', function() {
try {
Nexus.prototype.transform({ repo: 'developer', json: { data: {} } })
expect.fail('Expected to throw')
} catch (e) {
expect(e).to.be.an.instanceof(InvalidResponse)
expect(e.prettyMessage).to.equal('invalid artifact version')
}
})
})
})

View File

@@ -18,32 +18,36 @@ function mockNexusCreds() {
}
t.create('live: search release version valid artifact')
.get('/r/https/repository.jboss.org/nexus/jboss/jboss-client.json')
.timeout(15000)
.get('/r/https/oss.sonatype.org/com.google.guava/guava.json')
.expectBadge({
label: 'nexus',
message: isVersion,
})
t.create('live: search release version of an inexistent artifact')
.get('/r/https/repository.jboss.org/nexus/jboss/inexistent-artifact-id.json')
t.create('live: search release version of an nonexistent artifact')
.timeout(10000)
.get(
'/r/https/oss.sonatype.org/com.google.guava/nonexistent-artifact-id.json'
)
.expectBadge({
label: 'nexus',
message: 'artifact or version not found',
})
t.create('live: search snapshot version valid snapshot artifact')
.get('/s/https/repository.jboss.org/nexus/com.progress.fuse/fusehq.json')
.timeout(10000)
.get('/s/https/oss.sonatype.org/com.google.guava/guava.json')
.expectBadge({
label: 'nexus',
message: isVersion,
})
t.create('live: search snapshot version of a release artifact')
.get('/s/https/repository.jboss.org/nexus/jboss/jboss-client.json')
.expectBadge({ label: 'nexus', message: 'no snapshot versions found' })
t.create('live: search snapshot version of an inexistent artifact')
.get('/s/https/repository.jboss.org/nexus/jboss/inexistent-artifact-id.json')
t.create('live: search snapshot version of an nonexistent artifact')
.timeout(10000)
.get(
'/s/https/oss.sonatype.org/com.google.guava/nonexistent-artifact-id.json'
)
.expectBadge({
label: 'nexus',
message: 'artifact or version not found',
@@ -66,9 +70,9 @@ t.create('live: repository version with query')
message: isVersion,
})
t.create('live: repository version of an inexistent artifact')
t.create('live: repository version of an nonexistent artifact')
.get(
'/developer/https/repository.jboss.org/nexus/jboss/inexistent-artifact-id.json'
'/developer/https/repository.jboss.org/nexus/jboss/nonexistent-artifact-id.json'
)
.expectBadge({
label: 'nexus',