Adopt Prettier (but keep Standard) via ESLint (#48)

This commit is contained in:
Brian Beck 2017-11-06 21:54:56 -08:00 committed by GitHub
parent 50888c9fb9
commit 8c0a9f44ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
61 changed files with 1830 additions and 1487 deletions

2
.eslintignore Normal file
View file

@ -0,0 +1,2 @@
/coverage
/lib

18
.eslintrc.js Normal file
View file

@ -0,0 +1,18 @@
module.exports = {
extends: [
'standard',
'prettier',
'prettier/standard'
],
env: {
es6: true,
node: true
},
plugins: ['prettier'],
rules: {
'prettier/prettier': ['error', {
singleQuote: true,
semi: false
}]
}
}

View file

@ -30,8 +30,9 @@
"clean": "npm run clean:lib",
"clean:lib": "rm -rf lib",
"deploy": "./scripts/deploy.sh",
"lint": "standard --verbose | snazzy",
"lint:fix": "standard --verbose --fix",
"format": "npm run lint:fix",
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"postinstall": "postinstall-build lib --script build:lib",
"prepublish": "npm run clean:lib && npm run build:lib",
"preversion": "npm run update-schema && npm run build:docs && git add schema.json docs",
@ -100,15 +101,22 @@
"coveralls": "^3.0.0",
"cross-env": "^5.1.1",
"doctoc": "^1.3.0",
"eslint": "^4.10.0",
"eslint-config-prettier": "^2.7.0",
"eslint-config-standard": "^10.2.1",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-markdown": "^1.0.0-beta.6",
"eslint-plugin-node": "^5.2.1",
"eslint-plugin-prettier": "^2.3.1",
"eslint-plugin-promise": "^3.6.0",
"eslint-plugin-standard": "^3.0.1",
"graphql-markdown": "^3.2.0",
"nodemon": "^1.11.0",
"nyc": "^11.1.0",
"prettier": "^1.8.0",
"rimraf": "^2.6.1",
"sepia": "^2.0.2",
"sinon": "^4.0.2",
"snazzy": "^7.0.0",
"standard": "^10.0.3"
"sinon": "^4.0.2"
},
"standard": {
"parser": "babel-eslint"

View file

@ -14,14 +14,13 @@ function getSchemaJSON (schema) {
return graphql(schema, introspectionQuery).then(result => result.data)
}
Promise.all(extensionModules.map(extensionModule => {
Promise.all(
extensionModules.map(extensionModule => {
const extension = require(`../src/extensions/${extensionModule}`).default
console.log(`Generating docs for “${extension.name}” extension...`)
const schema = createSchema(baseSchema, { extensions: [extension] })
return Promise.all([
getSchemaJSON(baseSchema),
getSchemaJSON(schema)
]).then(([baseSchemaJSON, schemaJSON]) => {
return Promise.all([getSchemaJSON(baseSchema), getSchemaJSON(schema)]).then(
([baseSchemaJSON, schemaJSON]) => {
const outputSchema = diffSchema(baseSchemaJSON, schemaJSON, {
processTypeDiff(type) {
if (type.description === undefined) {
@ -41,9 +40,13 @@ Promise.all(extensionModules.map(extensionModule => {
unknownTypeURL: '../types.md',
headingLevel: 2
})
}
)
})
})).then((extensions) => {
)
.then(extensions => {
console.log(`Built docs for ${extensions.length} extension(s).`)
}).catch(err => {
})
.catch(err => {
console.log('Error:', err)
})

View file

@ -2,9 +2,11 @@ import { graphql, introspectionQuery, printSchema } from 'graphql'
import schema from '../src/schema'
if (process.argv[2] === '--json') {
graphql(schema, introspectionQuery).then(result => {
graphql(schema, introspectionQuery)
.then(result => {
console.log(JSON.stringify(result, null, 2))
}).catch(err => {
})
.catch(err => {
console.error(err)
})
} else {

View file

@ -28,7 +28,8 @@ export class ClientError extends ExtendableError {
}
export default class Client {
constructor ({
constructor(
{
baseURL,
userAgent = `${pkg.name}/${pkg.version} ` +
`( ${pkg.homepage || pkg.author.url || pkg.author.email} )`,
@ -47,7 +48,8 @@ export default class Client {
retryDelayMin = 100,
retryDelayMax = 60000,
randomizeRetry = true
} = {}) {
} = {}
) {
this.baseURL = baseURL
this.userAgent = userAgent
this.extraHeaders = extraHeaders
@ -130,7 +132,8 @@ export default class Client {
// This will increase the priority in our `RateLimit` queue for each
// retry, so that newer requests don't delay this one further.
const priority = currentAttempt
this.limiter.enqueue(fn, [path, options, { currentAttempt }], priority)
this.limiter
.enqueue(fn, [path, options, { currentAttempt }], priority)
.then(resolve)
.catch(err => {
if (!this.shouldRetry(err) || !operation.retry(err)) {

View file

@ -1,7 +1,3 @@
import MusicBrainz, { MusicBrainzError } from './musicbrainz'
export {
MusicBrainz as default,
MusicBrainz,
MusicBrainzError
}
export { MusicBrainz as default, MusicBrainz, MusicBrainzError }

View file

@ -4,8 +4,10 @@ import Client, { ClientError } from './client'
export class MusicBrainzError extends ClientError {}
export default class MusicBrainz extends Client {
constructor ({
baseURL = process.env.MUSICBRAINZ_BASE_URL || 'http://musicbrainz.org/ws/2/',
constructor(
{
baseURL = process.env.MUSICBRAINZ_BASE_URL ||
'http://musicbrainz.org/ws/2/',
errorClass = MusicBrainzError,
// MusicBrainz API requests are limited to an *average* of 1 req/sec.
// That means if, for example, we only need to make a few API requests to
@ -16,7 +18,8 @@ export default class MusicBrainz extends Client {
limit = 5,
period = 5500,
...options
} = {}) {
} = {}
) {
super({ baseURL, errorClass, limit, period, ...options })
}
@ -48,7 +51,7 @@ export default class MusicBrainz extends Client {
}
return qs.stringify(params, {
skipNulls: true,
filter: (key, value) => value === '' ? undefined : value
filter: (key, value) => (value === '' ? undefined : value)
})
}

View file

@ -5,7 +5,11 @@ const debug = require('debug')('graphbrainz:context')
export function extendContext(extension, context, options) {
if (extension.extendContext) {
if (typeof extension.extendContext === 'function') {
debug(`Extending context via a function from the “${extension.name}” extension.`)
debug(
`Extending context via a function from the “${
extension.name
} extension.`
)
context = extension.extendContext(context, options)
} else {
throw new Error(

View file

@ -1,12 +1,15 @@
import Client from '../../api/client'
export default class CoverArtArchiveClient extends Client {
constructor ({
baseURL = process.env.COVER_ART_ARCHIVE_BASE_URL || 'http://coverartarchive.org/',
constructor(
{
baseURL = process.env.COVER_ART_ARCHIVE_BASE_URL ||
'http://coverartarchive.org/',
limit = 10,
period = 1000,
...options
} = {}) {
} = {}
) {
super({ baseURL, limit, period, ...options })
}
@ -31,7 +34,8 @@ export default class CoverArtArchiveClient extends Client {
if (size != null) {
url += `-${size}`
}
return this.get(url, { method: 'HEAD', followRedirect: false })
.then(headers => headers.location)
return this.get(url, { method: 'HEAD', followRedirect: false }).then(
headers => headers.location
)
}
}

View file

@ -11,11 +11,15 @@ Archive](https://coverartarchive.org/).`,
extendContext(context, { coverArtClient, coverArtArchive = {} } = {}) {
const client = coverArtClient || new CoverArtArchiveClient(coverArtArchive)
const cacheSize = parseInt(
process.env.COVER_ART_ARCHIVE_CACHE_SIZE || process.env.GRAPHBRAINZ_CACHE_SIZE || 8192,
process.env.COVER_ART_ARCHIVE_CACHE_SIZE ||
process.env.GRAPHBRAINZ_CACHE_SIZE ||
8192,
10
)
const cacheTTL = parseInt(
process.env.COVER_ART_ARCHIVE_CACHE_TTL || process.env.GRAPHBRAINZ_CACHE_TTL || ONE_DAY,
process.env.COVER_ART_ARCHIVE_CACHE_TTL ||
process.env.GRAPHBRAINZ_CACHE_TTL ||
ONE_DAY,
10
)
return {

View file

@ -17,37 +17,50 @@ export default function createLoaders (options) {
cache.clear = cache.reset
return {
coverArtArchive: new DataLoader(keys => {
return Promise.all(keys.map(key => {
coverArtArchive: new DataLoader(
keys => {
return Promise.all(
keys.map(key => {
const [entityType, id] = key
return client.images(entityType, id)
return client
.images(entityType, id)
.catch(err => {
if (err.statusCode === 404) {
return { images: [] }
}
throw err
}).then(coverArt => ({
})
.then(coverArt => ({
...coverArt,
_entityType: entityType,
_id: id,
_releaseID: coverArt.release && coverArt.release.split('/').pop()
_releaseID:
coverArt.release && coverArt.release.split('/').pop()
}))
}))
}, {
})
)
},
{
cacheKeyFn: ([entityType, id]) => `${entityType}/${id}`,
cacheMap: cache
}),
coverArtArchiveURL: new DataLoader(keys => {
return Promise.all(keys.map(key => {
}
),
coverArtArchiveURL: new DataLoader(
keys => {
return Promise.all(
keys.map(key => {
const [entityType, id, type, size] = key
return client.imageURL(entityType, id, type, size)
}))
}, {
})
)
},
{
cacheKeyFn: ([entityType, id, type, size]) => {
const key = `${entityType}/${id}/${type}`
return size ? `${key}-${size}` : key
},
cacheMap: cache
})
}
)
}
}

View file

@ -1,13 +1,16 @@
import Client from '../../api/client'
export default class FanArtClient extends Client {
constructor ({
constructor(
{
apiKey = process.env.FANART_API_KEY,
baseURL = process.env.FANART_BASE_URL || 'http://webservice.fanart.tv/v3/',
baseURL = process.env.FANART_BASE_URL ||
'http://webservice.fanart.tv/v3/',
limit = 10,
period = 1000,
...options
} = {}) {
} = {}
) {
super({ baseURL, limit, period, ...options })
this.apiKey = apiKey
}
@ -15,9 +18,9 @@ export default class FanArtClient extends Client {
get(path, options = {}) {
const ClientError = this.errorClass
if (!this.apiKey) {
return Promise.reject(new ClientError(
'No API key was configured for the fanart.tv client.'
))
return Promise.reject(
new ClientError('No API key was configured for the fanart.tv client.')
)
}
options = {
json: true,
@ -40,9 +43,9 @@ export default class FanArtClient extends Client {
case 'release-group':
return this.musicAlbum(mbid)
default:
return Promise.reject(new ClientError(
`Entity type unsupported: ${entityType}`
))
return Promise.reject(
new ClientError(`Entity type unsupported: ${entityType}`)
)
}
}

View file

@ -11,11 +11,15 @@ from [fanart.tv](https://fanart.tv/).`,
extendContext(context, { fanArt = {} } = {}) {
const client = new FanArtClient(fanArt)
const cacheSize = parseInt(
process.env.FANART_CACHE_SIZE || process.env.GRAPHBRAINZ_CACHE_SIZE || 8192,
process.env.FANART_CACHE_SIZE ||
process.env.GRAPHBRAINZ_CACHE_SIZE ||
8192,
10
)
const cacheTTL = parseInt(
process.env.FANART_CACHE_TTL || process.env.GRAPHBRAINZ_CACHE_TTL || ONE_DAY,
process.env.FANART_CACHE_TTL ||
process.env.GRAPHBRAINZ_CACHE_TTL ||
ONE_DAY,
10
)
return {

View file

@ -16,10 +16,13 @@ export default function createLoader (options) {
cache.delete = cache.del
cache.clear = cache.reset
const loader = new DataLoader(keys => {
return Promise.all(keys.map(key => {
const loader = new DataLoader(
keys => {
return Promise.all(
keys.map(key => {
const [entityType, id] = key
return client.musicEntity(entityType, id)
return client
.musicEntity(entityType, id)
.catch(err => {
if (err.statusCode === 404) {
// 404s are OK, just return empty data.
@ -34,19 +37,27 @@ export default function createLoader (options) {
}
}
throw err
}).then(body => {
})
.then(body => {
if (entityType === 'artist') {
const releaseGroupIDs = Object.keys(body.albums)
debug(`Priming album cache with ${releaseGroupIDs.length} album(s).`)
releaseGroupIDs.forEach(key => loader.prime(['release-group', key], body))
debug(
`Priming album cache with ${releaseGroupIDs.length} album(s).`
)
releaseGroupIDs.forEach(key =>
loader.prime(['release-group', key], body)
)
}
return body
})
}))
}, {
})
)
},
{
cacheKeyFn: ([entityType, id]) => `${entityType}/${id}`,
cacheMap: cache
})
}
)
return loader
}

View file

@ -56,7 +56,8 @@ export default {
},
ReleaseGroup: {
fanArt: (releaseGroup, args, context) => {
return context.loaders.fanArt.load(['release-group', releaseGroup.id])
return context.loaders.fanArt
.load(['release-group', releaseGroup.id])
.then(artist => artist.albums[releaseGroup.id])
}
}

View file

@ -2,11 +2,7 @@ import URL from 'url'
import Client from '../../api/client'
export default class MediaWikiClient extends Client {
constructor ({
limit = 10,
period = 1000,
...options
} = {}) {
constructor({ limit = 10, period = 1000, ...options } = {}) {
super({ limit, period, ...options })
}
@ -15,9 +11,11 @@ export default class MediaWikiClient extends Client {
const ClientError = this.errorClass
if (!pageURL.pathname.startsWith('/wiki/')) {
return Promise.reject(new ClientError(
return Promise.reject(
new ClientError(
`MediaWiki page URL does not have the expected /wiki/ prefix: ${page}`
))
)
)
}
const apiURL = URL.format({
@ -34,8 +32,7 @@ export default class MediaWikiClient extends Client {
}
})
return this.get(apiURL, { json: true })
.then(body => {
return this.get(apiURL, { json: true }).then(body => {
const pageIDs = Object.keys(body.query.pages)
if (pageIDs.length !== 1) {
throw new ClientError(

View file

@ -11,11 +11,15 @@ image file URL and EXIF metadata.`,
extendContext(context, { mediaWiki = {} } = {}) {
const client = new MediaWikiClient(mediaWiki)
const cacheSize = parseInt(
process.env.MEDIAWIKI_CACHE_SIZE || process.env.GRAPHBRAINZ_CACHE_SIZE || 8192,
process.env.MEDIAWIKI_CACHE_SIZE ||
process.env.GRAPHBRAINZ_CACHE_SIZE ||
8192,
10
)
const cacheTTL = parseInt(
process.env.MEDIAWIKI_CACHE_TTL || process.env.GRAPHBRAINZ_CACHE_TTL || ONE_DAY,
process.env.MEDIAWIKI_CACHE_TTL ||
process.env.GRAPHBRAINZ_CACHE_TTL ||
ONE_DAY,
10
)
return {

View file

@ -16,7 +16,10 @@ export default function createLoader (options) {
cache.delete = cache.del
cache.clear = cache.reset
return new DataLoader(keys => {
return new DataLoader(
keys => {
return Promise.all(keys.map(key => client.imageInfo(key)))
}, { cacheMap: cache })
},
{ cacheMap: cache }
)
}

View file

@ -1,14 +1,16 @@
import URL from 'url'
function resolveMediaWikiImages(source, args, { loaders }) {
const isURL = (relation) => relation['target-type'] === 'url'
const isURL = relation => relation['target-type'] === 'url'
let rels = source.relations ? source.relations.filter(isURL) : []
if (!rels.length) {
rels = loaders.lookup.load([source._type, source.id, { inc: 'url-rels' }])
rels = loaders.lookup
.load([source._type, source.id, { inc: 'url-rels' }])
.then(source => source.relations.filter(isURL))
}
return Promise.resolve(rels).then(rels => {
const pages = rels.filter(rel => {
const pages = rels
.filter(rel => {
if (rel.type === args.type) {
const url = URL.parse(rel.url.resource)
if (url.pathname.match(/^\/wiki\/(File|Image):/)) {
@ -16,7 +18,8 @@ function resolveMediaWikiImages (source, args, { loaders }) {
}
}
return false
}).map(rel => rel.url.resource)
})
.map(rel => rel.url.resource)
return loaders.mediaWiki.loadMany(pages)
})
}
@ -57,13 +60,14 @@ export default {
const data = imageInfo.extmetadata.LicenseUrl
return data ? data.value : null
},
metadata: imageInfo => Object.keys(imageInfo.extmetadata).map(key => {
metadata: imageInfo =>
Object.keys(imageInfo.extmetadata).map(key => {
const data = imageInfo.extmetadata[key]
return { ...data, name: key }
})
},
MediaWikiImageMetadata: {
value: obj => obj.value == null ? obj.value : `${obj.value}`
value: obj => (obj.value == null ? obj.value : `${obj.value}`)
},
Artist: {
mediaWikiImages: resolveMediaWikiImages

View file

@ -1,13 +1,16 @@
import Client from '../../api/client'
export default class TheAudioDBClient extends Client {
constructor ({
constructor(
{
apiKey = process.env.THEAUDIODB_API_KEY,
baseURL = process.env.THEAUDIODB_BASE_URL || 'http://www.theaudiodb.com/api/v1/json/',
baseURL = process.env.THEAUDIODB_BASE_URL ||
'http://www.theaudiodb.com/api/v1/json/',
limit = 10,
period = 1000,
...options
} = {}) {
} = {}
) {
super({ baseURL, limit, period, ...options })
this.apiKey = apiKey
}
@ -15,9 +18,9 @@ export default class TheAudioDBClient extends Client {
get(path, options = {}) {
const ClientError = this.errorClass
if (!this.apiKey) {
return Promise.reject(new ClientError(
'No API key was configured for TheAudioDB client.'
))
return Promise.reject(
new ClientError('No API key was configured for TheAudioDB client.')
)
}
return super.get(`${this.apiKey}/${path}`, { json: true, ...options })
}
@ -32,15 +35,14 @@ export default class TheAudioDBClient extends Client {
case 'recording':
return this.track(mbid)
default:
return Promise.reject(new ClientError(
`Entity type unsupported: ${entityType}`
))
return Promise.reject(
new ClientError(`Entity type unsupported: ${entityType}`)
)
}
}
artist(mbid) {
return this.get('artist-mb.php', { qs: { i: mbid } })
.then(body => {
return this.get('artist-mb.php', { qs: { i: mbid } }).then(body => {
if (body.artists && body.artists.length === 1) {
return body.artists[0]
}
@ -49,8 +51,7 @@ export default class TheAudioDBClient extends Client {
}
album(mbid) {
return this.get('album-mb.php', { qs: { i: mbid } })
.then(body => {
return this.get('album-mb.php', { qs: { i: mbid } }).then(body => {
if (body.album && body.album.length === 1) {
return body.album[0]
}
@ -59,8 +60,7 @@ export default class TheAudioDBClient extends Client {
}
track(mbid) {
return this.get('track-mb.php', { qs: { i: mbid } })
.then(body => {
return this.get('track-mb.php', { qs: { i: mbid } }).then(body => {
if (body.track && body.track.length === 1) {
return body.track[0]
}

View file

@ -11,11 +11,15 @@ recordings from [TheAudioDB.com](http://www.theaudiodb.com/).`,
extendContext(context, { theAudioDB = {} } = {}) {
const client = new TheAudioDBClient(theAudioDB)
const cacheSize = parseInt(
process.env.THEAUDIODB_CACHE_SIZE || process.env.GRAPHBRAINZ_CACHE_SIZE || 8192,
process.env.THEAUDIODB_CACHE_SIZE ||
process.env.GRAPHBRAINZ_CACHE_SIZE ||
8192,
10
)
const cacheTTL = parseInt(
process.env.THEAUDIODB_CACHE_TTL || process.env.GRAPHBRAINZ_CACHE_TTL || ONE_DAY,
process.env.THEAUDIODB_CACHE_TTL ||
process.env.GRAPHBRAINZ_CACHE_TTL ||
ONE_DAY,
10
)
return {

View file

@ -16,13 +16,18 @@ export default function createLoader (options) {
cache.delete = cache.del
cache.clear = cache.reset
return new DataLoader(keys => {
return Promise.all(keys.map(key => {
return new DataLoader(
keys => {
return Promise.all(
keys.map(key => {
const [entityType, id] = key
return client.entity(entityType, id)
}))
}, {
})
)
},
{
cacheKeyFn: ([entityType, id]) => `${entityType}/${id}`,
cacheMap: cache
})
}
)
}

View file

@ -1,6 +1,6 @@
function handleImageSize(resolver) {
return (source, args, context, info) => {
const getURL = (url) => args.size === 'PREVIEW' ? `${url}/preview` : url
const getURL = url => (args.size === 'PREVIEW' ? `${url}/preview` : url)
const url = resolver(source, args, context, info)
if (!url) {
return null

View file

@ -8,13 +8,14 @@ import { createContext } from './context'
const debug = require('debug')('graphbrainz')
const formatError = (err) => ({
const formatError = err => ({
message: err.message,
locations: err.locations,
stack: err.stack
})
const middleware = ({
const middleware = (
{
client = new MusicBrainz(),
extensions = process.env.GRAPHBRAINZ_EXTENSIONS
? JSON.parse(process.env.GRAPHBRAINZ_EXTENSIONS)
@ -25,7 +26,8 @@ const middleware = ({
'./extensions/the-audio-db'
],
...middlewareOptions
} = {}) => {
} = {}
) => {
debug(`Loading ${extensions.length} extension(s).`)
const options = {
client,

View file

@ -19,8 +19,10 @@ export default function createLoaders (client) {
cache.delete = cache.del
cache.clear = cache.reset
const lookup = new DataLoader(keys => {
return Promise.all(keys.map(key => {
const lookup = new DataLoader(
keys => {
return Promise.all(
keys.map(key => {
const [entityType, id, params = {}] = key
return client.lookup(entityType, id, params).then(entity => {
if (entity) {
@ -30,14 +32,19 @@ export default function createLoaders (client) {
}
return entity
})
}))
}, {
cacheKeyFn: (key) => client.getLookupURL(...key),
cacheMap: cache
})
)
},
{
cacheKeyFn: key => client.getLookupURL(...key),
cacheMap: cache
}
)
const browse = new DataLoader(keys => {
return Promise.all(keys.map(key => {
const browse = new DataLoader(
keys => {
return Promise.all(
keys.map(key => {
const [entityType, params = {}] = key
return client.browse(entityType, params).then(list => {
list[toPlural(entityType)].forEach(entity => {
@ -47,14 +54,19 @@ export default function createLoaders (client) {
})
return list
})
}))
}, {
cacheKeyFn: (key) => client.getBrowseURL(...key),
cacheMap: cache
})
)
},
{
cacheKeyFn: key => client.getBrowseURL(...key),
cacheMap: cache
}
)
const search = new DataLoader(keys => {
return Promise.all(keys.map(key => {
const search = new DataLoader(
keys => {
return Promise.all(
keys.map(key => {
const [entityType, query, params = {}] = key
return client.search(entityType, query, params).then(list => {
list[toPlural(entityType)].forEach(entity => {
@ -64,11 +76,14 @@ export default function createLoaders (client) {
})
return list
})
}))
}, {
})
)
},
{
cacheKeyFn: key => client.getSearchURL(...key),
cacheMap: cache
})
}
)
return { lookup, browse, search }
}

View file

@ -173,7 +173,8 @@ release, but is not included in the credits for the release itself.`
export const browse = {
type: BrowseQuery,
description: 'Browse all MusicBrainz entities directly linked to another entity.',
description:
'Browse all MusicBrainz entities directly linked to another entity.',
// We only have work to do once we know what entity types are being requested,
// so this can just resolve to an empty object.
resolve: () => ({})

View file

@ -1,12 +1,14 @@
const debug = require('debug')('graphbrainz:rate-limit')
export default class RateLimit {
constructor ({
constructor(
{
limit = 1,
period = 1000,
concurrency = limit || 1,
defaultPriority = 1
} = {}) {
} = {}
) {
this.limit = limit
this.period = period
this.defaultPriority = defaultPriority
@ -29,7 +31,7 @@ export default class RateLimit {
enqueue(fn, args, priority = this.defaultPriority) {
priority = Math.max(0, priority)
return new Promise((resolve, reject) => {
const queue = this.queues[priority] = this.queues[priority] || []
const queue = (this.queues[priority] = this.queues[priority] || [])
const id = this.nextTaskID()
debug(`Enqueuing task. id=${id} priority=${priority}`)
queue.push({ fn, args, resolve, reject, id })
@ -83,12 +85,12 @@ export default class RateLimit {
}
this.numPending += 1
this.periodCapacity -= 1
const onResolve = (value) => {
const onResolve = value => {
this.numPending -= 1
resolve(value)
this.flush()
}
const onReject = (err) => {
const onReject = err => {
this.numPending -= 1
reject(err)
this.flush()

View file

@ -77,16 +77,12 @@ export function resolveLookup (root, { mbid, ...params }, { loaders }, info) {
return loaders.lookup.load([entityType, mbid, params])
}
export function resolveBrowse (root, {
first,
after,
type = [],
status = [],
discID,
isrc,
iswc,
...args
}, { loaders }, info) {
export function resolveBrowse(
root,
{ first, after, type = [], status = [], discID, isrc, iswc, ...args },
{ loaders },
info
) {
const pluralName = toDashed(info.fieldName)
const singularName = toSingular(pluralName)
let params = {
@ -127,7 +123,11 @@ export function resolveBrowse (root, {
[`${singularName}-count`]: arrayLength = arraySlice.length
} = list
const meta = { sliceStart, arrayLength }
const connection = connectionFromArraySlice(arraySlice, { first, after }, meta)
const connection = connectionFromArraySlice(
arraySlice,
{ first, after },
meta
)
return {
nodes: connection.edges.map(edge => edge.node),
totalCount: arrayLength,
@ -136,12 +136,12 @@ export function resolveBrowse (root, {
})
}
export function resolveSearch (root, {
after,
first,
query,
...args
}, { loaders }, info) {
export function resolveSearch(
root,
{ after, first, query, ...args },
{ loaders },
info
) {
const pluralName = toDashed(info.fieldName)
const singularName = toSingular(pluralName)
let params = {
@ -157,10 +157,17 @@ export function resolveSearch (root, {
count: arrayLength
} = list
const meta = { sliceStart, arrayLength }
const connection = connectionFromArraySlice(arraySlice, { first, after }, meta)
const connection = connectionFromArraySlice(
arraySlice,
{ first, after },
meta
)
// Move the `score` field up to the edge object and make sure it's a
// number (MusicBrainz returns a string).
const edges = connection.edges.map(edge => ({ ...edge, score: +edge.node.score }))
const edges = connection.edges.map(edge => ({
...edge,
score: +edge.node.score
}))
const connectionWithExtras = {
nodes: edges.map(edge => edge.node),
totalCount: arrayLength,
@ -203,7 +210,10 @@ export function resolveLinked (entity, args, context, info) {
* for a particular field that's being requested, make another request to grab
* it (after making sure it isn't already available).
*/
export function createSubqueryResolver ({ inc, key } = {}, handler = value => value) {
export function createSubqueryResolver(
{ inc, key } = {},
handler = value => value
) {
return (entity, args, { loaders }, info) => {
key = key || toDashed(info.fieldName)
let promise

View file

@ -9,7 +9,9 @@ export function applyExtension (extension, schema, options = {}) {
let outputSchema = schema
if (extension.extendSchema) {
if (typeof extension.extendSchema === 'object') {
debug(`Extending schema via an object from the “${extension.name}” extension.`)
debug(
`Extending schema via an object from the “${extension.name}” extension.`
)
const { schemas = [], resolvers } = extension.extendSchema
outputSchema = schemas.reduce((updatedSchema, extensionSchema) => {
if (typeof extensionSchema === 'string') {
@ -21,7 +23,11 @@ export function applyExtension (extension, schema, options = {}) {
addResolveFunctionsToSchema(outputSchema, resolvers)
}
} else if (typeof extension.extendSchema === 'function') {
debug(`Extending schema via a function from the “${extension.name}” extension.`)
debug(
`Extending schema via a function from the “${
extension.name
} extension.`
)
outputSchema = extension.extendSchema(schema, options)
} else {
throw new Error(

View file

@ -1,7 +1,4 @@
import {
GraphQLObjectType,
GraphQLBoolean
} from 'graphql/type'
import { GraphQLObjectType, GraphQLBoolean } from 'graphql/type'
import { Locale } from './scalars'
import { name, sortName, fieldWithID } from './helpers'

View file

@ -13,7 +13,7 @@ track, etc., and join phrases between them.`,
type: Artist,
description: `The entity representing the artist referenced in the
credits.`,
resolve: (source) => {
resolve: source => {
const { artist } = source
if (artist) {
artist._type = 'artist'

View file

@ -1,8 +1,4 @@
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString
} from 'graphql/type'
import { GraphQLObjectType, GraphQLNonNull, GraphQLString } from 'graphql/type'
import Node from './node'
import Entity from './entity'
import {

View file

@ -18,7 +18,8 @@ distinctive name.`,
},
ORCHESTRA: {
name: 'Orchestra',
description: 'This indicates an orchestra (a large instrumental ensemble).',
description:
'This indicates an orchestra (a large instrumental ensemble).',
value: 'Orchestra'
},
CHOIR: {

View file

@ -45,7 +45,8 @@ artists and works. See the [setlist documentation](https://musicbrainz.org/doc/E
for syntax and examples.`
},
...fieldWithID('type', {
description: 'What kind of event the event is, e.g. concert, festival, etc.'
description:
'What kind of event the event is, e.g. concert, festival, etc.'
}),
relationships,
collections,

View file

@ -66,7 +66,7 @@ export function resolveHyphenated (obj, args, context, info) {
}
export function resolveWithFallback(keys) {
return (obj) => {
return obj => {
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if (key in obj) {
@ -95,7 +95,8 @@ field.`,
if (fieldName in entity) {
return entity[fieldName]
}
return loaders.lookup.load([entity._type, entity.id])
return loaders.lookup
.load([entity._type, entity.id])
.then(data => data[fieldName])
}
}

View file

@ -12,7 +12,10 @@ export { default as Label, LabelConnection } from './label'
export { default as Place, PlaceConnection } from './place'
export { default as Recording, RecordingConnection } from './recording'
export { default as Release, ReleaseConnection } from './release'
export { default as ReleaseGroup, ReleaseGroupConnection } from './release-group'
export {
default as ReleaseGroup,
ReleaseGroupConnection
} from './release-group'
export { default as Series, SeriesConnection } from './series'
export { default as Tag, TagConnection } from './tag'
export { default as URL, URLConnection } from './url'

View file

@ -35,7 +35,8 @@ multi-disc release).`
},
discs: {
type: new GraphQLList(Disc),
description: 'A list of physical discs and their disc IDs for this medium.'
description:
'A list of physical discs and their disc IDs for this medium.'
}
})
})

View file

@ -13,7 +13,7 @@ const { nodeInterface, nodeField } = nodeDefinitions(
const entityType = toDashed(type)
return loaders.lookup.load([entityType, id])
},
(obj) => {
obj => {
const type = TYPE_MODULES[obj._type] || obj._type
try {
return require(`./${type}`).default

View file

@ -1,8 +1,4 @@
import {
GraphQLObjectType,
GraphQLList,
GraphQLBoolean
} from 'graphql/type'
import { GraphQLObjectType, GraphQLList, GraphQLBoolean } from 'graphql/type'
import Node from './node'
import Entity from './entity'
import { Duration, ISRC } from './scalars'

View file

@ -7,11 +7,7 @@ import {
} from 'graphql/type'
import { DateType } from './scalars'
import Entity from './entity'
import {
resolveHyphenated,
fieldWithID,
connectionWithExtras
} from './helpers'
import { resolveHyphenated, fieldWithID, connectionWithExtras } from './helpers'
const Relationship = new GraphQLObjectType({
name: 'Relationship',
@ -35,7 +31,8 @@ other and to URLs outside MusicBrainz.`,
},
targetType: {
type: new GraphQLNonNull(GraphQLString),
description: 'The type of entity on the receiving end of the relationship.',
description:
'The type of entity on the receiving end of the relationship.',
resolve: resolveHyphenated
},
sourceCredit: {
@ -56,7 +53,8 @@ from its main (performance) name.`,
},
end: {
type: DateType,
description: 'The date on which the relationship became no longer applicable.'
description:
'The date on which the relationship became no longer applicable.'
},
ended: {
type: GraphQLBoolean,

View file

@ -1,8 +1,4 @@
import {
GraphQLObjectType,
GraphQLString,
GraphQLList
} from 'graphql/type'
import { GraphQLObjectType, GraphQLString, GraphQLList } from 'graphql/type'
import Node from './node'
import Entity from './entity'
import { ASIN, DateType } from './scalars'

View file

@ -57,7 +57,8 @@ and its partners for product identification within the Amazon organization.`
export const DateType = createScalar({
name: 'Date',
description: 'Year, month (optional), and day (optional) in YYYY-MM-DD format.'
description:
'Year, month (optional), and day (optional) in YYYY-MM-DD format.'
})
export const Degrees = createScalar({

View file

@ -25,9 +25,10 @@ export function getFields (info, fragments = info.fragments) {
return selections.reduce(reducer, {})
}
export function prettyPrint (obj, { depth = 5,
colors = true,
breakLength = 120 } = {}) {
export function prettyPrint(
obj,
{ depth = 5, colors = true, breakLength = 120 } = {}
) {
console.log(util.inspect(obj, { depth, colors, breakLength }))
}

View file

@ -3,7 +3,10 @@ import Client from '../../src/api/client'
test('parseErrorMessage() returns the body or status code', t => {
const client = new Client()
t.is(client.parseErrorMessage({ statusCode: 500 }, 'something went wrong'), 'something went wrong')
t.is(
client.parseErrorMessage({ statusCode: 500 }, 'something went wrong'),
'something went wrong'
)
t.is(client.parseErrorMessage({ statusCode: 500 }, ''), '500')
t.is(client.parseErrorMessage({ statusCode: 404 }, {}), '404')
})

View file

@ -3,25 +3,36 @@ import MusicBrainz, { MusicBrainzError } from '../../src/api'
import client from '../helpers/client/musicbrainz'
test('getLookupURL() generates a lookup URL', t => {
t.is(client.getLookupURL('artist', 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8', {
t.is(
client.getLookupURL('artist', 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8', {
inc: ['recordings', 'release-groups']
}), 'artist/c8da2e40-bd28-4d4e-813a-bd2f51958ba8?inc=recordings%2Brelease-groups')
}),
'artist/c8da2e40-bd28-4d4e-813a-bd2f51958ba8?inc=recordings%2Brelease-groups'
)
})
test('getBrowseURL() generates a browse URL', t => {
t.is(client.getBrowseURL('recording', {
t.is(
client.getBrowseURL('recording', {
artist: 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8',
limit: null,
offset: 0
}), 'recording?artist=c8da2e40-bd28-4d4e-813a-bd2f51958ba8&offset=0')
}),
'recording?artist=c8da2e40-bd28-4d4e-813a-bd2f51958ba8&offset=0'
)
})
test('getSearchURL() generates a search URL', t => {
t.is(client.getSearchURL('artist', 'Lures', { inc: null }), 'artist?query=Lures')
t.is(
client.getSearchURL('artist', 'Lures', { inc: null }),
'artist?query=Lures'
)
})
test('lookup() sends a lookup query', t => {
return client.lookup('artist', 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8').then(response => {
return client
.lookup('artist', 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8')
.then(response => {
t.is(response.id, 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8')
t.is(response.type, 'Group')
})
@ -60,7 +71,10 @@ test('shouldRetry() retries only transient local connection issues', t => {
test('rejects non-MusicBrainz errors', t => {
const client = new MusicBrainz({ baseURL: '$!@#$' })
return t.throws(client.get('artist/5b11f4ce-a62d-471e-81fc-a69a8278c7da'), Error)
return t.throws(
client.get('artist/5b11f4ce-a62d-471e-81fc-a69a8278c7da'),
Error
)
})
test('uses the default error impementation if there is no JSON error', t => {

View file

@ -2,23 +2,35 @@ import test from 'ava'
import client from '../../helpers/client/cover-art-archive'
test('can retrieve a front image URL', t => {
return client.imageURL('release', '76df3287-6cda-33eb-8e9a-044b5e15ffdd', 'front')
return client
.imageURL('release', '76df3287-6cda-33eb-8e9a-044b5e15ffdd', 'front')
.then(url => {
t.is(url, 'http://archive.org/download/mbid-76df3287-6cda-33eb-8e9a-044b5e15ffdd/mbid-76df3287-6cda-33eb-8e9a-044b5e15ffdd-829521842.jpg')
t.is(
url,
'http://archive.org/download/mbid-76df3287-6cda-33eb-8e9a-044b5e15ffdd/mbid-76df3287-6cda-33eb-8e9a-044b5e15ffdd-829521842.jpg'
)
})
})
test('can retrieve a back image URL', t => {
return client.imageURL('release', '76df3287-6cda-33eb-8e9a-044b5e15ffdd', 'back')
return client
.imageURL('release', '76df3287-6cda-33eb-8e9a-044b5e15ffdd', 'back')
.then(url => {
t.is(url, 'http://archive.org/download/mbid-76df3287-6cda-33eb-8e9a-044b5e15ffdd/mbid-76df3287-6cda-33eb-8e9a-044b5e15ffdd-5769317885.jpg')
t.is(
url,
'http://archive.org/download/mbid-76df3287-6cda-33eb-8e9a-044b5e15ffdd/mbid-76df3287-6cda-33eb-8e9a-044b5e15ffdd-5769317885.jpg'
)
})
})
test('can retrieve a list of release images', t => {
return client.images('release', '76df3287-6cda-33eb-8e9a-044b5e15ffdd')
return client
.images('release', '76df3287-6cda-33eb-8e9a-044b5e15ffdd')
.then(data => {
t.is(data.release, 'http://musicbrainz.org/release/76df3287-6cda-33eb-8e9a-044b5e15ffdd')
t.is(
data.release,
'http://musicbrainz.org/release/76df3287-6cda-33eb-8e9a-044b5e15ffdd'
)
t.true(data.images.length >= 3)
data.images.forEach(image => {
t.true(image.approved)

View file

@ -17,7 +17,10 @@ function testData (t, query, handler) {
})
}
test('releases have a cover art summary', testData, `
test(
'releases have a cover art summary',
testData,
`
{
lookup {
release(mbid: "b84ee12a-09ef-421b-82de-0441a926375b") {
@ -28,13 +31,18 @@ test('releases have a cover art summary', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { coverArtArchive } = data.lookup.release
t.true(coverArtArchive.artwork)
t.true(coverArtArchive.count >= 10)
})
}
)
test('releases have a set of cover art images', testData, `
test(
'releases have a set of cover art images',
testData,
`
{
lookup {
release(mbid: "b84ee12a-09ef-421b-82de-0441a926375b") {
@ -59,28 +67,55 @@ test('releases have a set of cover art images', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { coverArtArchive } = data.lookup.release
t.is(coverArtArchive.front, 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/1611507818.jpg')
t.is(coverArtArchive.back, 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/13536418798.jpg')
t.is(
coverArtArchive.front,
'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/1611507818.jpg'
)
t.is(
coverArtArchive.back,
'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/13536418798.jpg'
)
t.true(coverArtArchive.images.length >= 10)
t.true(coverArtArchive.images.some(image => image.front === true))
t.true(coverArtArchive.images.some(image => image.back === true))
t.true(coverArtArchive.images.some(image => image.types.indexOf('Front') >= 0))
t.true(coverArtArchive.images.some(image => image.types.indexOf('Back') >= 0))
t.true(coverArtArchive.images.some(image => image.types.indexOf('Liner') >= 0))
t.true(coverArtArchive.images.some(image => image.types.indexOf('Poster') >= 0))
t.true(coverArtArchive.images.some(image => image.types.indexOf('Medium') >= 0))
t.true(
coverArtArchive.images.some(image => image.types.indexOf('Front') >= 0)
)
t.true(
coverArtArchive.images.some(image => image.types.indexOf('Back') >= 0)
)
t.true(
coverArtArchive.images.some(image => image.types.indexOf('Liner') >= 0)
)
t.true(
coverArtArchive.images.some(image => image.types.indexOf('Poster') >= 0)
)
t.true(
coverArtArchive.images.some(image => image.types.indexOf('Medium') >= 0)
)
t.true(coverArtArchive.images.some(image => image.edit === 18544122))
t.true(coverArtArchive.images.some(image => image.comment === ''))
t.true(coverArtArchive.images.some(image => image.fileID === '1611507818'))
t.true(coverArtArchive.images.some(image => image.image === 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/13536422691.jpg'))
t.true(
coverArtArchive.images.some(
image =>
image.image ===
'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/13536422691.jpg'
)
)
t.true(coverArtArchive.images.every(image => image.approved === true))
t.true(coverArtArchive.images.every(image => image.thumbnails.small))
t.true(coverArtArchive.images.every(image => image.thumbnails.large))
})
}
)
test('can request a size for front and back cover art', testData, `
test(
'can request a size for front and back cover art',
testData,
`
{
lookup {
release(mbid: "b84ee12a-09ef-421b-82de-0441a926375b") {
@ -92,14 +127,28 @@ test('can request a size for front and back cover art', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { coverArtArchive } = data.lookup.release
t.is(coverArtArchive.front, 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/1611507818-500.jpg')
t.is(coverArtArchive.back, 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/13536418798-250.jpg')
t.is(coverArtArchive.fullFront, 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/1611507818.jpg')
})
t.is(
coverArtArchive.front,
'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/1611507818-500.jpg'
)
t.is(
coverArtArchive.back,
'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/13536418798-250.jpg'
)
t.is(
coverArtArchive.fullFront,
'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/1611507818.jpg'
)
}
)
test('release groups have a front cover art image', testData, `
test(
'release groups have a front cover art image',
testData,
`
{
lookup {
releaseGroup(mbid: "f5093c06-23e3-404f-aeaa-40f72885ee3a") {
@ -118,17 +167,25 @@ test('release groups have a front cover art image', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { coverArtArchive } = data.lookup.releaseGroup
t.true(coverArtArchive.artwork)
t.is(coverArtArchive.front, 'http://coverartarchive.org/release/25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27/1675312275.jpg')
t.is(
coverArtArchive.front,
'http://coverartarchive.org/release/25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27/1675312275.jpg'
)
t.is(coverArtArchive.release.mbid, '25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27')
t.is(coverArtArchive.release.title, 'The Dark Side of the Moon')
t.is(coverArtArchive.images.length, 1)
t.true(coverArtArchive.images[0].front)
})
}
)
test('release groups have different cover art sizes available', testData, `
test(
'release groups have different cover art sizes available',
testData,
`
{
lookup {
releaseGroup(mbid: "f5093c06-23e3-404f-aeaa-40f72885ee3a") {
@ -139,13 +196,24 @@ test('release groups have different cover art sizes available', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { coverArtArchive } = data.lookup.releaseGroup
t.is(coverArtArchive.small, 'http://coverartarchive.org/release/25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27/1675312275-250.jpg')
t.is(coverArtArchive.large, 'http://coverartarchive.org/release/25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27/1675312275-500.jpg')
})
t.is(
coverArtArchive.small,
'http://coverartarchive.org/release/25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27/1675312275-250.jpg'
)
t.is(
coverArtArchive.large,
'http://coverartarchive.org/release/25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27/1675312275-500.jpg'
)
}
)
test('can retrieve cover art in searches', testData, `
test(
'can retrieve cover art in searches',
testData,
`
{
search {
releases(query: "You Want It Darker") {
@ -164,11 +232,13 @@ test('can retrieve cover art in searches', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const releases = data.search.releases.edges.map(edge => edge.node)
t.is(releases.length, 25)
t.true(releases.some(release => release.coverArtArchive.artwork === true))
t.true(releases.some(release => release.coverArtArchive.images.length > 0))
t.true(releases.some(release => release.coverArtArchive.front === null))
t.true(releases.some(release => release.coverArtArchive.back === null))
})
}
)

View file

@ -17,7 +17,10 @@ function testData (t, query, handler) {
})
}
test('artists have a fanArt field and preview images', testData, `
test(
'artists have a fanArt field and preview images',
testData,
`
{
lookup {
artist(mbid: "5b11f4ce-a62d-471e-81fc-a69a8278c7da") {
@ -56,7 +59,8 @@ test('artists have a fanArt field and preview images', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
t.snapshot(data)
const { fanArt } = data.lookup.artist
const allImages = []
@ -68,9 +72,13 @@ test('artists have a fanArt field and preview images', testData, `
allImages.forEach(image => {
t.not(image.url, image.fullSizeURL)
})
})
}
)
test('release groups have a fanArt field and preview images', testData, `
test(
'release groups have a fanArt field and preview images',
testData,
`
{
lookup {
releaseGroup(mbid: "f5093c06-23e3-404f-aeaa-40f72885ee3a") {
@ -92,18 +100,21 @@ test('release groups have a fanArt field and preview images', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
t.snapshot(data)
const { fanArt } = data.lookup.releaseGroup
const allImages = []
.concat(fanArt.albumCovers)
.concat(fanArt.discImages)
const allImages = [].concat(fanArt.albumCovers).concat(fanArt.discImages)
allImages.forEach(image => {
t.not(image.url, image.fullSizeURL)
})
})
}
)
test('labels have a fanArt field and preview images', testData, `
test(
'labels have a fanArt field and preview images',
testData,
`
{
lookup {
label(mbid: "0cf56645-50ec-4411-aeb6-c9f4ce0f8edb") {
@ -119,10 +130,12 @@ test('labels have a fanArt field and preview images', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
t.snapshot(data)
const { fanArt } = data.lookup.label
fanArt.logos.forEach(image => {
t.not(image.url, image.fullSizeURL)
})
})
}
)

View file

@ -40,7 +40,10 @@ const fragment = `
}
`
test('artists have a mediaWikiImages field', testData, `
test(
'artists have a mediaWikiImages field',
testData,
`
{
lookup {
artist(mbid: "5b11f4ce-a62d-471e-81fc-a69a8278c7da") {
@ -50,11 +53,16 @@ test('artists have a mediaWikiImages field', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
t.snapshot(data)
})
}
)
test('instruments have a mediaWikiImages field', testData, `
test(
'instruments have a mediaWikiImages field',
testData,
`
{
search {
instruments(query: "guitar", first: 20) {
@ -66,11 +74,16 @@ test('instruments have a mediaWikiImages field', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
t.snapshot(data)
})
}
)
test('labels have a mediaWikiImages field', testData, `
test(
'labels have a mediaWikiImages field',
testData,
`
{
search {
labels(query: "Sony", first: 50) {
@ -82,11 +95,16 @@ test('labels have a mediaWikiImages field', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
t.snapshot(data)
})
}
)
test('places have a mediaWikiImages field', testData, `
test(
'places have a mediaWikiImages field',
testData,
`
{
lookup {
place(mbid: "b5297256-8482-4cba-968a-25db61563faf") {
@ -96,6 +114,8 @@ test('places have a mediaWikiImages field', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
t.snapshot(data)
})
}
)

View file

@ -17,7 +17,10 @@ function testData (t, query, handler) {
})
}
test('artists have a theAudioDB field', testData, `
test(
'artists have a theAudioDB field',
testData,
`
{
lookup {
artist(mbid: "5b11f4ce-a62d-471e-81fc-a69a8278c7da") {
@ -41,11 +44,16 @@ test('artists have a theAudioDB field', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
t.snapshot(data)
})
}
)
test('release groups have a theAudioDB field', testData, `
test(
'release groups have a theAudioDB field',
testData,
`
{
lookup {
releaseGroup(mbid: "aa997ea0-2936-40bd-884d-3af8a0e064dc") {
@ -75,11 +83,16 @@ test('release groups have a theAudioDB field', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
t.snapshot(data)
})
}
)
test('recordings have a theAudioDB field', testData, `
test(
'recordings have a theAudioDB field',
testData,
`
{
lookup {
recording(mbid: "1109d8da-ce4a-4739-9414-242dc3e9b81c") {
@ -113,6 +126,8 @@ test('recordings have a theAudioDB field', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
t.snapshot(data)
})
}
)

View file

@ -4,8 +4,7 @@ import CoverArtArchiveClient from '../../../src/extensions/cover-art-archive/cli
sepia.fixtureDir(path.join(__dirname, '..', '..', 'fixtures'))
const options = process.env.VCR_MODE === 'playback'
? { limit: Infinity, period: 0 }
: {}
const options =
process.env.VCR_MODE === 'playback' ? { limit: Infinity, period: 0 } : {}
export default new CoverArtArchiveClient(options)

View file

@ -4,8 +4,7 @@ import MusicBrainz from '../../../src/api'
sepia.fixtureDir(path.join(__dirname, '..', '..', 'fixtures'))
const options = process.env.VCR_MODE === 'playback'
? { limit: Infinity, period: 0 }
: {}
const options =
process.env.VCR_MODE === 'playback' ? { limit: Infinity, period: 0 } : {}
export default new MusicBrainz(options)

View file

@ -26,7 +26,10 @@ function testThrows (t, query, handler) {
return handler(t, error)
}
test('schema has a node field', testData, `
test(
'schema has a node field',
testData,
`
{
node(id: "UmVsZWFzZUdyb3VwOmUzN2QyNzQwLTQ1MDMtNGUzZi1hYjZkLWU2MjJhMjVlOTY0ZA==") {
__typename
@ -35,16 +38,21 @@ test('schema has a node field', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
t.deepEqual(data, {
node: {
__typename: 'ReleaseGroup',
mbid: 'e37d2740-4503-4e3f-ab6d-e622a25e964d'
}
})
})
}
)
test('schema has a lookup query', testData, `
test(
'schema has a lookup query',
testData,
`
{
lookup {
artist (mbid: "c8da2e40-bd28-4d4e-813a-bd2f51958ba8") {
@ -54,7 +62,8 @@ test('schema has a lookup query', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
t.deepEqual(data, {
lookup: {
artist: {
@ -64,9 +73,13 @@ test('schema has a lookup query', testData, `
}
}
})
})
}
)
test('schema has a search query', testData, `
test(
'schema has a search query',
testData,
`
{
search {
recordings (query: "Burn the Witch") {
@ -81,14 +94,19 @@ test('schema has a search query', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { recordings } = data.search
t.true(recordings.totalCount > 0)
t.true(recordings.edges.length === 25)
recordings.edges.forEach(edge => t.true(edge.score > 0))
})
}
)
test('schema has a browse query', testData, `
test(
'schema has a browse query',
testData,
`
{
browse {
releaseGroups(artist: "c8da2e40-bd28-4d4e-813a-bd2f51958ba8") {
@ -109,14 +127,19 @@ test('schema has a browse query', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { releaseGroups } = data.browse
t.true(releaseGroups.totalCount > 0)
t.true(releaseGroups.edges.length > 0)
releaseGroups.edges.forEach(edge => t.truthy(edge.node.title))
})
}
)
test('supports deeply nested queries', testData, `
test(
'supports deeply nested queries',
testData,
`
query AppleRecordsMarriages {
search {
labels(query: "Apple Records", first: 1) {
@ -184,13 +207,18 @@ test('supports deeply nested queries', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { labels } = data.search
t.true(labels.edges.length > 0)
t.is(labels.edges[0].node.releases.edges.length, 1)
})
}
)
test('connections have a nodes shortcut field', testData, `
test(
'connections have a nodes shortcut field',
testData,
`
query AppleRecordsMarriages {
search {
labels(query: "Apple Records", first: 1) {
@ -248,14 +276,19 @@ test('connections have a nodes shortcut field', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { labels } = data.search
t.true(labels.nodes.length > 0)
t.is(labels.nodes[0].releases.nodes.length, 1)
})
}
)
// FIXME: https://github.com/graphql/graphql-js/issues/910
test('throws an error if given a malformed MBID', testThrows, `
test(
'throws an error if given a malformed MBID',
testThrows,
`
{
lookup {
artist(mbid: "ABC123") {
@ -263,13 +296,18 @@ test('throws an error if given a malformed MBID', testThrows, `
}
}
}
`, async (t, promise) => {
`,
async (t, promise) => {
const err = await promise
t.true(err instanceof TypeError)
t.is(err.message, 'Malformed MBID: ABC123')
})
}
)
test('artist areas access begin_area/end_area for lookup queries', testData, `
test(
'artist areas access begin_area/end_area for lookup queries',
testData,
`
{
lookup {
artist(mbid: "65314b12-0e08-43fa-ba33-baaa7b874c15") {
@ -282,13 +320,18 @@ test('artist areas access begin_area/end_area for lookup queries', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { artist } = data.lookup
t.is(artist.beginArea.name, 'Westmount')
t.is(artist.endArea.name, 'Los Angeles')
})
}
)
test('artist areas access begin_area/end_area for browse queries', testData, `
test(
'artist areas access begin_area/end_area for browse queries',
testData,
`
{
browse {
artists(area: "3f504d54-c40c-487d-bc16-c1990eac887f") {
@ -305,14 +348,19 @@ test('artist areas access begin_area/end_area for browse queries', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const artists = data.browse.artists.edges.map(edge => edge.node)
t.true(artists.length > 1)
t.true(artists.some(artist => artist.beginArea))
t.true(artists.some(artist => artist.endArea))
})
}
)
test('artist areas access begin-area/end-area for search queries', testData, `
test(
'artist areas access begin-area/end-area for search queries',
testData,
`
{
search {
artists(query: "Leonard Cohen", first: 1) {
@ -329,14 +377,19 @@ test('artist areas access begin-area/end-area for search queries', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const artists = data.search.artists.edges.map(edge => edge.node)
t.true(artists.length === 1)
t.is(artists[0].beginArea.name, 'Westmount')
t.is(artists[0].endArea.name, 'Los Angeles')
})
}
)
test('relationships are grouped by target type', testData, `
test(
'relationships are grouped by target type',
testData,
`
{
lookup {
artist(mbid: "65314b12-0e08-43fa-ba33-baaa7b874c15") {
@ -375,7 +428,8 @@ test('relationships are grouped by target type', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { relationships } = data.lookup.artist
t.is(relationships.artists.edges.length, 5)
relationships.artists.edges.forEach(edge => {
@ -392,9 +446,13 @@ test('relationships are grouped by target type', testData, `
t.is(edge.node.targetType, 'release')
t.is(edge.node.target.__typename, 'Release')
})
})
}
)
test('relationships can be filtered by type', testData, `
test(
'relationships can be filtered by type',
testData,
`
{
lookup {
artist(mbid: "65314b12-0e08-43fa-ba33-baaa7b874c15") {
@ -411,7 +469,8 @@ test('relationships can be filtered by type', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { artist } = data.lookup
const rels = artist.relationships.artists.edges.map(edge => edge.node)
t.is(rels.length, 2)
@ -419,9 +478,13 @@ test('relationships can be filtered by type', testData, `
t.is(rel.targetType, 'artist')
t.is(rel.type, 'parent')
})
})
}
)
test('relationships can be filtered by type ID', testData, `
test(
'relationships can be filtered by type ID',
testData,
`
{
lookup {
artist(mbid: "65314b12-0e08-43fa-ba33-baaa7b874c15") {
@ -438,7 +501,8 @@ test('relationships can be filtered by type ID', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { artist } = data.lookup
const rels = artist.relationships.artists.edges.map(edge => edge.node)
t.is(rels.length, 1)
@ -446,9 +510,13 @@ test('relationships can be filtered by type ID', testData, `
t.is(rel.targetType, 'artist')
t.is(rel.type, 'involved with')
})
})
}
)
test('relationships can be filtered by direction', testData, `
test(
'relationships can be filtered by direction',
testData,
`
{
lookup {
area(mbid: "10cb2ebd-1bc7-4c11-b10d-54f60c421d20") {
@ -473,7 +541,8 @@ test('relationships can be filtered by direction', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { area } = data.lookup
const isPartOf = area.relationships.isPartOf.edges.map(edge => edge.node)
const hasParts = area.relationships.hasParts.edges.map(edge => edge.node)
@ -487,9 +556,13 @@ test('relationships can be filtered by direction', testData, `
t.is(rel.type, 'part of')
t.is(rel.direction, 'forward')
})
})
}
)
test('area maps iso-3166-1-codes to isoCodes', testData, `
test(
'area maps iso-3166-1-codes to isoCodes',
testData,
`
{
lookup {
area(mbid: "489ce91b-6658-3307-9877-795b68554c98") {
@ -498,11 +571,16 @@ test('area maps iso-3166-1-codes to isoCodes', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
t.deepEqual(data.lookup.area.isoCodes, ['US'])
})
}
)
test('areas have a type and typeID', testData, `
test(
'areas have a type and typeID',
testData,
`
{
search {
areas(query: "Germany", first: 5) {
@ -514,11 +592,16 @@ test('areas have a type and typeID', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
t.snapshot(data)
})
}
)
test('alias locales use the locale scalar', testData, `
test(
'alias locales use the locale scalar',
testData,
`
{
lookup {
artist(mbid: "f99b7d67-4e63-4678-aa66-4c6ac0f7d24a") {
@ -529,13 +612,18 @@ test('alias locales use the locale scalar', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { aliases } = data.lookup.artist
t.is(aliases.find(alias => alias.locale === 'en').name, 'PSY')
t.is(aliases.find(alias => alias.locale === 'ko').name, '싸이')
})
}
)
test('work ISWCs use the ISWC scalar', testData, `
test(
'work ISWCs use the ISWC scalar',
testData,
`
{
lookup {
work(mbid: "ef7d0814-da6a-32f5-a600-ff81cffd1aed") {
@ -544,13 +632,18 @@ test('work ISWCs use the ISWC scalar', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { work } = data.lookup
t.is(work.title, 'Song of the French Partisan')
t.deepEqual(work.iswcs, ['T-900.755.682-3'])
})
}
)
test('URLs may be looked up by resource', testData, `
test(
'URLs may be looked up by resource',
testData,
`
{
lookup {
url(resource: "http://www.nirvana.com/") {
@ -559,14 +652,19 @@ test('URLs may be looked up by resource', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { url } = data.lookup
t.is(url.mbid, '4347ffe2-82ec-4059-9520-6a1a3f73a304')
t.is(url.resource, 'http://www.nirvana.com/')
})
}
)
// FIXME: https://github.com/graphql/graphql-js/issues/910
test('throws an error if given a malformed resource URL', testThrows, `
test(
'throws an error if given a malformed resource URL',
testThrows,
`
{
lookup {
url(resource: "http:foo") {
@ -575,13 +673,18 @@ test('throws an error if given a malformed resource URL', testThrows, `
}
}
}
`, async (t, promise) => {
`,
async (t, promise) => {
const err = await promise
t.true(err instanceof TypeError)
t.is(err.message, 'Malformed URL: http:foo')
})
}
)
test('release groups can be browsed by type', testData, `
test(
'release groups can be browsed by type',
testData,
`
{
browse {
releaseGroups(artist: "5b11f4ce-a62d-471e-81fc-a69a8278c7da", type: EP) {
@ -593,13 +696,18 @@ test('release groups can be browsed by type', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const releaseGroups = data.browse.releaseGroups.edges.map(edge => edge.node)
t.is(releaseGroups.length, 8)
releaseGroups.forEach(releaseGroup => t.is(releaseGroup.primaryType, 'EP'))
})
}
)
test('releases can be browsed by type and status', testData, `
test(
'releases can be browsed by type and status',
testData,
`
{
browse {
releases(artist: "5b11f4ce-a62d-471e-81fc-a69a8278c7da", type: EP, status: BOOTLEG) {
@ -611,13 +719,18 @@ test('releases can be browsed by type and status', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const releases = data.browse.releases.edges.map(edge => edge.node)
t.is(releases.length, 6)
releases.forEach(release => t.is(release.status, 'BOOTLEG'))
})
}
)
test('releases have an ASIN field', testData, `
test(
'releases have an ASIN field',
testData,
`
{
lookup {
release(mbid: "d5cdb7fd-c7e9-460a-9549-8a369655cc52") {
@ -625,12 +738,17 @@ test('releases have an ASIN field', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { release } = data.lookup
t.is(release.asin, 'B01KN6XDS6')
})
}
)
test('artists have a list of ISNIs and IPIs', testData, `
test(
'artists have a list of ISNIs and IPIs',
testData,
`
{
lookup {
artist(mbid: "65314b12-0e08-43fa-ba33-baaa7b874c15") {
@ -639,13 +757,18 @@ test('artists have a list of ISNIs and IPIs', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { artist } = data.lookup
t.deepEqual(artist.ipis, ['00006457004'])
t.deepEqual(artist.isnis, ['0000000110273481'])
})
}
)
test('artistCredits is an alias for artistCredit', testData, `
test(
'artistCredits is an alias for artistCredit',
testData,
`
{
lookup {
recording(mbid: "07649758-09c8-4d70-bc6f-5c37ab36334d") {
@ -680,7 +803,8 @@ test('artistCredits is an alias for artistCredit', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { recording, release, releaseGroup } = data.lookup
t.deepEqual(recording.artistCredit, [
{ name: 'Holly Golightly', joinPhrase: ' & ' },
@ -698,9 +822,13 @@ test('artistCredits is an alias for artistCredit', testData, `
{ name: 'Ill Bill', joinPhrase: '' }
])
t.deepEqual(releaseGroup.artistCredits, releaseGroup.artistCredit)
})
}
)
test('recordings can be browsed by ISRC', testData, `
test(
'recordings can be browsed by ISRC',
testData,
`
{
browse {
recordings(isrc: "USSUB0200002") {
@ -714,15 +842,20 @@ test('recordings can be browsed by ISRC', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const recordings = data.browse.recordings.edges.map(edge => edge.node)
t.is(data.browse.recordings.totalCount, 1)
t.deepEqual(recordings, [
{ title: 'About a Girl', isrcs: ['USSUB0200002', 'USUG10200084'] }
])
})
}
)
test('releases can be browsed by Disc ID', testData, `
test(
'releases can be browsed by Disc ID',
testData,
`
{
browse {
releases(discID: "XzPS7vW.HPHsYemQh0HBUGr8vuU-") {
@ -736,14 +869,27 @@ test('releases can be browsed by Disc ID', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const releases = data.browse.releases.edges.map(edge => edge.node)
t.true(data.browse.releases.totalCount >= 2)
t.true(releases.some(release => release.mbid === '5a6e5ad7-c2bd-3484-a20e-121bf981c883'))
t.true(releases.some(release => release.mbid === '96f6f90e-d831-4f37-bf72-ce2982e459fb'))
})
t.true(
releases.some(
release => release.mbid === '5a6e5ad7-c2bd-3484-a20e-121bf981c883'
)
)
t.true(
releases.some(
release => release.mbid === '96f6f90e-d831-4f37-bf72-ce2982e459fb'
)
)
}
)
test('works can be browsed by ISWC', testData, `
test(
'works can be browsed by ISWC',
testData,
`
{
browse {
works(iswc: "T-900.755.682-3") {
@ -757,15 +903,20 @@ test('works can be browsed by ISWC', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const works = data.browse.works.edges.map(edge => edge.node)
t.is(data.browse.works.totalCount, 1)
t.deepEqual(works, [
{ title: 'Song of the French Partisan', iswcs: ['T-900.755.682-3'] }
])
})
}
)
test('recordings have a length in milliseconds', testData, `
test(
'recordings have a length in milliseconds',
testData,
`
{
lookup {
recording(mbid: "9f9cf187-d6f9-437f-9d98-d59cdbd52757") {
@ -773,12 +924,17 @@ test('recordings have a length in milliseconds', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { recording } = data.lookup
t.is(recording.length, 383493)
})
}
)
test('collections can be browsed by the entities they contain', testData, `
test(
'collections can be browsed by the entities they contain',
testData,
`
{
browse {
collections(artist: "24f1766e-9635-4d58-a4d4-9413f9f98a4c") {
@ -803,20 +959,27 @@ test('collections can be browsed by the entities they contain', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const collections = data.browse.collections.edges.map(edge => edge.node)
t.true(collections.length >= 2)
t.true(collections.some(collection => collection.editor === 'arist.on'))
t.true(collections.some(collection => collection.editor === 'ListMyCDs.com'))
t.true(
collections.some(collection => collection.editor === 'ListMyCDs.com')
)
collections.forEach(collection => {
t.is(collection.entityType, 'artist')
t.is(collection.type, 'Artist')
t.true(collection.artists.totalCount > 0)
t.true(collection.artists.edges.length > 0)
})
})
}
)
test('collections can be looked up by MBID', testData, `
test(
'collections can be looked up by MBID',
testData,
`
{
lookup {
collection(mbid: "85da782d-2ec0-41ec-a97f-9be464bba309") {
@ -831,13 +994,18 @@ test('collections can be looked up by MBID', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { collection } = data.lookup
t.is(collection.name, 'Beets Music Collection')
t.is(collection.releases.edges.length, 25)
})
}
)
test('entities have a collections field', testData, `
test(
'entities have a collections field',
testData,
`
{
lookup {
release(mbid: "0702057c-cb90-43d3-b7b4-6d0cc37e8644") {
@ -864,14 +1032,19 @@ test('entities have a collections field', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { release, artist } = data.lookup
t.true(release.collections.totalCount > 0)
t.true(release.collections.edges.length > 0)
t.true(artist.collections.edges.length > 0)
})
}
)
test('releases support a list of media', testData, `
test(
'releases support a list of media',
testData,
`
{
lookup {
release(mbid: "a4864e94-6d75-4ade-bc93-0dabf3521453") {
@ -885,7 +1058,8 @@ test('releases support a list of media', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { release } = data.lookup
t.deepEqual(release.media, [
{
@ -903,9 +1077,13 @@ test('releases support a list of media', testData, `
trackCount: 11
}
])
})
}
)
test('throws an error if looking up a URL without an argument', testError, `
test(
'throws an error if looking up a URL without an argument',
testError,
`
{
lookup {
url {
@ -913,11 +1091,19 @@ test('throws an error if looking up a URL without an argument', testError, `
}
}
}
`, (t, errors) => {
t.is(errors[0].message, 'Lookups by a field other than MBID must provide: resource')
})
`,
(t, errors) => {
t.is(
errors[0].message,
'Lookups by a field other than MBID must provide: resource'
)
}
)
test('some entities support ratings', testData, `
test(
'some entities support ratings',
testData,
`
{
lookup {
event(mbid: "eec75a81-8864-4cea-b8b4-e99cd08b29f1") {
@ -978,7 +1164,8 @@ test('some entities support ratings', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { event, work } = data.lookup
const artists = data.browse.artists.edges.map(edge => edge.node)
const recordings = data.browse.recordings.edges.map(edge => edge.node)
@ -994,11 +1181,17 @@ test('some entities support ratings', testData, `
t.true(recordings.some(recording => recording.rating.value > 3))
t.true(labels.some(label => label.rating.voteCount > 0))
t.true(labels.some(label => label.rating.value > 3))
t.true(releaseGroups.some(releaseGroup => releaseGroup.rating.voteCount > 0))
t.true(
releaseGroups.some(releaseGroup => releaseGroup.rating.voteCount > 0)
)
t.true(releaseGroups.some(releaseGroup => releaseGroup.rating.value > 3))
})
}
)
test('discs can be looked up by disc ID', testData, `
test(
'discs can be looked up by disc ID',
testData,
`
{
lookup {
disc(discID: "TMXdzZkTcc9Jq24PD0w5J9_AXms-") {
@ -1018,20 +1211,36 @@ test('discs can be looked up by disc ID', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { disc } = data.lookup
t.is(disc.discID, 'TMXdzZkTcc9Jq24PD0w5J9_AXms-')
t.is(disc.offsetCount, 9)
t.is(disc.sectors, 193443)
t.deepEqual(disc.offsets, [
150, 18190, 34163, 66150, 87453, 116853, 151413, 166833, 184123
150,
18190,
34163,
66150,
87453,
116853,
151413,
166833,
184123
])
t.is(disc.releases.totalCount, 1)
t.is(disc.releases.edges.length, 1)
t.is(disc.releases.edges[0].node.mbid, '7f6d3088-837d-495e-905f-be5c70ac2d82')
})
t.is(
disc.releases.edges[0].node.mbid,
'7f6d3088-837d-495e-905f-be5c70ac2d82'
)
}
)
test('release media has a list of discs', testData, `
test(
'release media has a list of discs',
testData,
`
{
lookup {
release(mbid: "7f6d3088-837d-495e-905f-be5c70ac2d82") {
@ -1051,13 +1260,18 @@ test('release media has a list of discs', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { release } = data.lookup
t.is(release.media.length, 1)
t.is(release.media[0].discs.length, 2)
})
}
)
test('disc queries can be deeply nested', testData, `
test(
'disc queries can be deeply nested',
testData,
`
{
lookup {
disc(discID: "TMXdzZkTcc9Jq24PD0w5J9_AXms-") {
@ -1102,7 +1316,8 @@ test('disc queries can be deeply nested', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { disc } = data.lookup
t.true(disc.releases.edges.length > 0)
disc.releases.edges.forEach(release => {
@ -1120,9 +1335,13 @@ test('disc queries can be deeply nested', testData, `
})
})
})
})
}
)
test('entities support tags', testData, `
test(
'entities support tags',
testData,
`
{
lookup {
label(mbid: "38dc88de-7720-4100-9d5b-3cdc41b0c474") {
@ -1153,11 +1372,13 @@ test('entities support tags', testData, `
}
}
}
`, (t, data) => {
`,
(t, data) => {
const { label } = data.lookup
const artists = data.search.artists.edges.map(edge => edge.node)
t.true(label.tags.edges.some(edge => edge.node.name === 'indie folk'))
t.true(label.tags.edges.some(edge => edge.node.count > 0))
t.true(artists[0].tags.edges.some(edge => edge.node.name === 'blues rock'))
t.true(artists[0].tags.edges.some(edge => edge.node.count > 0))
})
}
)

View file

@ -1,6 +1,12 @@
import test from 'ava'
import { Kind } from 'graphql/language'
import { Duration, Locale, MBID, ISWC, URLString } from '../../src/types/scalars'
import {
Duration,
Locale,
MBID,
ISWC,
URLString
} from '../../src/types/scalars'
test('Locale scalar allows language code', t => {
t.is(Locale.parseLiteral({ kind: Kind.STRING, value: 'en' }), 'en')
@ -15,9 +21,18 @@ test('Locale scalar allows language and country code', t => {
})
test('Locale scalar allows language, country, and encoding', t => {
t.is(Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US.UTF-8' }), 'en_US.UTF-8')
t.is(Locale.parseLiteral({ kind: Kind.STRING, value: 'de_CH.utf8' }), 'de_CH.utf8')
t.is(Locale.parseLiteral({ kind: Kind.STRING, value: 'zh_TW.Big5' }), 'zh_TW.Big5')
t.is(
Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US.UTF-8' }),
'en_US.UTF-8'
)
t.is(
Locale.parseLiteral({ kind: Kind.STRING, value: 'de_CH.utf8' }),
'de_CH.utf8'
)
t.is(
Locale.parseLiteral({ kind: Kind.STRING, value: 'zh_TW.Big5' }),
'zh_TW.Big5'
)
})
test('Locale scalar only accepts strings', t => {
@ -26,16 +41,46 @@ test('Locale scalar only accepts strings', t => {
})
test('Locale scalar rejects malformed locales', t => {
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_' }), TypeError)
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_USA' }), TypeError)
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'EN' }), TypeError)
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_us' }), TypeError)
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en-US' }), TypeError)
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US_foo' }), TypeError)
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US-utf8' }), TypeError)
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: '12_US' }), TypeError)
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US.' }), TypeError)
t.throws(() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US.utf!' }), TypeError)
t.throws(
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_' }),
TypeError
)
t.throws(
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_USA' }),
TypeError
)
t.throws(
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'EN' }),
TypeError
)
t.throws(
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_us' }),
TypeError
)
t.throws(
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en-US' }),
TypeError
)
t.throws(
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US_foo' }),
TypeError
)
t.throws(
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US-utf8' }),
TypeError
)
t.throws(
() => Locale.parseLiteral({ kind: Kind.STRING, value: '12_US' }),
TypeError
)
t.throws(
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US.' }),
TypeError
)
t.throws(
() => Locale.parseLiteral({ kind: Kind.STRING, value: 'en_US.utf!' }),
TypeError
)
})
test('Duration scalar must be a positive integer', t => {
@ -43,8 +88,14 @@ test('Duration scalar must be a positive integer', t => {
t.is(Duration.parseLiteral({ kind: Kind.INT, value: 1 }), 1)
t.is(Duration.parseLiteral({ kind: Kind.INT, value: 3000 }), 3000)
t.is(Duration.parseLiteral({ kind: Kind.STRING, value: '1000' }), null)
t.throws(() => Duration.parseLiteral({ kind: Kind.INT, value: -1 }), TypeError)
t.throws(() => Duration.parseLiteral({ kind: Kind.INT, value: -1000 }), TypeError)
t.throws(
() => Duration.parseLiteral({ kind: Kind.INT, value: -1 }),
TypeError
)
t.throws(
() => Duration.parseLiteral({ kind: Kind.INT, value: -1000 }),
TypeError
)
t.is(Duration.parseValue(0), 0)
t.is(Duration.parseValue(1), 1)
t.is(Duration.parseValue(3000), 3000)
@ -54,11 +105,29 @@ test('Duration scalar must be a positive integer', t => {
test('URLString scalar must be a valid URL', t => {
t.is(URLString.parseLiteral({ kind: Kind.INT, value: 1000 }), null)
t.is(URLString.parseLiteral({ kind: Kind.STRING, value: 'http://www.google.com' }), 'http://www.google.com')
t.throws(() => URLString.parseLiteral({ kind: Kind.STRING, value: 'foo:bar' }), TypeError)
t.throws(() => URLString.parseLiteral({ kind: Kind.STRING, value: 'foo:/bar' }), TypeError)
t.throws(() => URLString.parseLiteral({ kind: Kind.STRING, value: 'foo://bar' }), TypeError)
t.throws(() => URLString.parseLiteral({ kind: Kind.STRING, value: 'foo://bar.' }), TypeError)
t.is(
URLString.parseLiteral({
kind: Kind.STRING,
value: 'http://www.google.com'
}),
'http://www.google.com'
)
t.throws(
() => URLString.parseLiteral({ kind: Kind.STRING, value: 'foo:bar' }),
TypeError
)
t.throws(
() => URLString.parseLiteral({ kind: Kind.STRING, value: 'foo:/bar' }),
TypeError
)
t.throws(
() => URLString.parseLiteral({ kind: Kind.STRING, value: 'foo://bar' }),
TypeError
)
t.throws(
() => URLString.parseLiteral({ kind: Kind.STRING, value: 'foo://bar.' }),
TypeError
)
})
test('ISWC scalar only accepts strings', t => {
@ -79,6 +148,19 @@ test('MBID scalar only accepts strings', t => {
})
test('MBID scalar must be a valid UUID', t => {
t.is(MBID.parseLiteral({ kind: Kind.STRING, value: 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8' }), 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8')
t.throws(() => MBID.parseLiteral({ kind: Kind.STRING, value: 'c8da2e40-bd28-4d4e-813a-bd2f51958bag' }), TypeError)
t.is(
MBID.parseLiteral({
kind: Kind.STRING,
value: 'c8da2e40-bd28-4d4e-813a-bd2f51958ba8'
}),
'c8da2e40-bd28-4d4e-813a-bd2f51958ba8'
)
t.throws(
() =>
MBID.parseLiteral({
kind: Kind.STRING,
value: 'c8da2e40-bd28-4d4e-813a-bd2f51958bag'
}),
TypeError
)
})

View file

@ -2,8 +2,12 @@ import test from 'ava'
import sinon from 'sinon'
import { prettyPrint } from '../src/util'
test.beforeEach(t => { sinon.stub(console, 'log') })
test.afterEach(t => { console.log.restore() })
test.beforeEach(t => {
sinon.stub(console, 'log')
})
test.afterEach(t => {
console.log.restore()
})
test('prettyPrint writes to stdout', t => {
prettyPrint('foo')

745
yarn.lock

File diff suppressed because it is too large Load diff