diff --git a/frontend/components/badge-examples.js b/frontend/components/badge-examples.js index e8c9f78a47489a268ceaf844f399ed1d7c0ef5bd..3b7cca205d08458d2ee5ac584db2e50e98d1a61c 100644 --- a/frontend/components/badge-examples.js +++ b/frontend/components/badge-examples.js @@ -1,8 +1,8 @@ -import React from 'react'; -import { Link } from "react-router-dom"; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import resolveBadgeUrl from '../lib/badge-url'; +import React from 'react' +import { Link } from 'react-router-dom' +import PropTypes from 'prop-types' +import classNames from 'classnames' +import resolveBadgeUrl from '../lib/badge-url' const Badge = ({ title, @@ -14,39 +14,49 @@ const Badge = ({ shouldDisplay = () => true, onClick, }) => { - const handleClick = onClick ? - () => onClick({ title, previewUri, exampleUri, documentation }) - : undefined; + const handleClick = onClick + ? () => onClick({ title, previewUri, exampleUri, documentation }) + : undefined - const previewImage = previewUri - ? (<img + const previewImage = previewUri ? ( + <img className={classNames('badge-img', { clickable: onClick })} onClick={handleClick} - src={resolveBadgeUrl(previewUri, baseUri, { longCache } )} - alt="" /> - ) : '\u00a0'; // non-breaking space + src={resolveBadgeUrl(previewUri, baseUri, { longCache })} + alt="" + /> + ) : ( + '\u00a0' + ) // non-breaking space const resolvedExampleUri = resolveBadgeUrl( exampleUri || previewUri, baseUri, - { longCache: false }); + { longCache: false } + ) if (shouldDisplay()) { return ( <tr> - <th className={classNames({ clickable: onClick })} onClick={handleClick}> - { title }: + <th + className={classNames({ clickable: onClick })} + onClick={handleClick} + > + {title}: </th> - <td>{ previewImage }</td> + <td>{previewImage}</td> <td> - <code className={classNames({ clickable: onClick })} onClick={handleClick}> - { resolvedExampleUri } + <code + className={classNames({ clickable: onClick })} + onClick={handleClick} + > + {resolvedExampleUri} </code> </td> </tr> - ); + ) } - return null; -}; + return null +} Badge.propTypes = { title: PropTypes.string.isRequired, previewUri: PropTypes.string, @@ -56,75 +66,74 @@ Badge.propTypes = { longCache: PropTypes.bool.isRequired, shouldDisplay: PropTypes.func, onClick: PropTypes.func.isRequired, -}; +} const Category = ({ category, examples, baseUri, longCache, onClick }) => { - if (examples.filter(example => example.shouldDisplay()).length === 0){ - return null; + if (examples.filter(example => example.shouldDisplay()).length === 0) { + return null } return ( <div> <Link to={'/examples/' + category.id}> - <h3 id={category.id}>{ category.name }</h3> + <h3 id={category.id}>{category.name}</h3> </Link> <table className="badge"> <tbody> - { - examples.map(badgeData => ( - <Badge - key={badgeData.key} - {...badgeData} - baseUri={baseUri} - longCache={longCache} - onClick={onClick} /> - )) - } + {examples.map(badgeData => ( + <Badge + key={badgeData.key} + {...badgeData} + baseUri={baseUri} + longCache={longCache} + onClick={onClick} + /> + ))} </tbody> </table> </div> - ); -}; + ) +} Category.propTypes = { category: PropTypes.shape({ id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, }).isRequired, - examples: PropTypes.arrayOf(PropTypes.shape({ - title: PropTypes.string.isRequired, - previewUri: PropTypes.string, - exampleUri: PropTypes.string, - documentation: PropTypes.string, - })).isRequired, + examples: PropTypes.arrayOf( + PropTypes.shape({ + title: PropTypes.string.isRequired, + previewUri: PropTypes.string, + exampleUri: PropTypes.string, + documentation: PropTypes.string, + }) + ).isRequired, baseUri: PropTypes.string, longCache: PropTypes.bool.isRequired, onClick: PropTypes.func.isRequired, -}; +} const BadgeExamples = ({ categories, baseUri, longCache, onClick }) => ( <div> - { - categories.map((categoryData, i) => ( - <Category - key={i} - {...categoryData} - baseUri={baseUri} - longCache={longCache} - onClick={onClick} /> - )) - } + {categories.map((categoryData, i) => ( + <Category + key={i} + {...categoryData} + baseUri={baseUri} + longCache={longCache} + onClick={onClick} + /> + ))} </div> -); +) BadgeExamples.propTypes = { - categories: PropTypes.arrayOf(PropTypes.shape({ - category: Category.propTypes.category, - examples: Category.propTypes.examples, - })), + categories: PropTypes.arrayOf( + PropTypes.shape({ + category: Category.propTypes.category, + examples: Category.propTypes.examples, + }) + ), baseUri: PropTypes.string, longCache: PropTypes.bool.isRequired, onClick: PropTypes.func.isRequired, -}; +} -export { - Badge, - BadgeExamples, -}; +export { Badge, BadgeExamples } diff --git a/frontend/components/dynamic-badge-maker.js b/frontend/components/dynamic-badge-maker.js index e6ac8a6bee8cc4c5c249e36bc75f2c3a0735945a..a8eb0f1957ac37d0666bf8ca6216c5190991ca1c 100644 --- a/frontend/components/dynamic-badge-maker.js +++ b/frontend/components/dynamic-badge-maker.js @@ -1,11 +1,11 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { dynamicBadgeUrl } from '../lib/badge-url'; +import React from 'react' +import PropTypes from 'prop-types' +import { dynamicBadgeUrl } from '../lib/badge-url' export default class DynamicBadgeMaker extends React.Component { static propTypes = { baseUri: PropTypes.string, - }; + } state = { datatype: '', @@ -15,22 +15,26 @@ export default class DynamicBadgeMaker extends React.Component { color: '', prefix: '', suffix: '', - }; + } - makeBadgeUri () { - const { datatype, label, url, query, color, prefix, suffix } = this.state; - const { baseUri: baseUrl = document.location.href } = this.props; - return dynamicBadgeUrl(baseUrl, datatype, label, url, query, { color, prefix, suffix }); + makeBadgeUri() { + const { datatype, label, url, query, color, prefix, suffix } = this.state + const { baseUri: baseUrl = document.location.href } = this.props + return dynamicBadgeUrl(baseUrl, datatype, label, url, query, { + color, + prefix, + suffix, + }) } handleSubmit(e) { - e.preventDefault(); - document.location = this.makeBadgeUri(); + e.preventDefault() + document.location = this.makeBadgeUri() } get isValid() { - const { datatype, label, url, query } = this.state; - return datatype && label && url && query; + const { datatype, label, url, query } = this.state + return datatype && label && url && query } render() { @@ -39,44 +43,60 @@ export default class DynamicBadgeMaker extends React.Component { <select className="short" value={this.state.datatype} - onChange={event => this.setState({ datatype: event.target.value })}> - <option value="" disabled>data type</option> + onChange={event => this.setState({ datatype: event.target.value })} + > + <option value="" disabled> + data type + </option> <option value="json">json</option> <option value="xml">xml</option> <option value="yaml">yaml</option> - </select> {} + </select>{' '} + {} <input className="short" value={this.state.label} onChange={event => this.setState({ label: event.target.value })} - placeholder="label" /> {} + placeholder="label" + />{' '} + {} <input className="short" value={this.state.url} onChange={event => this.setState({ url: event.target.value })} - placeholder="url" /> {} + placeholder="url" + />{' '} + {} <input className="short" value={this.state.query} onChange={event => this.setState({ query: event.target.value })} - placeholder="query" /> {} + placeholder="query" + />{' '} + {} <input className="short" value={this.state.color} onChange={event => this.setState({ color: event.target.value })} - placeholder="color" /> {} + placeholder="color" + />{' '} + {} <input className="short" value={this.state.prefix} onChange={event => this.setState({ prefix: event.target.value })} - placeholder="prefix" /> {} + placeholder="prefix" + />{' '} + {} <input className="short" value={this.state.suffix} onChange={event => this.setState({ suffix: event.target.value })} - placeholder="suffix" /> {} - <button disabled={! this.isValid}>Make Badge</button> + placeholder="suffix" + />{' '} + {} + <button disabled={!this.isValid}>Make Badge</button> </form> - ); + ) } } diff --git a/frontend/components/examples-page.js b/frontend/components/examples-page.js index 6a41794db2030be99d1ac07071a795b6c2307704..f60b512e612f8227123072d9033271add2bf88e5 100644 --- a/frontend/components/examples-page.js +++ b/frontend/components/examples-page.js @@ -1,27 +1,26 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Meta from './meta'; -import Header from './header'; -import SuggestionAndSearch from './suggestion-and-search'; -import SearchResults from './search-results'; -import MarkupModal from './markup-modal'; -import Usage from './usage'; -import Footer from './footer'; -import { baseUri, longCache } from '../constants'; +import React from 'react' +import PropTypes from 'prop-types' +import Meta from './meta' +import Header from './header' +import SuggestionAndSearch from './suggestion-and-search' +import SearchResults from './search-results' +import MarkupModal from './markup-modal' +import Usage from './usage' +import Footer from './footer' +import { baseUri, longCache } from '../constants' export default class ExamplesPage extends React.Component { - constructor(props) { - super(props); + super(props) this.state = { category: props.match.params.id, query: null, example: null, searchReady: true, - }; - this.searchTimeout = 0; - this.renderSearchResults = this.renderSearchResults.bind(this); - this.searchQueryChanged = this.searchQueryChanged.bind(this); + } + this.searchTimeout = 0 + this.renderSearchResults = this.renderSearchResults.bind(this) + this.searchQueryChanged = this.searchQueryChanged.bind(this) } static propTypes = { @@ -29,7 +28,7 @@ export default class ExamplesPage extends React.Component { } searchQueryChanged(query) { - this.setState({searchReady: false}); + this.setState({ searchReady: false }) /* Add a small delay before showing search results so that we wait until the user has stipped typing @@ -40,29 +39,32 @@ export default class ExamplesPage extends React.Component { b) stops the page from 'flashing' as the user types, like this: https://user-images.githubusercontent.com/7288322/42600206-9b278470-85b5-11e8-9f63-eb4a0c31cb4a.gif */ - window.clearTimeout(this.searchTimeout); + window.clearTimeout(this.searchTimeout) this.searchTimeout = window.setTimeout(() => { this.setState({ searchReady: true, - query: query - }); - }, 500); + query: query, + }) + }, 500) } renderSearchResults() { if (this.state.searchReady) { - if ((this.state.query != null) && (this.state.query.length === 1)) { - return (<div>Search term must have 2 or more characters</div>); + if (this.state.query != null && this.state.query.length === 1) { + return <div>Search term must have 2 or more characters</div> } else { return ( <SearchResults category={this.state.category} query={this.state.query} - clickHandler={example => { this.setState({ example }); }} /> - ); + clickHandler={example => { + this.setState({ example }) + }} + /> + ) } } else { - return (<div>searching...</div>); + return <div>searching...</div> } } @@ -73,32 +75,34 @@ export default class ExamplesPage extends React.Component { <Header /> <MarkupModal example={this.state.example} - onRequestClose={() => { this.setState({ example: null }); }} - baseUri={baseUri} /> + onRequestClose={() => { + this.setState({ example: null }) + }} + baseUri={baseUri} + /> <section> <SuggestionAndSearch queryChanged={this.searchQueryChanged} - onBadgeClick={example => { this.setState({ example }); }} + onBadgeClick={example => { + this.setState({ example }) + }} baseUri={baseUri} - longCache={longCache} /> - <a - className="donate" - href="https://opencollective.com/shields"> + longCache={longCache} + /> + <a className="donate" href="https://opencollective.com/shields"> donate </a> </section> - { this.renderSearchResults() } - <Usage - baseUri={baseUri} - longCache={longCache} /> + {this.renderSearchResults()} + <Usage baseUri={baseUri} longCache={longCache} /> <Footer baseUri={baseUri} /> <style jsx>{` .donate { text-decoration: none; - color: rgba(0,0,0,0.1); + color: rgba(0, 0, 0, 0.1); } `}</style> </div> - ); + ) } } diff --git a/frontend/components/footer.js b/frontend/components/footer.js index 71ed716e19e663a2a336a955f280ee1f8d4602c2..f7ae023690c43c7299ef44da5573d6ad97f1af4a 100644 --- a/frontend/components/footer.js +++ b/frontend/components/footer.js @@ -1,41 +1,63 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import resolveUrl from '../lib/resolve-url'; +import React from 'react' +import PropTypes from 'prop-types' +import resolveUrl from '../lib/resolve-url' const Footer = ({ baseUri }) => ( <section> <h2 id="like-this">Like This?</h2> <p> - What is your favorite badge service to use?<br /> - <a href="https://github.com/badges/shields/blob/master/CONTRIBUTING.md">Tell us</a> and we might bring it to you! + What is your favorite badge service to use? + <br /> + <a href="https://github.com/badges/shields/blob/master/CONTRIBUTING.md"> + Tell us + </a>{' '} + and we might bring it to you! </p> <p> <object - data={resolveUrl('/twitter/follow/shields_io.svg?style=social&label=Follow', baseUri)} - alt="Follow @shields_io" /> {} + data={resolveUrl( + '/twitter/follow/shields_io.svg?style=social&label=Follow', + baseUri + )} + alt="Follow @shields_io" + />{' '} + {} <a href="https://opencollective.com/shields" alt="Donate to us!"> <img src="https://opencollective.com/shields/backers/badge.svg?style=social" /> - </a> {} + </a>{' '} + {} <a href="https://opencollective.com/shields" alt="Donate to us!"> <img src="https://opencollective.com/shields/sponsors/badge.svg?style=social" /> - </a> {} + </a>{' '} + {} <object - data={resolveUrl('/github/forks/badges/shields.svg?style=social&label=Fork', baseUri)} - alt="Fork on GitHub" /> {} + data={resolveUrl( + '/github/forks/badges/shields.svg?style=social&label=Fork', + baseUri + )} + alt="Fork on GitHub" + />{' '} + {} <object - data={resolveUrl('/discord/308323056592486420.svg?style=social&label=Chat&link=https://discord.gg/HjJCwm5', baseUri)} - alt="chat on Discord" /> + data={resolveUrl( + '/discord/308323056592486420.svg?style=social&label=Chat&link=https://discord.gg/HjJCwm5', + baseUri + )} + alt="chat on Discord" + /> </p> <p> <a href="https://github.com/h5bp/lazyweb-requests/issues/150">This</a> is where the current server got started. </p> - <p><small>:wq</small></p> + <p> + <small>:wq</small> + </p> </section> -); -export default Footer; +) +export default Footer Footer.propTypes = { baseUri: PropTypes.string.isRequired, -}; +} diff --git a/frontend/components/header.js b/frontend/components/header.js index 83a59e40231a648162207fee4af90a5ca3212e82..2cbe19a71b65c494ce76dbe691706e7c7f9a05c8 100644 --- a/frontend/components/header.js +++ b/frontend/components/header.js @@ -1,5 +1,5 @@ -import { Link } from "react-router-dom"; -import React from 'react'; +import { Link } from 'react-router-dom' +import React from 'react' export default () => ( <section> @@ -10,12 +10,8 @@ export default () => ( <hr className="spacing" /> <p className="highlights"> - Pixel-perfect - Retina-ready - Fast - Consistent - Hackable - No tracking + Pixel-perfect Retina-ready Fast Consistent + Hackable No tracking </p> <style jsx>{` @@ -24,4 +20,4 @@ export default () => ( } `}</style> </section> -); +) diff --git a/frontend/components/markup-modal.js b/frontend/components/markup-modal.js index 93cca54bbb20c5349453c1fda912f543ca07b9cb..1e7231e73fdcfb08d2a987f7acd3f64d226b3563 100644 --- a/frontend/components/markup-modal.js +++ b/frontend/components/markup-modal.js @@ -1,10 +1,10 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Modal from 'react-modal'; -import ClickToSelect from '@mapbox/react-click-to-select'; -import resolveBadgeUrl from '../lib/badge-url'; -import generateAllMarkup from '../lib/generate-image-markup'; -import { advertisedStyles } from '../../supported-features.json'; +import React from 'react' +import PropTypes from 'prop-types' +import Modal from 'react-modal' +import ClickToSelect from '@mapbox/react-click-to-select' +import resolveBadgeUrl from '../lib/badge-url' +import generateAllMarkup from '../lib/generate-image-markup' +import { advertisedStyles } from '../../supported-features.json' export default class MarkupModal extends React.Component { static propTypes = { @@ -17,80 +17,87 @@ export default class MarkupModal extends React.Component { }), baseUri: PropTypes.string.isRequired, onRequestClose: PropTypes.func.isRequired, - }; + } state = { badgeUri: null, link: null, style: 'flat', - }; + } get isOpen() { - return this.props.example !== null; + return this.props.example !== null } componentWillReceiveProps(nextProps) { - const { example, baseUri } = nextProps; + const { example, baseUri } = nextProps - if (! example) { - return; + if (!example) { + return } // Transfer `badgeUri` and `link` into state so they can be edited by the // user. - const { exampleUri, previewUri, link } = example; + const { exampleUri, previewUri, link } = example this.setState({ - badgeUri: resolveBadgeUrl(exampleUri || previewUri, baseUri || window.location.href), + badgeUri: resolveBadgeUrl( + exampleUri || previewUri, + baseUri || window.location.href + ), link, - }); + }) } generateCompleteBadgeUrl() { - const { baseUri } = this.props; - const { badgeUri, style } = this.state; + const { baseUri } = this.props + const { badgeUri, style } = this.state return resolveBadgeUrl( badgeUri, baseUri || window.location.href, // Default style doesn't need to be specified. - style === 'flat' ? undefined : { style }); + style === 'flat' ? undefined : { style } + ) } generateMarkup() { - if (! this.isOpen) { - return {}; + if (!this.isOpen) { + return {} } - const { title } = this.props.example; - const { link } = this.state; - const completeBadgeUrl = this.generateCompleteBadgeUrl(); - return generateAllMarkup(completeBadgeUrl, link, title); + const { title } = this.props.example + const { link } = this.state + const completeBadgeUrl = this.generateCompleteBadgeUrl() + return generateAllMarkup(completeBadgeUrl, link, title) } renderDocumentation() { - if (! this.isOpen) { - return null; + if (!this.isOpen) { + return null } - const { documentation } = this.props.example; + const { documentation } = this.props.example return documentation ? ( <div> <h4>Documentation</h4> <div dangerouslySetInnerHTML={{ __html: documentation }} /> </div> - ) : null; + ) : null } render() { - const { markdown, reStructuredText, asciiDoc } = this.generateMarkup(); + const { markdown, reStructuredText, asciiDoc } = this.generateMarkup() - const completeBadgeUrl = this.isOpen ? this.generateCompleteBadgeUrl() : undefined; + const completeBadgeUrl = this.isOpen + ? this.generateCompleteBadgeUrl() + : undefined return ( <Modal isOpen={this.isOpen} onRequestClose={this.props.onRequestClose} - contentLabel="Example Modal"> + contentLabel="Example Modal" + > <form action=""> <p> <img className="badge-img" src={completeBadgeUrl} /> @@ -101,7 +108,10 @@ export default class MarkupModal extends React.Component { <input type="url" value={this.state.link} - onChange={event => { this.setState({ link: event.target.value }); }} /> + onChange={event => { + this.setState({ link: event.target.value }) + }} + /> </label> </p> <p> @@ -110,7 +120,10 @@ export default class MarkupModal extends React.Component { <input type="url" value={this.state.badgeUri} - onChange={event => { this.setState({ badgeUri: event.target.value }); }} /> + onChange={event => { + this.setState({ badgeUri: event.target.value }) + }} + /> </label> </p> <p> @@ -118,12 +131,15 @@ export default class MarkupModal extends React.Component { Style <select value={this.state.style} - onChange={event => { this.setState({ style: event.target.value }); }}> - { - advertisedStyles.map(style => ( - <option key={style} value={style}>{style}</option> - )) - } + onChange={event => { + this.setState({ style: event.target.value }) + }} + > + {advertisedStyles.map(style => ( + <option key={style} value={style}> + {style} + </option> + ))} </select> </label> </p> @@ -136,7 +152,11 @@ export default class MarkupModal extends React.Component { <p> reStructuredText <ClickToSelect> - <input className="code clickable" readOnly value={reStructuredText} /> + <input + className="code clickable" + readOnly + value={reStructuredText} + /> </ClickToSelect> </p> <p> @@ -145,11 +165,9 @@ export default class MarkupModal extends React.Component { <input className="code clickable" readOnly value={asciiDoc} /> </ClickToSelect> </p> - { - this.renderDocumentation() - } + {this.renderDocumentation()} </form> </Modal> - ); + ) } } diff --git a/frontend/components/meta.js b/frontend/components/meta.js index ffe4624c83593a00f4d78485b1286851e81d6d19..840ead75e179a26780e718a8d534949bb946c27e 100644 --- a/frontend/components/meta.js +++ b/frontend/components/meta.js @@ -1,9 +1,9 @@ -import React from 'react'; -import Head from 'next/head'; +import React from 'react' +import Head from 'next/head' const description = `We serve fast and scalable informational images as badges for GitHub, Travis CI, Jenkins, WordPress and many more services. Use them to -track the state of your projects, or for promotional purposes.`; +track the state of your projects, or for promotional purposes.` export default () => ( <Head> @@ -13,6 +13,9 @@ export default () => ( <meta name="description" content={description} /> <link rel="icon" type="image/png" href="favicon.png" /> <link href="/static/main.css" rel="stylesheet" /> - <link href="https://fonts.googleapis.com/css?family=Lekton" rel="stylesheet" /> + <link + href="https://fonts.googleapis.com/css?family=Lekton" + rel="stylesheet" + /> </Head> -); +) diff --git a/frontend/components/search-results.js b/frontend/components/search-results.js index c39cabddf4944d95595dd397036bad9d04e6e226..5343f39659c35c835908642f37b8ffdba19537a1 100644 --- a/frontend/components/search-results.js +++ b/frontend/components/search-results.js @@ -1,14 +1,12 @@ -import React from 'react'; -import { Link } from "react-router-dom"; -import PropTypes from 'prop-types'; -import { BadgeExamples } from './badge-examples'; -import badgeExampleData from '../../badge-examples.json'; -import { prepareExamples, predicateFromQuery } from '../lib/prepare-examples'; -import { baseUri, longCache } from '../constants'; - +import React from 'react' +import { Link } from 'react-router-dom' +import PropTypes from 'prop-types' +import { BadgeExamples } from './badge-examples' +import badgeExampleData from '../../badge-examples.json' +import { prepareExamples, predicateFromQuery } from '../lib/prepare-examples' +import { baseUri, longCache } from '../constants' export default class SearchResults extends React.Component { - static propTypes = { category: PropTypes.string, query: PropTypes.string, @@ -16,8 +14,10 @@ export default class SearchResults extends React.Component { } prepareExamples(category) { - const examples = category ? badgeExampleData.filter(example => example.category.id === category) : badgeExampleData; - return prepareExamples(examples, () => predicateFromQuery(this.props.query)); + const examples = category + ? badgeExampleData.filter(example => example.category.id === category) + : badgeExampleData + return prepareExamples(examples, () => predicateFromQuery(this.props.query)) } renderExamples() { @@ -26,31 +26,33 @@ export default class SearchResults extends React.Component { categories={this.preparedExamples} onClick={this.props.clickHandler} baseUri={baseUri} - longCache={longCache} /> - ); + longCache={longCache} + /> + ) } renderCategoryHeadings() { return this.preparedExamples.map(function(category, i) { return ( - <Link to={'/examples/' + category.category.id} key={category.category.id}> - <h3 id={category.category.id}>{ category.category.name }</h3> + <Link + to={'/examples/' + category.category.id} + key={category.category.id} + > + <h3 id={category.category.id}>{category.category.name}</h3> </Link> ) - }); + }) } render() { - this.preparedExamples = this.prepareExamples(this.props.category); + this.preparedExamples = this.prepareExamples(this.props.category) if (this.props.category) { - return this.renderExamples(); - } else if ((this.props.query == null) || (this.props.query.length === 0)) { - return this.renderCategoryHeadings(); + return this.renderExamples() + } else if (this.props.query == null || this.props.query.length === 0) { + return this.renderCategoryHeadings() } else { - return this.renderExamples(); + return this.renderExamples() } - } - } diff --git a/frontend/components/static-badge-maker.js b/frontend/components/static-badge-maker.js index 1ae584a684541aec925f841db2ef50520031e1dc..0c8b8c73c7f760be5d560623cb6ba2e03edb7f1b 100644 --- a/frontend/components/static-badge-maker.js +++ b/frontend/components/static-badge-maker.js @@ -1,26 +1,31 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { staticBadgeUrl } from '../lib/badge-url'; +import React from 'react' +import PropTypes from 'prop-types' +import { staticBadgeUrl } from '../lib/badge-url' export default class StaticBadgeMaker extends React.Component { static propTypes = { baseUri: PropTypes.string, - }; + } state = { subject: '', status: '', color: '', - }; + } - handleSubmit (e) { - e.preventDefault(); + handleSubmit(e) { + e.preventDefault() - const { baseUri } = this.props; - const { subject, status, color } = this.state; - const badgeUri = staticBadgeUrl(baseUri || window.location.href, subject, status, color); + const { baseUri } = this.props + const { subject, status, color } = this.state + const badgeUri = staticBadgeUrl( + baseUri || window.location.href, + subject, + status, + color + ) - document.location = badgeUri; + document.location = badgeUri } render() { @@ -30,18 +35,24 @@ export default class StaticBadgeMaker extends React.Component { className="short" value={this.state.subject} onChange={event => this.setState({ subject: event.target.value })} - placeholder="subject" /> {} + placeholder="subject" + />{' '} + {} <input className="short" value={this.state.status} onChange={event => this.setState({ status: event.target.value })} - placeholder="status" /> {} + placeholder="status" + />{' '} + {} <input className="short" value={this.state.color} onChange={event => this.setState({ color: event.target.value })} list="default-colors" - placeholder="color" /> {} + placeholder="color" + />{' '} + {} <datalist id="default-colors"> <option value="brightgreen" /> <option value="green" /> @@ -51,9 +62,10 @@ export default class StaticBadgeMaker extends React.Component { <option value="red" /> <option value="lightgrey" /> <option value="blue" /> - </datalist> {} + </datalist>{' '} + {} <button>Make Badge</button> </form> - ); + ) } } diff --git a/frontend/components/suggestion-and-search.js b/frontend/components/suggestion-and-search.js index 5c73f707de19e5e221e03de2aca6e858a681ffbd..2a1c30016cb868db33b1dddada62baeb230e9849 100644 --- a/frontend/components/suggestion-and-search.js +++ b/frontend/components/suggestion-and-search.js @@ -1,9 +1,9 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import fetchPonyfill from 'fetch-ponyfill'; -import debounce from 'lodash.debounce'; -import { Badge } from './badge-examples'; -import resolveUrl from '../lib/resolve-url'; +import React from 'react' +import PropTypes from 'prop-types' +import fetchPonyfill from 'fetch-ponyfill' +import debounce from 'lodash.debounce' +import { Badge } from './badge-examples' +import resolveUrl from '../lib/resolve-url' export default class SuggestionAndSearch extends React.Component { static propTypes = { @@ -11,11 +11,13 @@ export default class SuggestionAndSearch extends React.Component { onBadgeClick: PropTypes.func.isRequired, baseUri: PropTypes.string.isRequired, longCache: PropTypes.bool.isRequired, - }; + } constructor(props) { - super(props); - this.queryChangedDebounced = debounce(props.queryChanged, 50, { leading: true }); + super(props) + this.queryChangedDebounced = debounce(props.queryChanged, 50, { + leading: true, + }) } state = { @@ -23,63 +25,68 @@ export default class SuggestionAndSearch extends React.Component { inProgress: false, projectUrl: null, suggestions: [], - }; + } queryChanged(query) { - const isUri = query.startsWith('https://') || query.startsWith('http://'); + const isUri = query.startsWith('https://') || query.startsWith('http://') this.setState({ isUri, projectUri: isUri ? query : null, - }); + }) - this.queryChangedDebounced(query); + this.queryChangedDebounced(query) } getSuggestions() { this.setState({ inProgress: true }, () => { - const { baseUri } = this.props; - const { projectUri } = this.state; + const { baseUri } = this.props + const { projectUri } = this.state - const url = resolveUrl('/$suggest/v1', baseUri, { url: projectUri }); + const url = resolveUrl('/$suggest/v1', baseUri, { url: projectUri }) - const fetch = window.fetch || fetchPonyfill; + const fetch = window.fetch || fetchPonyfill fetch(url) .then(res => res.json()) .then(json => { - this.setState({ inProgress: false, suggestions: json.badges }); + this.setState({ inProgress: false, suggestions: json.badges }) }) .catch(() => { - this.setState({ inProgress: false, suggestions: [] }); - }); - }); + this.setState({ inProgress: false, suggestions: [] }) + }) + }) } renderSuggestions() { - const { baseUri, longCache } = this.props; - const { suggestions } = this.state; + const { baseUri, longCache } = this.props + const { suggestions } = this.state if (suggestions.length === 0) { - return null; + return null } return ( - <table className="badge"><tbody> - { suggestions.map(({ name, link, badge }, i) => ( - // TODO We need to deal with `link`. - <Badge - key={i} - title={name} - previewUri={badge} - onClick={() => this.props.onBadgeClick({ - title: name, - previewUri: badge, - link, - })} - baseUri={baseUri} - longCache={longCache} /> - ))} - </tbody></table> - ); + <table className="badge"> + <tbody> + {suggestions.map(({ name, link, badge }, i) => ( + // TODO We need to deal with `link`. + <Badge + key={i} + title={name} + previewUri={badge} + onClick={() => + this.props.onBadgeClick({ + title: name, + previewUri: badge, + link, + }) + } + baseUri={baseUri} + longCache={longCache} + /> + ))} + </tbody> + </table> + ) } render() { @@ -88,18 +95,21 @@ export default class SuggestionAndSearch extends React.Component { <form action="javascript:void 0" autoComplete="off"> <input onChange={event => this.queryChanged(event.target.value)} - autofill="off" autoFocus - placeholder="search / project URL" /> + autofill="off" + autoFocus + placeholder="search / project URL" + /> <br /> <button onClick={event => this.getSuggestions(event.target.value)} disabled={this.state.inProgress} - hidden={! this.state.isUri}> + hidden={!this.state.isUri} + > Suggest badges </button> </form> - { this.renderSuggestions() } + {this.renderSuggestions()} </section> - ); + ) } } diff --git a/frontend/components/usage.js b/frontend/components/usage.js index f61a57f8ba5ea6f58a5bcdf6d01fe1381720c0ee..547409e32796b30d5900fd9f4a772fffd8f7f783 100644 --- a/frontend/components/usage.js +++ b/frontend/components/usage.js @@ -1,18 +1,18 @@ -import { Fragment, default as React } from 'react'; -import PropTypes from 'prop-types'; -import StaticBadgeMaker from './static-badge-maker'; -import DynamicBadgeMaker from './dynamic-badge-maker'; -import { staticBadgeUrl } from '../lib/badge-url'; -import { advertisedStyles, logos } from '../../supported-features.json'; +import { Fragment, default as React } from 'react' +import PropTypes from 'prop-types' +import StaticBadgeMaker from './static-badge-maker' +import DynamicBadgeMaker from './dynamic-badge-maker' +import { staticBadgeUrl } from '../lib/badge-url' +import { advertisedStyles, logos } from '../../supported-features.json' export default class Usage extends React.PureComponent { static propTypes = { baseUri: PropTypes.string.isRequired, longCache: PropTypes.bool.isRequired, - }; + } - renderColorExamples () { - const { baseUri, longCache } = this.props; + renderColorExamples() { + const { baseUri, longCache } = this.props const colors = [ 'brightgreen', 'green', @@ -23,33 +23,35 @@ export default class Usage extends React.PureComponent { 'lightgrey', 'blue', 'ff69b4', - ]; + ] return ( <p> - { colors.map((color, i) => ( + {colors.map((color, i) => ( <Fragment key={i}> <img className="badge-img" - src={staticBadgeUrl(baseUri, 'color', color, color, { longCache })} - alt={color} /> {} + src={staticBadgeUrl(baseUri, 'color', color, color, { + longCache, + })} + alt={color} + />{' '} + {} </Fragment> ))} </p> - ); + ) } - renderStyleExamples () { - const { baseUri, longCache } = this.props; + renderStyleExamples() { + const { baseUri, longCache } = this.props return ( <table className="badge-img"> <tbody> - { advertisedStyles.map((style, i) => { - const badgeUri = staticBadgeUrl( - baseUri, - 'style', + {advertisedStyles.map((style, i) => { + const badgeUri = staticBadgeUrl(baseUri, 'style', style, 'green', { + longCache, style, - 'green', - { longCache, style }); + }) return ( <tr key={i}> <td> @@ -59,23 +61,23 @@ export default class Usage extends React.PureComponent { <code>{badgeUri}</code> </td> </tr> - ); + ) })} </tbody> </table> - ); + ) } static renderNamedLogos() { - const renderLogo = logo => <span className="nowrap">{logo}</span>; - const [first, ...rest] = logos; + const renderLogo = logo => <span className="nowrap">{logo}</span> + const [first, ...rest] = logos return [renderLogo(first)].concat( rest.reduce((result, logo) => result.concat([', ', renderLogo(logo)]), []) - ); + ) } render() { - const { baseUri } = this.props; + const { baseUri } = this.props return ( <section> <h2 id="your-badge">Your Badge</h2> @@ -87,7 +89,8 @@ export default class Usage extends React.PureComponent { <p> <code> - {baseUri}/badge/<SUBJECT>-<STATUS>-<COLOR>.svg + {baseUri} + /badge/<SUBJECT>-<STATUS>-<COLOR>.svg </code> </p> <table className="centered"> @@ -122,20 +125,50 @@ export default class Usage extends React.PureComponent { </tbody> </table> - { this.renderColorExamples() } + {this.renderColorExamples()} <h3 id="dynamic-badge">Dynamic</h3> <DynamicBadgeMaker baseUri={baseUri} /> <p> - <code>/badge/dynamic/json.svg?url=<URL>&label=<LABEL>&query=<<a href="https://www.npmjs.com/package/jsonpath" target="_BLANK" title="JSONdata syntax">$.DATA.SUBDATA</a>>&colorB=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX></code> + <code> + /badge/dynamic/json.svg?url=<URL>&label=<LABEL>&query=< + <a + href="https://www.npmjs.com/package/jsonpath" + target="_BLANK" + title="JSONdata syntax" + > + $.DATA.SUBDATA + </a> + >&colorB=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX> + </code> </p> <p> - <code>/badge/dynamic/xml.svg?url=<URL>&label=<LABEL>&query=<<a href="https://www.npmjs.com/package/xpath" target="_BLANK" title="XPath syntax">//data/subdata</a>>&colorB=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX></code> + <code> + /badge/dynamic/xml.svg?url=<URL>&label=<LABEL>&query=< + <a + href="https://www.npmjs.com/package/xpath" + target="_BLANK" + title="XPath syntax" + > + //data/subdata + </a> + >&colorB=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX> + </code> </p> <p> - <code>/badge/dynamic/yaml.svg?url=<URL>&label=<LABEL>&query=<<a href="https://www.npmjs.com/package/jsonpath" target="_BLANK" title="JSONdata syntax">$.DATA.SUBDATA</a>>&colorB=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX></code> + <code> + /badge/dynamic/yaml.svg?url=<URL>&label=<LABEL>&query=< + <a + href="https://www.npmjs.com/package/jsonpath" + target="_BLANK" + title="JSONdata syntax" + > + $.DATA.SUBDATA + </a> + >&colorB=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX> + </code> </p> <hr className="spacing" /> @@ -143,12 +176,14 @@ export default class Usage extends React.PureComponent { <h2 id="styles">Styles</h2> <p> - The following styles are available (flat is the default as of Feb 1st 2015): + The following styles are available (flat is the default as of Feb 1st + 2015): </p> - { this.renderStyleExamples() } + {this.renderStyleExamples()} <p> - Here are a few other parameters you can use: (connecting several with "&" is possible) + Here are a few other parameters you can use: (connecting several with + "&" is possible) </p> <table className="usage"> <tbody> @@ -169,7 +204,11 @@ export default class Usage extends React.PureComponent { <code>?logo=appveyor</code> </td> <td> - Insert one of the named logos from ({this.constructor.renderNamedLogos()}) or <a href="https://simpleicons.org/" target="_BLANK">simple-icons</a> + Insert one of the named logos from ( + {this.constructor.renderNamedLogos()}) or{' '} + <a href="https://simpleicons.org/" target="_BLANK"> + simple-icons + </a> </td> </tr> <tr> @@ -182,7 +221,10 @@ export default class Usage extends React.PureComponent { <td> <code>?logoColor=violet</code> </td> - <td>Set the color of the logo (hex, rgb, rgba, hsl, hsla and css named colors supported)</td> + <td> + Set the color of the logo (hex, rgb, rgba, hsl, hsla and css + named colors supported) + </td> </tr> <tr> <td> @@ -195,36 +237,45 @@ export default class Usage extends React.PureComponent { <code>?link=http://left&link=http://right</code> </td> <td> - Specify what clicking on the left/right of a badge should do (esp. - for social badge style) + Specify what clicking on the left/right of a badge should do + (esp. for social badge style) </td> </tr> <tr> <td> <code>?colorA=abcdef</code> </td> - <td>Set background of the left part (hex, rgb, rgba, hsl, hsla and css named colors supported)</td> + <td> + Set background of the left part (hex, rgb, rgba, hsl, hsla and + css named colors supported) + </td> </tr> <tr> <td> <code>?colorB=fedcba</code> </td> - <td>Set background of the right part (hex, rgb, rgba, hsl, hsla and css named colors supported)</td> + <td> + Set background of the right part (hex, rgb, rgba, hsl, hsla and + css named colors supported) + </td> </tr> <tr> <td> <code>?maxAge=3600</code> </td> - <td>Set the HTTP cache lifetime in secs (values below the default (currently 120 seconds) will be ignored)</td> + <td> + Set the HTTP cache lifetime in secs (values below the default + (currently 120 seconds) will be ignored) + </td> </tr> </tbody> </table> <p> - We support <code>.svg</code>, <code>.json</code>, <code>.png</code> and a - few others, but use them responsibly. + We support <code>.svg</code>, <code>.json</code>, <code>.png</code>{' '} + and a few others, but use them responsibly. </p> </section> - ); + ) } } diff --git a/frontend/constants.js b/frontend/constants.js index 9e03a30f751bb638c50856e823b2cde4bb8fc4d1..007db9888e22678ec8560aab1d04cbdf32ab4319 100644 --- a/frontend/constants.js +++ b/frontend/constants.js @@ -1,9 +1,6 @@ -import envFlag from 'node-env-flag'; +import envFlag from 'node-env-flag' -const baseUri = process.env.BASE_URL; -const longCache = envFlag(process.env.LONG_CACHE, false); +const baseUri = process.env.BASE_URL +const longCache = envFlag(process.env.LONG_CACHE, false) -export { - baseUri, - longCache -} +export { baseUri, longCache } diff --git a/frontend/lib/badge-url.js b/frontend/lib/badge-url.js index 6de3816118b5b51c51d5cdb521e7bb77768ca0ac..5280821c611688a919a16f688c2f023292c10cd8 100644 --- a/frontend/lib/badge-url.js +++ b/frontend/lib/badge-url.js @@ -1,47 +1,54 @@ -import resolveUrl from './resolve-url'; +import resolveUrl from './resolve-url' export default function resolveBadgeUrl(url, baseUrl, options) { - const { longCache, style, queryParams: inQueryParams } = options || {}; - const outQueryParams = Object.assign({}, inQueryParams); + const { longCache, style, queryParams: inQueryParams } = options || {} + const outQueryParams = Object.assign({}, inQueryParams) if (longCache) { - outQueryParams.maxAge = '2592000'; + outQueryParams.maxAge = '2592000' } if (style) { - outQueryParams.style = style; + outQueryParams.style = style } - return resolveUrl(url, baseUrl, outQueryParams); + return resolveUrl(url, baseUrl, outQueryParams) } export function encodeField(s) { - return encodeURIComponent(s.replace(/-/g, '--').replace(/_/g, '__')); + return encodeURIComponent(s.replace(/-/g, '--').replace(/_/g, '__')) } export function staticBadgeUrl(baseUrl, subject, status, color, options) { - const path = [subject, status, color].map(encodeField).join('-'); - return resolveUrl(`/badge/${path}.svg`, baseUrl, options); + const path = [subject, status, color].map(encodeField).join('-') + return resolveUrl(`/badge/${path}.svg`, baseUrl, options) } // Options can include: { prefix, suffix, color, longCache, style, queryParams } -export function dynamicBadgeUrl(baseUrl, datatype, label, dataUrl, query, options = {}) { - const { prefix, suffix, color, queryParams = {}, ...rest } = options; +export function dynamicBadgeUrl( + baseUrl, + datatype, + label, + dataUrl, + query, + options = {} +) { + const { prefix, suffix, color, queryParams = {}, ...rest } = options Object.assign(queryParams, { label, url: dataUrl, query, - }); + }) if (color) { - queryParams.colorB = color; + queryParams.colorB = color } if (prefix) { - queryParams.prefix = prefix; + queryParams.prefix = prefix } if (suffix) { - queryParams.suffix = suffix; + queryParams.suffix = suffix } - const outOptions = Object.assign({ queryParams }, rest); + const outOptions = Object.assign({ queryParams }, rest) - return resolveBadgeUrl(`/badge/dynamic/${datatype}.svg`, baseUrl, outOptions); + return resolveBadgeUrl(`/badge/dynamic/${datatype}.svg`, baseUrl, outOptions) } diff --git a/frontend/lib/badge-url.spec.js b/frontend/lib/badge-url.spec.js index 8d425354e2632eae0f2eb6a26b288378c5a720ed..f71dce3a6d97a524a07d65a07195e49cebca7275 100644 --- a/frontend/lib/badge-url.spec.js +++ b/frontend/lib/badge-url.spec.js @@ -1,61 +1,64 @@ -import { test, given } from 'sazerac'; +import { test, given } from 'sazerac' import { default as resolveBadgeUrl, encodeField, staticBadgeUrl, dynamicBadgeUrl, -} from './badge-url'; +} from './badge-url' const resolveBadgeUrlWithLongCache = (url, baseUrl) => resolveBadgeUrl(url, baseUrl, { longCache: true }) describe('Badge URL functions', function() { test(resolveBadgeUrl, () => { - given('/badge/foo-bar-blue.svg', undefined) - .expect('/badge/foo-bar-blue.svg'); - given('/badge/foo-bar-blue.svg', 'http://example.com') - .expect('http://example.com/badge/foo-bar-blue.svg'); - }); + given('/badge/foo-bar-blue.svg', undefined).expect( + '/badge/foo-bar-blue.svg' + ) + given('/badge/foo-bar-blue.svg', 'http://example.com').expect( + 'http://example.com/badge/foo-bar-blue.svg' + ) + }) test(resolveBadgeUrlWithLongCache, () => { - given('/badge/foo-bar-blue.svg', undefined) - .expect('/badge/foo-bar-blue.svg?maxAge=2592000'); - given('/badge/foo-bar-blue.svg', 'http://example.com') - .expect('http://example.com/badge/foo-bar-blue.svg?maxAge=2592000'); + given('/badge/foo-bar-blue.svg', undefined).expect( + '/badge/foo-bar-blue.svg?maxAge=2592000' + ) + given('/badge/foo-bar-blue.svg', 'http://example.com').expect( + 'http://example.com/badge/foo-bar-blue.svg?maxAge=2592000' + ) }) test(encodeField, () => { - given('foo').expect('foo'); - given('').expect(''); - given('happy go lucky').expect('happy%20go%20lucky'); - given('do-right').expect('do--right'); - given('it_is_a_snake').expect('it__is__a__snake'); - }); + given('foo').expect('foo') + given('').expect('') + given('happy go lucky').expect('happy%20go%20lucky') + given('do-right').expect('do--right') + given('it_is_a_snake').expect('it__is__a__snake') + }) test(staticBadgeUrl, () => { - given('http://img.example.com', 'foo', 'bar', 'blue', { style: 'plastic'}) - .expect('http://img.example.com/badge/foo-bar-blue.svg?style=plastic'); - }); + given('http://img.example.com', 'foo', 'bar', 'blue', { + style: 'plastic', + }).expect('http://img.example.com/badge/foo-bar-blue.svg?style=plastic') + }) test(dynamicBadgeUrl, () => { - const dataUrl = 'http://example.com/foo.json'; - const query = '$.bar'; - const prefix = 'value: '; + const dataUrl = 'http://example.com/foo.json' + const query = '$.bar' + const prefix = 'value: ' - given( - 'http://img.example.com', - 'json', - 'foo', - dataUrl, - query, - { prefix, style: 'plastic' } - ).expect([ - 'http://img.example.com/badge/dynamic/json.svg', - '?label=foo', - `&url=${encodeURIComponent(dataUrl)}`, - `&query=${encodeURIComponent(query)}`, - `&prefix=${encodeURIComponent(prefix)}`, - '&style=plastic', - ].join('')) - }); -}); + given('http://img.example.com', 'json', 'foo', dataUrl, query, { + prefix, + style: 'plastic', + }).expect( + [ + 'http://img.example.com/badge/dynamic/json.svg', + '?label=foo', + `&url=${encodeURIComponent(dataUrl)}`, + `&query=${encodeURIComponent(query)}`, + `&prefix=${encodeURIComponent(prefix)}`, + '&style=plastic', + ].join('') + ) + }) +}) diff --git a/frontend/lib/generate-image-markup.js b/frontend/lib/generate-image-markup.js index ff991a9e0542bcee8bdfda4d611ecb45119b83ef..31b3d022125c06393a856d77bc21b1c8d951885c 100644 --- a/frontend/lib/generate-image-markup.js +++ b/frontend/lib/generate-image-markup.js @@ -1,74 +1,75 @@ export function markdown(badgeUri, link, title) { - const withoutLink = ``; + const withoutLink = `` if (link) { - return `[${withoutLink}](${link})`; + return `[${withoutLink}](${link})` } else { - return withoutLink; + return withoutLink } } export function reStructuredText(badgeUri, link, title) { - let result = `.. image:: ${badgeUri}`; + let result = `.. image:: ${badgeUri}` if (title) { - result += ` :alt: ${title}`; + result += ` :alt: ${title}` } if (link) { - result += ` :target: ${link}`; + result += ` :target: ${link}` } - return result; + return result } function quoteAsciiDocAttribute(attr) { if (typeof attr === 'string') { - const withQuotesEscaped = attr.replace('"', '\\"'); - return `"${withQuotesEscaped}"`; + const withQuotesEscaped = attr.replace('"', '\\"') + return `"${withQuotesEscaped}"` } else if (attr == null) { - return 'None'; + return 'None' } else { - return attr; + return attr } } // lodash.mapvalues is huge! function mapValues(obj, iteratee) { - const result = {}; + const result = {} for (const k in obj) { - result[k] = iteratee(obj[k]); + result[k] = iteratee(obj[k]) } - return result; + return result } export function renderAsciiDocAttributes(positional, named) { // http://asciidoc.org/userguide.html#X21 - const needsQuoting = positional.some(attr => attr.includes(',')) || - Object.keys(named).length > 0; + const needsQuoting = + positional.some(attr => attr.includes(',')) || Object.keys(named).length > 0 if (needsQuoting) { - positional = positional.map(attr => quoteAsciiDocAttribute(attr)); - named = mapValues(named, attr => quoteAsciiDocAttribute(attr)); + positional = positional.map(attr => quoteAsciiDocAttribute(attr)) + named = mapValues(named, attr => quoteAsciiDocAttribute(attr)) } - const items = positional - .concat(Object.entries(named).map(([k, v]) => `${k}=${v}`)); + const items = positional.concat( + Object.entries(named).map(([k, v]) => `${k}=${v}`) + ) if (items.length) { - return `[${items.join(',')}]`; + return `[${items.join(',')}]` } else { - return ''; + return '' } } export function asciiDoc(badgeUri, link, title) { - const positional = title ? [title] : []; - const named = link ? { link } : {}; - const attrs = renderAsciiDocAttributes(positional, named); - return `image:${badgeUri}${attrs}`; + const positional = title ? [title] : [] + const named = link ? { link } : {} + const attrs = renderAsciiDocAttributes(positional, named) + return `image:${badgeUri}${attrs}` } export default function generateAllMarkup(badgeUri, link, title) { // This is a wee bit "clever". It runs each of the three functions on the // parameters provided, and returns the result in an object. - return mapValues( - { markdown, reStructuredText, asciiDoc }, - fn => fn(badgeUri, link, title)); + return mapValues({ markdown, reStructuredText, asciiDoc }, fn => + fn(badgeUri, link, title) + ) } diff --git a/frontend/lib/prepare-examples.js b/frontend/lib/prepare-examples.js index 787d395a1e44d7070b7842a71732470da4444439..4293d2aff7ebe4a7f7b52721054821602a84355f 100644 --- a/frontend/lib/prepare-examples.js +++ b/frontend/lib/prepare-examples.js @@ -1,36 +1,45 @@ -import escapeStringRegexp from 'escape-string-regexp'; +import escapeStringRegexp from 'escape-string-regexp' export function exampleMatchesRegex(example, regex) { - const { title, keywords } = example; - const haystack = [title].concat(keywords).join(' '); - return regex.test(haystack); + const { title, keywords } = example + const haystack = [title].concat(keywords).join(' ') + return regex.test(haystack) } export function predicateFromQuery(query) { if (query) { - const escaped = escapeStringRegexp(query); - const regex = new RegExp(escaped, 'i'); // Case-insensitive. - return example => exampleMatchesRegex(example, regex); + const escaped = escapeStringRegexp(query) + const regex = new RegExp(escaped, 'i') // Case-insensitive. + return example => exampleMatchesRegex(example, regex) } else { - return () => true; + return () => true } } export function mapExamples(categories, iteratee) { - return categories - .map(({ category, examples }) => ({ - category, - examples: iteratee(examples), - })) - // Remove empty categories. - .filter(({ category, examples }) => examples.length > 0); + return ( + categories + .map(({ category, examples }) => ({ + category, + examples: iteratee(examples), + })) + // Remove empty categories. + .filter(({ category, examples }) => examples.length > 0) + ) } export function prepareExamples(categories, predicateProvider) { - let nextKey = 0; - return mapExamples(categories, examples => examples.map(example => Object.assign({ - shouldDisplay: () => predicateProvider()(example), - // Assign each example a unique ID. - key: nextKey++, - }, example))); + let nextKey = 0 + return mapExamples(categories, examples => + examples.map(example => + Object.assign( + { + shouldDisplay: () => predicateProvider()(example), + // Assign each example a unique ID. + key: nextKey++, + }, + example + ) + ) + ) } diff --git a/frontend/lib/prepare-examples.spec.js b/frontend/lib/prepare-examples.spec.js index eedf187f0f8e6e746b754800e83936ad55850bcf..57b61caeaade913459156a99fd789d9d52dfcb7e 100644 --- a/frontend/lib/prepare-examples.spec.js +++ b/frontend/lib/prepare-examples.spec.js @@ -1,20 +1,18 @@ -import { test, given, forCases } from 'sazerac'; -import { predicateFromQuery } from './prepare-examples'; +import { test, given, forCases } from 'sazerac' +import { predicateFromQuery } from './prepare-examples' describe('Badge example functions', function() { - const exampleMatchesQuery = - (example, query) => predicateFromQuery(query)(example); + const exampleMatchesQuery = (example, query) => + predicateFromQuery(query)(example) test(exampleMatchesQuery, () => { - forCases([ - given({ title: 'node version' }, 'npm'), - ]).expect(false); + forCases([given({ title: 'node version' }, 'npm')]).expect(false) forCases([ given({ title: 'node version', keywords: ['npm'] }, 'node'), given({ title: 'node version', keywords: ['npm'] }, 'npm'), // https://github.com/badges/shields/issues/1578 given({ title: 'c++ is the best language' }, 'c++'), - ]).expect(true); - }); -}); + ]).expect(true) + }) +}) diff --git a/frontend/lib/resolve-url.js b/frontend/lib/resolve-url.js index f59f03f0c7c3fc5a132d0eb9badf6bfd741460b2..17103a687238ac58d2882553ef6e8579f493051d 100644 --- a/frontend/lib/resolve-url.js +++ b/frontend/lib/resolve-url.js @@ -2,13 +2,13 @@ // right thing. Previously this was based on url-path, which patched around // the URL API. This caused problems in Firefox 57, but only in the production // build. -import { resolve, parse, format } from 'url'; +import { resolve, parse, format } from 'url' // baseUrl and queryParams are optional. -export default function resolveUrl (url, baseUrl, queryParams) { - const resolved = baseUrl ? resolve(baseUrl, url) : url; - const parsed = parse(resolved, /* parseQueryString */ true); - parsed.query = Object.assign({}, parsed.query, queryParams); - delete parsed.search; - return format(parsed); +export default function resolveUrl(url, baseUrl, queryParams) { + const resolved = baseUrl ? resolve(baseUrl, url) : url + const parsed = parse(resolved, /* parseQueryString */ true) + parsed.query = Object.assign({}, parsed.query, queryParams) + delete parsed.search + return format(parsed) } diff --git a/frontend/lib/resolve-url.spec.js b/frontend/lib/resolve-url.spec.js index f4c824bc14ce426063bdf1cabff7cd7f169aa0fa..25c5ffb9af3f8a1d24c4f6444217e57b2eb5f47e 100644 --- a/frontend/lib/resolve-url.spec.js +++ b/frontend/lib/resolve-url.spec.js @@ -1,5 +1,5 @@ -import { test, given, forCases } from 'sazerac'; -import resolveUrl from './resolve-url'; +import { test, given, forCases } from 'sazerac' +import resolveUrl from './resolve-url' describe('URL resolver', function() { test(resolveUrl, () => { @@ -20,10 +20,10 @@ describe('URL resolver', function() { given('/bar', 'http://foo/'), ]).expect('http://foo/bar') - given('/foo/bar', '/baz', { baz: 'bazinga' }) - .expect('/foo/bar?baz=bazinga'); + given('/foo/bar', '/baz', { baz: 'bazinga' }).expect('/foo/bar?baz=bazinga') - given('/foo/bar?thing=1', undefined, { other: '2' }) - .expect('/foo/bar?thing=1&other=2'); + given('/foo/bar?thing=1', undefined, { other: '2' }).expect( + '/foo/bar?thing=1&other=2' + ) }) -}); +}) diff --git a/lib/all-badge-examples.js b/lib/all-badge-examples.js index f44c5031278564db389709440aaf7689d320838d..79bfab146fe5a6da706218866b249df1ba1871df 100644 --- a/lib/all-badge-examples.js +++ b/lib/all-badge-examples.js @@ -1,6 +1,6 @@ -'use strict'; +'use strict' -const { loadServiceClasses } = require('../services'); +const { loadServiceClasses } = require('../services') const visualStudioTeamServicesDoc = ` <p> @@ -22,7 +22,7 @@ const visualStudioTeamServicesDoc = ` Your badge will then have the form <code>https://img.shields.io/vso/build/TEAM_NAME/PROJECT_ID/BUILD_DEFINITION_ID</code>. </p> -`; +` const websiteDoc = ` <p> @@ -84,7 +84,7 @@ const websiteDoc = ` </td></tr> </tbody></table> </p> -`; +` const githubDoc = ` <p> @@ -94,13 +94,13 @@ const githubDoc = ` <a href="https://img.shields.io/github-auth">going to this page</a> to add Shields as a GitHub application on your GitHub account. </p> -`; +` const bugzillaDoc = ` <p> If your Bugzilla badge errors, it might be because you are trying to load a private bug. </p> -`; +` const jiraSprintCompletionDoc = ` <p> @@ -108,7 +108,7 @@ const jiraSprintCompletionDoc = ` right click on your sprint name and get the value of <code>data-sprint-id</code>. </p> -`; +` const allBadgeExamples = [ { @@ -1754,33 +1754,33 @@ const allBadgeExamples = [ }, ], }, -]; +] function findCategory(wantedCategory) { return allBadgeExamples.find( thisCat => thisCat.category.id === wantedCategory - ); + ) } function loadExamples() { loadServiceClasses().forEach(ServiceClass => { - const category = findCategory(ServiceClass.category); + const category = findCategory(ServiceClass.category) if (category === undefined) { if (ServiceClass.category === 'debug') { // we don't want to show debug services on the examples page - return; + return } throw Error( `Unknown category ${ServiceClass.category} referenced in ${ ServiceClass.name }` - ); + ) } - const prepared = ServiceClass.prepareExamples(); - category.examples = category.examples.concat(prepared); - }); + const prepared = ServiceClass.prepareExamples() + category.examples = category.examples.concat(prepared) + }) } -loadExamples(); +loadExamples() -module.exports = allBadgeExamples; -module.exports.findCategory = findCategory; +module.exports = allBadgeExamples +module.exports.findCategory = findCategory diff --git a/lib/all-badge-examples.spec.js b/lib/all-badge-examples.spec.js index eb3dc77406b2d3c00fa4f5d15e8d9586f4857d71..fd4758959fbf6bca4f493442b9adbb32f5698e0f 100644 --- a/lib/all-badge-examples.spec.js +++ b/lib/all-badge-examples.spec.js @@ -1,15 +1,16 @@ -'use strict'; +'use strict' -const { expect } = require('chai'); +const { expect } = require('chai') -const allBadgeExamples = require('./all-badge-examples'); +const allBadgeExamples = require('./all-badge-examples') -describe('The badge examples', function () { - it('should include AppVeyor, which is added automatically', function () { - const { examples } = allBadgeExamples.findCategory('build'); +describe('The badge examples', function() { + it('should include AppVeyor, which is added automatically', function() { + const { examples } = allBadgeExamples.findCategory('build') - const appVeyorBuildExamples = examples.filter(ex => ex.title.includes('AppVeyor')) - .filter(ex => ! ex.title.includes('tests')); + const appVeyorBuildExamples = examples + .filter(ex => ex.title.includes('AppVeyor')) + .filter(ex => !ex.title.includes('tests')) expect(appVeyorBuildExamples).to.deep.equal([ { @@ -24,6 +25,6 @@ describe('The badge examples', function () { exampleUri: undefined, documentation: undefined, }, - ]); - }); -}); + ]) + }) +}) diff --git a/lib/analytics.js b/lib/analytics.js index ecff6ba7b7bd2f06e69aa583085ea0d5e29e5dc9..3f4c88c68f4883f1785a1e7c74a54c5b6d0991c2 100644 --- a/lib/analytics.js +++ b/lib/analytics.js @@ -1,137 +1,141 @@ -'use strict'; +'use strict' -const fs = require('fs'); +const fs = require('fs') // We can either use a process-wide object regularly saved to a JSON file, // or a Redis equivalent (for multi-process / when the filesystem is unreliable. -let redis; -let useRedis = false; +let redis +let useRedis = false if (process.env.REDISTOGO_URL) { - const redisToGo = require('url').parse(process.env.REDISTOGO_URL); - redis = require('redis').createClient(redisToGo.port, redisToGo.hostname); - redis.auth(redisToGo.auth.split(':')[1]); - useRedis = true; + const redisToGo = require('url').parse(process.env.REDISTOGO_URL) + redis = require('redis').createClient(redisToGo.port, redisToGo.hostname) + redis.auth(redisToGo.auth.split(':')[1]) + useRedis = true } -let analytics = {}; -let autosaveIntervalId; +let analytics = {} +let autosaveIntervalId -const analyticsPath = process.env.SHIELDS_ANALYTICS_FILE || './analytics.json'; +const analyticsPath = process.env.SHIELDS_ANALYTICS_FILE || './analytics.json' function performAutosave() { - const contents = JSON.stringify(analytics); + const contents = JSON.stringify(analytics) if (useRedis) { - redis.set(analyticsPath, contents); + redis.set(analyticsPath, contents) } else { - fs.writeFileSync(analyticsPath, contents); + fs.writeFileSync(analyticsPath, contents) } } function scheduleAutosaving() { - const analyticsAutoSavePeriod = 10000; - autosaveIntervalId = setInterval(performAutosave, analyticsAutoSavePeriod); + const analyticsAutoSavePeriod = 10000 + autosaveIntervalId = setInterval(performAutosave, analyticsAutoSavePeriod) } // For a clean shutdown. function cancelAutosaving() { if (autosaveIntervalId) { - clearInterval(autosaveIntervalId); - autosaveIntervalId = null; + clearInterval(autosaveIntervalId) + autosaveIntervalId = null } - performAutosave(); + performAutosave() } function defaultAnalytics() { - const analytics = Object.create(null); + const analytics = Object.create(null) // In case something happens on the 36th. - analytics.vendorMonthly = new Array(36); - resetMonthlyAnalytics(analytics.vendorMonthly); - analytics.rawMonthly = new Array(36); - resetMonthlyAnalytics(analytics.rawMonthly); - analytics.vendorFlatMonthly = new Array(36); - resetMonthlyAnalytics(analytics.vendorFlatMonthly); - analytics.rawFlatMonthly = new Array(36); - resetMonthlyAnalytics(analytics.rawFlatMonthly); - analytics.vendorFlatSquareMonthly = new Array(36); - resetMonthlyAnalytics(analytics.vendorFlatSquareMonthly); - analytics.rawFlatSquareMonthly = new Array(36); - resetMonthlyAnalytics(analytics.rawFlatSquareMonthly); - return analytics; + analytics.vendorMonthly = new Array(36) + resetMonthlyAnalytics(analytics.vendorMonthly) + analytics.rawMonthly = new Array(36) + resetMonthlyAnalytics(analytics.rawMonthly) + analytics.vendorFlatMonthly = new Array(36) + resetMonthlyAnalytics(analytics.vendorFlatMonthly) + analytics.rawFlatMonthly = new Array(36) + resetMonthlyAnalytics(analytics.rawFlatMonthly) + analytics.vendorFlatSquareMonthly = new Array(36) + resetMonthlyAnalytics(analytics.vendorFlatSquareMonthly) + analytics.rawFlatSquareMonthly = new Array(36) + resetMonthlyAnalytics(analytics.rawFlatSquareMonthly) + return analytics } function load() { - const defaultAnalyticsObject = defaultAnalytics(); + const defaultAnalyticsObject = defaultAnalytics() if (useRedis) { redis.get(analyticsPath, function(err, value) { if (err == null && value != null) { // if/try/return trick: // if error, then the rest of the function is run. try { - analytics = JSON.parse(value); + analytics = JSON.parse(value) // Extend analytics with a new value. for (const key in defaultAnalyticsObject) { if (!(key in analytics)) { - analytics[key] = defaultAnalyticsObject[key]; + analytics[key] = defaultAnalyticsObject[key] } } - return; - } catch(e) { - console.error('Invalid Redis analytics, resetting.'); - console.error(e); + return + } catch (e) { + console.error('Invalid Redis analytics, resetting.') + console.error(e) } } - analytics = defaultAnalyticsObject; - }); + analytics = defaultAnalyticsObject + }) } else { // Not using Redis. try { - analytics = JSON.parse(fs.readFileSync(analyticsPath)); + analytics = JSON.parse(fs.readFileSync(analyticsPath)) // Extend analytics with a new value. for (const key in defaultAnalyticsObject) { if (!(key in analytics)) { - analytics[key] = defaultAnalyticsObject[key]; + analytics[key] = defaultAnalyticsObject[key] } } - } catch(e) { + } catch (e) { if (e.code !== 'ENOENT') { - console.error('Invalid JSON file for analytics, resetting.'); - console.error(e); + console.error('Invalid JSON file for analytics, resetting.') + console.error(e) } - analytics = defaultAnalyticsObject; + analytics = defaultAnalyticsObject } } } -let lastDay = (new Date()).getDate(); +let lastDay = new Date().getDate() function resetMonthlyAnalytics(monthlyAnalytics) { for (let i = 0; i < monthlyAnalytics.length; i++) { - monthlyAnalytics[i] = 0; + monthlyAnalytics[i] = 0 } } function incrMonthlyAnalytics(monthlyAnalytics) { try { - const currentDay = (new Date()).getDate(); + const currentDay = new Date().getDate() // If we changed month, reset empty days. while (lastDay !== currentDay) { // Assumption: at least a hit a month. - lastDay = (lastDay + 1) % monthlyAnalytics.length; - monthlyAnalytics[lastDay] = 0; + lastDay = (lastDay + 1) % monthlyAnalytics.length + monthlyAnalytics[lastDay] = 0 } - monthlyAnalytics[currentDay]++; - } catch(e) { console.error(e.stack); } + monthlyAnalytics[currentDay]++ + } catch (e) { + console.error(e.stack) + } } function noteRequest(queryParams, match) { - incrMonthlyAnalytics(analytics.vendorMonthly); + incrMonthlyAnalytics(analytics.vendorMonthly) if (queryParams.style === 'flat') { - incrMonthlyAnalytics(analytics.vendorFlatMonthly); + incrMonthlyAnalytics(analytics.vendorFlatMonthly) } else if (queryParams.style === 'flat-square') { - incrMonthlyAnalytics(analytics.vendorFlatSquareMonthly); + incrMonthlyAnalytics(analytics.vendorFlatSquareMonthly) } } -function setRoutes (server) { - server.ajax.on('analytics/v1', (json, end) => { end(analytics); }); +function setRoutes(server) { + server.ajax.on('analytics/v1', (json, end) => { + end(analytics) + }) } module.exports = { @@ -139,5 +143,5 @@ module.exports = { scheduleAutosaving, cancelAutosaving, noteRequest, - setRoutes -}; + setRoutes, +} diff --git a/lib/badge-cli.js b/lib/badge-cli.js index 2cc55d998ac4b631a162c57ea9ddd3c4a0f9d92e..7dd2b1fac4a0d375cd296eef715ea40439f9a03b 100755 --- a/lib/badge-cli.js +++ b/lib/badge-cli.js @@ -1,87 +1,92 @@ #!/usr/bin/env node -'use strict'; +'use strict' -const { PDFKitTextMeasurer } = require('./text-measurer'); -const { makeBadge } = require('./make-badge'); -const svg2img = require('./svg-to-img'); -const colorscheme = require('./colorscheme.json'); -const defaults = require('./defaults'); +const { PDFKitTextMeasurer } = require('./text-measurer') +const { makeBadge } = require('./make-badge') +const svg2img = require('./svg-to-img') +const colorscheme = require('./colorscheme.json') +const defaults = require('./defaults') if (process.argv.length < 4) { - console.log('Usage: badge subject status [:colorscheme] [.output] [@style]'); - console.log('Or: badge subject status right-color [left-color] [.output] [@style]'); - console.log(); - console.log(' colorscheme: one of ' - + Object.keys(colorscheme).join(', ') + '.'); - console.log(' left-color, right-color:'); - console.log(' #xxx (three hex digits)'); - console.log(' #xxxxxx (six hex digits)'); - console.log(' color (CSS color)'); - console.log(' output:'); - console.log(' svg, png, jpg, or gif'); - console.log(); - console.log('Eg: badge cactus grown :green @flat'); - console.log(); - process.exit(); + console.log('Usage: badge subject status [:colorscheme] [.output] [@style]') + console.log( + 'Or: badge subject status right-color [left-color] [.output] [@style]' + ) + console.log() + console.log( + ' colorscheme: one of ' + Object.keys(colorscheme).join(', ') + '.' + ) + console.log(' left-color, right-color:') + console.log(' #xxx (three hex digits)') + console.log(' #xxxxxx (six hex digits)') + console.log(' color (CSS color)') + console.log(' output:') + console.log(' svg, png, jpg, or gif') + console.log() + console.log('Eg: badge cactus grown :green @flat') + console.log() + process.exit() } -const fontPath = process.env.FONT_PATH || defaults.font.path; +const fontPath = process.env.FONT_PATH || defaults.font.path // Find a format specifier. -let format = 'svg'; -let style = ''; +let format = 'svg' +let style = '' for (let i = 4; i < process.argv.length; i++) { if (process.argv[i][0] === '.') { - format = process.argv[i].slice(1); - process.argv.splice(i, 1); - continue; + format = process.argv[i].slice(1) + process.argv.splice(i, 1) + continue } if (process.argv[i][0] === '@') { - style = process.argv[i].slice(1); - process.argv.splice(i, 1); - continue; + style = process.argv[i].slice(1) + process.argv.splice(i, 1) + continue } } -const subject = process.argv[2]; -const status = process.argv[3]; -let color = process.argv[4] || ':green'; -const colorA = process.argv[5]; +const subject = process.argv[2] +const status = process.argv[3] +let color = process.argv[4] || ':green' +const colorA = process.argv[5] -const badgeData = {text: [subject, status], format: format}; +const badgeData = { text: [subject, status], format: format } if (style) { - badgeData.template = style; + badgeData.template = style } if (color[0] === ':') { - color = color.slice(1); + color = color.slice(1) if (colorscheme[color] == null) { // Colorscheme not found. - console.error('Invalid color scheme.'); - process.exit(1); + console.error('Invalid color scheme.') + process.exit(1) } - badgeData.colorscheme = color; + badgeData.colorscheme = color } else { - badgeData.colorB = color; - if (colorA) { badgeData.colorA = colorA; } + badgeData.colorB = color + if (colorA) { + badgeData.colorA = colorA + } } async function main() { // The widths are going to be off if Helvetica-Bold is used, though this // should print a warning. - const measurer = new PDFKitTextMeasurer(fontPath, 'Helvetica-Bold'); - const svg = makeBadge(measurer, badgeData); + const measurer = new PDFKitTextMeasurer(fontPath, 'Helvetica-Bold') + const svg = makeBadge(measurer, badgeData) if (/png|jpg|gif/.test(format)) { - const data = await svg2img(svg, format); - process.stdout.write(data); + const data = await svg2img(svg, format) + process.stdout.write(data) } else { - console.log(svg); + console.log(svg) } } -(async () => { +;(async () => { try { await main() } catch (e) { diff --git a/lib/badge-cli.spec.js b/lib/badge-cli.spec.js index 8e99ac7704b52dfab3d60c2bbdb646edc94380f6..8464633e8e94e005a0df6314946d86726d1bdf93 100644 --- a/lib/badge-cli.spec.js +++ b/lib/badge-cli.spec.js @@ -1,53 +1,55 @@ -'use strict'; +'use strict' -const { expect } = require('chai'); -const isPng = require('is-png'); -const isSvg = require('is-svg'); -const { spawn } = require('child-process-promise'); +const { expect } = require('chai') +const isPng = require('is-png') +const isSvg = require('is-svg') +const { spawn } = require('child-process-promise') // https://github.com/badges/shields/pull/1419#discussion_r159957055 -require('./register-chai-plugins.spec'); +require('./register-chai-plugins.spec') -function runCli (args) { +function runCli(args) { return spawn('node', ['lib/badge-cli.js', ...args], { capture: ['stdout'] }) } -describe('The CLI', function () { - it('should provide a help message', async function () { - const { stdout } = await runCli([]); - expect(stdout).to.startWith('Usage'); - }); +describe('The CLI', function() { + it('should provide a help message', async function() { + const { stdout } = await runCli([]) + expect(stdout).to.startWith('Usage') + }) - it('should produce default badges', async function () { - const { stdout } = await runCli(['cactus', 'grown']); + it('should produce default badges', async function() { + const { stdout } = await runCli(['cactus', 'grown']) expect(stdout) .to.satisfy(isSvg) .and.to.include('cactus') - .and.to.include('grown'); - }); + .and.to.include('grown') + }) - it('should produce colorschemed badges', async function () { - const { stdout } = await runCli(['cactus', 'grown', ':green']); - expect(stdout).to.satisfy(isSvg); - }); + it('should produce colorschemed badges', async function() { + const { stdout } = await runCli(['cactus', 'grown', ':green']) + expect(stdout).to.satisfy(isSvg) + }) - it('should produce right-color badges', async function () { - const { stdout } = await runCli(['cactus', 'grown', '#abcdef']); + it('should produce right-color badges', async function() { + const { stdout } = await runCli(['cactus', 'grown', '#abcdef']) expect(stdout) .to.satisfy(isSvg) - .and.to.include('#abcdef'); - }); + .and.to.include('#abcdef') + }) - it('should produce PNG badges', async function () { - const child = runCli(['cactus', 'grown', '.png']); + it('should produce PNG badges', async function() { + const child = runCli(['cactus', 'grown', '.png']) // The buffering done by `child-process-promise` doesn't seem correctly to // handle binary data. - let chunk; - child.childProcess.stdout.once('data', data => { chunk = data; }); + let chunk + child.childProcess.stdout.once('data', data => { + chunk = data + }) - await child; + await child - expect(chunk).to.satisfy(isPng); - }); -}); + expect(chunk).to.satisfy(isPng) + }) +}) diff --git a/lib/badge-data.js b/lib/badge-data.js index 8ac9f7db97b8dba21808edfb78f45fd970c5bf61..68abf42f92108193d0f95857f05293d4bd5f8445 100644 --- a/lib/badge-data.js +++ b/lib/badge-data.js @@ -1,109 +1,120 @@ -'use strict'; +'use strict' -const isCSSColor = require('is-css-color'); -const logos = require('./load-logos')(); -const simpleIcons = require('./load-simple-icons')(); -const { - svg2base64, - isDataUri, -} = require('./logo-helper'); -const colorschemes = require('./colorscheme.json'); +const isCSSColor = require('is-css-color') +const logos = require('./load-logos')() +const simpleIcons = require('./load-simple-icons')() +const { svg2base64, isDataUri } = require('./logo-helper') +const colorschemes = require('./colorscheme.json') function toArray(val) { if (val === undefined) { - return []; + return [] } else if (Object(val) instanceof Array) { - return val; + return val } else { - return [val]; + return [val] } } function prependPrefix(s, prefix) { if (s === undefined) { - return undefined; + return undefined } - s = '' + s; + s = '' + s if (s.startsWith(prefix)) { - return s; + return s } else { - return prefix + s; + return prefix + s } } -function isHexColor (s = ''){ - return /^([\da-f]{3}){1,2}$/i.test(s); +function isHexColor(s = '') { + return /^([\da-f]{3}){1,2}$/i.test(s) } function makeColor(color) { if (isHexColor(color)) { - return '#' + color; - } else if (colorschemes[color] !== undefined){ - return colorschemes[color].colorB; - } else if (isCSSColor(color)){ - return color; + return '#' + color + } else if (colorschemes[color] !== undefined) { + return colorschemes[color].colorB + } else if (isCSSColor(color)) { + return color } else { - return undefined; + return undefined } } function makeColorB(defaultColor, overrides) { - return makeColor(overrides.colorB || defaultColor); + return makeColor(overrides.colorB || defaultColor) } function setBadgeColor(badgeData, color) { if (isHexColor(color)) { - badgeData.colorB = '#' + color; - delete badgeData.colorscheme; - } else if (colorschemes[color] !== undefined){ - badgeData.colorscheme = color; - delete badgeData.colorB; - } else if (isCSSColor(color)){ - badgeData.colorB = color; - delete badgeData.colorscheme; + badgeData.colorB = '#' + color + delete badgeData.colorscheme + } else if (colorschemes[color] !== undefined) { + badgeData.colorscheme = color + delete badgeData.colorB + } else if (isCSSColor(color)) { + badgeData.colorB = color + delete badgeData.colorscheme } else { - badgeData.colorscheme = 'red'; - delete badgeData.colorB; + badgeData.colorscheme = 'red' + delete badgeData.colorB } - return badgeData; + return badgeData } function makeLabel(defaultLabel, overrides) { - return '' + (overrides.label === undefined ? defaultLabel || '' : overrides.label); + return ( + '' + (overrides.label === undefined ? defaultLabel || '' : overrides.label) + ) } -function getShieldsIcon(icon = '', color = ''){ - icon = typeof icon === 'string' ? icon.toLowerCase() : ''; - if (!logos[icon]){ - return undefined; +function getShieldsIcon(icon = '', color = '') { + icon = typeof icon === 'string' ? icon.toLowerCase() : '' + if (!logos[icon]) { + return undefined } - color = makeColor(color); - return color ? logos[icon].svg.replace(/fill="(.+?)"/g, `fill="${color}"`) : logos[icon].base64; + color = makeColor(color) + return color + ? logos[icon].svg.replace(/fill="(.+?)"/g, `fill="${color}"`) + : logos[icon].base64 } -function getSimpleIcon(icon = '', color = null){ - icon = typeof icon === 'string' ? icon.toLowerCase().replace(/ /g, '-') : ''; - if (!simpleIcons[icon]){ - return undefined; +function getSimpleIcon(icon = '', color = null) { + icon = typeof icon === 'string' ? icon.toLowerCase().replace(/ /g, '-') : '' + if (!simpleIcons[icon]) { + return undefined } - color = makeColor(color); - return color ? simpleIcons[icon].svg.replace('<svg', `<svg fill="${color}"`) : simpleIcons[icon].base64; + color = makeColor(color) + return color + ? simpleIcons[icon].svg.replace('<svg', `<svg fill="${color}"`) + : simpleIcons[icon].base64 } function makeLogo(defaultNamedLogo, overrides) { - if (overrides.logo === undefined){ - return svg2base64(getShieldsIcon(defaultNamedLogo, overrides.logoColor) || getSimpleIcon(defaultNamedLogo, overrides.logoColor)); + if (overrides.logo === undefined) { + return svg2base64( + getShieldsIcon(defaultNamedLogo, overrides.logoColor) || + getSimpleIcon(defaultNamedLogo, overrides.logoColor) + ) } // +'s are replaced with spaces when used in query params, this returns them to +'s, then removes remaining whitespace - #1546 - const maybeDataUri = prependPrefix(overrides.logo, 'data:').replace(/ /g, '+').replace(/\s/g, ''); + const maybeDataUri = prependPrefix(overrides.logo, 'data:') + .replace(/ /g, '+') + .replace(/\s/g, '') if (isDataUri(maybeDataUri)) { - return maybeDataUri; + return maybeDataUri } else { - return svg2base64(getShieldsIcon(overrides.logo, overrides.logoColor) || getSimpleIcon(overrides.logo, overrides.logoColor)); + return svg2base64( + getShieldsIcon(overrides.logo, overrides.logoColor) || + getSimpleIcon(overrides.logo, overrides.logoColor) + ) } } @@ -133,7 +144,7 @@ function makeBadgeData(defaultLabel, overrides) { links: toArray(overrides.link), colorA: makeColor(overrides.colorA), colorB: makeColor(overrides.colorB), - }; + } } module.exports = { @@ -145,5 +156,5 @@ module.exports = { makeBadgeData, makeColor, makeColorB, - setBadgeColor -}; + setBadgeColor, +} diff --git a/lib/badge-data.spec.js b/lib/badge-data.spec.js index 75fe9d885895c8b1d427381223eaeb99d0590a38..5654f38d801475531803e9f21150f7be6433de2b 100644 --- a/lib/badge-data.spec.js +++ b/lib/badge-data.spec.js @@ -1,7 +1,7 @@ -'use strict'; +'use strict' -const { expect } = require('chai'); -const { test, given, forCases } = require('sazerac'); +const { expect } = require('chai') +const { test, given, forCases } = require('sazerac') const { prependPrefix, isHexColor, @@ -9,35 +9,30 @@ const { makeLogo, makeBadgeData, makeColor, - setBadgeColor -} = require('./badge-data'); + setBadgeColor, +} = require('./badge-data') describe('Badge data helpers', function() { test(prependPrefix, () => { - given('data:image/svg+xml;base64,PHN2ZyB4bWxu', 'data:').expect('data:image/svg+xml;base64,PHN2ZyB4bWxu'); - given('foobar', 'data:').expect('data:foobar'); - given(undefined, 'data:').expect(undefined); - }); + given('data:image/svg+xml;base64,PHN2ZyB4bWxu', 'data:').expect( + 'data:image/svg+xml;base64,PHN2ZyB4bWxu' + ) + given('foobar', 'data:').expect('data:foobar') + given(undefined, 'data:').expect(undefined) + }) test(isHexColor, () => { - forCases([ - given('f00bae'), - given('4c1'), - ]).expect(true); - forCases([ - given('f00bar'), - given(''), - given(undefined), - ]).expect(false); - }); + forCases([given('f00bae'), given('4c1')]).expect(true) + forCases([given('f00bar'), given(''), given(undefined)]).expect(false) + }) test(makeLabel, () => { - given('my badge', {}).expect('my badge'); - given('my badge', { label: 'no, my badge' }).expect('no, my badge'); - given('my badge', { label: false }).expect('false'); - given('my badge', { label: 0 }).expect('0'); - given('my badge', { label: '' }).expect(''); - }); + given('my badge', {}).expect('my badge') + given('my badge', { label: 'no, my badge' }).expect('no, my badge') + given('my badge', { label: false }).expect('false') + given('my badge', { label: 0 }).expect('0') + given('my badge', { label: '' }).expect('') + }) test(makeLogo, () => { forCases([ @@ -45,14 +40,15 @@ describe('Badge data helpers', function() { given('gratipay', { logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu' }), given('gratipay', { logo: 'data:image/svg xml;base64,PHN2ZyB4bWxu' }), given('gratipay', { logo: 'data:image/svg+xml;base64,PHN2ZyB\n4bWxu' }), - ]).expect('data:image/svg+xml;base64,PHN2ZyB4bWxu'); - forCases([ - given('gratipay', { logo: '' }), - given(undefined, {}), - ]).expect(undefined); - given('gratipay', {}) - .assert('should not be empty', v => expect(v).not.to.be.empty); - }); + ]).expect('data:image/svg+xml;base64,PHN2ZyB4bWxu') + forCases([given('gratipay', { logo: '' }), given(undefined, {})]).expect( + undefined + ) + given('gratipay', {}).assert( + 'should not be empty', + v => expect(v).not.to.be.empty + ) + }) test(makeBadgeData, () => { given('my badge', { @@ -74,26 +70,38 @@ describe('Badge data helpers', function() { links: ['https://example.com/'], colorA: '#007ec6', colorB: '#f00bae', - }); - }); + }) + }) test(makeColor, () => { - given('red').expect('#e05d44'); - given('blue').expect('#007ec6'); - given('4c1').expect('#4c1'); - given('f00f00').expect('#f00f00'); - given('papayawhip').expect('papayawhip'); - given('purple').expect('purple'); - }); + given('red').expect('#e05d44') + given('blue').expect('#007ec6') + given('4c1').expect('#4c1') + given('f00f00').expect('#f00f00') + given('papayawhip').expect('papayawhip') + given('purple').expect('purple') + }) test(setBadgeColor, () => { - given({}, 'red').expect({ colorscheme: 'red' }); - given({}, 'f00f00').expect({ colorB: '#f00f00' }); - given({ colorB: '#f00f00', colorscheme: 'blue' }, 'red').expect({ colorscheme: 'red' }); - given({ colorB: '#f00f00', colorscheme: 'blue' }, 'blue').expect({ colorscheme: 'blue' }); - given({ colorB: '#f00f00', colorscheme: 'blue' }, 'papayawhip').expect({ colorB: 'papayawhip' }); - given({ colorB: '#f00f00', colorscheme: 'blue' }, 'purple').expect({ colorB: 'purple' }); - given({ colorB: '#b00b00', colorscheme: 'blue' }, '4c1').expect({ colorB: '#4c1' }); - given({ colorB: '#b00b00', colorscheme: 'blue' }, 'f00f00').expect({ colorB: '#f00f00' }); - }); -}); + given({}, 'red').expect({ colorscheme: 'red' }) + given({}, 'f00f00').expect({ colorB: '#f00f00' }) + given({ colorB: '#f00f00', colorscheme: 'blue' }, 'red').expect({ + colorscheme: 'red', + }) + given({ colorB: '#f00f00', colorscheme: 'blue' }, 'blue').expect({ + colorscheme: 'blue', + }) + given({ colorB: '#f00f00', colorscheme: 'blue' }, 'papayawhip').expect({ + colorB: 'papayawhip', + }) + given({ colorB: '#f00f00', colorscheme: 'blue' }, 'purple').expect({ + colorB: 'purple', + }) + given({ colorB: '#b00b00', colorscheme: 'blue' }, '4c1').expect({ + colorB: '#4c1', + }) + given({ colorB: '#b00b00', colorscheme: 'blue' }, 'f00f00').expect({ + colorB: '#f00f00', + }) + }) +}) diff --git a/lib/color-formatters.js b/lib/color-formatters.js index c54dadb6dda8d47cc4f84dd81ba488d4b6e1b4f6..16b417fc5bb1faff50a6923214c50c7240a89749 100644 --- a/lib/color-formatters.js +++ b/lib/color-formatters.js @@ -2,67 +2,67 @@ * Commonly-used functions for determining the colour to use for a badge, * including colours based off download count, version number, etc. */ -'use strict'; +'use strict' -const moment = require('moment'); +const moment = require('moment') function version(version) { - if (typeof(version) !== 'string' && typeof(version) !== 'number') { - throw new Error(`Can't generate a version color for ${version}`); + if (typeof version !== 'string' && typeof version !== 'number') { + throw new Error(`Can't generate a version color for ${version}`) } - version = '' + version; - let first = version[0]; + version = '' + version + let first = version[0] if (first === 'v') { - first = version[1]; + first = version[1] } if (first === '0' || /alpha|beta|snapshot|dev|pre/i.test(version)) { - return 'orange'; + return 'orange' } else { - return 'blue'; + return 'blue' } } function downloadCount(downloads) { - return floorCount(downloads, 10, 100, 1000); + return floorCount(downloads, 10, 100, 1000) } function coveragePercentage(percentage) { - return floorCount(percentage, 80, 90, 100); + return floorCount(percentage, 80, 90, 100) } function floorCount(value, yellow, yellowgreen, green) { if (value <= 0) { - return 'red'; + return 'red' } else if (value < yellow) { - return 'yellow'; + return 'yellow' } else if (value < yellowgreen) { - return 'yellowgreen'; + return 'yellowgreen' } else if (value < green) { - return 'green'; + return 'green' } else { - return 'brightgreen'; + return 'brightgreen' } } function letterScore(score) { if (score === 'A') { - return 'brightgreen'; + return 'brightgreen' } else if (score === 'B') { - return 'green'; + return 'green' } else if (score === 'C') { - return 'yellowgreen'; + return 'yellowgreen' } else if (score === 'D') { - return 'yellow'; + return 'yellow' } else if (score === 'E') { - return 'orange'; + return 'orange' } else { - return 'red'; + return 'red' } } function colorScale(steps, colors, reversed) { if (steps === undefined) { - throw Error('When invoking colorScale, steps should be provided.'); + throw Error('When invoking colorScale, steps should be provided.') } const defaultColors = { @@ -71,37 +71,39 @@ function colorScale(steps, colors, reversed) { 3: ['red', 'yellow', 'green', 'brightgreen'], 4: ['red', 'yellow', 'yellowgreen', 'green', 'brightgreen'], 5: ['red', 'orange', 'yellow', 'yellowgreen', 'green', 'brightgreen'], - }; + } if (typeof colors === 'undefined') { if (steps.length in defaultColors) { - colors = defaultColors[steps.length]; + colors = defaultColors[steps.length] } else { - throw Error(`No default colors for ${steps.length} steps.`); + throw Error(`No default colors for ${steps.length} steps.`) } } if (steps.length !== colors.length - 1) { - throw Error('When colors are provided, there should be n + 1 colors for n steps.'); + throw Error( + 'When colors are provided, there should be n + 1 colors for n steps.' + ) } if (reversed) { - colors = Array.from(colors).reverse(); + colors = Array.from(colors).reverse() } return value => { - const stepIndex = steps.findIndex(step => value < step); + const stepIndex = steps.findIndex(step => value < step) // For the final step, stepIndex is -1, so in all cases this expression // works swimmingly. - return colors.slice(stepIndex)[0]; - }; + return colors.slice(stepIndex)[0] + } } function age(date) { - const colorByAge = colorScale([7, 30, 180, 365, 730], undefined, true); - const daysElapsed = moment().diff(moment(date), 'days'); - return colorByAge(daysElapsed); + const colorByAge = colorScale([7, 30, 180, 365, 730], undefined, true) + const daysElapsed = moment().diff(moment(date), 'days') + return colorByAge(daysElapsed) } module.exports = { @@ -111,5 +113,5 @@ module.exports = { floorCount, letterScore, colorScale, - age -}; + age, +} diff --git a/lib/color-formatters.spec.js b/lib/color-formatters.spec.js index fe4a0370b73b6a96a1f7419356cd809f4d30e56b..af632b51b0be02dc9c0e951c06baec5c5886034a 100644 --- a/lib/color-formatters.spec.js +++ b/lib/color-formatters.spec.js @@ -1,77 +1,85 @@ -'use strict'; +'use strict' -const { test, given, forCases } = require('sazerac'); -const { expect } = require('chai'); +const { test, given, forCases } = require('sazerac') +const { expect } = require('chai') const { coveragePercentage, colorScale, letterScore, age, - version -} = require('./color-formatters'); + version, +} = require('./color-formatters') describe('Color formatters', function() { - const byPercentage = colorScale([Number.EPSILON, 80, 90, 100]); + const byPercentage = colorScale([Number.EPSILON, 80, 90, 100]) test(byPercentage, () => { - given(-1).expect('red'); - given(0).expect('red'); - given(0.5).expect('yellow'); - given(1).expect('yellow'); - given(50).expect('yellow'); - given(80).expect('yellowgreen'); - given(85).expect('yellowgreen'); - given(90).expect('green'); - given(100).expect('brightgreen'); - given(101).expect('brightgreen'); + given(-1).expect('red') + given(0).expect('red') + given(0.5).expect('yellow') + given(1).expect('yellow') + given(50).expect('yellow') + given(80).expect('yellowgreen') + given(85).expect('yellowgreen') + given(90).expect('green') + given(100).expect('brightgreen') + given(101).expect('brightgreen') - forCases([-1, 0, 0.5, 1, 50, 80, 85, 90, 100, 101] - .map(v => given(v).expect(coveragePercentage(v)))) - .should("return '%s', for parity with coveragePercentage()"); - }); + forCases( + [-1, 0, 0.5, 1, 50, 80, 85, 90, 100, 101].map(v => + given(v).expect(coveragePercentage(v)) + ) + ).should("return '%s', for parity with coveragePercentage()") + }) - context('when reversed', function () { + context('when reversed', function() { test(colorScale([7, 30, 180, 365, 730], undefined, true), () => { - given(3).expect('brightgreen'); - given(7).expect('green'); - given(10).expect('green'); - given(60).expect('yellowgreen'); - given(250).expect('yellow'); - given(400).expect('orange'); - given(800).expect('red'); - }); - }); + given(3).expect('brightgreen') + given(7).expect('green') + given(10).expect('green') + given(60).expect('yellowgreen') + given(250).expect('yellow') + given(400).expect('orange') + given(800).expect('red') + }) + }) test(letterScore, () => { - given('A').expect('brightgreen'); - given('B').expect('green'); - given('C').expect('yellowgreen'); - given('D').expect('yellow'); - given('E').expect('orange'); - given('F').expect('red'); - given('Z').expect('red'); - }); + given('A').expect('brightgreen') + given('B').expect('green') + given('C').expect('yellowgreen') + given('D').expect('yellow') + given('E').expect('orange') + given('F').expect('red') + given('Z').expect('red') + }) const monthsAgo = months => { - const result = new Date(); + const result = new Date() // This looks wack but it works. - result.setMonth(result.getMonth() - months); - return result; - }; + result.setMonth(result.getMonth() - months) + return result + } test(age, () => { - given(Date.now()).describe('when given the current timestamp').expect('brightgreen'); - given(new Date()).describe('when given the current Date').expect('brightgreen'); - given(new Date(2001, 1, 1)).describe('when given a Date many years ago').expect('red'); - given(monthsAgo(2)).describe('when given a Date two months ago').expect('yellowgreen'); - given(monthsAgo(15)).describe('when given a Date 15 months ago').expect('orange'); - }); + given(Date.now()) + .describe('when given the current timestamp') + .expect('brightgreen') + given(new Date()) + .describe('when given the current Date') + .expect('brightgreen') + given(new Date(2001, 1, 1)) + .describe('when given a Date many years ago') + .expect('red') + given(monthsAgo(2)) + .describe('when given a Date two months ago') + .expect('yellowgreen') + given(monthsAgo(15)) + .describe('when given a Date 15 months ago') + .expect('orange') + }) test(version, () => { - forCases([ - given('1.0'), - given(9), - given(1.0), - ]).expect('blue'); + forCases([given('1.0'), given(9), given(1.0)]).expect('blue') forCases([ given(0.1), @@ -81,11 +89,23 @@ describe('Color formatters', function() { given('6.0-SNAPSHOT'), given('1.0.1-dev'), given('2.1.6-prerelease'), - ]).expect('orange'); + ]).expect('orange') - expect(() => version(null)).to.throw(Error, "Can't generate a version color for null"); - expect(() => version(undefined)).to.throw(Error, "Can't generate a version color for undefined"); - expect(() => version(true)).to.throw(Error, "Can't generate a version color for true"); - expect(() => version({})).to.throw(Error, "Can't generate a version color for [object Object]"); - }); -}); + expect(() => version(null)).to.throw( + Error, + "Can't generate a version color for null" + ) + expect(() => version(undefined)).to.throw( + Error, + "Can't generate a version color for undefined" + ) + expect(() => version(true)).to.throw( + Error, + "Can't generate a version color for true" + ) + expect(() => version({})).to.throw( + Error, + "Can't generate a version color for [object Object]" + ) + }) +}) diff --git a/lib/defaults.js b/lib/defaults.js index 2dbf2b1e9c30c3e58046c468d62f31f950410cc6..5f7f7a1c37ebd2b03832ca0ae7be258bce63d821 100644 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -1,10 +1,10 @@ -'use strict'; +'use strict' -const path = require('path'); +const path = require('path') module.exports = { font: { // i.e. Verdana.ttf in the root of the project. path: path.join(__dirname, '..', 'Verdana.ttf'), }, -}; +} diff --git a/lib/deprecated-services.js b/lib/deprecated-services.js index 1fee40f48b452ba6e782850e6d28f55c731c37ea..31e818feb736599d67a23d8a8b886c8145a108cc 100644 --- a/lib/deprecated-services.js +++ b/lib/deprecated-services.js @@ -1,17 +1,17 @@ -'use strict'; +'use strict' const deprecatedServices = { - 'gittip': new Date('2017-12-29'), - 'gratipay': new Date('2017-12-29'), - 'gemnasium': new Date('2018-05-15'), - 'snap': new Date('2018-01-23'), + gittip: new Date('2017-12-29'), + gratipay: new Date('2017-12-29'), + gemnasium: new Date('2018-05-15'), + snap: new Date('2018-01-23'), 'snap-ci': new Date('2018-01-23'), - 'cauditor': new Date('2018-02-15'), - 'dotnetstatus': new Date('2018-04-01'), - 'magnumci': new Date('2018-07-08'), - 'bithound': new Date('2018-07-08'), -}; + cauditor: new Date('2018-02-15'), + dotnetstatus: new Date('2018-04-01'), + magnumci: new Date('2018-07-08'), + bithound: new Date('2018-07-08'), +} module.exports = { - deprecatedServices + deprecatedServices, } diff --git a/lib/deprecation-helpers.js b/lib/deprecation-helpers.js index 6473db75a27bc6773e8e34ec1bd7cfc1f0326ba2..c9f26194c4f178253aefde4f8a2f028b7591dc09 100644 --- a/lib/deprecation-helpers.js +++ b/lib/deprecation-helpers.js @@ -1,23 +1,27 @@ -'use strict'; +'use strict' -const { makeBadgeData, setBadgeColor } = require('./badge-data'); -const { deprecatedServices } = require('./deprecated-services'); +const { makeBadgeData, setBadgeColor } = require('./badge-data') +const { deprecatedServices } = require('./deprecated-services') -const isDeprecated = function (service, now=new Date(), depServices=deprecatedServices) { +const isDeprecated = function( + service, + now = new Date(), + depServices = deprecatedServices +) { if (!(service in depServices)) { - return false; + return false } - return now.getTime() >= depServices[service].getTime(); -}; + return now.getTime() >= depServices[service].getTime() +} -const getDeprecatedBadge = function (label, data) { - const badgeData = makeBadgeData(label, data); - setBadgeColor(badgeData, 'lightgray'); - badgeData.text[1] = 'no longer available'; - return badgeData; -}; +const getDeprecatedBadge = function(label, data) { + const badgeData = makeBadgeData(label, data) + setBadgeColor(badgeData, 'lightgray') + badgeData.text[1] = 'no longer available' + return badgeData +} module.exports = { isDeprecated, getDeprecatedBadge, -}; +} diff --git a/lib/deprecation-helpers.spec.js b/lib/deprecation-helpers.spec.js index 25fb207f0d05fff927c177f457afa2760cf80d3d..867231d61c2f3242055a5260f2ea3fdaa3a69e83 100644 --- a/lib/deprecation-helpers.spec.js +++ b/lib/deprecation-helpers.spec.js @@ -1,39 +1,33 @@ -'use strict'; +'use strict' -const { expect } = require('chai'); -const { test, given } = require('sazerac'); -const { isDeprecated, getDeprecatedBadge } = require('./deprecation-helpers'); +const { expect } = require('chai') +const { test, given } = require('sazerac') +const { isDeprecated, getDeprecatedBadge } = require('./deprecation-helpers') describe('Deprecated Badge Helper', function() { it('makes "no longer available" badge', function() { - const badge = getDeprecatedBadge('foo', {}); - expect(badge.text[0]).to.equal('foo'); - expect(badge.text[1]).to.equal('no longer available'); - expect(badge.colorscheme).to.equal('lightgray'); - }); + const badge = getDeprecatedBadge('foo', {}) + expect(badge.text[0]).to.equal('foo') + expect(badge.text[1]).to.equal('no longer available') + expect(badge.colorscheme).to.equal('lightgray') + }) it('ignores colorB param', function() { - const badge = getDeprecatedBadge('foo', {colorB: 'fedcba'}); - expect(badge.colorscheme).to.equal('lightgray'); - }); -}); - -describe('isDeprecated function', function () { - test(isDeprecated, function () { - - given('fooservice', new Date(), {}).expect(false); - - given( - 'fooservice', - new Date('2001-01-11 23:59:00Z'), - {'fooservice': new Date('2001-01-12')} - ).expect(false); - - given( - 'fooservice', - new Date('2001-01-12 00:00:01Z'), - {'fooservice': new Date('2001-01-12')} - ).expect(true); - - }); -}); + const badge = getDeprecatedBadge('foo', { colorB: 'fedcba' }) + expect(badge.colorscheme).to.equal('lightgray') + }) +}) + +describe('isDeprecated function', function() { + test(isDeprecated, function() { + given('fooservice', new Date(), {}).expect(false) + + given('fooservice', new Date('2001-01-11 23:59:00Z'), { + fooservice: new Date('2001-01-12'), + }).expect(false) + + given('fooservice', new Date('2001-01-12 00:00:01Z'), { + fooservice: new Date('2001-01-12'), + }).expect(true) + }) +}) diff --git a/lib/error-helper.js b/lib/error-helper.js index cc4b3786ea2351479e1f1316eeafd03f19de136e..0fed84ae322cf1f1c7a22dc358b8865a7dc752fb 100644 --- a/lib/error-helper.js +++ b/lib/error-helper.js @@ -1,52 +1,56 @@ -'use strict'; +'use strict' -const { - NotFound, - InvalidResponse, -} = require('../services/errors'); +const { NotFound, InvalidResponse } = require('../services/errors') -const checkErrorResponse = function(badgeData, err, res, notFoundMessage = 'not found') { +const checkErrorResponse = function( + badgeData, + err, + res, + notFoundMessage = 'not found' +) { if (err != null) { - badgeData.text[1] = 'inaccessible'; - badgeData.colorscheme = 'red'; - return true; + badgeData.text[1] = 'inaccessible' + badgeData.colorscheme = 'red' + return true } else if (res.statusCode === 404) { - badgeData.text[1] = notFoundMessage; - badgeData.colorscheme = 'lightgrey'; - return true; + badgeData.text[1] = notFoundMessage + badgeData.colorscheme = 'lightgrey' + return true } else if (res.statusCode !== 200) { - badgeData.text[1] = 'invalid'; - badgeData.colorscheme = 'lightgrey'; - return true; + badgeData.text[1] = 'invalid' + badgeData.colorscheme = 'lightgrey' + return true } else { - return false; + return false } -}; +} -checkErrorResponse.asPromise = function ({ notFoundMessage } = {}) { - return async function ({ buffer, res }) { +checkErrorResponse.asPromise = function({ notFoundMessage } = {}) { + return async function({ buffer, res }) { if (res.statusCode === 404) { - throw new NotFound({ prettyMessage: notFoundMessage }); + throw new NotFound({ prettyMessage: notFoundMessage }) } else if (res.statusCode !== 200) { - const underlying = Error(`Got status code ${res.statusCode} (expected 200)`); - throw new InvalidResponse({ underlyingError: underlying}); + const underlying = Error( + `Got status code ${res.statusCode} (expected 200)` + ) + throw new InvalidResponse({ underlyingError: underlying }) } - return { buffer, res }; - }; -}; + return { buffer, res } + } +} async function asJson({ buffer, res }) { try { - return JSON.parse(buffer); + return JSON.parse(buffer) } catch (err) { throw new InvalidResponse({ prettyMessage: 'unparseable json response', underlyingError: err, - }); + }) } } module.exports = { checkErrorResponse, asJson, -}; +} diff --git a/lib/error-helper.spec.js b/lib/error-helper.spec.js index b3c4c3e4642ed7576c6c1927e79f18ad231bc98f..35729f06df695eeadabb9d96a5e85dd73a1df62d 100644 --- a/lib/error-helper.spec.js +++ b/lib/error-helper.spec.js @@ -1,95 +1,105 @@ -'use strict'; +'use strict' -const chai = require('chai'); -const { assert, expect } = chai; -const { checkErrorResponse } = require('./error-helper'); -const { NotFound, InvalidResponse } = require('../services/errors'); +const chai = require('chai') +const { assert, expect } = chai +const { checkErrorResponse } = require('./error-helper') +const { NotFound, InvalidResponse } = require('../services/errors') -chai.use(require('chai-as-promised')); +chai.use(require('chai-as-promised')) describe('Standard Error Handler', function() { it('makes inaccessible badge', function() { - const badgeData = {'text': []}; - assert.equal(true, checkErrorResponse(badgeData, 'something other than null', {})); - assert.equal('inaccessible', badgeData.text[1]); - assert.equal('red', badgeData.colorscheme); - }); + const badgeData = { text: [] } + assert.equal( + true, + checkErrorResponse(badgeData, 'something other than null', {}) + ) + assert.equal('inaccessible', badgeData.text[1]) + assert.equal('red', badgeData.colorscheme) + }) it('makes not found badge', function() { - const badgeData = {'text': []}; - assert.equal(true, checkErrorResponse(badgeData, null, {statusCode: 404})); - assert.equal('not found', badgeData.text[1]); - assert.equal('lightgrey', badgeData.colorscheme); - }); + const badgeData = { text: [] } + assert.equal(true, checkErrorResponse(badgeData, null, { statusCode: 404 })) + assert.equal('not found', badgeData.text[1]) + assert.equal('lightgrey', badgeData.colorscheme) + }) it('makes not found badge with custom error', function() { - const badgeData = {'text': []}; - assert.equal(true, checkErrorResponse(badgeData, null, {statusCode: 404}, 'custom message')); - assert.equal('custom message', badgeData.text[1]); - assert.equal('lightgrey', badgeData.colorscheme); - }); + const badgeData = { text: [] } + assert.equal( + true, + checkErrorResponse(badgeData, null, { statusCode: 404 }, 'custom message') + ) + assert.equal('custom message', badgeData.text[1]) + assert.equal('lightgrey', badgeData.colorscheme) + }) it('makes invalid badge', function() { - const badgeData = {'text': []}; - assert.equal(true, checkErrorResponse(badgeData, null, {statusCode: 500})); - assert.equal('invalid', badgeData.text[1]); - assert.equal('lightgrey', badgeData.colorscheme); - }); + const badgeData = { text: [] } + assert.equal(true, checkErrorResponse(badgeData, null, { statusCode: 500 })) + assert.equal('invalid', badgeData.text[1]) + assert.equal('lightgrey', badgeData.colorscheme) + }) it('return false on 200 status', function() { - assert.equal(false, checkErrorResponse({'text': []}, null, {statusCode: 200})); - }); -}); + assert.equal( + false, + checkErrorResponse({ text: [] }, null, { statusCode: 200 }) + ) + }) +}) describe('async error handler', function() { context('when status is 200', function() { it('passes through the inputs', async function() { - const args = { buffer: 'buffer', res: { statusCode: 200 } }; - expect(await checkErrorResponse.asPromise()(args)) - .to.deep.equal(args); - }); - }); + const args = { buffer: 'buffer', res: { statusCode: 200 } } + expect(await checkErrorResponse.asPromise()(args)).to.deep.equal(args) + }) + }) context('when status is 404', function() { - const res = { statusCode: 404 }; + const res = { statusCode: 404 } it('throws NotFound', async function() { try { - await checkErrorResponse.asPromise()({ res }); - expect.fail('Expected to throw'); + await checkErrorResponse.asPromise()({ res }) + expect.fail('Expected to throw') } catch (e) { - expect(e).to.be.an.instanceof(NotFound); - expect(e.message).to.equal('Not Found'); - expect(e.prettyMessage).to.equal('not found'); + expect(e).to.be.an.instanceof(NotFound) + expect(e.message).to.equal('Not Found') + expect(e.prettyMessage).to.equal('not found') } - }); + }) it('displays the custom not found message', async function() { - const notFoundMessage = 'no goblins found'; - const res = { statusCode: 404 }; + const notFoundMessage = 'no goblins found' + const res = { statusCode: 404 } try { - await checkErrorResponse.asPromise({ notFoundMessage })({ res }); - expect.fail('Expected to throw'); + await checkErrorResponse.asPromise({ notFoundMessage })({ res }) + expect.fail('Expected to throw') } catch (e) { - expect(e).to.be.an.instanceof(NotFound); - expect(e.message).to.equal('Not Found: no goblins found'); - expect(e.prettyMessage).to.equal('no goblins found'); + expect(e).to.be.an.instanceof(NotFound) + expect(e.message).to.equal('Not Found: no goblins found') + expect(e.prettyMessage).to.equal('no goblins found') } - }); - }); + }) + }) context('when status is 500', function() { - const res = { statusCode: 500 }; + const res = { statusCode: 500 } it('throws InvalidResponse', async function() { try { - await checkErrorResponse.asPromise()({ res }); - expect.fail('Expected to throw'); + await checkErrorResponse.asPromise()({ res }) + expect.fail('Expected to throw') } catch (e) { - expect(e).to.be.an.instanceof(InvalidResponse); - expect(e.message).to.equal('Invalid Response: Got status code 500 (expected 200)'); - expect(e.prettyMessage).to.equal('invalid'); + expect(e).to.be.an.instanceof(InvalidResponse) + expect(e.message).to.equal( + 'Invalid Response: Got status code 500 (expected 200)' + ) + expect(e.prettyMessage).to.equal('invalid') } - }); - }); -}); + }) + }) +}) diff --git a/lib/export-badge-examples-cli.js b/lib/export-badge-examples-cli.js index 230059f32c3a9834ddf884ed871c05949a2d3608..ab1ba8eb49b02a1529696dd4708cf5d7ede373c2 100644 --- a/lib/export-badge-examples-cli.js +++ b/lib/export-badge-examples-cli.js @@ -1,5 +1,5 @@ -'use strict'; +'use strict' -const allBadgeExamples = require('./all-badge-examples'); +const allBadgeExamples = require('./all-badge-examples') -process.stdout.write(JSON.stringify(allBadgeExamples)); +process.stdout.write(JSON.stringify(allBadgeExamples)) diff --git a/lib/export-supported-features-cli.js b/lib/export-supported-features-cli.js index 7757412239738955ff7e0ca0de5fad503ef135b0..19fd67d5276729ae8622b39bda9d1dc3f927b83e 100644 --- a/lib/export-supported-features-cli.js +++ b/lib/export-supported-features-cli.js @@ -1,7 +1,7 @@ -'use strict'; +'use strict' -const path = require('path'); -const glob = require('glob'); +const path = require('path') +const glob = require('glob') const supportedFeatures = { logos: glob @@ -16,6 +16,6 @@ const supportedFeatures = { 'popout-square', 'social', ], -}; +} -console.log(JSON.stringify(supportedFeatures, null, 2)); +console.log(JSON.stringify(supportedFeatures, null, 2)) diff --git a/lib/github-auth.js b/lib/github-auth.js index 9082c8122596a8b611f8f29fe2b064e27af3f0ee..24ccc5e07f70d759ef2cbbc7317935ef14e9215b 100644 --- a/lib/github-auth.js +++ b/lib/github-auth.js @@ -1,69 +1,83 @@ -'use strict'; +'use strict' -const path = require('path'); -const crypto = require('crypto'); -const log = require('./log'); -const queryString = require('query-string'); -const request = require('request'); -const autosave = require('json-autosave'); -const serverSecrets = require('./server-secrets'); -const mapKeys = require('lodash.mapkeys'); +const path = require('path') +const crypto = require('crypto') +const log = require('./log') +const queryString = require('query-string') +const request = require('request') +const autosave = require('json-autosave') +const serverSecrets = require('./server-secrets') +const mapKeys = require('lodash.mapkeys') // This is an initial value which makes the code work while the initial data // is loaded. In the then() callback of scheduleAutosaving(), it's reassigned // with a JsonSave object. -let githubUserTokens = {data: []}; +let githubUserTokens = { data: [] } function scheduleAutosaving(config) { - const { dir: persistenceDir } = config; - const githubUserTokensFile = path.resolve(persistenceDir, 'github-user-tokens.json'); - autosave(githubUserTokensFile, {data: []}).then(save => { - githubUserTokens = save; - for (let i = 0; i < githubUserTokens.data.length; i++) { - addGithubToken(githubUserTokens.data[i]); - } - // Personal tokens allow access to GitHub private repositories. - // You can manage your personal GitHub token at - // <https://github.com/settings/tokens>. - if (serverSecrets && serverSecrets.gh_token) { - addGithubToken(serverSecrets.gh_token); - } - }).catch(e => { - console.error('Could not create ' + githubUserTokensFile); - }); + const { dir: persistenceDir } = config + const githubUserTokensFile = path.resolve( + persistenceDir, + 'github-user-tokens.json' + ) + autosave(githubUserTokensFile, { data: [] }) + .then(save => { + githubUserTokens = save + for (let i = 0; i < githubUserTokens.data.length; i++) { + addGithubToken(githubUserTokens.data[i]) + } + // Personal tokens allow access to GitHub private repositories. + // You can manage your personal GitHub token at + // <https://github.com/settings/tokens>. + if (serverSecrets && serverSecrets.gh_token) { + addGithubToken(serverSecrets.gh_token) + } + }) + .catch(e => { + console.error('Could not create ' + githubUserTokensFile) + }) } function cancelAutosaving() { if (githubUserTokens.stop) { - githubUserTokens.stop(); - githubUserTokens.save(); - githubUserTokens = {data: []}; + githubUserTokens.stop() + githubUserTokens.save() + githubUserTokens = { data: [] } } } function setRoutes(server) { - const baseUrl = process.env.BASE_URL || 'https://img.shields.io'; + const baseUrl = process.env.BASE_URL || 'https://img.shields.io' server.route(/^\/github-auth$/, function(data, match, end, ask) { if (!(serverSecrets && serverSecrets.gh_client_id)) { - return end('This server is missing GitHub client secrets.'); + return end('This server is missing GitHub client secrets.') } const query = queryString.stringify({ client_id: serverSecrets.gh_client_id, redirect_uri: baseUrl + '/github-auth/done', - }); - ask.res.statusCode = 302; // Found. - ask.res.setHeader('Location', 'https://github.com/login/oauth/authorize?' + query); - end(''); - }); + }) + ask.res.statusCode = 302 // Found. + ask.res.setHeader( + 'Location', + 'https://github.com/login/oauth/authorize?' + query + ) + end('') + }) server.route(/^\/github-auth\/done$/, function(data, match, end, ask) { - if (!(serverSecrets && serverSecrets.gh_client_id && serverSecrets.gh_client_secret)) { - return end('This server is missing GitHub client secrets.'); + if ( + !( + serverSecrets && + serverSecrets.gh_client_id && + serverSecrets.gh_client_secret + ) + ) { + return end('This server is missing GitHub client secrets.') } if (!data.code) { - log(`GitHub OAuth data.code: ${JSON.stringify(data)}`); - return end('GitHub OAuth authentication failed to provide a code.'); + log(`GitHub OAuth data.code: ${JSON.stringify(data)}`) + return end('GitHub OAuth authentication failed to provide a code.') } const options = { url: 'https://github.com/login/oauth/access_token', @@ -77,20 +91,25 @@ function setRoutes(server) { code: data.code, }), method: 'POST', - }; + } request(options, function(err, res, body) { - if (err != null) { return end('The connection to GitHub failed.'); } - let content; + if (err != null) { + return end('The connection to GitHub failed.') + } + let content try { - content = queryString.parse(body); - } catch(e) { return end('The GitHub OAuth token could not be parsed.'); } - const token = content.access_token; + content = queryString.parse(body) + } catch (e) { + return end('The GitHub OAuth token could not be parsed.') + } + const token = content.access_token if (!token) { - return end('The GitHub OAuth process did not return a user token.'); + return end('The GitHub OAuth process did not return a user token.') } - ask.res.setHeader('Content-Type', 'text/html'); - end('<p>Shields.io has received your app-specific GitHub user token. ' + + ask.res.setHeader('Content-Type', 'text/html') + end( + '<p>Shields.io has received your app-specific GitHub user token. ' + 'You can revoke it by going to ' + '<a href="https://github.com/settings/applications">GitHub</a>.</p>' + '<p>Until you do, you have now increased the rate limit for GitHub ' + @@ -98,97 +117,105 @@ function setRoutes(server) { 'therefore more robust.</p>' + '<p>Thanks for contributing to a smoother experience for ' + 'everyone!</p>' + - '<p><a href="/">Back to the website</a></p>'); + '<p><a href="/">Back to the website</a></p>' + ) - sendTokenToAllServers(token) - .catch(function(e) { - console.error('GitHub user token transmission failed:', e); - }); - }); - }); + sendTokenToAllServers(token).catch(function(e) { + console.error('GitHub user token transmission failed:', e) + }) + }) + }) server.route(/^\/github-auth\/add-token$/, function(data, match, end, ask) { - if (!crypto.timingSafeEqual(data.shieldsSecret, serverSecrets.shieldsSecret)) { + if ( + !crypto.timingSafeEqual(data.shieldsSecret, serverSecrets.shieldsSecret) + ) { // An unknown entity tries to connect. Let the connection linger for 10s. - return setTimeout(function() { end('Invalid secret.'); }, 10000); + return setTimeout(function() { + end('Invalid secret.') + }, 10000) } - addGithubToken(data.token); - end('Thanks!'); - }); + addGithubToken(data.token) + end('Thanks!') + }) } function sendTokenToAllServers(token) { - const ips = serverSecrets.shieldsIps; - return Promise.all(ips.map(function(ip) { - return new Promise(function(resolve, reject) { - const options = { - url: 'https://' + ip + '/github-auth/add-token', - method: 'POST', - form: { - shieldsSecret: serverSecrets.shieldsSecret, - token: token, - }, - // We target servers by IP, and we use HTTPS. Assuming that - // 1. Internet routers aren't hacked, and - // 2. We don't unknowingly lose our IP to someone else, - // we're not leaking people's and our information. - // (If we did, it would have no impact, as we only ask for a token, - // no GitHub scope. The malicious entity would only be able to use - // our rate limit pool.) - // FIXME: use letsencrypt. - strictSSL: false, - }; - request(options, function(err, res, body) { - if (err != null) { return reject(err); } - resolve(); - }); - }); - })); + const ips = serverSecrets.shieldsIps + return Promise.all( + ips.map(function(ip) { + return new Promise(function(resolve, reject) { + const options = { + url: 'https://' + ip + '/github-auth/add-token', + method: 'POST', + form: { + shieldsSecret: serverSecrets.shieldsSecret, + token: token, + }, + // We target servers by IP, and we use HTTPS. Assuming that + // 1. Internet routers aren't hacked, and + // 2. We don't unknowingly lose our IP to someone else, + // we're not leaking people's and our information. + // (If we did, it would have no impact, as we only ask for a token, + // no GitHub scope. The malicious entity would only be able to use + // our rate limit pool.) + // FIXME: use letsencrypt. + strictSSL: false, + } + request(options, function(err, res, body) { + if (err != null) { + return reject(err) + } + resolve() + }) + }) + }) + ) } // Track rate limit requests remaining. // Ideally, we would want priority queues here. -const reqRemaining = new Map(); // From token to requests remaining. -const reqReset = new Map(); // From token to timestamp. +const reqRemaining = new Map() // From token to requests remaining. +const reqReset = new Map() // From token to timestamp. // token: client token as a string. // reqs: number of requests remaining. // reset: timestamp when the number of remaining requests is reset. function setReqRemaining(token, reqs, reset) { - reqRemaining.set(token, reqs); - reqReset.set(token, reset); + reqRemaining.set(token, reqs) + reqReset.set(token, reset) } function rmReqRemaining(token) { - reqRemaining.delete(token); - reqReset.delete(token); + reqRemaining.delete(token) + reqReset.delete(token) } function utcEpochSeconds() { - return ((Date.now() / 1000) >>> 0); + return (Date.now() / 1000) >>> 0 } -const userTokenRateLimit = 12500; +const userTokenRateLimit = 12500 // Return false if the token cannot reasonably be expected to perform // a GitHub request. function isTokenUsable(token, now) { - const reqs = reqRemaining.get(token); - const reset = reqReset.get(token); + const reqs = reqRemaining.get(token) + const reset = reqReset.get(token) // We don't want to empty more than 3/4 of a user's rate limit. - const hasRemainingReqs = reqs > (userTokenRateLimit / 4); - const isBeyondRateLimitReset = reset < now; - return hasRemainingReqs || isBeyondRateLimitReset; + const hasRemainingReqs = reqs > userTokenRateLimit / 4 + const isBeyondRateLimitReset = reset < now + return hasRemainingReqs || isBeyondRateLimitReset } // Return a list of tokens (as strings) which can be used for a GitHub request, // with a reasonable chance that the request will succeed. function usableTokens() { - const now = utcEpochSeconds(); + const now = utcEpochSeconds() return githubUserTokens.data.filter(function(token) { - return isTokenUsable(token, now); - }); + return isTokenUsable(token, now) + }) } // Retrieve a user token if there is one for which we believe there are requests @@ -197,57 +224,58 @@ function getReqRemainingToken() { // Go through the user tokens. // Among usable ones, use the one with the highest number of remaining // requests. - const tokens = usableTokens(); - let highestReq = -1; - let highestToken; + const tokens = usableTokens() + let highestReq = -1 + let highestToken for (let i = 0; i < tokens.length; i++) { - const token = tokens[i]; - const reqs = reqRemaining.get(token); + const token = tokens[i] + const reqs = reqRemaining.get(token) if (reqs > highestReq) { - highestReq = reqs; - highestToken = token; + highestReq = reqs + highestToken = token } } - return highestToken; + return highestToken } function addGithubToken(token) { // A reset date of 0 has to be in the past. - setReqRemaining(token, userTokenRateLimit, 0); + setReqRemaining(token, userTokenRateLimit, 0) // Insert it only if it is not registered yet. if (githubUserTokens.data.indexOf(token) === -1) { - githubUserTokens.data.push(token); + githubUserTokens.data.push(token) } } function rmGithubToken(token) { - rmReqRemaining(token); + rmReqRemaining(token) // Remove it only if it is in there. - const idx = githubUserTokens.data.indexOf(token); + const idx = githubUserTokens.data.indexOf(token) if (idx >= 0) { - githubUserTokens.data.splice(idx, 1); + githubUserTokens.data.splice(idx, 1) } } // Convert an ES6 Map to an object. function mapToObject(map) { - const result = {}; + const result = {} for (const [k, v] of map) { - result[k] = v; + result[k] = v } - return result; + return result } // Compute a one-way hash of the input string. function sha(str) { - return crypto.createHash('sha256') + return crypto + .createHash('sha256') .update(str, 'utf-8') - .digest('hex'); + .digest('hex') } function serializeDebugInfo(options) { // Apply defaults. - const { sanitize } = Object.assign({ sanitize: true }, options); + const { sanitize } = Object.assign({ sanitize: true }, options) const unsanitized = { tokens: githubUserTokens.data, @@ -255,7 +283,7 @@ function serializeDebugInfo(options) { reqReset: mapToObject(reqReset), utcEpochSeconds: utcEpochSeconds(), sanitized: false, - }; + } if (sanitize) { return { @@ -264,9 +292,9 @@ function serializeDebugInfo(options) { reqReset: mapKeys(unsanitized.reqReset, (v, k) => sha(k)), utcEpochSeconds: unsanitized.utcEpochSeconds, sanitized: true, - }; + } } else { - return unsanitized; + return unsanitized } } @@ -275,48 +303,54 @@ function serializeDebugInfo(options) { // is provided, and more predictable failures if that token is exhausted. // // You can manage your personal GitHub token at https://github.com/settings/tokens -const globalToken = (serverSecrets || {}).gh_token; +const globalToken = (serverSecrets || {}).gh_token // Act like request(), but tweak headers and query to avoid hitting a rate // limit. function githubRequest(request, url, query, cb) { - query = query || {}; + query = query || {} // A special User-Agent is required: // http://developer.github.com/v3/#user-agent-required const headers = { 'User-Agent': 'Shields.io', - 'Accept': 'application/vnd.github.v3+json', - }; + Accept: 'application/vnd.github.v3+json', + } - const githubToken = globalToken === undefined ? getReqRemainingToken() : globalToken; + const githubToken = + globalToken === undefined ? getReqRemainingToken() : globalToken if (githubToken != null) { // Typically, GitHub user tokens grants us 12500 req/hour. - headers['Authorization'] = 'token ' + githubToken; + headers['Authorization'] = 'token ' + githubToken } else if (serverSecrets && serverSecrets.gh_client_id) { // Using our OAuth App secret grants us 5000 req/hour // instead of the standard 60 req/hour. - query.client_id = serverSecrets.gh_client_id; - query.client_secret = serverSecrets.gh_client_secret; + query.client_id = serverSecrets.gh_client_id + query.client_secret = serverSecrets.gh_client_secret } - const qs = queryString.stringify(query); - if (qs) { url += '?' + qs; } + const qs = queryString.stringify(query) + if (qs) { + url += '?' + qs + } - request(url, {headers: headers}, function(err, res, buffer) { + request(url, { headers: headers }, function(err, res, buffer) { if (globalToken !== null && githubToken !== null && err === null) { - if (res.statusCode === 401) { // Unauthorized. - rmGithubToken(githubToken); + if (res.statusCode === 401) { + // Unauthorized. + rmGithubToken(githubToken) } else { - const remaining = +res.headers['x-ratelimit-remaining']; + const remaining = +res.headers['x-ratelimit-remaining'] // reset is in UTC epoch seconds. - const reset = +res.headers['x-ratelimit-reset']; - setReqRemaining(githubToken, remaining, reset); - if (remaining === 0) { return; } // Hope for the best in the cache. + const reset = +res.headers['x-ratelimit-reset'] + setReqRemaining(githubToken, remaining, reset) + if (remaining === 0) { + return + } // Hope for the best in the cache. } } - cb(err, res, buffer); - }); + cb(err, res, buffer) + }) } module.exports = { @@ -325,4 +359,4 @@ module.exports = { request: githubRequest, setRoutes, serializeDebugInfo, -}; +} diff --git a/lib/github-auth/is-valid-token.js b/lib/github-auth/is-valid-token.js index 6de42568a75ad11dd35d3a8685819bd121600aee..bfb8b7f018fbf58df33b9fdff4f6e7cb3c8006da 100644 --- a/lib/github-auth/is-valid-token.js +++ b/lib/github-auth/is-valid-token.js @@ -1,8 +1,8 @@ -'use strict'; +'use strict' // This is only used by the TokenProviders, though probably the acceptor // should use it too. -const isValidToken = t => /^[0-9a-f]{40}$/.test(t); +const isValidToken = t => /^[0-9a-f]{40}$/.test(t) -module.exports = isValidToken; +module.exports = isValidToken diff --git a/lib/github-helpers.js b/lib/github-helpers.js index 918b59ebfdf508036c88ca34fbbe2b405371feab..aef5a6cb3a5e8e991d09a630c39f8258f470df59 100644 --- a/lib/github-helpers.js +++ b/lib/github-helpers.js @@ -1,30 +1,42 @@ -'use strict'; +'use strict' -const { colorScale } = require('./color-formatters'); -const { checkErrorResponse: standardCheckErrorResponse } = require('./error-helper'); +const { colorScale } = require('./color-formatters') +const { + checkErrorResponse: standardCheckErrorResponse, +} = require('./error-helper') function stateColor(s) { - return { open: '2cbe4e', closed: 'cb2431', merged: '6f42c1' }[s]; + return { open: '2cbe4e', closed: 'cb2431', merged: '6f42c1' }[s] } function checkStateColor(s) { - return { pending: 'dbab09', success: '2cbe4e', failure: 'cb2431', error: 'cb2431' }[s]; + return { + pending: 'dbab09', + success: '2cbe4e', + failure: 'cb2431', + error: 'cb2431', + }[s] } -function checkErrorResponse(badgeData, err, res, notFoundMessage = 'repo not found') { +function checkErrorResponse( + badgeData, + err, + res, + notFoundMessage = 'repo not found' +) { if (res && res.statusCode === 422) { - badgeData.text[1] = notFoundMessage; - badgeData.colorscheme = 'lightgrey'; - return true; + badgeData.text[1] = notFoundMessage + badgeData.colorscheme = 'lightgrey' + return true } - return standardCheckErrorResponse(badgeData, err, res, notFoundMessage); + return standardCheckErrorResponse(badgeData, err, res, notFoundMessage) } -const commentsColor = colorScale([1, 3, 10, 25], undefined, true); +const commentsColor = colorScale([1, 3, 10, 25], undefined, true) module.exports = { stateColor, checkStateColor, commentsColor, - checkErrorResponse -}; + checkErrorResponse, +} diff --git a/lib/github-helpers.spec.js b/lib/github-helpers.spec.js index cf06b8258e549dfabb64942fe7652e30248942db..079410ba41d098eb75a95d7816590d295fe4e39d 100644 --- a/lib/github-helpers.spec.js +++ b/lib/github-helpers.spec.js @@ -1,13 +1,15 @@ -'use strict'; +'use strict' -const { expect } = require('chai'); -const { checkErrorResponse } = require('./github-helpers'); +const { expect } = require('chai') +const { checkErrorResponse } = require('./github-helpers') describe('GitHub Error Handler', function() { it('makes not found badge when 422 is returned', function() { - const badgeData = {'text': []}; - expect(checkErrorResponse(badgeData, null, {statusCode: 422}, 'repo not found')).to.be.true; - expect(badgeData.text[1]).to.equal('repo not found'); - expect(badgeData.colorscheme).to.equal('lightgrey'); - }); -}); + const badgeData = { text: [] } + expect( + checkErrorResponse(badgeData, null, { statusCode: 422 }, 'repo not found') + ).to.be.true + expect(badgeData.text[1]).to.equal('repo not found') + expect(badgeData.colorscheme).to.equal('lightgrey') + }) +}) diff --git a/lib/github-provider.js b/lib/github-provider.js index 5bade1b26d07aa24da174d9a4f840e428fadbe0f..b077f797d0ead8257c620629c4523881f0232caa 100644 --- a/lib/github-provider.js +++ b/lib/github-provider.js @@ -1,126 +1,124 @@ -'use strict'; -const moment = require('moment'); +'use strict' +const moment = require('moment') const { makeBadgeData: getBadgeData, makeLabel: getLabel, makeLogo: getLogo, -} = require('./badge-data'); -const { - formatDate -} = require('./text-formatters'); +} = require('./badge-data') +const { formatDate } = require('./text-formatters') -const { - age -} = require('./color-formatters'); +const { age } = require('./color-formatters') // GitHub commits since integration. function mapGithubCommitsSince({ camp, cache }, githubApiProvider) { - camp.route(/^\/github\/commits-since\/([^/]+)\/([^/]+)\/([^/]+)\.(svg|png|gif|jpg|json)$/, - cache(function (data, match, sendBadge, request) { - const user = match[1]; // eg, SubtitleEdit - const repo = match[2]; // eg, subtitleedit - const version = match[3]; // eg, 3.4.7 or latest - const format = match[4]; - const badgeData = getBadgeData('commits since ' + version, data); + camp.route( + /^\/github\/commits-since\/([^/]+)\/([^/]+)\/([^/]+)\.(svg|png|gif|jpg|json)$/, + cache(function(data, match, sendBadge, request) { + const user = match[1] // eg, SubtitleEdit + const repo = match[2] // eg, subtitleedit + const version = match[3] // eg, 3.4.7 or latest + const format = match[4] + const badgeData = getBadgeData('commits since ' + version, data) function setCommitsSinceBadge(user, repo, version) { - const apiUrl = `/repos/${user}/${repo}/compare/${version}...master`; + const apiUrl = `/repos/${user}/${repo}/compare/${version}...master` if (badgeData.template === 'social') { - badgeData.logo = getLogo('github', data); + badgeData.logo = getLogo('github', data) } githubApiProvider.request(request, apiUrl, {}, (err, res, buffer) => { if (err != null) { - badgeData.text[1] = 'inaccessible'; - sendBadge(format, badgeData); - return; + badgeData.text[1] = 'inaccessible' + sendBadge(format, badgeData) + return } try { - const result = JSON.parse(buffer); - badgeData.text[1] = result.ahead_by; - badgeData.colorscheme = 'blue'; - badgeData.text[0] = getLabel('commits since ' + version, data); - sendBadge(format, badgeData); - + const result = JSON.parse(buffer) + badgeData.text[1] = result.ahead_by + badgeData.colorscheme = 'blue' + badgeData.text[0] = getLabel('commits since ' + version, data) + sendBadge(format, badgeData) } catch (e) { - badgeData.text[1] = 'invalid'; - sendBadge(format, badgeData); + badgeData.text[1] = 'invalid' + sendBadge(format, badgeData) } - }); + }) } if (version === 'latest') { - const url = `/repos/${user}/${repo}/releases/latest`; + const url = `/repos/${user}/${repo}/releases/latest` githubApiProvider.request(request, url, {}, (err, res, buffer) => { if (err != null) { - badgeData.text[1] = 'inaccessible'; - sendBadge(format, badgeData); - return; + badgeData.text[1] = 'inaccessible' + sendBadge(format, badgeData) + return } try { - const data = JSON.parse(buffer); - setCommitsSinceBadge(user, repo, data.tag_name); + const data = JSON.parse(buffer) + setCommitsSinceBadge(user, repo, data.tag_name) } catch (e) { - badgeData.text[1] = 'invalid'; - sendBadge(format, badgeData); + badgeData.text[1] = 'invalid' + sendBadge(format, badgeData) } - }); + }) } else { - setCommitsSinceBadge(user, repo, version); + setCommitsSinceBadge(user, repo, version) } - })); + }) + ) } //Github Release & Pre-Release Date Integration release-date-pre (?:\/(all))? function mapGithubReleaseDate({ camp, cache }, githubApiProvider) { - camp.route(/^\/github\/(release-date|release-date-pre)\/([^/]+)\/([^/]+)\.(svg|png|gif|jpg|json)$/, - cache(function (data, match, sendBadge, request) { - const releaseType = match[1]; // eg, release-date-pre / release-date - const user = match[2]; // eg, microsoft - const repo = match[3]; // eg, vscode - const format = match[4]; - let apiUrl = `/repos/${user}/${repo}/releases`; - if(releaseType === 'release-date') { - apiUrl += '/latest'; + camp.route( + /^\/github\/(release-date|release-date-pre)\/([^/]+)\/([^/]+)\.(svg|png|gif|jpg|json)$/, + cache(function(data, match, sendBadge, request) { + const releaseType = match[1] // eg, release-date-pre / release-date + const user = match[2] // eg, microsoft + const repo = match[3] // eg, vscode + const format = match[4] + let apiUrl = `/repos/${user}/${repo}/releases` + if (releaseType === 'release-date') { + apiUrl += '/latest' } - const badgeData = getBadgeData('release date', data); + const badgeData = getBadgeData('release date', data) if (badgeData.template === 'social') { - badgeData.logo = getLogo('github', data); + badgeData.logo = getLogo('github', data) } githubApiProvider.request(request, apiUrl, {}, (err, res, buffer) => { if (err != null) { - badgeData.text[1] = 'inaccessible'; - sendBadge(format, badgeData); - return; + badgeData.text[1] = 'inaccessible' + sendBadge(format, badgeData) + return } //github return 404 if repo not found or no release - if(res.statusCode === 404) { - badgeData.text[1] = 'no releases or repo not found'; - sendBadge(format, badgeData); - return; + if (res.statusCode === 404) { + badgeData.text[1] = 'no releases or repo not found' + sendBadge(format, badgeData) + return } try { - let data = JSON.parse(buffer); - if(releaseType === 'release-date-pre') { - data = data[0]; + let data = JSON.parse(buffer) + if (releaseType === 'release-date-pre') { + data = data[0] } - const releaseDate = moment(data.created_at); - badgeData.text[1] = formatDate(releaseDate); - badgeData.colorscheme = age(releaseDate); - sendBadge(format, badgeData); + const releaseDate = moment(data.created_at) + badgeData.text[1] = formatDate(releaseDate) + badgeData.colorscheme = age(releaseDate) + sendBadge(format, badgeData) } catch (e) { - badgeData.text[1] = 'invalid'; - sendBadge(format, badgeData); + badgeData.text[1] = 'invalid' + sendBadge(format, badgeData) } - }); - })); + }) + }) + ) } - module.exports = { mapGithubCommitsSince, - mapGithubReleaseDate -}; + mapGithubReleaseDate, +} diff --git a/lib/in-process-server-test-helpers.js b/lib/in-process-server-test-helpers.js index 1f5c166f6a6a0fb7dd92cb131e2915b4ed6e2caf..4679a8c9c821a1ef9ea8784d45c21e0796105882 100644 --- a/lib/in-process-server-test-helpers.js +++ b/lib/in-process-server-test-helpers.js @@ -10,11 +10,11 @@ * after('Shut down the server', function () { serverHelpers.stop(server); }); */ -'use strict'; +'use strict' -const config = require('./test-config'); +const config = require('./test-config') -let startCalled = false; +let startCalled = false /** * Start the server. @@ -22,21 +22,23 @@ let startCalled = false; * @param {Number} port number (optional) * @return {Object} The scoutcamp instance */ -function start () { +function start() { if (startCalled) { - throw Error('Because of the way Shields works, you can only use this ' + - 'once per node process. Once you call stop(), the game is over.'); + throw Error( + 'Because of the way Shields works, you can only use this ' + + 'once per node process. Once you call stop(), the game is over.' + ) } - startCalled = true; + startCalled = true - const originalArgv = process.argv; + const originalArgv = process.argv // Modifying argv during import is a bit dirty, but it works, and avoids // making bigger changes to server.js. - process.argv = ['', '', config.port, 'localhost']; - const server = require('../server'); + process.argv = ['', '', config.port, 'localhost'] + const server = require('../server') - process.argv = originalArgv; - return server; + process.argv = originalArgv + return server } /** @@ -44,8 +46,8 @@ function start () { * * @param {Object} server instance */ -function reset (server) { - server.reset(); +function reset(server) { + server.reset() } /** @@ -53,14 +55,14 @@ function reset (server) { * * @param {Object} server instance */ -function stop (server) { +function stop(server) { if (server) { - server.stop(); + server.stop() } } module.exports = { start, reset, - stop -}; + stop, +} diff --git a/lib/licenses.js b/lib/licenses.js index f131128062839f59ecb30dae84c8ae4ec460b7e5..544345b63c18922ebd381c92a78782169bdc8edd 100644 --- a/lib/licenses.js +++ b/lib/licenses.js @@ -1,33 +1,62 @@ -'use strict'; +'use strict' const licenseTypes = { // permissive licenses - not public domain and not copyleft - 'permissive': { - spdxLicenseIds: ['AFL-3.0', 'Apache-2.0', 'Artistic-2.0', 'BSD-2-Clause', 'BSD-3-Clause', 'BSD-3-Clause-Clear', - 'BSL-1.0', 'CC-BY-4.0', 'ECL-2.0', 'ISC', 'MIT', 'MS-PL', 'NCSA', 'PostgreSQL', 'Zlib'], - color: 'green' + permissive: { + spdxLicenseIds: [ + 'AFL-3.0', + 'Apache-2.0', + 'Artistic-2.0', + 'BSD-2-Clause', + 'BSD-3-Clause', + 'BSD-3-Clause-Clear', + 'BSL-1.0', + 'CC-BY-4.0', + 'ECL-2.0', + 'ISC', + 'MIT', + 'MS-PL', + 'NCSA', + 'PostgreSQL', + 'Zlib', + ], + color: 'green', }, // copyleft licenses require 'Disclose source' (https://choosealicense.com/appendix/#disclose-source) // or 'Same license' (https://choosealicense.com/appendix/#same-license) - 'copyleft': { - spdxLicenseIds: ['AGPL-3.0', 'CC-BY-SA-4.0', 'EPL-1.0', 'EUPL-1.1', 'GPL-2.0', 'GPL-3.0', 'LGPL-2.1', 'LGPL-3.0', - 'LPPL-1.3c', 'MPL-2.0', 'MS-RL', 'OFL-1.1', 'OSL-3.0'], - color: 'orange' + copyleft: { + spdxLicenseIds: [ + 'AGPL-3.0', + 'CC-BY-SA-4.0', + 'EPL-1.0', + 'EUPL-1.1', + 'GPL-2.0', + 'GPL-3.0', + 'LGPL-2.1', + 'LGPL-3.0', + 'LPPL-1.3c', + 'MPL-2.0', + 'MS-RL', + 'OFL-1.1', + 'OSL-3.0', + ], + color: 'orange', }, // public domain licenses do not require 'License and copyright notice' (https://choosealicense.com/appendix/#include-copyright) 'public-domain': { spdxLicenseIds: ['CC0-1.0', 'Unlicense', 'WTFPL'], - color: '7cd958' - } -}; + color: '7cd958', + }, +} -const licenseToColorMap = {}; +const licenseToColorMap = {} Object.keys(licenseTypes).forEach(licenseType => { - const { spdxLicenseIds, color } = licenseTypes[licenseType]; + const { spdxLicenseIds, color } = licenseTypes[licenseType] spdxLicenseIds.forEach(license => { - licenseToColorMap[license] = color; - }); -}); -const defaultLicenseColor = 'lightgrey'; -const licenseToColor = (spdxId) => licenseToColorMap[spdxId] || defaultLicenseColor; + licenseToColorMap[license] = color + }) +}) +const defaultLicenseColor = 'lightgrey' +const licenseToColor = spdxId => + licenseToColorMap[spdxId] || defaultLicenseColor -module.exports = { licenseToColor }; +module.exports = { licenseToColor } diff --git a/lib/licenses.spec.js b/lib/licenses.spec.js index 1e3fc3834484520b2ce927da44c66f9a43842321..8ac6910912257f650af1730cef4cd92a95c91245 100644 --- a/lib/licenses.spec.js +++ b/lib/licenses.spec.js @@ -1,14 +1,14 @@ -'use strict'; +'use strict' -const {test, given} = require('sazerac'); -const {licenseToColor} = require('./licenses'); +const { test, given } = require('sazerac') +const { licenseToColor } = require('./licenses') describe('license helpers', () => { test(licenseToColor, () => { - given('MIT').expect('green'); - given('MPL-2.0').expect('orange'); - given('Unlicense').expect('7cd958'); - given('unknown-license').expect('lightgrey'); - given(null).expect('lightgrey'); - }); -}); + given('MIT').expect('green') + given('MPL-2.0').expect('orange') + given('Unlicense').expect('7cd958') + given('unknown-license').expect('lightgrey') + given(null).expect('lightgrey') + }) +}) diff --git a/lib/load-logos.js b/lib/load-logos.js index e7503f5c0112e9701212a03554fbcc553f53c3be..e9c2ec049ff392c0006fbd5ea45621a3f21f7dfc 100644 --- a/lib/load-logos.js +++ b/lib/load-logos.js @@ -1,28 +1,30 @@ -'use strict'; +'use strict' -const fs = require('fs'); -const path = require('path'); -const { svg2base64 } = require('./logo-helper'); +const fs = require('fs') +const path = require('path') +const { svg2base64 } = require('./logo-helper') -function loadLogos () { +function loadLogos() { // Cache svg logos from disk in base64 string - const logos = {}; - const logoDir = path.join(__dirname, '..', 'logo'); - const logoFiles = fs.readdirSync(logoDir); + const logos = {} + const logoDir = path.join(__dirname, '..', 'logo') + const logoFiles = fs.readdirSync(logoDir) logoFiles.forEach(function(filename) { - if (filename[0] === '.') { return; } + if (filename[0] === '.') { + return + } // filename is eg, github.svg - const svg = fs.readFileSync(logoDir + '/' + filename).toString(); - const base64 = svg2base64(svg); + const svg = fs.readFileSync(logoDir + '/' + filename).toString() + const base64 = svg2base64(svg) // eg, github - const name = filename.slice(0, -('.svg'.length)).toLowerCase(); + const name = filename.slice(0, -'.svg'.length).toLowerCase() logos[name] = { svg, - base64 - }; - }); - return logos; + base64, + } + }) + return logos } -module.exports = loadLogos; +module.exports = loadLogos diff --git a/lib/load-simple-icons.js b/lib/load-simple-icons.js index 278f381c19e7dc7f49b1ef7d0dbeac8a29712593..f7f8d8f17df4ca0a118f154c617381fb3d920047 100644 --- a/lib/load-simple-icons.js +++ b/lib/load-simple-icons.js @@ -1,18 +1,20 @@ -'use strict'; +'use strict' -const simpleIcons = require('simple-icons'); -const { svg2base64 } = require('./logo-helper'); +const simpleIcons = require('simple-icons') +const { svg2base64 } = require('./logo-helper') -function loadSimpleIcons(){ - Object.keys(simpleIcons).forEach(function (key) { - const k = key.toLowerCase().replace(/ /g, '-'); +function loadSimpleIcons() { + Object.keys(simpleIcons).forEach(function(key) { + const k = key.toLowerCase().replace(/ /g, '-') if (k !== key) { - simpleIcons[k] = simpleIcons[key]; - delete simpleIcons[key]; + simpleIcons[k] = simpleIcons[key] + delete simpleIcons[key] } - simpleIcons[k].base64 = svg2base64(simpleIcons[k].svg.replace('<svg', `<svg fill="#${simpleIcons[k].hex}"`)); - }); - return (simpleIcons); + simpleIcons[k].base64 = svg2base64( + simpleIcons[k].svg.replace('<svg', `<svg fill="#${simpleIcons[k].hex}"`) + ) + }) + return simpleIcons } -module.exports = loadSimpleIcons; +module.exports = loadSimpleIcons diff --git a/lib/log.js b/lib/log.js index e12aca9000cdcc8bf211dc8305ebb38d158cf894..2f0cd06981fa144b36562fcda276cc31223d87e8 100644 --- a/lib/log.js +++ b/lib/log.js @@ -1,52 +1,61 @@ -'use strict'; -const Raven = require('raven'); -const throttle = require('lodash.throttle'); +'use strict' +const Raven = require('raven') +const throttle = require('lodash.throttle') -const listeners = []; +const listeners = [] // Zero-pad a number in a string. // eg. 4 becomes 04 but 17 stays 17. function pad(string) { - string = String(string); - return (string.length < 2) ? ('0' + string) : string; + string = String(string) + return string.length < 2 ? '0' + string : string } // Compact date representation. // eg. 0611093840 for June 11, 9:38:40 UTC. function date() { - const date = new Date(); - return pad(date.getUTCMonth() + 1) + + const date = new Date() + return ( + pad(date.getUTCMonth() + 1) + pad(date.getUTCDate()) + pad(date.getUTCHours()) + pad(date.getUTCMinutes()) + - pad(date.getUTCSeconds()); + pad(date.getUTCSeconds()) + ) } module.exports = function log(...msg) { - const d = date(); + const d = date() listeners.forEach(f => f(d, ...msg)) - console.log(d, ...msg); -}; + console.log(d, ...msg) +} -const throttledConsoleError = throttle(console.error, 10000, { trailing: false }); +const throttledConsoleError = throttle(console.error, 10000, { + trailing: false, +}) module.exports.error = function error(...msg) { - const d = date(); - listeners.forEach(f => f(d, ...msg)); - Raven.captureException(msg, function (sendErr) { + const d = date() + listeners.forEach(f => f(d, ...msg)) + Raven.captureException(msg, function(sendErr) { if (sendErr) { - throttledConsoleError('Failed to send captured exception to Sentry', sendErr.message); + throttledConsoleError( + 'Failed to send captured exception to Sentry', + sendErr.message + ) } - }); - console.error(d, ...msg); -}; + }) + console.error(d, ...msg) +} module.exports.addListener = function addListener(func) { - listeners.push(func); -}; + listeners.push(func) +} module.exports.removeListener = function removeListener(func) { - const index = listeners.indexOf(func); - if (index < 0) { return; } - listeners.splice(index, 1); -}; + const index = listeners.indexOf(func) + if (index < 0) { + return + } + listeners.splice(index, 1) +} diff --git a/lib/log.spec.js b/lib/log.spec.js index 7bf809f374b8caf04b4ddf536d1681dcfa56a89c..a5804be0b0353fc6ea8aa295bbf43786141d0787 100644 --- a/lib/log.spec.js +++ b/lib/log.spec.js @@ -1,48 +1,65 @@ -'use strict'; -const chai = require('chai'); -const expect = chai.expect; -const sinon = require('sinon'); -const sinonChai = require('sinon-chai');; -const Raven = require('raven'); +'use strict' +const chai = require('chai') +const expect = chai.expect +const sinon = require('sinon') +const sinonChai = require('sinon-chai') +const Raven = require('raven') -chai.use(sinonChai); +chai.use(sinonChai) -function requireUncached(module){ - delete require.cache[require.resolve(module)] - return require(module) +function requireUncached(module) { + delete require.cache[require.resolve(module)] + return require(module) } -describe('log', () => { - describe('error', () => { - before(() => { - this.clock = sinon.useFakeTimers(); - sinon.stub(Raven, 'captureException').callsFake(function fakeFn(e, callback) { - callback(new Error(`Cannot send message "${e}" to Sentry.`)); - }); - // we have to create a spy before requiring 'error' function - sinon.spy(console, 'error'); - this.error = requireUncached('./log').error; - }); - - after(() => { - this.clock.restore(); - console.error.restore(); - Raven.captureException.restore(); - }); - - it('should throttle errors from Raven client', () => { - this.error('test error 1'); - this.error('test error 2'); - this.error('test error 3'); - this.clock.tick(11000); - this.error('test error 4'); - this.error('test error 5'); +describe('log', () => { + describe('error', () => { + before(() => { + this.clock = sinon.useFakeTimers() + sinon + .stub(Raven, 'captureException') + .callsFake(function fakeFn(e, callback) { + callback(new Error(`Cannot send message "${e}" to Sentry.`)) + }) + // we have to create a spy before requiring 'error' function + sinon.spy(console, 'error') + this.error = requireUncached('./log').error + }) - expect(console.error).to.be.calledWithExactly('Failed to send captured exception to Sentry', 'Cannot send message "test error 1" to Sentry.'); - expect(console.error).to.not.be.calledWithExactly('Failed to send captured exception to Sentry', 'Cannot send message "test error 2" to Sentry.'); - expect(console.error).to.not.be.calledWithExactly('Failed to send captured exception to Sentry', 'Cannot send message "test error 3" to Sentry.'); - expect(console.error).to.be.calledWithExactly('Failed to send captured exception to Sentry', 'Cannot send message "test error 4" to Sentry.'); - expect(console.error).to.not.be.calledWithExactly('Failed to send captured exception to Sentry', 'Cannot send message "test error 5" to Sentry.'); - }); - }); -}); + after(() => { + this.clock.restore() + console.error.restore() + Raven.captureException.restore() + }) + + it('should throttle errors from Raven client', () => { + this.error('test error 1') + this.error('test error 2') + this.error('test error 3') + this.clock.tick(11000) + this.error('test error 4') + this.error('test error 5') + + expect(console.error).to.be.calledWithExactly( + 'Failed to send captured exception to Sentry', + 'Cannot send message "test error 1" to Sentry.' + ) + expect(console.error).to.not.be.calledWithExactly( + 'Failed to send captured exception to Sentry', + 'Cannot send message "test error 2" to Sentry.' + ) + expect(console.error).to.not.be.calledWithExactly( + 'Failed to send captured exception to Sentry', + 'Cannot send message "test error 3" to Sentry.' + ) + expect(console.error).to.be.calledWithExactly( + 'Failed to send captured exception to Sentry', + 'Cannot send message "test error 4" to Sentry.' + ) + expect(console.error).to.not.be.calledWithExactly( + 'Failed to send captured exception to Sentry', + 'Cannot send message "test error 5" to Sentry.' + ) + }) + }) +}) diff --git a/lib/logo-helper.js b/lib/logo-helper.js index 93d88535c16cbe0a1f6526b73baac9666d04ef14..bcec10cc612adde769249cc7a70b23882d1d8957 100644 --- a/lib/logo-helper.js +++ b/lib/logo-helper.js @@ -1,15 +1,17 @@ -'use strict'; +'use strict' function isDataUri(s) { - return s !== undefined && /^(data:)([^;]+);([^,]+),(.+)$/.test(s); + return s !== undefined && /^(data:)([^;]+);([^,]+),(.+)$/.test(s) } -function svg2base64(svg){ - if (typeof svg !== 'string'){ - return undefined; +function svg2base64(svg) { + if (typeof svg !== 'string') { + return undefined } // Check if logo is already base64 - return isDataUri(svg) ? svg : 'data:image/svg+xml;base64,' + Buffer.from(svg).toString('base64'); + return isDataUri(svg) + ? svg + : 'data:image/svg+xml;base64,' + Buffer.from(svg).toString('base64') } module.exports = { diff --git a/lib/logo-helper.spec.js b/lib/logo-helper.spec.js index 0efc4b17edc0f412688c54734167521df8eb58e3..2acdad7b36082768ec948419dba17dacb1531bdc 100644 --- a/lib/logo-helper.spec.js +++ b/lib/logo-helper.spec.js @@ -1,27 +1,21 @@ -'use strict'; +'use strict' -const { - test, - given, - forCases, -} = require('sazerac'); -const { - svg2base64, - isDataUri, -} = require('./logo-helper'); +const { test, given, forCases } = require('sazerac') +const { svg2base64, isDataUri } = require('./logo-helper') describe('Logo helpers', function() { test(svg2base64, () => { - given('data:image/svg+xml;base64,PHN2ZyB4bWxu').expect('data:image/svg+xml;base64,PHN2ZyB4bWxu'); - given('<svg xmlns="http://www.w3.org/2000/svg"/>').expect('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLz4='); - given(undefined).expect(undefined); - }); + given('data:image/svg+xml;base64,PHN2ZyB4bWxu').expect( + 'data:image/svg+xml;base64,PHN2ZyB4bWxu' + ) + given('<svg xmlns="http://www.w3.org/2000/svg"/>').expect( + 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLz4=' + ) + given(undefined).expect(undefined) + }) test(isDataUri, () => { - given('data:image/svg+xml;base64,PHN2ZyB4bWxu').expect(true); - forCases([ - given('data:foobar'), - given('foobar'), - ]).expect(false); - }); -}); + given('data:image/svg+xml;base64,PHN2ZyB4bWxu').expect(true) + forCases([given('data:foobar'), given('foobar')]).expect(false) + }) +}) diff --git a/lib/lru-cache.js b/lib/lru-cache.js index 6a5959521ee598f2add0eeaf0efe3f6b1f2ec5dd..2ed2942152a1ef1639322137f1bba443ef345719 100644 --- a/lib/lru-cache.js +++ b/lib/lru-cache.js @@ -1,132 +1,138 @@ -'use strict'; +'use strict' // In-memory KV, remove the oldest data when the capacity is reached. const typeEnum = { unit: 0, heap: 1, -}; +} function CacheSlot(key, value) { - this.key = key; - this.value = value; - this.older = null; // Newest slot that is older than this slot. - this.newer = null; // Oldest slot that is newer than this slot. + this.key = key + this.value = value + this.older = null // Newest slot that is older than this slot. + this.newer = null // Oldest slot that is newer than this slot. } function Cache(capacity, type) { - if (!(this instanceof Cache)) { return new Cache(capacity, type); } - type = type || 'unit'; - this.capacity = capacity; - this.type = typeEnum[type]; - this.cache = new Map(); // Maps cache keys to CacheSlots. - this.newest = null; // Newest slot in the cache. - this.oldest = null; + if (!(this instanceof Cache)) { + return new Cache(capacity, type) + } + type = type || 'unit' + this.capacity = capacity + this.type = typeEnum[type] + this.cache = new Map() // Maps cache keys to CacheSlots. + this.newest = null // Newest slot in the cache. + this.oldest = null } Cache.prototype = { set: function addToCache(cacheKey, cached) { - let slot = this.cache.get(cacheKey); + let slot = this.cache.get(cacheKey) if (slot === undefined) { - slot = new CacheSlot(cacheKey, cached); - this.cache.set(cacheKey, slot); + slot = new CacheSlot(cacheKey, cached) + this.cache.set(cacheKey, slot) } - this.makeNewest(slot); - const numItemsToRemove = this.limitReached(); + this.makeNewest(slot) + const numItemsToRemove = this.limitReached() if (numItemsToRemove > 0) { for (let i = 0; i < numItemsToRemove; i++) { - this.removeOldest(); + this.removeOldest() } } }, get: function getFromCache(cacheKey) { - const slot = this.cache.get(cacheKey); + const slot = this.cache.get(cacheKey) if (slot !== undefined) { - this.makeNewest(slot); - return slot.value; + this.makeNewest(slot) + return slot.value } }, has: function hasInCache(cacheKey) { - return this.cache.has(cacheKey); + return this.cache.has(cacheKey) }, makeNewest: function makeNewestSlot(slot) { - const previousNewest = this.newest; - if (previousNewest === slot) { return; } - const older = slot.older; - const newer = slot.newer; + const previousNewest = this.newest + if (previousNewest === slot) { + return + } + const older = slot.older + const newer = slot.newer if (older !== null) { - older.newer = newer; + older.newer = newer } else if (newer !== null) { - this.oldest = newer; + this.oldest = newer } if (newer !== null) { - newer.older = older; + newer.older = older } - this.newest = slot; + this.newest = slot if (previousNewest !== null) { - slot.older = previousNewest; - slot.newer = null; - previousNewest.newer = slot; + slot.older = previousNewest + slot.newer = null + previousNewest.newer = slot } else { // If previousNewest is null, the cache used to be empty. - this.oldest = slot; + this.oldest = slot } }, removeOldest: function removeOldest() { - const cacheKey = this.oldest.key; + const cacheKey = this.oldest.key if (this.oldest !== null) { - this.oldest = this.oldest.newer; + this.oldest = this.oldest.newer if (this.oldest !== null) { - this.oldest.older = null; + this.oldest.older = null } } - this.cache.delete(cacheKey); + this.cache.delete(cacheKey) }, // Returns the number of elements to remove if we're past the limit. limitReached: function heuristic() { if (this.type === typeEnum.unit) { // Remove the excess. - return Math.max(0, (this.cache.size - this.capacity)); + return Math.max(0, this.cache.size - this.capacity) } else if (this.type === typeEnum.heap) { if (getHeapSize() >= this.capacity) { - console.log('LRU HEURISTIC heap:', getHeapSize()); + console.log('LRU HEURISTIC heap:', getHeapSize()) // Remove half of them. - return (this.cache.size >> 1); - } else { return 0; } + return this.cache.size >> 1 + } else { + return 0 + } } else { - console.error("Unknown heuristic '" + this.type + "' for LRU cache."); - return 1; + console.error("Unknown heuristic '" + this.type + "' for LRU cache.") + return 1 } }, - clear: function () { - this.cache.clear(); - this.newest = null; - this.oldest = null; - } -}; + clear: function() { + this.cache.clear() + this.newest = null + this.oldest = null + }, +} // In bytes. -let heapSize; -let heapSizeTimeout; +let heapSize +let heapSizeTimeout function getHeapSize() { if (heapSizeTimeout == null) { // Compute the heap size every 60 seconds. - heapSizeTimeout = setInterval(computeHeapSize, 60 * 1000); - return computeHeapSize(); + heapSizeTimeout = setInterval(computeHeapSize, 60 * 1000) + return computeHeapSize() } else { - return heapSize; + return heapSize } } function computeHeapSize() { - return (heapSize = process.memoryUsage().heapTotal); + return (heapSize = process.memoryUsage().heapTotal) } -module.exports = Cache; +module.exports = Cache diff --git a/lib/lru-cache.spec.js b/lib/lru-cache.spec.js index 67a33cb5bc649c1a0dabce1ca3ee0f55d29c405d..754753334850c80489843f89b94508422e3d8297 100644 --- a/lib/lru-cache.spec.js +++ b/lib/lru-cache.spec.js @@ -1,139 +1,139 @@ -'use strict'; +'use strict' -const { expect } = require('chai'); -const LRU = require('./lru-cache'); +const { expect } = require('chai') +const LRU = require('./lru-cache') function expectCacheSlots(cache, keys) { - expect(cache.cache.size).to.equal(keys.length); + expect(cache.cache.size).to.equal(keys.length) - const slots = keys.map(k => cache.cache.get(k)); + const slots = keys.map(k => cache.cache.get(k)) - const first = slots[0]; - const last = slots.slice(-1)[0]; + const first = slots[0] + const last = slots.slice(-1)[0] - expect(cache.oldest).to.equal(first); - expect(cache.newest).to.equal(last); + expect(cache.oldest).to.equal(first) + expect(cache.newest).to.equal(last) - expect(first.older).to.be.null; - expect(last.newer).to.be.null; + expect(first.older).to.be.null + expect(last.newer).to.be.null for (let i = 0; i + 1 < slots.length; ++i) { - const current = slots[i]; - const next = slots[i+1]; - expect(current.newer).to.equal(next); - expect(next.older).to.equal(current); + const current = slots[i] + const next = slots[i + 1] + expect(current.newer).to.equal(next) + expect(next.older).to.equal(current) } } -describe('The LRU cache', function () { - it('should support being called without new', function () { - const cache = LRU(1); - expect(cache).to.be.an.instanceof(LRU); - }); +describe('The LRU cache', function() { + it('should support being called without new', function() { + const cache = LRU(1) + expect(cache).to.be.an.instanceof(LRU) + }) - it('should support a zero capacity', function () { - const cache = new LRU(0); - cache.set('key', 'value'); - expect(cache.cache.size).to.equal(0); - }); + it('should support a zero capacity', function() { + const cache = new LRU(0) + cache.set('key', 'value') + expect(cache.cache.size).to.equal(0) + }) - it('should support a one capacity', function () { - const cache = new LRU(1); - cache.set('key1', 'value1'); - expectCacheSlots(cache, ['key1']); - cache.set('key2', 'value2'); - expectCacheSlots(cache, ['key2']); - expect(cache.get('key1')).to.be.undefined; - expect(cache.get('key2')).to.equal('value2'); - }); + it('should support a one capacity', function() { + const cache = new LRU(1) + cache.set('key1', 'value1') + expectCacheSlots(cache, ['key1']) + cache.set('key2', 'value2') + expectCacheSlots(cache, ['key2']) + expect(cache.get('key1')).to.be.undefined + expect(cache.get('key2')).to.equal('value2') + }) - it('should remove the oldest element when reaching capacity', function () { - const cache = new LRU(2); + it('should remove the oldest element when reaching capacity', function() { + const cache = new LRU(2) - cache.set('key1', 'value1'); - cache.set('key2', 'value2'); - cache.set('key3', 'value3'); - cache.cache.get('key1'); + cache.set('key1', 'value1') + cache.set('key2', 'value2') + cache.set('key3', 'value3') + cache.cache.get('key1') - expectCacheSlots(cache, ['key2', 'key3']); - expect(cache.cache.get('key1')).to.be.undefined; - expect(cache.get('key1')).to.be.undefined; - expect(cache.get('key2')).to.equal('value2'); - expect(cache.get('key3')).to.equal('value3'); - }); + expectCacheSlots(cache, ['key2', 'key3']) + expect(cache.cache.get('key1')).to.be.undefined + expect(cache.get('key1')).to.be.undefined + expect(cache.get('key2')).to.equal('value2') + expect(cache.get('key3')).to.equal('value3') + }) - it('should make sure that resetting a key in cache makes it newest', function () { - const cache = new LRU(2); + it('should make sure that resetting a key in cache makes it newest', function() { + const cache = new LRU(2) - cache.set('key', 'value'); - cache.set('key2', 'value2'); + cache.set('key', 'value') + cache.set('key2', 'value2') - expectCacheSlots(cache, ['key', 'key2']); + expectCacheSlots(cache, ['key', 'key2']) - cache.set('key', 'value'); + cache.set('key', 'value') - expectCacheSlots(cache, ['key2', 'key']); - }); + expectCacheSlots(cache, ['key2', 'key']) + }) - describe('getting a key in the cache', function () { - context('when the requested key is oldest', function () { - it('should leave the keys in the expected order', function () { - const cache = new LRU(2); - cache.set('key1', 'value1'); - cache.set('key2', 'value2'); + describe('getting a key in the cache', function() { + context('when the requested key is oldest', function() { + it('should leave the keys in the expected order', function() { + const cache = new LRU(2) + cache.set('key1', 'value1') + cache.set('key2', 'value2') - expectCacheSlots(cache, ['key1', 'key2']); + expectCacheSlots(cache, ['key1', 'key2']) - expect(cache.get('key1')).to.equal('value1'); + expect(cache.get('key1')).to.equal('value1') - expectCacheSlots(cache, ['key2', 'key1']); - }); - }); + expectCacheSlots(cache, ['key2', 'key1']) + }) + }) - context('when the requested key is newest', function () { - it('should leave the keys in the expected order', function () { - const cache = new LRU(2); - cache.set('key1', 'value1'); - cache.set('key2', 'value2'); + context('when the requested key is newest', function() { + it('should leave the keys in the expected order', function() { + const cache = new LRU(2) + cache.set('key1', 'value1') + cache.set('key2', 'value2') - expect(cache.get('key2')).to.equal('value2'); + expect(cache.get('key2')).to.equal('value2') - expectCacheSlots(cache, ['key1', 'key2']); - }); - }); + expectCacheSlots(cache, ['key1', 'key2']) + }) + }) - context('when the requested key is in the middle', function () { - it('should leave the keys in the expected order', function () { - const cache = new LRU(3); - cache.set('key1', 'value1'); - cache.set('key2', 'value2'); - cache.set('key3', 'value3'); + context('when the requested key is in the middle', function() { + it('should leave the keys in the expected order', function() { + const cache = new LRU(3) + cache.set('key1', 'value1') + cache.set('key2', 'value2') + cache.set('key3', 'value3') - expectCacheSlots(cache, ['key1', 'key2', 'key3']); + expectCacheSlots(cache, ['key1', 'key2', 'key3']) - expect(cache.get('key2')).to.equal('value2'); + expect(cache.get('key2')).to.equal('value2') - expectCacheSlots(cache, ['key1', 'key3', 'key2']); - }); - }); - }); + expectCacheSlots(cache, ['key1', 'key3', 'key2']) + }) + }) + }) - it('should clear', function () { + it('should clear', function() { // Set up. - const cache = new LRU(2); - cache.set('key1', 'value1'); - cache.set('key2', 'value2'); + const cache = new LRU(2) + cache.set('key1', 'value1') + cache.set('key2', 'value2') // Confidence check. - expect(cache.get('key1')).to.equal('value1'); - expect(cache.get('key2')).to.equal('value2'); + expect(cache.get('key1')).to.equal('value1') + expect(cache.get('key2')).to.equal('value2') // Run. - cache.clear(); + cache.clear() // Test. - expect(cache.get('key1')).to.be.undefined; - expect(cache.get('key2')).to.be.undefined; - expect(cache.cache.size).to.equal(0); - }); -}); + expect(cache.get('key1')).to.be.undefined + expect(cache.get('key2')).to.be.undefined + expect(cache.cache.size).to.equal(0) + }) +}) diff --git a/lib/luarocks-version.js b/lib/luarocks-version.js index 563a3365bbaea7854e8566454e8396c46de69030..d2b73d84d2d42309136ad06cda448e74dc2671d9 100644 --- a/lib/luarocks-version.js +++ b/lib/luarocks-version.js @@ -3,7 +3,7 @@ * This compares version numbers using the algorithm * followed by luarocks command-line utility */ -'use strict'; +'use strict' // Compare two arrays containing splitted and transformed to // positive/negative numbers parts of version strings, @@ -13,30 +13,30 @@ // zero if v1 = v2, // a positive value otherwise. function compareVersionLists(v1, v2) { - const maxLength = Math.max(v1.length, v2.length); - let p1, p2; + const maxLength = Math.max(v1.length, v2.length) + let p1, p2 for (let i = 0; i < maxLength; i++) { - p1 = v1[i] || 0; - p2 = v2[i] || 0; - if (p1 > p2) return 1; - if (p1 < p2) return -1; + p1 = v1[i] || 0 + p2 = v2[i] || 0 + if (p1 > p2) return 1 + if (p1 < p2) return -1 } - return 0; + return 0 } -exports.compareVersionLists = compareVersionLists; +exports.compareVersionLists = compareVersionLists // Parse a dotted version string to an array of numbers // 'rc', 'pre', 'beta', 'alpha' are converted to negative numbers function parseVersion(versionString) { - versionString = versionString.toLowerCase().replace('-', '.'); - const versionList = []; + versionString = versionString.toLowerCase().replace('-', '.') + const versionList = [] versionString.split('.').forEach(function(versionPart) { - const parsedPart = /(\d*)([a-z]*)(\d*)/.exec(versionPart); + const parsedPart = /(\d*)([a-z]*)(\d*)/.exec(versionPart) if (parsedPart[1]) { - versionList.push(parseInt(parsedPart[1])); + versionList.push(parseInt(parsedPart[1])) } if (parsedPart[2]) { - let weight; + let weight // calculate weight as a negative number // 'rc' > 'pre' > 'beta' > 'alpha' > any other value switch (parsedPart[2]) { @@ -44,16 +44,16 @@ function parseVersion(versionString) { case 'beta': case 'pre': case 'rc': - weight = (parsedPart[2].charCodeAt(0) - 128) * 100; - break; + weight = (parsedPart[2].charCodeAt(0) - 128) * 100 + break default: - weight = -10000; + weight = -10000 } // add positive number, i.e. 'beta5' > 'beta2' - weight += parseInt(parsedPart[3]) || 0; - versionList.push(weight); + weight += parseInt(parsedPart[3]) || 0 + versionList.push(weight) } - }); - return versionList; + }) + return versionList } -exports.parseVersion = parseVersion; +exports.parseVersion = parseVersion diff --git a/lib/luarocks-version.spec.js b/lib/luarocks-version.spec.js index f9dcf2b1162d1f39a5b65b1a69c30fb11e2297e3..1cce5bb2194ad926b161b5fc5905c44c2410bc74 100644 --- a/lib/luarocks-version.spec.js +++ b/lib/luarocks-version.spec.js @@ -1,7 +1,7 @@ -'use strict'; +'use strict' -const { test, given, forCases } = require('sazerac'); -const {parseVersion, compareVersionLists} = require('./luarocks-version'); +const { test, given, forCases } = require('sazerac') +const { parseVersion, compareVersionLists } = require('./luarocks-version') describe('LuaRocks-specific helpers', function() { test(compareVersionLists, () => { @@ -11,9 +11,10 @@ describe('LuaRocks-specific helpers', function() { given([1, 2], [1, 2, 0, 0]), given([-1, -2], [-1, -2, 0, 0]), given([], []), - ]).describe('when given [%s] and [%s]') + ]) + .describe('when given [%s] and [%s]') .expect(0) - .should('should be equal'); + .should('should be equal') forCases([ given([1, 2], [2, 1]), @@ -22,9 +23,10 @@ describe('LuaRocks-specific helpers', function() { given([3, 2, -1], [3, 2]), given([-1], []), given([], [1]), - ]).describe('when given [%s] and [%s]') + ]) + .describe('when given [%s] and [%s]') .expect(-1) - .should('should be less'); + .should('should be less') forCases([ given([1, 2, 1, 2], [1, 2, 0, 2]), @@ -34,17 +36,18 @@ describe('LuaRocks-specific helpers', function() { given([1, 2, 0, -1], [1, 2, -1, 1]), given([], [-1, 2]), given([1, -1], []), - ]).describe('when given [%s] and [%s]') + ]) + .describe('when given [%s] and [%s]') .expect(1) - .should('should be greater'); - }); + .should('should be greater') + }) test(parseVersion, () => { - given('1.2.3-1').expect([1, 2, 3, 1]); - given('10.02-3').expect([10, 2, 3]); - given('3.0rc1-2').expect([3, 0, -1399, 2]); - given('2.0-alpha').expect([2, 0, -3100]); - given('2.0-beta').expect([2, 0, -3000]); - given('2.0-beta5').expect([2, 0, -2995]); - }); -}); + given('1.2.3-1').expect([1, 2, 3, 1]) + given('10.02-3').expect([10, 2, 3]) + given('3.0rc1-2').expect([3, 0, -1399, 2]) + given('2.0-alpha').expect([2, 0, -3100]) + given('2.0-beta').expect([2, 0, -3000]) + given('2.0-beta5').expect([2, 0, -2995]) + }) +}) diff --git a/lib/make-badge-test-helpers.js b/lib/make-badge-test-helpers.js index 83fcc1835c521f981e4684e0dcc669061a1c7523..a2a87b5c31505d5378a804717bdb0c19791a7a25 100644 --- a/lib/make-badge-test-helpers.js +++ b/lib/make-badge-test-helpers.js @@ -1,17 +1,24 @@ -'use strict'; +'use strict' -const path = require('path'); -const { PDFKitTextMeasurer } = require('./text-measurer'); -const { makeMakeBadgeFn } = require('./make-badge'); +const path = require('path') +const { PDFKitTextMeasurer } = require('./text-measurer') +const { makeMakeBadgeFn } = require('./make-badge') module.exports = { font: { - path: path.join(__dirname, '..', 'node_modules', 'dejavu-fonts-ttf', 'ttf', 'DejaVuSans.ttf'), + path: path.join( + __dirname, + '..', + 'node_modules', + 'dejavu-fonts-ttf', + 'ttf', + 'DejaVuSans.ttf' + ), }, measurer() { - return new PDFKitTextMeasurer(this.font.path); + return new PDFKitTextMeasurer(this.font.path) }, makeBadge() { - return makeMakeBadgeFn(this.measurer()); + return makeMakeBadgeFn(this.measurer()) }, -}; +} diff --git a/lib/make-badge.js b/lib/make-badge.js index dfe43523abe4612c4f12bd4680b795d5348cea2c..03ca3498f784b0cc032247c96cc603a2f33200b4 100644 --- a/lib/make-badge.js +++ b/lib/make-badge.js @@ -1,176 +1,189 @@ -'use strict'; +'use strict' -const fs = require('fs'); -const path = require('path'); -const SVGO = require('svgo'); -const dot = require('dot'); -const LruCache = require('./lru-cache'); -const isCSSColor = require('is-css-color'); +const fs = require('fs') +const path = require('path') +const SVGO = require('svgo') +const dot = require('dot') +const LruCache = require('./lru-cache') +const isCSSColor = require('is-css-color') // Holds widths of badge keys (left hand side of badge). -const badgeKeyWidthCache = new LruCache(1000); +const badgeKeyWidthCache = new LruCache(1000) // cache templates. -const templates = {}; -const templateFiles = fs.readdirSync(path.join(__dirname, '..', 'templates')); -dot.templateSettings.strip = false; // Do not strip whitespace. +const templates = {} +const templateFiles = fs.readdirSync(path.join(__dirname, '..', 'templates')) +dot.templateSettings.strip = false // Do not strip whitespace. templateFiles.forEach(async function(filename) { - if (filename[0] === '.') { return; } - const templateData = fs.readFileSync( - path.join(__dirname, '..', 'templates', filename)).toString(); - const extension = path.extname(filename).slice(1); - const style = filename.slice(0, -(('-template.' + extension).length)); + if (filename[0] === '.') { + return + } + const templateData = fs + .readFileSync(path.join(__dirname, '..', 'templates', filename)) + .toString() + const extension = path.extname(filename).slice(1) + const style = filename.slice(0, -('-template.' + extension).length) // Compile the template. Necessary to always have a working template. - templates[style + '-' + extension] = dot.template(templateData); + templates[style + '-' + extension] = dot.template(templateData) if (extension === 'svg') { // Substitute dot code. - const mapping = new Map(); - let mappingIndex = 1; + const mapping = new Map() + let mappingIndex = 1 const untemplatedSvg = templateData.replace(/{{.*?}}/g, function(match) { // Weird substitution that currently works for all templates. - const mapKey = '99999990' + mappingIndex + '.1'; - mappingIndex++; - mapping.set(mapKey, match); - return mapKey; - }); + const mapKey = '99999990' + mappingIndex + '.1' + mappingIndex++ + mapping.set(mapKey, match) + return mapKey + }) - const svgo = new SVGO(); - const { data, error } = await svgo.optimize(untemplatedSvg); + const svgo = new SVGO() + const { data, error } = await svgo.optimize(untemplatedSvg) if (error !== undefined) { - console.error(`Template ${filename}: ${error}\n` + - ' Generated untemplated SVG:\n' + - `---\n${untemplatedSvg}---\n`); - return; + console.error( + `Template ${filename}: ${error}\n` + + ' Generated untemplated SVG:\n' + + `---\n${untemplatedSvg}---\n` + ) + return } // Substitute dot code back. - let svg = data; - const unmappedKeys = []; + let svg = data + const unmappedKeys = [] mapping.forEach(function(value, key) { - let keySubstituted = false; + let keySubstituted = false svg = svg.replace(RegExp(key, 'g'), function() { - keySubstituted = true; - return value; - }); + keySubstituted = true + return value + }) if (!keySubstituted) { - unmappedKeys.push(key); + unmappedKeys.push(key) } - }); + }) if (unmappedKeys.length > 0) { - console.error(`Template ${filename} has unmapped keys ` + - `${unmappedKeys.join(', ')}.\n` + - ' Generated untemplated SVG:\n' + - `---\n${untemplatedSvg}\n---\n` + - ' Generated template:\n' + - `---\n${svg}\n---\n`); - return; + console.error( + `Template ${filename} has unmapped keys ` + + `${unmappedKeys.join(', ')}.\n` + + ' Generated untemplated SVG:\n' + + `---\n${untemplatedSvg}\n---\n` + + ' Generated template:\n' + + `---\n${svg}\n---\n` + ) + return } - templates[style + '-' + extension] = dot.template(svg); + templates[style + '-' + extension] = dot.template(svg) } -}); +}) function escapeXml(s) { if (s === undefined || typeof s !== 'string') { - return undefined; + return undefined } else { - return s.replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); + return s + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') } } function capitalize(s) { - return s.charAt(0).toUpperCase() + s.slice(1); + return s.charAt(0).toUpperCase() + s.slice(1) } // check if colorA/B is a colorscheme else check if it's a valid css3 color else return undefined and let the badge assign the default color function assignColor(color = '', colorschemeType = 'colorB') { if (definedColorschemes[color] !== undefined) { - return definedColorschemes[color][colorschemeType] || undefined; + return definedColorschemes[color][colorschemeType] || undefined } else if (isCSSColor(color)) { - return color; + return color } else { - return undefined; + return undefined } } -const definedColorschemes = require(path.join(__dirname, 'colorscheme.json')); +const definedColorschemes = require(path.join(__dirname, 'colorscheme.json')) // Inject the measurer to avoid placing any persistent state in this module. -function makeBadge (measurer, { - format, - template, - text, - colorscheme, - colorA, - colorB, - logo, - logoPosition, - logoWidth, - links = ['', ''], -}) { +function makeBadge( + measurer, + { + format, + template, + text, + colorscheme, + colorA, + colorB, + logo, + logoPosition, + logoWidth, + links = ['', ''], + } +) { // String coercion. - text = text.map(value => '' + value); + text = text.map(value => '' + value) if (format !== 'json') { - format = 'svg'; + format = 'svg' } if (!(`${template}-${format}` in templates)) { - template = format === 'svg' ? 'flat' : 'default'; + template = format === 'svg' ? 'flat' : 'default' } if (template.startsWith('popout')) { - if (logo){ - logoPosition = (logoPosition <= 10 && logoPosition >= -10) ? logoPosition : 0; - logoWidth = +logoWidth || 32; + if (logo) { + logoPosition = + logoPosition <= 10 && logoPosition >= -10 ? logoPosition : 0 + logoWidth = +logoWidth || 32 } else { - template = template.replace('popout', 'flat'); + template = template.replace('popout', 'flat') } } if (template === 'social') { - text[0] = capitalize(text[0]); + text[0] = capitalize(text[0]) } else if (template === 'for-the-badge') { - text = text.map(value => value.toUpperCase()); + text = text.map(value => value.toUpperCase()) } // colorA/B have a higher priority than colorscheme - colorA = colorA || colorscheme || undefined; - colorB = colorB || colorscheme || undefined; - colorA = assignColor(colorA, 'colorA'); - colorB = assignColor(colorB, 'colorB'); + colorA = colorA || colorscheme || undefined + colorB = colorB || colorscheme || undefined + colorA = assignColor(colorA, 'colorA') + colorB = assignColor(colorB, 'colorB') - const [left, right] = text; - let leftWidth = badgeKeyWidthCache.get(left); + const [left, right] = text + let leftWidth = badgeKeyWidthCache.get(left) if (leftWidth === undefined) { - leftWidth = measurer.widthOf(left) | 0; + leftWidth = measurer.widthOf(left) | 0 // Increase chances of pixel grid alignment. - if (leftWidth % 2 === 0) { leftWidth++; } - badgeKeyWidthCache.set(left, leftWidth); + if (leftWidth % 2 === 0) { + leftWidth++ + } + badgeKeyWidthCache.set(left, leftWidth) } - let rightWidth = measurer.widthOf(right) | 0; + let rightWidth = measurer.widthOf(right) | 0 // Increase chances of pixel grid alignment. - if (rightWidth % 2 === 0) { rightWidth++; } + if (rightWidth % 2 === 0) { + rightWidth++ + } - logoWidth = +logoWidth || (logo ? 14 : 0); + logoWidth = +logoWidth || (logo ? 14 : 0) - let logoPadding; + let logoPadding if (left.length === 0) { - logoPadding = 0; + logoPadding = 0 } else { - logoPadding = logo ? 3 : 0; + logoPadding = logo ? 3 : 0 } const context = { text: [left, right], escapedText: text.map(escapeXml), - widths: [ - leftWidth + 10 + logoWidth + logoPadding, - rightWidth + 10, - ], + widths: [leftWidth + 10 + logoWidth + logoPadding, rightWidth + 10], links: links.map(escapeXml), logo, logoPosition, @@ -179,12 +192,12 @@ function makeBadge (measurer, { colorA, colorB, escapeXml, - }; + } - const templateFn = templates[`${template}-${format}`]; + const templateFn = templates[`${template}-${format}`] // The call to template() can raise an exception. - return templateFn(context); + return templateFn(context) } module.exports = { @@ -192,4 +205,4 @@ module.exports = { makeMakeBadgeFn: measurer => data => makeBadge(measurer, data), // Expose for testing. _badgeKeyWidthCache: badgeKeyWidthCache, -}; +} diff --git a/lib/make-badge.spec.js b/lib/make-badge.spec.js index 898e2eebff4dd1ff956639451a8c668f05da4220..658f0cb4a14d537de83242eab5d189f40a02e0ac 100644 --- a/lib/make-badge.spec.js +++ b/lib/make-badge.spec.js @@ -1,43 +1,50 @@ -'use strict'; - -const { test, given, forCases } = require('sazerac'); -const { expect } = require('chai'); -const snapshot = require('snap-shot-it'); -const { _badgeKeyWidthCache } = require('./make-badge'); -const isSvg = require('is-svg'); -const testHelpers = require('./make-badge-test-helpers'); -const colorschemes = require('./colorscheme.json'); - -const makeBadge = testHelpers.makeBadge(); - -function testColor(color = ''){ - return JSON.parse(makeBadge({ text: ['name', 'Bob'], colorB: color, format: 'json', template: '_shields_test' })).colorB; +'use strict' + +const { test, given, forCases } = require('sazerac') +const { expect } = require('chai') +const snapshot = require('snap-shot-it') +const { _badgeKeyWidthCache } = require('./make-badge') +const isSvg = require('is-svg') +const testHelpers = require('./make-badge-test-helpers') +const colorschemes = require('./colorscheme.json') + +const makeBadge = testHelpers.makeBadge() + +function testColor(color = '') { + return JSON.parse( + makeBadge({ + text: ['name', 'Bob'], + colorB: color, + format: 'json', + template: '_shields_test', + }) + ).colorB } describe('The badge generator', () => { beforeEach(() => { - _badgeKeyWidthCache.clear(); - }); + _badgeKeyWidthCache.clear() + }) describe('color test', () => { test(testColor, () => { // valid hex - given('#4c1').expect('#4c1'); - given('#4C1').expect('#4C1'); - given('#abc123').expect('#abc123'); - given('#ABC123').expect('#ABC123'); + given('#4c1').expect('#4c1') + given('#4C1').expect('#4C1') + given('#abc123').expect('#abc123') + given('#ABC123').expect('#ABC123') // valid rgb(a) - given('rgb(0,128,255)').expect('rgb(0,128,255)'); - given('rgba(0,128,255,0)').expect('rgba(0,128,255,0)'); + given('rgb(0,128,255)').expect('rgb(0,128,255)') + given('rgba(0,128,255,0)').expect('rgba(0,128,255,0)') // valid hsl(a) - given('hsl(100, 56%, 10%)').expect('hsl(100, 56%, 10%)'); - given('hsla(25,20%,0%,0.1)').expect('hsla(25,20%,0%,0.1)'); + given('hsl(100, 56%, 10%)').expect('hsl(100, 56%, 10%)') + given('hsla(25,20%,0%,0.1)').expect('hsla(25,20%,0%,0.1)') // either a css named color or colorscheme - given('papayawhip').expect('papayawhip'); - given('red').expect(colorschemes['red'].colorB); - given('green').expect(colorschemes['green'].colorB); - given('blue').expect(colorschemes['blue'].colorB); - given('yellow').expect(colorschemes['yellow'].colorB); + given('papayawhip').expect('papayawhip') + given('red').expect(colorschemes['red'].colorB) + given('green').expect(colorschemes['green'].colorB) + given('blue').expect(colorschemes['blue'].colorB) + given('yellow').expect(colorschemes['yellow'].colorB) forCases( // invalid hex @@ -56,96 +63,158 @@ describe('The badge generator', () => { given('bluish'), given('almostred'), given('brightmaroon'), - given('cactus'), + given('cactus') ).expect(undefined) - }); - }); + }) + }) describe('SVG', () => { it('should produce SVG', () => { - const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' }); + const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' }) expect(svg) .to.satisfy(isSvg) .and.to.include('cactus') - .and.to.include('grown'); - }); + .and.to.include('grown') + }) it('should always produce the same SVG (unless we have changed something!)', () => { - const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' }); - snapshot(svg); - }); + const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' }) + snapshot(svg) + }) it('should cache width of badge key', () => { - makeBadge({ text: ['cached', 'not-cached'], format: 'svg' }); - expect(_badgeKeyWidthCache.cache).to.have.keys('cached'); - }); - }); + makeBadge({ text: ['cached', 'not-cached'], format: 'svg' }) + expect(_badgeKeyWidthCache.cache).to.have.keys('cached') + }) + }) describe('JSON', () => { it('should always produce the same JSON (unless we have changed something!)', () => { - const json = makeBadge({ text: ['cactus', 'grown'], format: 'json' }); - snapshot(json); - }); + const json = makeBadge({ text: ['cactus', 'grown'], format: 'json' }) + snapshot(json) + }) it('should replace unknown json template with "default"', () => { - const jsonBadgeWithUnknownStyle = makeBadge({ text: ['name', 'Bob'], format: 'json', template: 'unknown_style' }); - const jsonBadgeWithDefaultStyle = makeBadge({ text: ['name', 'Bob'], format: 'json', template: 'default' }); - expect(jsonBadgeWithUnknownStyle).to.equal(jsonBadgeWithDefaultStyle); - expect(JSON.parse(jsonBadgeWithUnknownStyle)).to.deep.equal({name: "name", value: "Bob"}) - }); + const jsonBadgeWithUnknownStyle = makeBadge({ + text: ['name', 'Bob'], + format: 'json', + template: 'unknown_style', + }) + const jsonBadgeWithDefaultStyle = makeBadge({ + text: ['name', 'Bob'], + format: 'json', + template: 'default', + }) + expect(jsonBadgeWithUnknownStyle).to.equal(jsonBadgeWithDefaultStyle) + expect(JSON.parse(jsonBadgeWithUnknownStyle)).to.deep.equal({ + name: 'name', + value: 'Bob', + }) + }) it('should replace unknown svg template with "flat"', () => { - const jsonBadgeWithUnknownStyle = makeBadge({ text: ['name', 'Bob'], format: 'svg', template: 'unknown_style' }); - const jsonBadgeWithDefaultStyle = makeBadge({ text: ['name', 'Bob'], format: 'svg', template: 'flat' }); - expect(jsonBadgeWithUnknownStyle).to.equal(jsonBadgeWithDefaultStyle) - .and.to.satisfy(isSvg); - }); - }); + const jsonBadgeWithUnknownStyle = makeBadge({ + text: ['name', 'Bob'], + format: 'svg', + template: 'unknown_style', + }) + const jsonBadgeWithDefaultStyle = makeBadge({ + text: ['name', 'Bob'], + format: 'svg', + template: 'flat', + }) + expect(jsonBadgeWithUnknownStyle) + .to.equal(jsonBadgeWithDefaultStyle) + .and.to.satisfy(isSvg) + }) + }) describe('"for-the-badge" template badge generation', () => { - // https://github.com/badges/shields/issues/1280 + // https://github.com/badges/shields/issues/1280 it('numbers should produce a string', () => { - const svg = makeBadge({ text: [1998, 1999], format: 'svg', template: 'for-the-badge' }); - expect(svg).to.include('1998').and.to.include('1999'); - }); + const svg = makeBadge({ + text: [1998, 1999], + format: 'svg', + template: 'for-the-badge', + }) + expect(svg) + .to.include('1998') + .and.to.include('1999') + }) it('lowercase/mixedcase string should produce uppercase string', () => { - const svg = makeBadge({ text: ["Label", "1 string"], format: 'svg', template: 'for-the-badge' }); - expect(svg).to.include('LABEL').and.to.include('1 STRING'); - }); - }); + const svg = makeBadge({ + text: ['Label', '1 string'], + format: 'svg', + template: 'for-the-badge', + }) + expect(svg) + .to.include('LABEL') + .and.to.include('1 STRING') + }) + }) describe('"social" template badge generation', () => { it('should produce capitalized string for badge key', () => { - const svg = makeBadge({ text: ["some-key", "some-value"], format: 'svg', template: 'social' }); - expect(svg).to.include('Some-key').and.to.include('some-value'); - }); + const svg = makeBadge({ + text: ['some-key', 'some-value'], + format: 'svg', + template: 'social', + }) + expect(svg) + .to.include('Some-key') + .and.to.include('some-value') + }) - // https://github.com/badges/shields/issues/1606 + // https://github.com/badges/shields/issues/1606 it('should handle empty strings used as badge keys', () => { - const svg = makeBadge({ text: ["", "some-value"], format: 'json', template: 'social' }); - expect(svg).to.include('""').and.to.include('some-value'); - }); - }); + const svg = makeBadge({ + text: ['', 'some-value'], + format: 'json', + template: 'social', + }) + expect(svg) + .to.include('""') + .and.to.include('some-value') + }) + }) describe('badges with logos should always produce the same badge', () => { it('shields GitHub logo default color (#333333)', () => { - const svg = makeBadge({ text: ['label', 'message'], format: 'svg', logo: 'github' }); - snapshot(svg); - }); + const svg = makeBadge({ + text: ['label', 'message'], + format: 'svg', + logo: 'github', + }) + snapshot(svg) + }) it('shields GitHub logo custom color (whitesmoke)', () => { - const svg = makeBadge({ text: ['label', 'message'], format: 'svg', logo: 'github', logoColor: 'whitesmoke' }); - snapshot(svg); - }); + const svg = makeBadge({ + text: ['label', 'message'], + format: 'svg', + logo: 'github', + logoColor: 'whitesmoke', + }) + snapshot(svg) + }) it('simple-icons javascript logo default color (#F7DF1E)', () => { - const svg = makeBadge({ text: ['label', 'message'], format: 'svg', logo: 'javascript' }); - snapshot(svg); - }); + const svg = makeBadge({ + text: ['label', 'message'], + format: 'svg', + logo: 'javascript', + }) + snapshot(svg) + }) it('simple-icons javascript logo custom color (rgba(46,204,113,0.8))', () => { - const svg = makeBadge({ text: ['label', 'message'], format: 'svg', logo: 'javascript', logoColor: 'rgba(46,204,113,0.8)' }); - snapshot(svg); - }); - }); -}); + const svg = makeBadge({ + text: ['label', 'message'], + format: 'svg', + logo: 'javascript', + logoColor: 'rgba(46,204,113,0.8)', + }) + snapshot(svg) + }) + }) +}) diff --git a/lib/nexus-version.js b/lib/nexus-version.js index 3f3a51d3068136546ead79801403af4904270efa..f612b1e59cec8e76167a188882deb00b6baa96c4 100644 --- a/lib/nexus-version.js +++ b/lib/nexus-version.js @@ -1,10 +1,10 @@ -'use strict'; +'use strict' function isSnapshotVersion(version) { - const pattern = /(\d+\.)*\d-SNAPSHOT/; - return version && version.match(pattern); + const pattern = /(\d+\.)*\d-SNAPSHOT/ + return version && version.match(pattern) } module.exports = { - isSnapshotVersion -}; + isSnapshotVersion, +} diff --git a/lib/nodeify-sync.js b/lib/nodeify-sync.js index a5aa04ae52023ec958ea5f6aed73284f45f25afb..82786faab112eba1ea4076dfb65bc5bfccdfaac4 100644 --- a/lib/nodeify-sync.js +++ b/lib/nodeify-sync.js @@ -1,17 +1,17 @@ -'use strict'; +'use strict' // Execute a synchronous block and invoke a standard error-first callback with // the result. function nodeifySync(resultFn, callback) { - let result, error; + let result, error try { - result = resultFn(); + result = resultFn() } catch (e) { - error = e; + error = e } - callback(error, result); + callback(error, result) } -module.exports = nodeifySync; +module.exports = nodeifySync diff --git a/lib/nodeify-sync.spec.js b/lib/nodeify-sync.spec.js index 431c5360e5c0af06ce6ff3a956f08a1540d9da9c..f288b23fb7b928da8d15283fb6dddfcc400f137c 100644 --- a/lib/nodeify-sync.spec.js +++ b/lib/nodeify-sync.spec.js @@ -1,24 +1,32 @@ -'use strict'; +'use strict' -const { expect } = require('chai'); -const nodeifySync = require('./nodeify-sync'); +const { expect } = require('chai') +const nodeifySync = require('./nodeify-sync') describe('nodeifySync()', function() { it('Should return the result via the callback', function(done) { - const exampleValue = {}; - nodeifySync(() => exampleValue, (err, result) => { - expect(err).to.be.undefined; - expect(result).to.equal(exampleValue); - done(); - }); - }); + const exampleValue = {} + nodeifySync( + () => exampleValue, + (err, result) => { + expect(err).to.be.undefined + expect(result).to.equal(exampleValue) + done() + } + ) + }) it('Should catch an error and return it via the callback', function(done) { - const exampleError = Error('This is my error!'); - nodeifySync(() => { throw exampleError; }, (err, result) => { - expect(err).to.equal(exampleError); - expect(result).to.be.undefined; - done(); - }); - }); -}); + const exampleError = Error('This is my error!') + nodeifySync( + () => { + throw exampleError + }, + (err, result) => { + expect(err).to.equal(exampleError) + expect(result).to.be.undefined + done() + } + ) + }) +}) diff --git a/lib/nuget-provider.js b/lib/nuget-provider.js index 549cbbdf7da08e9017ee92ad3cadc075e542617d..4879bd4d10e48db5802e7fcb2cab752a05a7918c 100644 --- a/lib/nuget-provider.js +++ b/lib/nuget-provider.js @@ -1,295 +1,329 @@ -'use strict'; +'use strict' -const { downloadCount: downloadCountColor } = require('./color-formatters'); -const { makeBadgeData: getBadgeData } = require('./badge-data'); -const { metric } = require('./text-formatters'); -const { regularUpdate } = require('./regular-update'); +const { downloadCount: downloadCountColor } = require('./color-formatters') +const { makeBadgeData: getBadgeData } = require('./badge-data') +const { metric } = require('./text-formatters') +const { regularUpdate } = require('./regular-update') function mapNugetFeedv2({ camp, cache }, pattern, offset, getInfo) { - const vRegex = new RegExp('^\\/' + pattern + '\\/v\\/(.*)\\.(svg|png|gif|jpg|json)$'); - const vPreRegex = new RegExp('^\\/' + pattern + '\\/vpre\\/(.*)\\.(svg|png|gif|jpg|json)$'); - const dtRegex = new RegExp('^\\/' + pattern + '\\/dt\\/(.*)\\.(svg|png|gif|jpg|json)$'); + const vRegex = new RegExp( + '^\\/' + pattern + '\\/v\\/(.*)\\.(svg|png|gif|jpg|json)$' + ) + const vPreRegex = new RegExp( + '^\\/' + pattern + '\\/vpre\\/(.*)\\.(svg|png|gif|jpg|json)$' + ) + const dtRegex = new RegExp( + '^\\/' + pattern + '\\/dt\\/(.*)\\.(svg|png|gif|jpg|json)$' + ) function getNugetPackage(apiUrl, id, includePre, request, done) { - const filter = includePre ? - 'Id eq \'' + id + '\' and IsAbsoluteLatestVersion eq true' : - 'Id eq \'' + id + '\' and IsLatestVersion eq true'; - const reqUrl = apiUrl + '/Packages()?$filter=' + encodeURIComponent(filter); - request(reqUrl, - { headers: { 'Accept': 'application/atom+json,application/json' } }, - function(err, res, buffer) { - if (err != null) { - done(new Error('inaccessible')); - return; - } + const filter = includePre + ? "Id eq '" + id + "' and IsAbsoluteLatestVersion eq true" + : "Id eq '" + id + "' and IsLatestVersion eq true" + const reqUrl = apiUrl + '/Packages()?$filter=' + encodeURIComponent(filter) + request( + reqUrl, + { headers: { Accept: 'application/atom+json,application/json' } }, + function(err, res, buffer) { + if (err != null) { + done(new Error('inaccessible')) + return + } - try { - const data = JSON.parse(buffer); - const result = data.d.results[0]; - if (result == null) { - if (includePre === null) { - getNugetPackage(apiUrl, id, true, request, done); + try { + const data = JSON.parse(buffer) + const result = data.d.results[0] + if (result == null) { + if (includePre === null) { + getNugetPackage(apiUrl, id, true, request, done) + } else { + done(new Error('not found')) + } } else { - done(new Error('not found')); + done(null, result) } - } else { - done(null, result); + } catch (e) { + done(new Error('invalid')) } - } catch (e) { - done(new Error('invalid')); } - }); + ) } - camp.route(vRegex, - cache(function(data, match, sendBadge, request) { - const info = getInfo(match); - const site = info.site; // eg, `Chocolatey`, or `YoloDev` - const repo = match[offset + 1]; // eg, `Nuget.Core`. - const format = match[offset + 2]; - const apiUrl = info.feed; - const badgeData = getBadgeData(site, data); - getNugetPackage(apiUrl, repo, null, request, function(err, data) { - if (err != null) { - badgeData.text[1] = err.message; - sendBadge(format, badgeData); - return; - } - const version = data.NormalizedVersion || data.Version; - badgeData.text[1] = 'v' + version; - if (version.indexOf('-') !== -1) { - badgeData.colorscheme = 'yellow'; - } else if (version[0] === '0') { - badgeData.colorscheme = 'orange'; - } else { - badgeData.colorscheme = 'blue'; - } - sendBadge(format, badgeData); - }); - })); + camp.route( + vRegex, + cache(function(data, match, sendBadge, request) { + const info = getInfo(match) + const site = info.site // eg, `Chocolatey`, or `YoloDev` + const repo = match[offset + 1] // eg, `Nuget.Core`. + const format = match[offset + 2] + const apiUrl = info.feed + const badgeData = getBadgeData(site, data) + getNugetPackage(apiUrl, repo, null, request, function(err, data) { + if (err != null) { + badgeData.text[1] = err.message + sendBadge(format, badgeData) + return + } + const version = data.NormalizedVersion || data.Version + badgeData.text[1] = 'v' + version + if (version.indexOf('-') !== -1) { + badgeData.colorscheme = 'yellow' + } else if (version[0] === '0') { + badgeData.colorscheme = 'orange' + } else { + badgeData.colorscheme = 'blue' + } + sendBadge(format, badgeData) + }) + }) + ) - camp.route(vPreRegex, - cache(function(data, match, sendBadge, request) { - const info = getInfo(match); - const site = info.site; // eg, `Chocolatey`, or `YoloDev` - const repo = match[offset + 1]; // eg, `Nuget.Core`. - const format = match[offset + 2]; - const apiUrl = info.feed; - const badgeData = getBadgeData(site, data); - getNugetPackage(apiUrl, repo, true, request, function(err, data) { - if (err != null) { - badgeData.text[1] = err.message; - sendBadge(format, badgeData); - return; - } - const version = data.NormalizedVersion || data.Version; - badgeData.text[1] = 'v' + version; - if (version.indexOf('-') !== -1) { - badgeData.colorscheme = 'yellow'; - } else if (version[0] === '0') { - badgeData.colorscheme = 'orange'; - } else { - badgeData.colorscheme = 'blue'; - } - sendBadge(format, badgeData); - }); - })); + camp.route( + vPreRegex, + cache(function(data, match, sendBadge, request) { + const info = getInfo(match) + const site = info.site // eg, `Chocolatey`, or `YoloDev` + const repo = match[offset + 1] // eg, `Nuget.Core`. + const format = match[offset + 2] + const apiUrl = info.feed + const badgeData = getBadgeData(site, data) + getNugetPackage(apiUrl, repo, true, request, function(err, data) { + if (err != null) { + badgeData.text[1] = err.message + sendBadge(format, badgeData) + return + } + const version = data.NormalizedVersion || data.Version + badgeData.text[1] = 'v' + version + if (version.indexOf('-') !== -1) { + badgeData.colorscheme = 'yellow' + } else if (version[0] === '0') { + badgeData.colorscheme = 'orange' + } else { + badgeData.colorscheme = 'blue' + } + sendBadge(format, badgeData) + }) + }) + ) - camp.route(dtRegex, - cache(function(data, match, sendBadge, request) { - const info = getInfo(match); - const repo = match[offset+ 1]; // eg, `Nuget.Core`. - const format = match[offset + 2]; - const apiUrl = info.feed; - const badgeData = getBadgeData('downloads', data); - getNugetPackage(apiUrl, repo, null, request, function(err, data) { - if (err != null) { - badgeData.text[1] = err.message; - sendBadge(format, badgeData); - return; - } - const downloads = data.DownloadCount; - badgeData.text[1] = metric(downloads); - badgeData.colorscheme = downloadCountColor(downloads); - sendBadge(format, badgeData); - }); - })); + camp.route( + dtRegex, + cache(function(data, match, sendBadge, request) { + const info = getInfo(match) + const repo = match[offset + 1] // eg, `Nuget.Core`. + const format = match[offset + 2] + const apiUrl = info.feed + const badgeData = getBadgeData('downloads', data) + getNugetPackage(apiUrl, repo, null, request, function(err, data) { + if (err != null) { + badgeData.text[1] = err.message + sendBadge(format, badgeData) + return + } + const downloads = data.DownloadCount + badgeData.text[1] = metric(downloads) + badgeData.colorscheme = downloadCountColor(downloads) + sendBadge(format, badgeData) + }) + }) + ) } function mapNugetFeed({ camp, cache }, pattern, offset, getInfo) { - const vRegex = new RegExp('^\\/' + pattern + '\\/v\\/(.*)\\.(svg|png|gif|jpg|json)$'); - const vPreRegex = new RegExp('^\\/' + pattern + '\\/vpre\\/(.*)\\.(svg|png|gif|jpg|json)$'); - const dtRegex = new RegExp('^\\/' + pattern + '\\/dt\\/(.*)\\.(svg|png|gif|jpg|json)$'); + const vRegex = new RegExp( + '^\\/' + pattern + '\\/v\\/(.*)\\.(svg|png|gif|jpg|json)$' + ) + const vPreRegex = new RegExp( + '^\\/' + pattern + '\\/vpre\\/(.*)\\.(svg|png|gif|jpg|json)$' + ) + const dtRegex = new RegExp( + '^\\/' + pattern + '\\/dt\\/(.*)\\.(svg|png|gif|jpg|json)$' + ) function getNugetData(apiUrl, id, request, done) { // get service index document - regularUpdate({ - url: apiUrl + '/index.json', - // The endpoint changes once per year (ie, a period of n = 1 year). - // We minimize the users' waiting time for information. - // With l = latency to fetch the endpoint and x = endpoint update period - // both in years, the yearly number of queries for the endpoint are 1/x, - // and when the endpoint changes, we wait for up to x years to get the - // right endpoint. - // So the waiting time within n years is n*l/x + x years, for which a - // derivation yields an optimum at x = sqrt(n*l), roughly 42 minutes. - intervalMillis: 42 * 60 * 1000, - json: false, - scraper: function(data) { return data; }, - }, (err, buf) => { + regularUpdate( + { + url: apiUrl + '/index.json', + // The endpoint changes once per year (ie, a period of n = 1 year). + // We minimize the users' waiting time for information. + // With l = latency to fetch the endpoint and x = endpoint update period + // both in years, the yearly number of queries for the endpoint are 1/x, + // and when the endpoint changes, we wait for up to x years to get the + // right endpoint. + // So the waiting time within n years is n*l/x + x years, for which a + // derivation yields an optimum at x = sqrt(n*l), roughly 42 minutes. + intervalMillis: 42 * 60 * 1000, + json: false, + scraper: function(data) { + return data + }, + }, + (err, buf) => { if (err != null) { - done(new Error('inaccessible')); - return; + done(new Error('inaccessible')) + return } try { const searchQueryResources = JSON.parse(buf).resources.filter( resource => resource['@type'] === 'SearchQueryService' - ); + ) // query autocomplete service - const randomEndpointIdx = Math.floor(Math.random() * searchQueryResources.length); - const reqUrl = searchQueryResources[randomEndpointIdx]['@id'] - + '?q=packageid:' + encodeURIComponent(id.toLowerCase()) // NuGet package id (lowercase) - + '&prerelease=true'; // Include prerelease versions? + const randomEndpointIdx = Math.floor( + Math.random() * searchQueryResources.length + ) + const reqUrl = + searchQueryResources[randomEndpointIdx]['@id'] + + '?q=packageid:' + + encodeURIComponent(id.toLowerCase()) + // NuGet package id (lowercase) + '&prerelease=true' // Include prerelease versions? request(reqUrl, (err, res, buffer) => { if (err != null) { - done(new Error('inaccessible')); - return; + done(new Error('inaccessible')) + return } try { - const data = JSON.parse(buffer); + const data = JSON.parse(buffer) if (!Array.isArray(data.data) || data.data.length !== 1) { - done(new Error('not found')); - return; + done(new Error('not found')) + return } - done(null, data.data[0]); + done(null, data.data[0]) } catch (e) { - done(new Error('invalid')); + done(new Error('invalid')) } - }); - + }) } catch (e) { - done(new Error('invalid')); + done(new Error('invalid')) } - }); + } + ) } function getNugetVersion(apiUrl, id, includePre, request, done) { getNugetData(apiUrl, id, request, function(err, data) { if (err) { - done(err); - return; + done(err) + return } - let versions = data.versions || []; + let versions = data.versions || [] if (!includePre) { // Remove prerelease versions. const filteredVersions = versions.filter(function(version) { - return !/-/.test(version.version); - }); + return !/-/.test(version.version) + }) if (filteredVersions.length > 0) { - versions = filteredVersions; + versions = filteredVersions } } - const lastVersion = versions[versions.length - 1]; - done(null, lastVersion.version); - }); + const lastVersion = versions[versions.length - 1] + done(null, lastVersion.version) + }) } - camp.route(vRegex, - cache(function(data, match, sendBadge, request) { - const info = getInfo(match); - const site = info.site; // eg, `Chocolatey`, or `YoloDev` - const repo = match[offset + 1]; // eg, `Nuget.Core`. - const format = match[offset + 2]; - const apiUrl = info.feed; - const badgeData = getBadgeData(site, data); - getNugetVersion(apiUrl, repo, false, request, function(err, version) { - if (err != null) { - badgeData.text[1] = err.message; - sendBadge(format, badgeData); - return; - } - try { - badgeData.text[1] = 'v' + version; - if (version.indexOf('-') !== -1) { - badgeData.colorscheme = 'yellow'; - } else if (version[0] === '0') { - badgeData.colorscheme = 'orange'; - } else { - badgeData.colorscheme = 'blue'; + camp.route( + vRegex, + cache(function(data, match, sendBadge, request) { + const info = getInfo(match) + const site = info.site // eg, `Chocolatey`, or `YoloDev` + const repo = match[offset + 1] // eg, `Nuget.Core`. + const format = match[offset + 2] + const apiUrl = info.feed + const badgeData = getBadgeData(site, data) + getNugetVersion(apiUrl, repo, false, request, function(err, version) { + if (err != null) { + badgeData.text[1] = err.message + sendBadge(format, badgeData) + return } - sendBadge(format, badgeData); - } catch(e) { - badgeData.text[1] = 'invalid'; - sendBadge(format, badgeData); - } - }); - })); - - camp.route(vPreRegex, - cache(function(data, match, sendBadge, request) { - const info = getInfo(match); - const site = info.site; // eg, `Chocolatey`, or `YoloDev` - const repo = match[offset + 1]; // eg, `Nuget.Core`. - const format = match[offset + 2]; - const apiUrl = info.feed; - const badgeData = getBadgeData(site, data); - getNugetVersion(apiUrl, repo, true, request, function(err, version) { - if (err != null) { - badgeData.text[1] = err.message; - sendBadge(format, badgeData); - return; - } - try { - badgeData.text[1] = 'v' + version; - if (version.indexOf('-') !== -1) { - badgeData.colorscheme = 'yellow'; - } else if (version[0] === '0') { - badgeData.colorscheme = 'orange'; - } else { - badgeData.colorscheme = 'blue'; + try { + badgeData.text[1] = 'v' + version + if (version.indexOf('-') !== -1) { + badgeData.colorscheme = 'yellow' + } else if (version[0] === '0') { + badgeData.colorscheme = 'orange' + } else { + badgeData.colorscheme = 'blue' + } + sendBadge(format, badgeData) + } catch (e) { + badgeData.text[1] = 'invalid' + sendBadge(format, badgeData) } - sendBadge(format, badgeData); - } catch(e) { - badgeData.text[1] = 'invalid'; - sendBadge(format, badgeData); - } - }); - })); + }) + }) + ) + camp.route( + vPreRegex, + cache(function(data, match, sendBadge, request) { + const info = getInfo(match) + const site = info.site // eg, `Chocolatey`, or `YoloDev` + const repo = match[offset + 1] // eg, `Nuget.Core`. + const format = match[offset + 2] + const apiUrl = info.feed + const badgeData = getBadgeData(site, data) + getNugetVersion(apiUrl, repo, true, request, function(err, version) { + if (err != null) { + badgeData.text[1] = err.message + sendBadge(format, badgeData) + return + } + try { + badgeData.text[1] = 'v' + version + if (version.indexOf('-') !== -1) { + badgeData.colorscheme = 'yellow' + } else if (version[0] === '0') { + badgeData.colorscheme = 'orange' + } else { + badgeData.colorscheme = 'blue' + } + sendBadge(format, badgeData) + } catch (e) { + badgeData.text[1] = 'invalid' + sendBadge(format, badgeData) + } + }) + }) + ) - camp.route(dtRegex, - cache(function(data, match, sendBadge, request) { - const info = getInfo(match); - const repo = match[offset + 1]; // eg, `Nuget.Core`. - const format = match[offset + 2]; - const apiUrl = info.feed; - const badgeData = getBadgeData('downloads', data); - getNugetData(apiUrl, repo, request, function(err, nugetData) { - if (err != null) { - badgeData.text[1] = err.message; - sendBadge(format, badgeData); - return; - } - try { - // Official NuGet server uses "totalDownloads" whereas MyGet uses - // "totaldownloads" (lowercase D). Ugh. - const downloads = nugetData.totalDownloads || nugetData.totaldownloads || 0; - badgeData.text[1] = metric(downloads); - badgeData.colorscheme = downloadCountColor(downloads); - sendBadge(format, badgeData); - } catch(e) { - badgeData.text[1] = 'invalid'; - sendBadge(format, badgeData); - } - }); - })); + camp.route( + dtRegex, + cache(function(data, match, sendBadge, request) { + const info = getInfo(match) + const repo = match[offset + 1] // eg, `Nuget.Core`. + const format = match[offset + 2] + const apiUrl = info.feed + const badgeData = getBadgeData('downloads', data) + getNugetData(apiUrl, repo, request, function(err, nugetData) { + if (err != null) { + badgeData.text[1] = err.message + sendBadge(format, badgeData) + return + } + try { + // Official NuGet server uses "totalDownloads" whereas MyGet uses + // "totaldownloads" (lowercase D). Ugh. + const downloads = + nugetData.totalDownloads || nugetData.totaldownloads || 0 + badgeData.text[1] = metric(downloads) + badgeData.colorscheme = downloadCountColor(downloads) + sendBadge(format, badgeData) + } catch (e) { + badgeData.text[1] = 'invalid' + sendBadge(format, badgeData) + } + }) + }) + ) } module.exports = { mapNugetFeedv2, - mapNugetFeed -}; + mapNugetFeed, +} diff --git a/lib/path-helpers.js b/lib/path-helpers.js index 5dfb6dea3ec18d806c5c2ee1fb16ff8b51e30a2b..bc0d63f162f9a232ce847b8c5c87eac0c13ae7cf 100644 --- a/lib/path-helpers.js +++ b/lib/path-helpers.js @@ -1,24 +1,30 @@ -'use strict'; +'use strict' // Escapes `t` using the format specified in // <https://github.com/espadrine/gh-badges/issues/12#issuecomment-31518129> function escapeFormat(t) { - return t - // Inline single underscore. - .replace(/([^_])_([^_])/g, '$1 $2') - // Leading or trailing underscore. - .replace(/([^_])_$/, '$1 ').replace(/^_([^_])/, ' $1') - // Double underscore and double dash. - .replace(/__/g, '_').replace(/--/g, '-'); + return ( + t + // Inline single underscore. + .replace(/([^_])_([^_])/g, '$1 $2') + // Leading or trailing underscore. + .replace(/([^_])_$/, '$1 ') + .replace(/^_([^_])/, ' $1') + // Double underscore and double dash. + .replace(/__/g, '_') + .replace(/--/g, '-') + ) } function escapeFormatSlashes(t) { - return escapeFormat(t) - // Double slash - .replace(/\/\//g, '/'); + return ( + escapeFormat(t) + // Double slash + .replace(/\/\//g, '/') + ) } module.exports = { escapeFormat, - escapeFormatSlashes -}; + escapeFormatSlashes, +} diff --git a/lib/php-version.js b/lib/php-version.js index 2e3b9a4193ecbd271ed630dc2cc0530197db27b9..eaaf1f28ab1a2727918862ba8e7695d06f50d63f 100644 --- a/lib/php-version.js +++ b/lib/php-version.js @@ -3,23 +3,23 @@ * using the algorithm followed by Composer (see * https://getcomposer.org/doc/04-schema.md#version). */ -'use strict'; +'use strict' -const request = require('request'); -const uniq = require('lodash.uniq'); -const {listCompare} = require('./version'); -const {omitv} = require('./text-formatters'); -const { regularUpdate } = require('./regular-update'); +const request = require('request') +const uniq = require('lodash.uniq') +const { listCompare } = require('./version') +const { omitv } = require('./text-formatters') +const { regularUpdate } = require('./regular-update') // Return a negative value if v1 < v2, // zero if v1 = v2, a positive value otherwise. function asciiVersionCompare(v1, v2) { if (v1 < v2) { - return -1; + return -1 } else if (v1 > v2) { - return 1; + return 1 } else { - return 0; + return 0 } } @@ -29,8 +29,8 @@ function asciiVersionCompare(v1, v2) { function numberedVersionData(version) { // A version has a numbered part and a modifier part // (eg, 1.0.0-patch, 2.0.x-dev). - const parts = version.split('-'); - const numbered = parts[0]; + const parts = version.split('-') + const numbered = parts[0] // Aliases that get caught here. if (numbered === 'dev') { @@ -38,74 +38,79 @@ function numberedVersionData(version) { numbers: parts[1], modifier: 5, modifierCount: 1, - }; + } } - let modifierLevel = 3; - let modifierLevelCount = 0; + let modifierLevel = 3 + let modifierLevelCount = 0 if (parts.length > 1) { - const modifier = parts[parts.length - 1]; - const firstLetter = modifier.charCodeAt(0); - let modifierLevelCountString; + const modifier = parts[parts.length - 1] + const firstLetter = modifier.charCodeAt(0) + let modifierLevelCountString // Modifiers: alpha < beta < RC < normal < patch < dev - if (firstLetter === 97) { // a - modifierLevel = 0; + if (firstLetter === 97) { + // a + modifierLevel = 0 if (/^alpha/.test(modifier)) { - modifierLevelCountString = + (modifier.slice(5)); + modifierLevelCountString = +modifier.slice(5) } else { - modifierLevelCountString = + (modifier.slice(1)); + modifierLevelCountString = +modifier.slice(1) } - } else if (firstLetter === 98) { // b - modifierLevel = 1; + } else if (firstLetter === 98) { + // b + modifierLevel = 1 if (/^beta/.test(modifier)) { - modifierLevelCountString = + (modifier.slice(4)); + modifierLevelCountString = +modifier.slice(4) } else { - modifierLevelCountString = + (modifier.slice(1)); + modifierLevelCountString = +modifier.slice(1) } - } else if (firstLetter === 82) { // R - modifierLevel = 2; - modifierLevelCountString = + (modifier.slice(2)); - } else if (firstLetter === 112) { // p - modifierLevel = 4; + } else if (firstLetter === 82) { + // R + modifierLevel = 2 + modifierLevelCountString = +modifier.slice(2) + } else if (firstLetter === 112) { + // p + modifierLevel = 4 if (/^patch/.test(modifier)) { - modifierLevelCountString = + (modifier.slice(5)); + modifierLevelCountString = +modifier.slice(5) } else { - modifierLevelCountString = + (modifier.slice(1)); + modifierLevelCountString = +modifier.slice(1) } - } else if (firstLetter === 100) { // d - modifierLevel = 5; + } else if (firstLetter === 100) { + // d + modifierLevel = 5 if (/^dev/.test(modifier)) { - modifierLevelCountString = + (modifier.slice(3)); + modifierLevelCountString = +modifier.slice(3) } else { - modifierLevelCountString = + (modifier.slice(1)); + modifierLevelCountString = +modifier.slice(1) } } // If we got the empty string, it defaults to a modifier count of 1. if (!modifierLevelCountString) { - modifierLevelCount = 1; + modifierLevelCount = 1 } else { - modifierLevelCount = + modifierLevelCountString; + modifierLevelCount = +modifierLevelCountString } } // Try to convert to a list of numbers. - function toNum (s) { - let n = +s; + function toNum(s) { + let n = +s if (Number.isNaN(n)) { - n = 0xffffffff; + n = 0xffffffff } - return n; + return n } - const numberList = numbered.split('.').map(toNum); + const numberList = numbered.split('.').map(toNum) return { numbers: numberList, modifier: modifierLevel, modifierCount: modifierLevelCount, - }; + } } // Return a negative value if v1 < v2, @@ -116,111 +121,121 @@ function numberedVersionData(version) { // and https://github.com/badges/shields/issues/319#issuecomment-74411045 function compare(v1, v2) { // Omit the starting `v`. - const rawv1 = omitv(v1); - const rawv2 = omitv(v2); - let v1data, v2data; + const rawv1 = omitv(v1) + const rawv2 = omitv(v2) + let v1data, v2data try { - v1data = numberedVersionData(rawv1); - v2data = numberedVersionData(rawv2); - } catch(e) { - return asciiVersionCompare(rawv1, rawv2); + v1data = numberedVersionData(rawv1) + v2data = numberedVersionData(rawv2) + } catch (e) { + return asciiVersionCompare(rawv1, rawv2) } // Compare the numbered part (eg, 1.0.0 < 2.0.0). - const numbersCompare = listCompare(v1data.numbers, v2data.numbers); + const numbersCompare = listCompare(v1data.numbers, v2data.numbers) if (numbersCompare !== 0) { - return numbersCompare; + return numbersCompare } // Compare the modifiers (eg, alpha < beta). if (v1data.modifier < v2data.modifier) { - return -1; + return -1 } else if (v1data.modifier > v2data.modifier) { - return 1; + return 1 } // Compare the modifier counts (eg, alpha1 < alpha3). if (v1data.modifierCount < v2data.modifierCount) { - return -1; + return -1 } else if (v1data.modifierCount > v2data.modifierCount) { - return 1; + return 1 } - return 0; + return 0 } function latest(versions) { - let latest = versions[0]; + let latest = versions[0] for (let i = 1; i < versions.length; i++) { if (compare(latest, versions[i]) < 0) { - latest = versions[i]; + latest = versions[i] } } - return latest; + return latest } function isStable(version) { - const rawVersion = omitv(version); - let versionData; + const rawVersion = omitv(version) + let versionData try { - versionData = numberedVersionData(rawVersion); - } catch(e) { - return false; + versionData = numberedVersionData(rawVersion) + } catch (e) { + return false } // normal or patch - return (versionData.modifier === 3) || (versionData.modifier === 4); + return versionData.modifier === 3 || versionData.modifier === 4 } function minorVersion(version) { - const result = version.match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/); + const result = version.match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/) if (result === null) { - return ''; + return '' } - return result[1] + '.' + (result[2] ? result[2] : '0'); + return result[1] + '.' + (result[2] ? result[2] : '0') } function versionReduction(versions, phpReleases) { if (!versions.length) { - return ''; + return '' } // versions intersect - versions = uniq(versions).sort().filter((n) => phpReleases.includes(n)); + versions = uniq(versions) + .sort() + .filter(n => phpReleases.includes(n)) // nothing to reduction if (versions.length < 2) { - return versions.length ? versions[0] : ''; + return versions.length ? versions[0] : '' } - const first = phpReleases.indexOf(versions[0]); - const last = phpReleases.indexOf(versions[versions.length - 1]); + const first = phpReleases.indexOf(versions[0]) + const last = phpReleases.indexOf(versions[versions.length - 1]) // no missed versions if (first + versions.length - 1 === last) { if (last === phpReleases.length - 1) { - return '>= ' + (versions[0][2] === '0' ? versions[0][0] : versions[0]); // 7.0 -> 7 + return '>= ' + (versions[0][2] === '0' ? versions[0][0] : versions[0]) // 7.0 -> 7 } - return versions[0] + ' - ' + versions[versions.length - 1]; + return versions[0] + ' - ' + versions[versions.length - 1] } - return versions.join(', '); + return versions.join(', ') } function getPhpReleases(githubApiProvider, cb) { - regularUpdate({ - url: '/repos/php/php-src/git/refs/tags', - intervalMillis: 24 * 3600 * 1000, // 1 day - scraper: tags => uniq( - tags - // only releases - .filter(tag => tag.ref.match(/^refs\/tags\/php-\d+\.\d+\.\d+$/) != null) - // get minor version of release - .map(tag => tag.ref.match(/^refs\/tags\/php-(\d+\.\d+)\.\d+$/)[1])), - request: (url, options, cb) => githubApiProvider.request(request, url, {}, cb), - }, cb); + regularUpdate( + { + url: '/repos/php/php-src/git/refs/tags', + intervalMillis: 24 * 3600 * 1000, // 1 day + scraper: tags => + uniq( + tags + // only releases + .filter( + tag => tag.ref.match(/^refs\/tags\/php-\d+\.\d+\.\d+$/) != null + ) + // get minor version of release + .map(tag => tag.ref.match(/^refs\/tags\/php-(\d+\.\d+)\.\d+$/)[1]) + ), + request: (url, options, cb) => + githubApiProvider.request(request, url, {}, cb), + }, + cb + ) } module.exports = { @@ -230,4 +245,4 @@ module.exports = { minorVersion, versionReduction, getPhpReleases, -}; +} diff --git a/lib/php-version.spec.js b/lib/php-version.spec.js index 1d1606049e77f2157e0b1518a94f13a153bc31b6..c73e959593b45b40b55c9bea80f9c45ad626f4f2 100644 --- a/lib/php-version.spec.js +++ b/lib/php-version.spec.js @@ -1,64 +1,76 @@ -'use strict'; +'use strict' -const { test, given } = require('sazerac'); -const { - compare, - minorVersion, - versionReduction -} = require('./php-version'); +const { test, given } = require('sazerac') +const { compare, minorVersion, versionReduction } = require('./php-version') -const phpReleases = ['5.0', '5.1', '5.2', '5.3', '5.4', '5.5', '5.6', '7.0', '7.1', '7.2']; +const phpReleases = [ + '5.0', + '5.1', + '5.2', + '5.3', + '5.4', + '5.5', + '5.6', + '7.0', + '7.1', + '7.2', +] describe('Text PHP version', function() { test(minorVersion, () => { - given('7').expect('7.0'); - given('7.1').expect('7.1'); - given('5.3.3').expect('5.3'); - given('hhvm').expect(''); - }); + given('7').expect('7.0') + given('7.1').expect('7.1') + given('5.3.3').expect('5.3') + given('hhvm').expect('') + }) test(versionReduction, () => { - given(['5.3', '5.4', '5.5'], phpReleases).expect('5.3 - 5.5'); - given(['5.4', '5.5', '5.6', '7.0', '7.1'], phpReleases).expect('5.4 - 7.1'); - given(['5.5', '5.6', '7.0', '7.1', '7.2'], phpReleases).expect('>= 5.5'); - given(['5.5', '5.6', '7.1', '7.2'], phpReleases).expect('5.5, 5.6, 7.1, 7.2'); - given(['7.0', '7.1', '7.2'], phpReleases).expect('>= 7'); - given(['5.0', '5.1', '5.2', '5.3', '5.4', '5.5', '5.6', '7.0', '7.1', '7.2'], phpReleases).expect('>= 5'); - given(['7.1', '7.2'], phpReleases).expect('>= 7.1'); - given(['7.1'], phpReleases).expect('7.1'); - given(['8.1'], phpReleases).expect(''); - given([]).expect(''); - }); -}); + given(['5.3', '5.4', '5.5'], phpReleases).expect('5.3 - 5.5') + given(['5.4', '5.5', '5.6', '7.0', '7.1'], phpReleases).expect('5.4 - 7.1') + given(['5.5', '5.6', '7.0', '7.1', '7.2'], phpReleases).expect('>= 5.5') + given(['5.5', '5.6', '7.1', '7.2'], phpReleases).expect( + '5.5, 5.6, 7.1, 7.2' + ) + given(['7.0', '7.1', '7.2'], phpReleases).expect('>= 7') + given( + ['5.0', '5.1', '5.2', '5.3', '5.4', '5.5', '5.6', '7.0', '7.1', '7.2'], + phpReleases + ).expect('>= 5') + given(['7.1', '7.2'], phpReleases).expect('>= 7.1') + given(['7.1'], phpReleases).expect('7.1') + given(['8.1'], phpReleases).expect('') + given([]).expect('') + }) +}) describe('Composer version comparison', function() { test(compare, () => { // composer version scheme ordering - given('0.9.0', '1.0.0-alpha').expect(-1); - given('1.0.0-alpha', '1.0.0-alpha2').expect(-1); - given('1.0.0-alpha2', '1.0.0-beta').expect(-1); - given('1.0.0-beta', '1.0.0-beta2').expect(-1); - given('1.0.0-beta2', '1.0.0-RC').expect(-1); - given('1.0.0-RC', '1.0.0-RC2').expect(-1); - given('1.0.0-RC2', '1.0.0').expect(-1); - given('1.0.0', '1.0.0-patch').expect(-1); - given('1.0.0-patch', '1.0.0-dev').expect(-1); - given('1.0.0-dev', '1.0.1').expect(-1); - given('1.0.1', '1.0.x-dev').expect(-1); + given('0.9.0', '1.0.0-alpha').expect(-1) + given('1.0.0-alpha', '1.0.0-alpha2').expect(-1) + given('1.0.0-alpha2', '1.0.0-beta').expect(-1) + given('1.0.0-beta', '1.0.0-beta2').expect(-1) + given('1.0.0-beta2', '1.0.0-RC').expect(-1) + given('1.0.0-RC', '1.0.0-RC2').expect(-1) + given('1.0.0-RC2', '1.0.0').expect(-1) + given('1.0.0', '1.0.0-patch').expect(-1) + given('1.0.0-patch', '1.0.0-dev').expect(-1) + given('1.0.0-dev', '1.0.1').expect(-1) + given('1.0.1', '1.0.x-dev').expect(-1) // short versions should compare equal to long versions - given('1.0.0-p', '1.0.0-patch').expect(0); - given('1.0.0-a', '1.0.0-alpha').expect(0); - given('1.0.0-a2', '1.0.0-alpha2').expect(0); - given('1.0.0-b', '1.0.0-beta').expect(0); - given('1.0.0-b2', '1.0.0-beta2').expect(0); + given('1.0.0-p', '1.0.0-patch').expect(0) + given('1.0.0-a', '1.0.0-alpha').expect(0) + given('1.0.0-a2', '1.0.0-alpha2').expect(0) + given('1.0.0-b', '1.0.0-beta').expect(0) + given('1.0.0-b2', '1.0.0-beta2').expect(0) // numeric suffixes - given('1.0.0-b1', '1.0.0-b2').expect(-1); - given('1.0.0-b10', '1.0.0-b11').expect(-1); - given('1.0.0-a1', '1.0.0-a2').expect(-1); - given('1.0.0-a10', '1.0.0-a11').expect(-1); - given('1.0.0-RC1', '1.0.0-RC2').expect(-1); - given('1.0.0-RC10', '1.0.0-RC11').expect(-1); - }); -}); + given('1.0.0-b1', '1.0.0-b2').expect(-1) + given('1.0.0-b10', '1.0.0-b11').expect(-1) + given('1.0.0-a1', '1.0.0-a2').expect(-1) + given('1.0.0-a10', '1.0.0-a11').expect(-1) + given('1.0.0-RC1', '1.0.0-RC2').expect(-1) + given('1.0.0-RC10', '1.0.0-RC11').expect(-1) + }) +}) diff --git a/lib/pypi-helpers.js b/lib/pypi-helpers.js index beaeb5a488d86b0577b4d84e993e01c2b805499d..b51d018883315a58fb21bb56f9fb270687976348 100644 --- a/lib/pypi-helpers.js +++ b/lib/pypi-helpers.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' /* Django versions will be specified in the form major.minor @@ -9,43 +9,49 @@ */ const parseDjangoVersionString = function(str) { - if (typeof(str) !== 'string') { return false; } - const x = str.split('.'); - const maj = parseInt(x[0]) || 0; - const min = parseInt(x[1]) || 0; + if (typeof str !== 'string') { + return false + } + const x = str.split('.') + const maj = parseInt(x[0]) || 0 + const min = parseInt(x[1]) || 0 return { - major: maj, - minor: min - }; -}; + major: maj, + minor: min, + } +} // sort an array of django versions low to high const sortDjangoVersions = function(versions) { return versions.sort(function(a, b) { - if (parseDjangoVersionString(a).major === parseDjangoVersionString(b).major) { - return parseDjangoVersionString(a).minor - parseDjangoVersionString(b).minor; + if ( + parseDjangoVersionString(a).major === parseDjangoVersionString(b).major + ) { + return ( + parseDjangoVersionString(a).minor - parseDjangoVersionString(b).minor + ) } else { - return parseDjangoVersionString(a).major - parseDjangoVersionString(b).major; + return ( + parseDjangoVersionString(a).major - parseDjangoVersionString(b).major + ) } - }); -}; - + }) +} // extract classifiers from a pypi json response based on a regex const parseClassifiers = function(parsedData, pattern) { - const results = []; + const results = [] for (let i = 0; i < parsedData.info.classifiers.length; i++) { - const matched = pattern.exec(parsedData.info.classifiers[i]); + const matched = pattern.exec(parsedData.info.classifiers[i]) if (matched && matched[1]) { - results.push(matched[1].toLowerCase()); + results.push(matched[1].toLowerCase()) } } - return results; -}; - + return results +} module.exports = { parseClassifiers, parseDjangoVersionString, - sortDjangoVersions -}; + sortDjangoVersions, +} diff --git a/lib/pypi-helpers.spec.js b/lib/pypi-helpers.spec.js index 65041b13660409dceb61596b28296c449af3a19e..1d62eeff972d6dc185cd48fd957473903690ad25 100644 --- a/lib/pypi-helpers.spec.js +++ b/lib/pypi-helpers.spec.js @@ -1,78 +1,102 @@ -'use strict'; +'use strict' -const { test, given } = require('sazerac'); +const { test, given } = require('sazerac') const { parseClassifiers, parseDjangoVersionString, - sortDjangoVersions -} = require('./pypi-helpers.js'); + sortDjangoVersions, +} = require('./pypi-helpers.js') const classifiersFixture = { info: { classifiers: [ - "Development Status :: 5 - Production/Stable", - "Environment :: Web Environment", - "Framework :: Django", - "Framework :: Django :: 1.10", - "Framework :: Django :: 1.11", - "Intended Audience :: Developers", - "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", - "Operating System :: OS Independent", - "Natural Language :: English", - "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Topic :: Internet :: WWW/HTTP", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy" - ] - } -}; + 'Development Status :: 5 - Production/Stable', + 'Environment :: Web Environment', + 'Framework :: Django', + 'Framework :: Django :: 1.10', + 'Framework :: Django :: 1.11', + 'Intended Audience :: Developers', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Natural Language :: English', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Topic :: Internet :: WWW/HTTP', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + ], + }, +} describe('PyPI helpers', function() { test(parseClassifiers, function() { - given(classifiersFixture, /^Programming Language :: Python :: ([\d.]+)$/) - .expect(["2","2.7","3","3.4","3.5","3.6"]); + given( + classifiersFixture, + /^Programming Language :: Python :: ([\d.]+)$/ + ).expect(['2', '2.7', '3', '3.4', '3.5', '3.6']) - given(classifiersFixture, /^Framework :: Django :: ([\d.]+)$/) - .expect(["1.10", "1.11"]); + given(classifiersFixture, /^Framework :: Django :: ([\d.]+)$/).expect([ + '1.10', + '1.11', + ]) - given(classifiersFixture, /^Programming Language :: Python :: Implementation :: (\S+)$/) - .expect(["cpython", "pypy"]); + given( + classifiersFixture, + /^Programming Language :: Python :: Implementation :: (\S+)$/ + ).expect(['cpython', 'pypy']) // regex that matches everything - given(classifiersFixture, /^([\S\s+]+)$/) - .expect(classifiersFixture.info.classifiers.map(function(e) { - return e.toLowerCase(); - })); + given(classifiersFixture, /^([\S\s+]+)$/).expect( + classifiersFixture.info.classifiers.map(function(e) { + return e.toLowerCase() + }) + ) // regex that matches nothing - given(classifiersFixture, /^(?!.*)*$/) - .expect([]); - }); + given(classifiersFixture, /^(?!.*)*$/).expect([]) + }) test(parseDjangoVersionString, function() { - given("1").expect({ major: 1, minor: 0}); - given("1.0").expect({ major: 1, minor: 0}); - given("7.2").expect({ major: 7, minor: 2}); - given("7.2derpderp").expect({ major: 7, minor: 2}); - given("7.2.9.5.8.3").expect({ major: 7, minor: 2}); - given("foo").expect({ major: 0, minor: 0}); - }); + given('1').expect({ major: 1, minor: 0 }) + given('1.0').expect({ major: 1, minor: 0 }) + given('7.2').expect({ major: 7, minor: 2 }) + given('7.2derpderp').expect({ major: 7, minor: 2 }) + given('7.2.9.5.8.3').expect({ major: 7, minor: 2 }) + given('foo').expect({ major: 0, minor: 0 }) + }) test(sortDjangoVersions, function() { - given(["2.0", "1.9", "10", "1.11", "2.1", "2.11",]) - .expect(["1.9", "1.11", "2.0", "2.1", "2.11", "10"]); + given(['2.0', '1.9', '10', '1.11', '2.1', '2.11']).expect([ + '1.9', + '1.11', + '2.0', + '2.1', + '2.11', + '10', + ]) - given(["2", "1.9", "10", "1.11", "2.1", "2.11",]) - .expect(["1.9", "1.11", "2", "2.1", "2.11", "10"]); + given(['2', '1.9', '10', '1.11', '2.1', '2.11']).expect([ + '1.9', + '1.11', + '2', + '2.1', + '2.11', + '10', + ]) - given(["2.0rc1", "10", "1.9", "1.11", "2.1", "2.11",]) - .expect(["1.9", "1.11", "2.0rc1", "2.1", "2.11", "10"]); - }); -}); + given(['2.0rc1', '10', '1.9', '1.11', '2.1', '2.11']).expect([ + '1.9', + '1.11', + '2.0rc1', + '2.1', + '2.11', + '10', + ]) + }) +}) diff --git a/lib/register-chai-plugins.spec.js b/lib/register-chai-plugins.spec.js index c10ec2cc17581a721cd60a6c150732306ff709a1..0873f4c86344f6fbbcb7edfe7e21efa05694ec9a 100644 --- a/lib/register-chai-plugins.spec.js +++ b/lib/register-chai-plugins.spec.js @@ -1,6 +1,6 @@ -'use strict'; +'use strict' -const { use } = require('chai'); +const { use } = require('chai') -use(require('chai-string')); -use(require('sinon-chai')); +use(require('chai-string')) +use(require('sinon-chai')) diff --git a/lib/regular-update.js b/lib/regular-update.js index 3a79b0b965f74d8dc1c22909ff5ccc2db5f82a24..54651d1ec24c1b2c1453cd73124e7ad292e41af0 100644 --- a/lib/regular-update.js +++ b/lib/regular-update.js @@ -1,53 +1,64 @@ -'use strict'; +'use strict' // Map from URL to { timestamp: last fetch time, data: data }. -let regularUpdateCache = Object.create(null); +let regularUpdateCache = Object.create(null) // url: a string, scraper: a function that takes string data at that URL. // interval: number in milliseconds. // cb: a callback function that takes an error and data returned by the scraper. -function regularUpdate({ - url, - intervalMillis, - json = true, - scraper = buffer => buffer, - options = {}, - request = require('request'), -}, cb) { - const timestamp = Date.now(); - const cached = regularUpdateCache[url]; - if (cached != null && - (timestamp - cached.timestamp) < intervalMillis) { - cb(null, cached.data); - return; +function regularUpdate( + { + url, + intervalMillis, + json = true, + scraper = buffer => buffer, + options = {}, + request = require('request'), + }, + cb +) { + const timestamp = Date.now() + const cached = regularUpdateCache[url] + if (cached != null && timestamp - cached.timestamp < intervalMillis) { + cb(null, cached.data) + return } request(url, options, (err, res, buffer) => { - if (err != null) { cb(err); return; } + if (err != null) { + cb(err) + return + } - let reqData; + let reqData if (json) { try { - reqData = JSON.parse(buffer); - } catch(e) { cb(e); return; } + reqData = JSON.parse(buffer) + } catch (e) { + cb(e) + return + } } else { - reqData = buffer; + reqData = buffer } - let data; + let data try { - data = scraper(reqData); - } catch(e) { cb(e); return; } + data = scraper(reqData) + } catch (e) { + cb(e) + return + } - regularUpdateCache[url] = { timestamp, data }; - cb(null, data); - }); + regularUpdateCache[url] = { timestamp, data } + cb(null, data) + }) } function clearRegularUpdateCache() { - regularUpdateCache = Object.create(null); + regularUpdateCache = Object.create(null) } module.exports = { regularUpdate, - clearRegularUpdateCache -}; + clearRegularUpdateCache, +} diff --git a/lib/request-handler.js b/lib/request-handler.js index 80f34396bc6abc9e5e546446e995e2d962bbc75b..bde110f2aedcdf038e335a2bc116f779e875d7c5 100644 --- a/lib/request-handler.js +++ b/lib/request-handler.js @@ -1,19 +1,19 @@ -'use strict'; +'use strict' // eslint-disable-next-line node/no-deprecated-api -const domain = require('domain'); -const request = require('request'); -const { makeBadgeData: getBadgeData } = require('./badge-data'); -const log = require('./log'); -const LruCache = require('./lru-cache'); -const analytics = require('./analytics'); -const { makeSend } = require('./result-sender'); -const queryString = require('query-string'); -const { Inaccessible } = require('../services/errors'); +const domain = require('domain') +const request = require('request') +const { makeBadgeData: getBadgeData } = require('./badge-data') +const log = require('./log') +const LruCache = require('./lru-cache') +const analytics = require('./analytics') +const { makeSend } = require('./result-sender') +const queryString = require('query-string') +const { Inaccessible } = require('../services/errors') // We avoid calling the vendor's server for computation of the information in a // number of badges. -const minAccuracy = 0.75; +const minAccuracy = 0.75 // The quotient of (vendor) data change frequency by badge request frequency // must be lower than this to trigger sending the cached data *before* @@ -22,16 +22,16 @@ const minAccuracy = 0.75; // A(Δt) = 1 - min(# data change over Δt, # requests over Δt) // / (# requests over Δt) // = 1 - max(1, df) / rf -const freqRatioMax = 1 - minAccuracy; +const freqRatioMax = 1 - minAccuracy // Request cache size of 5MB (~5000 bytes/image). -const requestCache = new LruCache(1000); +const requestCache = new LruCache(1000) // Deep error handling for vendor hooks. -const vendorDomain = domain.create(); +const vendorDomain = domain.create() vendorDomain.on('error', err => { - log.error('Vendor hook error:', err.stack); -}); + log.error('Vendor hook error:', err.stack) +}) // These query parameters are available to any badge. For the most part they // are used by makeBadgeData (see `lib/badge-data.js`) and related functions. @@ -46,14 +46,14 @@ const globalQueryParams = new Set([ 'link', 'colorA', 'colorB', -]); +]) function flattenQueryParams(queryParams) { - const union = new Set(globalQueryParams); - (queryParams || []).forEach(name => { - union.add(name); - }); - return Array.from(union).sort(); + const union = new Set(globalQueryParams) + ;(queryParams || []).forEach(name => { + union.add(name) + }) + return Array.from(union).sort() } // handlerOptions can contain: @@ -71,178 +71,201 @@ function flattenQueryParams(queryParams) { // Pass just the handler function as shorthand. // // Inject `makeBadge` as a dependency. -function handleRequest (makeBadge, handlerOptions) { +function handleRequest(makeBadge, handlerOptions) { if (typeof handlerOptions === 'function') { handlerOptions = { handler: handlerOptions } } - const allowedKeys = flattenQueryParams(handlerOptions.queryParams); + const allowedKeys = flattenQueryParams(handlerOptions.queryParams) return (queryParams, match, end, ask) => { - const reqTime = new Date(); + const reqTime = new Date() - let maxAge = isInt(process.env.BADGE_MAX_AGE_SECONDS) ? parseInt(process.env.BADGE_MAX_AGE_SECONDS) : 120; - if ( - isInt(queryParams.maxAge) - && parseInt(queryParams.maxAge) > maxAge - ) { + let maxAge = isInt(process.env.BADGE_MAX_AGE_SECONDS) + ? parseInt(process.env.BADGE_MAX_AGE_SECONDS) + : 120 + if (isInt(queryParams.maxAge) && parseInt(queryParams.maxAge) > maxAge) { // only queryParams.maxAge to override the default // if it is greater than env.BADGE_MAX_AGE_SECONDS - maxAge = parseInt(queryParams.maxAge); + maxAge = parseInt(queryParams.maxAge) } // send both Cache-Control max-age and Expires // in case the client implements HTTP/1.0 but not HTTP/1.1 if (maxAge === 0) { - ask.res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); - ask.res.setHeader('Expires', reqTime.toGMTString()); + ask.res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate') + ask.res.setHeader('Expires', reqTime.toGMTString()) } else { - ask.res.setHeader('Cache-Control', 'max-age=' + maxAge); - ask.res.setHeader('Expires', new Date(+reqTime + maxAge * 1000).toGMTString()); + ask.res.setHeader('Cache-Control', 'max-age=' + maxAge) + ask.res.setHeader( + 'Expires', + new Date(+reqTime + maxAge * 1000).toGMTString() + ) } - ask.res.setHeader('Date', reqTime.toGMTString()); + ask.res.setHeader('Date', reqTime.toGMTString()) - analytics.noteRequest(queryParams, match); + analytics.noteRequest(queryParams, match) - const filteredQueryParams = {}; + const filteredQueryParams = {} allowedKeys.forEach(key => { - filteredQueryParams[key] = queryParams[key]; - }); + filteredQueryParams[key] = queryParams[key] + }) // Use sindresorhus query-string because it sorts the keys, whereas the // builtin querystring module relies on the iteration order. - const stringified = queryString.stringify(filteredQueryParams); - const cacheIndex = `${match[0]}?${stringified}`; + const stringified = queryString.stringify(filteredQueryParams) + const cacheIndex = `${match[0]}?${stringified}` // Should we return the data right away? - const cached = requestCache.get(cacheIndex); - let cachedVersionSent = false; + const cached = requestCache.get(cacheIndex) + let cachedVersionSent = false if (cached !== undefined) { // A request was made not long ago. - const tooSoon = (+reqTime - cached.time) < cached.interval; - if (tooSoon || (cached.dataChange / cached.reqs <= freqRatioMax)) { - const svg = makeBadge(cached.data.badgeData); - makeSend(cached.data.format, ask.res, end)(svg); - cachedVersionSent = true; + const tooSoon = +reqTime - cached.time < cached.interval + if (tooSoon || cached.dataChange / cached.reqs <= freqRatioMax) { + const svg = makeBadge(cached.data.badgeData) + makeSend(cached.data.format, ask.res, end)(svg) + cachedVersionSent = true // We do not wish to call the vendor servers. - if (tooSoon) { return; } + if (tooSoon) { + return + } } } // In case our vendor servers are unresponsive. - let serverUnresponsive = false; + let serverUnresponsive = false const serverResponsive = setTimeout(() => { - serverUnresponsive = true; - if (cachedVersionSent) { return; } + serverUnresponsive = true + if (cachedVersionSent) { + return + } if (requestCache.has(cacheIndex)) { - const cached = requestCache.get(cacheIndex).data; - const svg = makeBadge(cached.badgeData); - makeSend(cached.format, ask.res, end)(svg); - return; + const cached = requestCache.get(cacheIndex).data + const svg = makeBadge(cached.badgeData) + makeSend(cached.format, ask.res, end)(svg) + return } - ask.res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); - const badgeData = getBadgeData('vendor', filteredQueryParams); - badgeData.text[1] = 'unresponsive'; - let extension; + ask.res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate') + const badgeData = getBadgeData('vendor', filteredQueryParams) + badgeData.text[1] = 'unresponsive' + let extension try { - extension = match[0].split('.').pop(); - } catch(e) { extension = 'svg'; } + extension = match[0].split('.').pop() + } catch (e) { + extension = 'svg' + } const svg = makeBadge(badgeData) - makeSend(extension, ask.res, end)(svg); - }, 25000); + makeSend(extension, ask.res, end)(svg) + }, 25000) // Only call vendor servers when last request is older than… - let cacheInterval = 5000; // milliseconds - function cachingRequest (uri, options, callback) { - if ((typeof options === 'function') && !callback) { callback = options; } + let cacheInterval = 5000 // milliseconds + function cachingRequest(uri, options, callback) { + if (typeof options === 'function' && !callback) { + callback = options + } if (options && typeof options === 'object') { - options.uri = uri; + options.uri = uri } else if (typeof uri === 'string') { - options = {uri: uri}; + options = { uri: uri } } else { - options = uri; + options = uri } - options.headers = options.headers || {}; - options.headers['User-Agent'] = options.headers['User-Agent'] || 'Shields.io'; + options.headers = options.headers || {} + options.headers['User-Agent'] = + options.headers['User-Agent'] || 'Shields.io' request(options, (err, res, body) => { if (res != null && res.headers != null) { - const cacheControl = res.headers['cache-control']; + const cacheControl = res.headers['cache-control'] if (cacheControl != null) { - const age = cacheControl.match(/max-age=([0-9]+)/); + const age = cacheControl.match(/max-age=([0-9]+)/) // Would like to get some more test coverage on this before changing it. // eslint-disable-next-line no-self-compare - if (age != null && (+age[1] === +age[1])) { - cacheInterval = +age[1] * 1000; + if (age != null && +age[1] === +age[1]) { + cacheInterval = +age[1] * 1000 } } } - callback(err, res, body); - }); + callback(err, res, body) + }) } // Wrapper around `cachingRequest` that returns a promise rather than // needing to pass a callback. - cachingRequest.asPromise = (uri, options) => new Promise((resolve, reject) => { - cachingRequest(uri, options, (err, res, buffer) => { - if (err) { - // Wrap the error in an Inaccessible so it can be identified - // by the BaseService handler. - reject(new Inaccessible({ underlyingError: err })); - } else { - resolve({ res, buffer }); - } - }); - }); + cachingRequest.asPromise = (uri, options) => + new Promise((resolve, reject) => { + cachingRequest(uri, options, (err, res, buffer) => { + if (err) { + // Wrap the error in an Inaccessible so it can be identified + // by the BaseService handler. + reject(new Inaccessible({ underlyingError: err })) + } else { + resolve({ res, buffer }) + } + }) + }) vendorDomain.run(() => { - const result = handlerOptions.handler(filteredQueryParams, match, function sendBadge(format, badgeData) { - if (serverUnresponsive) { return; } - clearTimeout(serverResponsive); - // Check for a change in the data. - let dataHasChanged = false; - if (cached !== undefined - && cached.data.badgeData.text[1] !== badgeData.text[1]) { - dataHasChanged = true; - } - // Add format to badge data. - badgeData.format = format; - // Update information in the cache. - const updatedCache = { - reqs: cached? (cached.reqs + 1): 1, - dataChange: cached? (cached.dataChange + (dataHasChanged? 1: 0)) - : 1, - time: +reqTime, - interval: cacheInterval, - data: { format: format, badgeData: badgeData } - }; - requestCache.set(cacheIndex, updatedCache); - if (!cachedVersionSent) { - const svg = makeBadge(badgeData); - makeSend(format, ask.res, end)(svg); - } - }, cachingRequest); + const result = handlerOptions.handler( + filteredQueryParams, + match, + function sendBadge(format, badgeData) { + if (serverUnresponsive) { + return + } + clearTimeout(serverResponsive) + // Check for a change in the data. + let dataHasChanged = false + if ( + cached !== undefined && + cached.data.badgeData.text[1] !== badgeData.text[1] + ) { + dataHasChanged = true + } + // Add format to badge data. + badgeData.format = format + // Update information in the cache. + const updatedCache = { + reqs: cached ? cached.reqs + 1 : 1, + dataChange: cached + ? cached.dataChange + (dataHasChanged ? 1 : 0) + : 1, + time: +reqTime, + interval: cacheInterval, + data: { format: format, badgeData: badgeData }, + } + requestCache.set(cacheIndex, updatedCache) + if (!cachedVersionSent) { + const svg = makeBadge(badgeData) + makeSend(format, ask.res, end)(svg) + } + }, + cachingRequest + ) if (result && result.catch) { result.catch(err => { - throw err; - }); + throw err + }) } - }); - }; + }) + } } function clearRequestCache() { - requestCache.clear(); + requestCache.clear() } function isInt(number) { - return number !== undefined && /^[0-9]+$/.test(number); + return number !== undefined && /^[0-9]+$/.test(number) } module.exports = { handleRequest, - makeHandleRequestFn: makeBadge => handlerOptions => handleRequest(makeBadge, handlerOptions), + makeHandleRequestFn: makeBadge => handlerOptions => + handleRequest(makeBadge, handlerOptions), clearRequestCache, // Expose for testing. - _requestCache: requestCache -}; + _requestCache: requestCache, +} diff --git a/lib/request-handler.spec.js b/lib/request-handler.spec.js index 1582ed56391709d0473d494d4982491e42459867..d3fc1b4f12b8b7e272f889b701fb450e859f1598 100644 --- a/lib/request-handler.spec.js +++ b/lib/request-handler.spec.js @@ -1,188 +1,209 @@ -'use strict'; - -const { expect } = require('chai'); -const fetch = require('node-fetch'); -const config = require('./test-config'); -const Camp = require('camp'); -const analytics = require('./analytics'); -const { makeBadgeData: getBadgeData } = require('./badge-data'); +'use strict' + +const { expect } = require('chai') +const fetch = require('node-fetch') +const config = require('./test-config') +const Camp = require('camp') +const analytics = require('./analytics') +const { makeBadgeData: getBadgeData } = require('./badge-data') const { makeHandleRequestFn, clearRequestCache, - _requestCache -} = require('./request-handler'); -const testHelpers = require('./make-badge-test-helpers'); + _requestCache, +} = require('./request-handler') +const testHelpers = require('./make-badge-test-helpers') -const handleRequest = makeHandleRequestFn(testHelpers.makeBadge()); +const handleRequest = makeHandleRequestFn(testHelpers.makeBadge()) -const baseUri = `http://127.0.0.1:${config.port}`; +const baseUri = `http://127.0.0.1:${config.port}` -async function performTwoRequests (first, second) { - expect((await fetch(`${baseUri}${first}`)).ok).to.be.true; - expect((await fetch(`${baseUri}${second}`)).ok).to.be.true; +async function performTwoRequests(first, second) { + expect((await fetch(`${baseUri}${first}`)).ok).to.be.true + expect((await fetch(`${baseUri}${second}`)).ok).to.be.true } function fakeHandler(queryParams, match, sendBadge, request) { - const [, someValue, format] = match; - const badgeData = getBadgeData('testing', queryParams); - badgeData.text[1] = someValue; - sendBadge(format, badgeData); + const [, someValue, format] = match + const badgeData = getBadgeData('testing', queryParams) + badgeData.text[1] = someValue + sendBadge(format, badgeData) } describe('The request handler', function() { - before(analytics.load); + before(analytics.load) - let camp; - const initialBadgeMaxAge = process.env.BADGE_MAX_AGE_SECONDS; + let camp + const initialBadgeMaxAge = process.env.BADGE_MAX_AGE_SECONDS - beforeEach(function (done) { - camp = Camp.start({ port: config.port, hostname: '::' }); - camp.on('listening', () => done()); - }); - afterEach(function (done) { - clearRequestCache(); + beforeEach(function(done) { + camp = Camp.start({ port: config.port, hostname: '::' }) + camp.on('listening', () => done()) + }) + afterEach(function(done) { + clearRequestCache() if (camp) { - camp.close(() => done()); - camp = null; + camp.close(() => done()) + camp = null } - process.env.BADGE_MAX_AGE_SECONDS = initialBadgeMaxAge; - }); + process.env.BADGE_MAX_AGE_SECONDS = initialBadgeMaxAge + }) describe('the options object calling style', function() { - beforeEach(function () { - camp.route(/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/, - handleRequest({ handler: fakeHandler })); - }); - - it('should return the expected response', async function () { - const res = await fetch(`${baseUri}/testing/123.json`); - expect(res.ok).to.be.true; - expect(await res.json()).to.deep.equal({ name: 'testing', value: '123' }); - }); - }); + beforeEach(function() { + camp.route( + /^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/, + handleRequest({ handler: fakeHandler }) + ) + }) + + it('should return the expected response', async function() { + const res = await fetch(`${baseUri}/testing/123.json`) + expect(res.ok).to.be.true + expect(await res.json()).to.deep.equal({ name: 'testing', value: '123' }) + }) + }) describe('the function shorthand calling style', function() { - beforeEach(function () { - camp.route(/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/, - handleRequest(fakeHandler)); - }); - - it('should return the expected response', async function () { - const res = await fetch(`${baseUri}/testing/123.json`); - expect(res.ok).to.be.true; - expect(await res.json()).to.deep.equal({ name: 'testing', value: '123' }); - }); - }); - - describe('caching', function () { - - describe('standard query parameters', function () { - let handlerCallCount; - beforeEach(function () { - handlerCallCount = 0; - camp.route(/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/, + beforeEach(function() { + camp.route( + /^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/, + handleRequest(fakeHandler) + ) + }) + + it('should return the expected response', async function() { + const res = await fetch(`${baseUri}/testing/123.json`) + expect(res.ok).to.be.true + expect(await res.json()).to.deep.equal({ name: 'testing', value: '123' }) + }) + }) + + describe('caching', function() { + describe('standard query parameters', function() { + let handlerCallCount + beforeEach(function() { + handlerCallCount = 0 + camp.route( + /^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/, handleRequest((queryParams, match, sendBadge, request) => { - ++handlerCallCount; - fakeHandler(queryParams, match, sendBadge, request); - })); - }); - - it('should cache identical requests', async function () { - await performTwoRequests('/testing/123.svg', '/testing/123.svg'); - expect(handlerCallCount).to.equal(1); - }); - - it('should differentiate known query parameters', async function () { + ++handlerCallCount + fakeHandler(queryParams, match, sendBadge, request) + }) + ) + }) + + it('should cache identical requests', async function() { + await performTwoRequests('/testing/123.svg', '/testing/123.svg') + expect(handlerCallCount).to.equal(1) + }) + + it('should differentiate known query parameters', async function() { await performTwoRequests( '/testing/123.svg?label=foo', '/testing/123.svg?label=bar' - ); - expect(handlerCallCount).to.equal(2); - }); + ) + expect(handlerCallCount).to.equal(2) + }) - it('should ignore unknown query parameters', async function () { + it('should ignore unknown query parameters', async function() { await performTwoRequests( '/testing/123.svg?foo=1', '/testing/123.svg?foo=2' - ); - expect(handlerCallCount).to.equal(1); - }); - - it('should set the expires header to current time + BADGE_MAX_AGE_SECONDS', async function () { - process.env.BADGE_MAX_AGE_SECONDS = 900; - const res = await fetch(`${baseUri}/testing/123.json`); - const expectedExpiry = new Date(+(new Date(res.headers.get('date'))) + 900000).toGMTString(); - expect(res.headers.get('expires')).to.equal(expectedExpiry); - expect(res.headers.get('cache-control')).to.equal('max-age=900'); - }); - - it('should set the expires header to current time + maxAge', async function () { - process.env.BADGE_MAX_AGE_SECONDS = 0; - const res = await fetch(`${baseUri}/testing/123.json?maxAge=3600`); - const expectedExpiry = new Date(+(new Date(res.headers.get('date'))) + 3600000).toGMTString(); - expect(res.headers.get('expires')).to.equal(expectedExpiry); - expect(res.headers.get('cache-control')).to.equal('max-age=3600'); - }); - - it('should ignore maxAge if maxAge < BADGE_MAX_AGE_SECONDS', async function () { - process.env.BADGE_MAX_AGE_SECONDS = 600; - const res = await fetch(`${baseUri}/testing/123.json?maxAge=300`); - const expectedExpiry = new Date(+(new Date(res.headers.get('date'))) + 600000).toGMTString(); - expect(res.headers.get('expires')).to.equal(expectedExpiry); - expect(res.headers.get('cache-control')).to.equal('max-age=600'); - }); - - it('should set Cache-Control: no-cache, no-store, must-revalidate if maxAge=0', async function () { - process.env.BADGE_MAX_AGE_SECONDS = 0; - const res = await fetch(`${baseUri}/testing/123.json`); - expect(res.headers.get('expires')).to.equal(res.headers.get('date')); - expect(res.headers.get('cache-control')).to.equal('no-cache, no-store, must-revalidate'); - }); - - it('should set the expires header to current time + 120 if BADGE_MAX_AGE_SECONDS not set', async function () { - delete process.env.BADGE_MAX_AGE_SECONDS; - const res = await fetch(`${baseUri}/testing/123.json`); - const expectedExpiry = new Date(+(new Date(res.headers.get('date'))) + 120000).toGMTString(); - expect(res.headers.get('expires')).to.equal(expectedExpiry); - expect(res.headers.get('cache-control')).to.equal('max-age=120'); - }); - - describe('the cache key', function () { - const expectedCacheKey = '/testing/123.json?colorB=123&label=foo'; - it('should match expected and use canonical order - 1', async function () { - const res = await fetch(`${baseUri}/testing/123.json?colorB=123&label=foo`); - expect(res.ok).to.be.true; - expect(_requestCache.cache).to.have.keys(expectedCacheKey); - }); - it('should match expected and use canonical order - 2', async function () { - const res = await fetch(`${baseUri}/testing/123.json?label=foo&colorB=123`); - expect(res.ok).to.be.true; - expect(_requestCache.cache).to.have.keys(expectedCacheKey); - }); - }); - }); + ) + expect(handlerCallCount).to.equal(1) + }) + + it('should set the expires header to current time + BADGE_MAX_AGE_SECONDS', async function() { + process.env.BADGE_MAX_AGE_SECONDS = 900 + const res = await fetch(`${baseUri}/testing/123.json`) + const expectedExpiry = new Date( + +new Date(res.headers.get('date')) + 900000 + ).toGMTString() + expect(res.headers.get('expires')).to.equal(expectedExpiry) + expect(res.headers.get('cache-control')).to.equal('max-age=900') + }) + + it('should set the expires header to current time + maxAge', async function() { + process.env.BADGE_MAX_AGE_SECONDS = 0 + const res = await fetch(`${baseUri}/testing/123.json?maxAge=3600`) + const expectedExpiry = new Date( + +new Date(res.headers.get('date')) + 3600000 + ).toGMTString() + expect(res.headers.get('expires')).to.equal(expectedExpiry) + expect(res.headers.get('cache-control')).to.equal('max-age=3600') + }) + + it('should ignore maxAge if maxAge < BADGE_MAX_AGE_SECONDS', async function() { + process.env.BADGE_MAX_AGE_SECONDS = 600 + const res = await fetch(`${baseUri}/testing/123.json?maxAge=300`) + const expectedExpiry = new Date( + +new Date(res.headers.get('date')) + 600000 + ).toGMTString() + expect(res.headers.get('expires')).to.equal(expectedExpiry) + expect(res.headers.get('cache-control')).to.equal('max-age=600') + }) + + it('should set Cache-Control: no-cache, no-store, must-revalidate if maxAge=0', async function() { + process.env.BADGE_MAX_AGE_SECONDS = 0 + const res = await fetch(`${baseUri}/testing/123.json`) + expect(res.headers.get('expires')).to.equal(res.headers.get('date')) + expect(res.headers.get('cache-control')).to.equal( + 'no-cache, no-store, must-revalidate' + ) + }) + + it('should set the expires header to current time + 120 if BADGE_MAX_AGE_SECONDS not set', async function() { + delete process.env.BADGE_MAX_AGE_SECONDS + const res = await fetch(`${baseUri}/testing/123.json`) + const expectedExpiry = new Date( + +new Date(res.headers.get('date')) + 120000 + ).toGMTString() + expect(res.headers.get('expires')).to.equal(expectedExpiry) + expect(res.headers.get('cache-control')).to.equal('max-age=120') + }) + + describe('the cache key', function() { + const expectedCacheKey = '/testing/123.json?colorB=123&label=foo' + it('should match expected and use canonical order - 1', async function() { + const res = await fetch( + `${baseUri}/testing/123.json?colorB=123&label=foo` + ) + expect(res.ok).to.be.true + expect(_requestCache.cache).to.have.keys(expectedCacheKey) + }) + it('should match expected and use canonical order - 2', async function() { + const res = await fetch( + `${baseUri}/testing/123.json?label=foo&colorB=123` + ) + expect(res.ok).to.be.true + expect(_requestCache.cache).to.have.keys(expectedCacheKey) + }) + }) + }) describe('custom query parameters', function() { - let handlerCallCount; - beforeEach(function () { - handlerCallCount = 0; - camp.route(/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/, + let handlerCallCount + beforeEach(function() { + handlerCallCount = 0 + camp.route( + /^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/, handleRequest({ queryParams: ['foo'], handler: (queryParams, match, sendBadge, request) => { - ++handlerCallCount; - fakeHandler(queryParams, match, sendBadge, request); + ++handlerCallCount + fakeHandler(queryParams, match, sendBadge, request) }, - })) - }); + }) + ) + }) - it('should differentiate them', async function () { + it('should differentiate them', async function() { await performTwoRequests( '/testing/123.svg?foo=1', '/testing/123.svg?foo=2' - ); - expect(handlerCallCount).to.equal(2); - }); - }); - }); -}); + ) + expect(handlerCallCount).to.equal(2) + }) + }) + }) +}) diff --git a/lib/result-sender.js b/lib/result-sender.js index 6ea6ab697f3bccbaa9e8100b6e9c01cade78bff4..3aeed28907e8737b2a00cdcff0197ed95b7f27b0 100644 --- a/lib/result-sender.js +++ b/lib/result-sender.js @@ -1,49 +1,52 @@ -'use strict'; +'use strict' -const stream = require('stream'); -const log = require('./log'); -const svg2img = require('./svg-to-img'); +const stream = require('stream') +const log = require('./log') +const svg2img = require('./svg-to-img') function streamFromString(str) { - const newStream = new stream.Readable(); - newStream._read = () => { newStream.push(str); newStream.push(null); }; - return newStream; + const newStream = new stream.Readable() + newStream._read = () => { + newStream.push(str) + newStream.push(null) + } + return newStream } function makeSend(format, askres, end) { if (format === 'svg') { - return res => sendSVG(res, askres, end); + return res => sendSVG(res, askres, end) } else if (format === 'json') { - return res => sendJSON(res, askres, end); + return res => sendJSON(res, askres, end) } else { - return res => sendOther(format, res, askres, end); + return res => sendOther(format, res, askres, end) } } function sendSVG(res, askres, end) { - askres.setHeader('Content-Type', 'image/svg+xml;charset=utf-8'); - end(null, {template: streamFromString(res)}); + askres.setHeader('Content-Type', 'image/svg+xml;charset=utf-8') + end(null, { template: streamFromString(res) }) } function sendOther(format, res, askres, end) { - askres.setHeader('Content-Type', 'image/' + format); + askres.setHeader('Content-Type', 'image/' + format) svg2img(res, format) .then(data => { - end(null, {template: streamFromString(data)}); + end(null, { template: streamFromString(data) }) }) .catch(err => { // This emits status code 200, though 500 would be preferable. - log.error('svg2img error', err); - end(null, {template: '500.html'}); + log.error('svg2img error', err) + end(null, { template: '500.html' }) }) } function sendJSON(res, askres, end) { - askres.setHeader('Content-Type', 'application/json'); - askres.setHeader('Access-Control-Allow-Origin', '*'); - end(null, {template: streamFromString(res)}); + askres.setHeader('Content-Type', 'application/json') + askres.setHeader('Access-Control-Allow-Origin', '*') + end(null, { template: streamFromString(res) }) } module.exports = { - makeSend -}; + makeSend, +} diff --git a/lib/server-config.js b/lib/server-config.js index d4d6af3db66d72eb6a7a72b4e87ff93f13d2b4b5..e3634df31952f328abbf1f04b83ae5c37af80f4a 100644 --- a/lib/server-config.js +++ b/lib/server-config.js @@ -1,34 +1,38 @@ -'use strict'; +'use strict' // This file should only be required by server.js. To simplify testing, config // should be injected into other components needing it. -const url = require('url'); -const envFlag = require('node-env-flag'); -const defaults = require('./defaults'); +const url = require('url') +const envFlag = require('node-env-flag') +const defaults = require('./defaults') function envArray(envVar, defaultValue, delimiter) { - delimiter = delimiter || ','; + delimiter = delimiter || ',' if (envVar) { - return envVar.split(delimiter); + return envVar.split(delimiter) } else { - return defaultValue; + return defaultValue } } -const isSecure = envFlag(process.env.HTTPS, false); -const port = +process.env.PORT || +process.argv[2] || (isSecure ? 443 : 80); -const address = process.env.BIND_ADDRESS || process.argv[3] || '::'; +const isSecure = envFlag(process.env.HTTPS, false) +const port = +process.env.PORT || +process.argv[2] || (isSecure ? 443 : 80) +const address = process.env.BIND_ADDRESS || process.argv[3] || '::' const baseUri = url.format({ protocol: isSecure ? 'https' : 'http', hostname: address, port, pathname: '/', -}); +}) // The base URI provides a suitable value for development. Production should // configure this. -const allowedOrigin = envArray(process.env.ALLOWED_ORIGIN, baseUri.replace(/\/$/, ''), ','); +const allowedOrigin = envArray( + process.env.ALLOWED_ORIGIN, + baseUri.replace(/\/$/, ''), + ',' +) const config = { bind: { @@ -66,10 +70,10 @@ const config = { }, rateLimit: envFlag(process.env.RATE_LIMIT, true), handleInternalErrors: envFlag(process.env.HANDLE_INTERNAL_ERRORS, true), -}; +} if (config.font.fallbackPath) { - console.log('FALLBACK_FONT_PATH is deprecated. Please use FONT_PATH.'); + console.log('FALLBACK_FONT_PATH is deprecated. Please use FONT_PATH.') } -module.exports = config; +module.exports = config diff --git a/lib/server-secrets.js b/lib/server-secrets.js index f5205cfdfa42475db0de482aecd521a482607bfe..d06c3ff60a0ef4703faa40fd0aa22301173cdf18 100644 --- a/lib/server-secrets.js +++ b/lib/server-secrets.js @@ -1,20 +1,22 @@ -'use strict'; +'use strict' // Everything that cannot be checked in but is useful server-side is stored in // a JSON data file: private/secret.json. -const fs = require('fs'); -const path = require('path'); +const fs = require('fs') +const path = require('path') -const secretsPath = path.join(__dirname, '..', 'private', 'secret.json'); +const secretsPath = path.join(__dirname, '..', 'private', 'secret.json') if (fs.existsSync(secretsPath)) { try { - module.exports = require(secretsPath); - } catch(e) { - console.error(`Error loading secret data: ${e.message}`); - process.exit(1); + module.exports = require(secretsPath) + } catch (e) { + console.error(`Error loading secret data: ${e.message}`) + process.exit(1) } } else { - console.warn(`No secret data found at ${secretsPath} (see lib/server-secrets.js)`); + console.warn( + `No secret data found at ${secretsPath} (see lib/server-secrets.js)` + ) } diff --git a/lib/service-test-runner/cli.js b/lib/service-test-runner/cli.js index fbb5db80553d41b7adb3759f99e51fc8c3e7a0e7..9336f1b0d93075a00fd96d9abd63fece564d9282 100644 --- a/lib/service-test-runner/cli.js +++ b/lib/service-test-runner/cli.js @@ -43,52 +43,60 @@ // Relying on npm scripts is safer. Using "pre" makes it impossible to run // the second step without the first. -'use strict'; +'use strict' -const minimist = require('minimist'); -const readAllStdinSync = require('read-all-stdin-sync'); -const Runner = require('./runner'); -const serverHelpers = require('../../lib/in-process-server-test-helpers'); +const minimist = require('minimist') +const readAllStdinSync = require('read-all-stdin-sync') +const Runner = require('./runner') +const serverHelpers = require('../../lib/in-process-server-test-helpers') -require('../../lib/unhandled-rejection.spec'); +require('../../lib/unhandled-rejection.spec') -let server; -before('Start running the server', function () { - this.timeout(5000); - server = serverHelpers.start(); -}); -after('Shut down the server', function () { serverHelpers.stop(server); }); +let server +before('Start running the server', function() { + this.timeout(5000) + server = serverHelpers.start() +}) +after('Shut down the server', function() { + serverHelpers.stop(server) +}) -const runner = new Runner(); -runner.prepare(); +const runner = new Runner() +runner.prepare() // The server's request cache causes side effects between tests. -runner.beforeEach = () => { serverHelpers.reset(server); }; +runner.beforeEach = () => { + serverHelpers.reset(server) +} -const args = minimist(process.argv.slice(3)); -const stdinOption = args.stdin; -const onlyOption = args.only; +const args = minimist(process.argv.slice(3)) +const stdinOption = args.stdin +const onlyOption = args.only -let onlyServices; +let onlyServices if (stdinOption && onlyOption) { - console.error('Do not use --only with --stdin'); + console.error('Do not use --only with --stdin') } else if (stdinOption) { - const allStdin = readAllStdinSync().trim(); - onlyServices = allStdin ? allStdin.split('\n') : []; + const allStdin = readAllStdinSync().trim() + onlyServices = allStdin ? allStdin.split('\n') : [] } else if (onlyOption) { - onlyServices = onlyOption.split(','); + onlyServices = onlyOption.split(',') } if (typeof onlyServices === 'undefined') { - console.info('Running all service tests.'); + console.info('Running all service tests.') } else if (onlyServices.length === 0) { - console.info('No service tests to run. Exiting.'); - process.exit(0); + console.info('No service tests to run. Exiting.') + process.exit(0) } else { - console.info(`Running tests for ${onlyServices.length} services: ${onlyServices.join(', ')}.\n`); - runner.only(onlyServices); + console.info( + `Running tests for ${onlyServices.length} services: ${onlyServices.join( + ', ' + )}.\n` + ) + runner.only(onlyServices) } -runner.toss(); +runner.toss() // Invoke run() asynchronously, because Mocha will not start otherwise. -process.nextTick(run); +process.nextTick(run) diff --git a/lib/service-test-runner/infer-pull-request.js b/lib/service-test-runner/infer-pull-request.js index ec17d2793b369dd35812276e070acacbd36a30d9..2b64e92f5a3a604a32c9daef4479f3a1f7d9f972 100644 --- a/lib/service-test-runner/infer-pull-request.js +++ b/lib/service-test-runner/infer-pull-request.js @@ -1,26 +1,30 @@ -'use strict'; +'use strict' -const { parse: urlParse, format: urlFormat } = require('url'); +const { parse: urlParse, format: urlFormat } = require('url') function formatSlug(owner, repo, pullRequest) { - return `${owner}/${repo}#${pullRequest}`; + return `${owner}/${repo}#${pullRequest}` } function parseGithubPullRequestUrl(url, options = {}) { - const { verifyBaseUrl } = options; + const { verifyBaseUrl } = options - const parsed = urlParse(url); - const components = parsed.path.substr(1).split('/'); + const parsed = urlParse(url) + const components = parsed.path.substr(1).split('/') if (components[2] !== 'pull' || components.length !== 4) { - throw Error(`Invalid GitHub pull request URL: ${url}`); + throw Error(`Invalid GitHub pull request URL: ${url}`) } - const [owner, repo, , pullRequest] = components; + const [owner, repo, , pullRequest] = components - delete parsed.pathname; - const baseUrl = urlFormat(parsed, { auth: false, fragment: false, search: false }); + delete parsed.pathname + const baseUrl = urlFormat(parsed, { + auth: false, + fragment: false, + search: false, + }) if (verifyBaseUrl && baseUrl !== verifyBaseUrl) { - throw Error(`Expected base URL to be ${verifyBaseUrl} but got ${baseUrl}`); + throw Error(`Expected base URL to be ${verifyBaseUrl} but got ${baseUrl}`) } return { @@ -29,42 +33,46 @@ function parseGithubPullRequestUrl(url, options = {}) { repo, pullRequest: +pullRequest, slug: formatSlug(owner, repo, pullRequest), - }; + } } function parseGithubRepoSlug(slug) { - const components = slug.split('/'); + const components = slug.split('/') if (components.length !== 2) { - throw Error(`Invalid GitHub repo slug: ${slug}`); + throw Error(`Invalid GitHub repo slug: ${slug}`) } - const [owner, repo] = components; - return { owner, repo }; + const [owner, repo] = components + return { owner, repo } } function _inferPullRequestFromTravisEnv(env) { - const { owner, repo } = parseGithubRepoSlug(env.TRAVIS_REPO_SLUG); - const pullRequest = +env.TRAVIS_PULL_REQUEST; + const { owner, repo } = parseGithubRepoSlug(env.TRAVIS_REPO_SLUG) + const pullRequest = +env.TRAVIS_PULL_REQUEST return { owner, repo, pullRequest, slug: formatSlug(owner, repo, pullRequest), - }; + } } function _inferPullRequestFromCircleEnv(env) { - return parseGithubPullRequestUrl(env.CI_PULL_REQUEST); + return parseGithubPullRequestUrl(env.CI_PULL_REQUEST) } function inferPullRequest(env = process.env) { if (env.TRAVIS) { - return _inferPullRequestFromTravisEnv(env); + return _inferPullRequestFromTravisEnv(env) } else if (env.CIRCLECI) { - return _inferPullRequestFromCircleEnv(env); + return _inferPullRequestFromCircleEnv(env) } else if (env.CI) { - throw Error('Unsupported CI system. Unable to obtain pull request information from the environment.'); + throw Error( + 'Unsupported CI system. Unable to obtain pull request information from the environment.' + ) } else { - throw Error('Unable to obtain pull request information from the environment. Is this running in CI?'); + throw Error( + 'Unable to obtain pull request information from the environment. Is this running in CI?' + ) } } @@ -72,4 +80,4 @@ module.exports = { parseGithubPullRequestUrl, parseGithubRepoSlug, inferPullRequest, -}; +} diff --git a/lib/service-test-runner/infer-pull-request.spec.js b/lib/service-test-runner/infer-pull-request.spec.js index 553924d5b5aed149c4b740d6a8b5b2e895bd1d80..e3f46d3255593fb0568c562fddff6254cb85ae91 100644 --- a/lib/service-test-runner/infer-pull-request.spec.js +++ b/lib/service-test-runner/infer-pull-request.spec.js @@ -1,27 +1,32 @@ -'use strict'; +'use strict' -const { test, given, forCases } = require('sazerac'); +const { test, given, forCases } = require('sazerac') const { parseGithubPullRequestUrl, inferPullRequest, -} = require('./infer-pull-request'); +} = require('./infer-pull-request') describe('Pull request inference', function() { test(parseGithubPullRequestUrl, () => { forCases([ given('https://github.com/badges/shields/pull/1234'), - given('https://github.com/badges/shields/pull/1234', { verifyBaseUrl: 'https://github.com' }), + given('https://github.com/badges/shields/pull/1234', { + verifyBaseUrl: 'https://github.com', + }), ]).expect({ baseUrl: 'https://github.com', owner: 'badges', repo: 'shields', pullRequest: 1234, slug: 'badges/shields#1234', - }); + }) - given('https://github.com/badges/shields/pull/1234', { verifyBaseUrl: 'https://example.com' }) - .expectError('Expected base URL to be https://example.com but got https://github.com'); - }); + given('https://github.com/badges/shields/pull/1234', { + verifyBaseUrl: 'https://example.com', + }).expectError( + 'Expected base URL to be https://example.com but got https://github.com' + ) + }) test(inferPullRequest, () => { const expected = { @@ -29,17 +34,17 @@ describe('Pull request inference', function() { repo: 'shields', pullRequest: 1234, slug: 'badges/shields#1234', - }; + } given({ CIRCLECI: '1', CI_PULL_REQUEST: 'https://github.com/badges/shields/pull/1234', - }).expect(Object.assign({ baseUrl: 'https://github.com' }, expected)); + }).expect(Object.assign({ baseUrl: 'https://github.com' }, expected)) given({ TRAVIS: '1', TRAVIS_REPO_SLUG: 'badges/shields', TRAVIS_PULL_REQUEST: '1234', - }).expect(expected); - }); -}); + }).expect(expected) + }) +}) diff --git a/lib/service-test-runner/pull-request-services-cli.js b/lib/service-test-runner/pull-request-services-cli.js index ec20dd6c8c23c747c231b3b9a964632501579d37..0818f21b235e5352270c757cb926aff940d70e9e 100644 --- a/lib/service-test-runner/pull-request-services-cli.js +++ b/lib/service-test-runner/pull-request-services-cli.js @@ -13,44 +13,46 @@ // // TRAVIS=1 TRAVIS_REPO_SLUG=badges/shields TRAVIS_PULL_REQUEST=1108 npm run test:services:pr:prepare -'use strict'; +'use strict' -const fetch = require('node-fetch'); -const { inferPullRequest } = require('./infer-pull-request'); -const servicesForTitle = require('./services-for-title'); +const fetch = require('node-fetch') +const { inferPullRequest } = require('./infer-pull-request') +const servicesForTitle = require('./services-for-title') -async function getTitle (owner, repo, pullRequest) { - let uri = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullRequest}`; +async function getTitle(owner, repo, pullRequest) { + let uri = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullRequest}` if (process.env.GITHUB_TOKEN) { - uri += `?access_token=${process.env.GITHUB_TOKEN}`; + uri += `?access_token=${process.env.GITHUB_TOKEN}` } - const options = { headers: { 'User-Agent': 'badges/shields' } }; - const res = await fetch(uri, options); - if (! res.ok) { - throw Error(`${res.status} ${res.statusText}`); + const options = { headers: { 'User-Agent': 'badges/shields' } } + const res = await fetch(uri, options) + if (!res.ok) { + throw Error(`${res.status} ${res.statusText}`) } - const { title } = await res.json(); - return title; + const { title } = await res.json() + return title } async function main() { - const { owner, repo, pullRequest, slug } = inferPullRequest(); - console.error(`PR: ${slug}`); + const { owner, repo, pullRequest, slug } = inferPullRequest() + console.error(`PR: ${slug}`) - const title = await getTitle(owner, repo, pullRequest); + const title = await getTitle(owner, repo, pullRequest) - console.error(`Title: ${title}\n`); - const services = servicesForTitle(title); + console.error(`Title: ${title}\n`) + const services = servicesForTitle(title) if (services.length === 0) { - console.error('No services found. Nothing to do.'); + console.error('No services found. Nothing to do.') } else { - console.error(`Services: (${services.length} found) ${services.join(', ')}\n`); - console.log(services.join('\n')); + console.error( + `Services: (${services.length} found) ${services.join(', ')}\n` + ) + console.log(services.join('\n')) } } -(async () => { +;(async () => { try { await main() } catch (e) { diff --git a/lib/service-test-runner/runner.js b/lib/service-test-runner/runner.js index 60096a16ebe288309a59452502f381e8ba63e3b2..59fdf93bd3c02b1c5eaac0a54e71c3ef99c10421 100644 --- a/lib/service-test-runner/runner.js +++ b/lib/service-test-runner/runner.js @@ -1,6 +1,6 @@ -'use strict'; +'use strict' -const { loadTesters } = require('../../services'); +const { loadTesters } = require('../../services') /** * Load a collection of ServiceTester objects and register them with Mocha. @@ -10,20 +10,22 @@ class Runner { * Function to invoke before each test. This is a stub which can be * overridden on instances. */ - beforeEach () {} + beforeEach() {} /** * Prepare the runner by loading up all the ServiceTester objects. */ - prepare () { - this.testers = loadTesters(); + prepare() { + this.testers = loadTesters() this.testers.forEach(tester => { - tester.beforeEach = () => { this.beforeEach(); }; - }); + tester.beforeEach = () => { + this.beforeEach() + } + }) } - _testersForService (service) { - return this.testers.filter(t => t.id.toLowerCase() === service); + _testersForService(service) { + return this.testers.filter(t => t.id.toLowerCase() === service) } /** @@ -31,31 +33,35 @@ class Runner { * * @param services An array of service ids to run */ - only (services) { - const normalizedServices = new Set(services.map(v => v.toLowerCase())); + only(services) { + const normalizedServices = new Set(services.map(v => v.toLowerCase())) - const missingServices = []; + const missingServices = [] normalizedServices.forEach(service => { - const testers = this._testersForService(service); + const testers = this._testersForService(service) if (testers.length === 0) { - missingServices.push(service); + missingServices.push(service) } - testers.forEach(tester => { tester.only(); }); - }); + testers.forEach(tester => { + tester.only() + }) + }) // Throw at the end, to provide a better error message. if (missingServices.length > 0) { - throw Error('Unknown services: ' + missingServices.join(', ')); + throw Error('Unknown services: ' + missingServices.join(', ')) } } /** * Register the tests with Mocha. */ - toss () { - this.testers.forEach(tester => { tester.toss(); }); + toss() { + this.testers.forEach(tester => { + tester.toss() + }) } } -module.exports = Runner; +module.exports = Runner diff --git a/lib/service-test-runner/services-for-title.js b/lib/service-test-runner/services-for-title.js index cf56ae52c958969625996d96f77146a9199506c7..59bbf0bdb43124e3f38cd111c29071f6753bccb5 100644 --- a/lib/service-test-runner/services-for-title.js +++ b/lib/service-test-runner/services-for-title.js @@ -1,21 +1,21 @@ -'use strict'; +'use strict' -const difference = require('lodash.difference'); +const difference = require('lodash.difference') function servicesForTitle(title) { - const bracketed = /\[([^\]]+)\]/g; + const bracketed = /\[([^\]]+)\]/g - const preNormalized = title.toLowerCase(); + const preNormalized = title.toLowerCase() - let services = []; - let match; + let services = [] + let match while ((match = bracketed.exec(preNormalized))) { - const [, bracketed] = match; - services = services.concat(bracketed.split(' ')); + const [, bracketed] = match + services = services.concat(bracketed.split(' ')) } - const blacklist = ['wip', 'rfc']; - return difference(services.map(service => service.toLowerCase()), blacklist); + const blacklist = ['wip', 'rfc'] + return difference(services.map(service => service.toLowerCase()), blacklist) } -module.exports = servicesForTitle; +module.exports = servicesForTitle diff --git a/lib/service-test-runner/services-for-title.spec.js b/lib/service-test-runner/services-for-title.spec.js index 902f9012e207e5cdad98f90b95f0fcb3d46af6ad..ac0f0d5cdae32e7df3caeedde4e29808e1d75033 100644 --- a/lib/service-test-runner/services-for-title.spec.js +++ b/lib/service-test-runner/services-for-title.spec.js @@ -1,23 +1,19 @@ -'use strict'; +'use strict' -const { test, given } = require('sazerac'); -const servicesForTitle = require('./services-for-title'); +const { test, given } = require('sazerac') +const servicesForTitle = require('./services-for-title') describe('Services from PR title', function() { test(servicesForTitle, () => { - given('[Travis] Fix timeout issues').expect(['travis']); + given('[Travis] Fix timeout issues').expect(['travis']) given('[Travis Sonar] Support user token authentication').expect([ 'travis', 'sonar', - ]); - given('[CRAN CPAN CTAN] Add test coverage').expect([ - 'cran', - 'cpan', - 'ctan', - ]); + ]) + given('[CRAN CPAN CTAN] Add test coverage').expect(['cran', 'cpan', 'ctan']) given( '[RFC] Add Joi-based request validation to BaseJsonService and rewrite [NPM] badges' - ).expect(['npm']); - given('make changes to [CRAN] and [CPAN]').expect(['cran', 'cpan']); - }); -}); + ).expect(['npm']) + given('make changes to [CRAN] and [CPAN]').expect(['cran', 'cpan']) + }) +}) diff --git a/lib/suggest.js b/lib/suggest.js index 24cd23462fa22fd768c02b03667a667025ab7121..937a156acc27be688db6dbe6defba96089827563 100644 --- a/lib/suggest.js +++ b/lib/suggest.js @@ -1,7 +1,7 @@ -'use strict'; +'use strict' -const nodeUrl = require('url'); -const request = require('request'); +const nodeUrl = require('url') +const request = require('request') // data: {url}, JSON-serializable object. // end: function(json), with json of the form: @@ -9,130 +9,149 @@ const request = require('request'); // - link: target as a string URL. // - badge: shields image URL. // - name: string -function suggest (allowedOrigin, githubApiProvider, data, end, ask) { +function suggest(allowedOrigin, githubApiProvider, data, end, ask) { // The typical dev and production setups are cross-origin. However, in // Heroku deploys and some self-hosted deploys these requests may come from // the same host. - const origin = ask.req.headers.origin; + const origin = ask.req.headers.origin if (origin) { if (allowedOrigin.includes(origin)) { - ask.res.setHeader('Access-Control-Allow-Origin', origin); + ask.res.setHeader('Access-Control-Allow-Origin', origin) } else { - ask.res.setHeader('Access-Control-Allow-Origin', 'null'); - end({err: 'Disallowed'}); - return; + ask.res.setHeader('Access-Control-Allow-Origin', 'null') + end({ err: 'Disallowed' }) + return } } - let url; + let url try { - url = nodeUrl.parse(data.url); - } catch(e) { end({err: ''+e}); return; } - findSuggestions(githubApiProvider, url, end); + url = nodeUrl.parse(data.url) + } catch (e) { + end({ err: '' + e }) + return + } + findSuggestions(githubApiProvider, url, end) } // url: string // cb: function({badges}) -function findSuggestions (githubApiProvider, url, cb) { - let promises = []; +function findSuggestions(githubApiProvider, url, cb) { + let promises = [] if (url.hostname === 'github.com') { - const userRepo = url.pathname.slice(1).split('/'); - const user = userRepo[0]; - const repo = userRepo[1]; + const userRepo = url.pathname.slice(1).split('/') + const user = userRepo[0] + const repo = userRepo[1] promises = promises.concat([ githubIssues(user, repo), githubForks(user, repo), githubStars(user, repo), githubLicense(githubApiProvider, user, repo), - ]); + ]) } - promises.push(twitterPage(url)); - Promise.all(promises).then(function(badges) { - // eslint-disable-next-line standard/no-callback-literal - cb({badges: badges.filter(function(b) { return b != null; })}); - }).catch(function(err) { - // eslint-disable-next-line standard/no-callback-literal - cb({badges: [], err: err}); - }); + promises.push(twitterPage(url)) + Promise.all(promises) + .then(function(badges) { + // eslint-disable-next-line standard/no-callback-literal + cb({ + badges: badges.filter(function(b) { + return b != null + }), + }) + }) + .catch(function(err) { + // eslint-disable-next-line standard/no-callback-literal + cb({ badges: [], err: err }) + }) } -function twitterPage (url) { - if (url.protocol === null) { return Promise.resolve(null); } - const schema = url.protocol.slice(0, -1); - const host = url.host; - const path = url.path; +function twitterPage(url) { + if (url.protocol === null) { + return Promise.resolve(null) + } + const schema = url.protocol.slice(0, -1) + const host = url.host + const path = url.path return Promise.resolve({ name: 'Twitter', - link: 'https://twitter.com/intent/tweet?text=Wow:&url=' + encodeURIComponent(url.href), - badge: 'https://img.shields.io/twitter/url/' + schema + '/' + host + path + '.svg?style=social', - }); + link: + 'https://twitter.com/intent/tweet?text=Wow:&url=' + + encodeURIComponent(url.href), + badge: + 'https://img.shields.io/twitter/url/' + + schema + + '/' + + host + + path + + '.svg?style=social', + }) } -function githubIssues (user, repo) { - const userRepo = user + '/' + repo; +function githubIssues(user, repo) { + const userRepo = user + '/' + repo return Promise.resolve({ name: 'GitHub issues', link: 'https://github.com/' + userRepo + '/issues', badge: 'https://img.shields.io/github/issues/' + userRepo + '.svg', - }); + }) } -function githubForks (user, repo) { - const userRepo = user + '/' + repo; +function githubForks(user, repo) { + const userRepo = user + '/' + repo return Promise.resolve({ name: 'GitHub forks', link: 'https://github.com/' + userRepo + '/network', badge: 'https://img.shields.io/github/forks/' + userRepo + '.svg', - }); + }) } -function githubStars (user, repo) { - const userRepo = user + '/' + repo; +function githubStars(user, repo) { + const userRepo = user + '/' + repo return Promise.resolve({ name: 'GitHub stars', link: 'https://github.com/' + userRepo + '/stargazers', badge: 'https://img.shields.io/github/stars/' + userRepo + '.svg', - }); + }) } -function githubLicense (githubApiProvider, user, repo) { - return new Promise((resolve) => { - const apiUrl = `/repos/${user}/${repo}/license`; +function githubLicense(githubApiProvider, user, repo) { + return new Promise(resolve => { + const apiUrl = `/repos/${user}/${repo}/license` githubApiProvider.request(request, apiUrl, {}, function(err, res, buffer) { if (err !== null) { - resolve(null); - return; + resolve(null) + return } const defaultBadge = { name: 'GitHub license', link: `https://github.com/${user}/${repo}`, - badge: `https://img.shields.io/github/license/${user}/${repo}.svg` - }; + badge: `https://img.shields.io/github/license/${user}/${repo}.svg`, + } if (res.statusCode !== 200) { - resolve(defaultBadge); + resolve(defaultBadge) } try { - const data = JSON.parse(buffer); + const data = JSON.parse(buffer) if (data.html_url) { - defaultBadge.link = data.html_url; - resolve(defaultBadge); + defaultBadge.link = data.html_url + resolve(defaultBadge) } else { - resolve(defaultBadge); + resolve(defaultBadge) } - } catch(e) { - resolve(defaultBadge); + } catch (e) { + resolve(defaultBadge) } }) - }); + }) } -function setRoutes (allowedOrigin, githubApiProvider, server) { - server.ajax.on( - 'suggest/v1', - (data, end, ask) => suggest(allowedOrigin, githubApiProvider, data, end, ask)); +function setRoutes(allowedOrigin, githubApiProvider, server) { + server.ajax.on('suggest/v1', (data, end, ask) => + suggest(allowedOrigin, githubApiProvider, data, end, ask) + ) } module.exports = { suggest, setRoutes, -}; +} diff --git a/lib/svg-badge-parser.js b/lib/svg-badge-parser.js index 81c4f7434993d255674ce634d25da86074913a09..bd26d9f2293513afe04aeea2069c68f3f83bfe69 100644 --- a/lib/svg-badge-parser.js +++ b/lib/svg-badge-parser.js @@ -1,20 +1,20 @@ -'use strict'; +'use strict' -const nodeifySync = require('./nodeify-sync'); +const nodeifySync = require('./nodeify-sync') -const leadingWhitespace = /(?:\r\n\s*|\r\s*|\n\s*)/g; -const getValue = />([^<>]+)<\/text><\/g>/; +const leadingWhitespace = /(?:\r\n\s*|\r\s*|\n\s*)/g +const getValue = />([^<>]+)<\/text><\/g>/ function valueFromSvgBadge(svg) { if (typeof svg !== 'string') { - throw TypeError('Parameter should be a string'); + throw TypeError('Parameter should be a string') } - const stripped = svg.replace(leadingWhitespace, ''); - const match = getValue.exec(stripped); + const stripped = svg.replace(leadingWhitespace, '') + const match = getValue.exec(stripped) if (match) { - return match[1]; + return match[1] } else { - throw Error(`Can't get value from SVG:\n${svg}`); + throw Error(`Can't get value from SVG:\n${svg}`) } } @@ -23,14 +23,14 @@ function valueFromSvgBadge(svg) { function fetchFromSvg(request, url, cb) { request(url, function(err, res, buffer) { if (err !== null) { - cb(err); + cb(err) } else { - nodeifySync(() => valueFromSvgBadge(buffer), cb); + nodeifySync(() => valueFromSvgBadge(buffer), cb) } - }); + }) } module.exports = { valueFromSvgBadge, - fetchFromSvg -}; + fetchFromSvg, +} diff --git a/lib/svg-badge-parser.spec.js b/lib/svg-badge-parser.spec.js index b807e938f823fcb8a3ae13e215f65745ce112c1b..abff3a46390f3fb872f3bd52031e667cc78bad8a 100644 --- a/lib/svg-badge-parser.spec.js +++ b/lib/svg-badge-parser.spec.js @@ -1,19 +1,19 @@ -'use strict'; +'use strict' -const { expect } = require('chai'); -const { makeBadgeData } = require('./badge-data'); -const { valueFromSvgBadge } = require('./svg-badge-parser'); -const testHelpers = require('./make-badge-test-helpers'); +const { expect } = require('chai') +const { makeBadgeData } = require('./badge-data') +const { valueFromSvgBadge } = require('./svg-badge-parser') +const testHelpers = require('./make-badge-test-helpers') -const makeBadge = testHelpers.makeBadge(); +const makeBadge = testHelpers.makeBadge() describe('The SVG badge parser', function() { it('should find the correct value', function() { - const badgeData = makeBadgeData('this is the label', {}); - badgeData.text[1] = 'this is the result!'; + const badgeData = makeBadgeData('this is the label', {}) + badgeData.text[1] = 'this is the result!' - const exampleSvg = makeBadge(badgeData); + const exampleSvg = makeBadge(badgeData) - expect(valueFromSvgBadge(exampleSvg)).to.equal('this is the result!'); - }); -}); + expect(valueFromSvgBadge(exampleSvg)).to.equal('this is the result!') + }) +}) diff --git a/lib/svg-to-img.js b/lib/svg-to-img.js index 3b4a55387b9c241fe842a557b64ba59d9ce546d5..979272d457d30aaf933e2a50adffa0c44b29ac45 100644 --- a/lib/svg-to-img.js +++ b/lib/svg-to-img.js @@ -1,41 +1,43 @@ -'use strict'; +'use strict' -const gm = require('gm'); -const LruCache = require('./lru-cache'); +const gm = require('gm') +const LruCache = require('./lru-cache') -const imageMagick = gm.subClass({ imageMagick: true }); +const imageMagick = gm.subClass({ imageMagick: true }) // The following is an arbitrary limit (~1.5MB, 1.5kB/image). -const imgCache = new LruCache(1000); +const imgCache = new LruCache(1000) -function svgToImg (svg, format) { +function svgToImg(svg, format) { return new Promise((resolve, reject) => { - const cacheIndex = format + svg; + const cacheIndex = format + svg if (imgCache.has(cacheIndex)) { // We own a cache for this svg conversion. - const result = imgCache.get(cacheIndex); - resolve(result); - return; + const result = imgCache.get(cacheIndex) + resolve(result) + return } - const buf = Buffer.from('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + svg); + const buf = Buffer.from( + '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + svg + ) imageMagick(buf, 'image.' + format) .density(90) .background(format === 'jpg' ? '#FFFFFF' : 'none') .flatten() .toBuffer(format, (err, data) => { if (err) { - reject(err); + reject(err) } else { - imgCache.set(cacheIndex, data); - resolve(data); + imgCache.set(cacheIndex, data) + resolve(data) } - }); - }); -}; + }) + }) +} -module.exports = svgToImg; +module.exports = svgToImg // To simplify testing. -module.exports._imgCache = imgCache; -module.exports._imageMagick = imageMagick; +module.exports._imgCache = imgCache +module.exports._imageMagick = imageMagick diff --git a/lib/svg-to-img.spec.js b/lib/svg-to-img.spec.js index 55a4a0055c929f1535f8d65d77ad486953aa16b8..d87f9370b346b39b48ec0b083951280e47765307 100644 --- a/lib/svg-to-img.spec.js +++ b/lib/svg-to-img.spec.js @@ -1,31 +1,35 @@ -'use strict'; +'use strict' -const { expect } = require('chai'); -const isPng = require('is-png'); -const sinon = require('sinon'); -const svg2img = require('./svg-to-img'); -const testHelpers = require('./make-badge-test-helpers'); +const { expect } = require('chai') +const isPng = require('is-png') +const sinon = require('sinon') +const svg2img = require('./svg-to-img') +const testHelpers = require('./make-badge-test-helpers') -const makeBadge = testHelpers.makeBadge(); +const makeBadge = testHelpers.makeBadge() -describe('The rasterizer', function () { - let cacheGet; - beforeEach(function () { cacheGet = sinon.spy(svg2img._imgCache, 'get'); }); - afterEach(function () { cacheGet.restore(); }); +describe('The rasterizer', function() { + let cacheGet + beforeEach(function() { + cacheGet = sinon.spy(svg2img._imgCache, 'get') + }) + afterEach(function() { + cacheGet.restore() + }) it('should produce PNG', async function() { - const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' }); - const data = await svg2img(svg, 'png'); - expect(data).to.satisfy(isPng); - }); + const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' }) + const data = await svg2img(svg, 'png') + expect(data).to.satisfy(isPng) + }) it('should cache its results', async function() { - const svg = makeBadge({ text: ['will-this', 'be-cached?'], format: 'svg' }); - const data1 = await svg2img(svg, 'png'); - expect(data1).to.satisfy(isPng); - expect(cacheGet).not.to.have.been.called; + const svg = makeBadge({ text: ['will-this', 'be-cached?'], format: 'svg' }) + const data1 = await svg2img(svg, 'png') + expect(data1).to.satisfy(isPng) + expect(cacheGet).not.to.have.been.called const data2 = await svg2img(svg, 'png') - expect(data2).to.satisfy(isPng); - expect(cacheGet).to.have.been.calledOnce; - }); -}); + expect(data2).to.satisfy(isPng) + expect(cacheGet).to.have.been.calledOnce + }) +}) diff --git a/lib/sys/monitor.js b/lib/sys/monitor.js index 6aa3cf00aacdae693b53da4749ce4a6deb3d57df..0ff815e17eb53a6daf3406bdac52944f1973ec16 100644 --- a/lib/sys/monitor.js +++ b/lib/sys/monitor.js @@ -1,69 +1,83 @@ -'use strict'; +'use strict' -const secretIsValid = require('./secret-is-valid'); -const serverSecrets = require('../server-secrets'); -const config = require('../server-config'); -const RateLimit = require('./rate-limit'); -const log = require('../log'); +const secretIsValid = require('./secret-is-valid') +const serverSecrets = require('../server-secrets') +const config = require('../server-config') +const RateLimit = require('./rate-limit') +const log = require('../log') function secretInvalid(req, res) { if (!secretIsValid(req.password)) { // An unknown entity tries to connect. Let the connection linger for a minute. setTimeout(function() { - res.json({errors: [{code: 'invalid_secrets'}]}); - }, 10000); - return true; + res.json({ errors: [{ code: 'invalid_secrets' }] }) + }, 10000) + return true } - return false; + return false } function setRoutes(server) { const ipRateLimit = new RateLimit({ whitelist: /^192\.30\.252\.\d+$/, // Whitelist GitHub IPs. - }); - const badgeTypeRateLimit = new RateLimit({maxHitsPerPeriod: 3000}); + }) + const badgeTypeRateLimit = new RateLimit({ maxHitsPerPeriod: 3000 }) const refererRateLimit = new RateLimit({ maxHitsPerPeriod: 300, whitelist: /^https?:\/\/shields\.io\/$/, - }); + }) server.handle(function monitorHandler(req, res, next) { if (req.url.startsWith('/sys/')) { - if (secretInvalid(req, res)) { return; } + if (secretInvalid(req, res)) { + return + } } if (config.rateLimit) { - const ip = (req.headers['x-forwarded-for'] || '').split(', ')[0] - || req.socket.remoteAddress; - const badgeType = req.url.split(/[/-]/).slice(0, 3).join(''); - const referer = req.headers['referer']; + const ip = + (req.headers['x-forwarded-for'] || '').split(', ')[0] || + req.socket.remoteAddress + const badgeType = req.url + .split(/[/-]/) + .slice(0, 3) + .join('') + const referer = req.headers['referer'] - if (ipRateLimit.isBanned(ip, req, res)) { return; } - if (badgeTypeRateLimit.isBanned(badgeType, req, res)) { return; } - if (refererRateLimit.isBanned(referer, req, res)) { return; } + if (ipRateLimit.isBanned(ip, req, res)) { + return + } + if (badgeTypeRateLimit.isBanned(badgeType, req, res)) { + return + } + if (refererRateLimit.isBanned(referer, req, res)) { + return + } } - next(); - }); + next() + }) server.get('/sys/network', (req, res) => { - res.json({ips: serverSecrets.shieldsIps}); - }); + res.json({ ips: serverSecrets.shieldsIps }) + }) server.ws('/sys/logs', socket => { - const listener = (...msg) => socket.send(msg.join(' ')); - socket.on('close', () => log.removeListener(listener)); + const listener = (...msg) => socket.send(msg.join(' ')) + socket.on('close', () => log.removeListener(listener)) socket.on('message', msg => { - let req; + let req try { - req = JSON.parse(msg); - } catch(e) { return; } + req = JSON.parse(msg) + } catch (e) { + return + } if (!secretIsValid(req.secret)) { - return socket.close(); + return socket.close() } - log.addListener(listener); - }); - }); + log.addListener(listener) + }) + }) server.get('/sys/rate-limit', (req, res) => { res.json({ @@ -71,9 +85,9 @@ function setRoutes(server) { badgeType: badgeTypeRateLimit.toJSON(), referer: refererRateLimit.toJSON(), }) - }); + }) } module.exports = { setRoutes, -}; +} diff --git a/lib/sys/rate-limit.js b/lib/sys/rate-limit.js index 063f9bc20b9f1dd56d1f90542171dd7143f8b1d8..8e64f73dd618cd8abda7c81f16305756c53c1671 100644 --- a/lib/sys/rate-limit.js +++ b/lib/sys/rate-limit.js @@ -1,44 +1,48 @@ -'use strict'; +'use strict' // A rate limit ensures that a request parameter gets flagged if it goes // above a limit. module.exports = class RateLimit { constructor(options = {}) { // this.hits: Map from request parameters to the number of hits. - this.hits = new Map(); - this.period = options.period || 200; // 3 min ⅓, in seconds - this.maxHitsPerPeriod = options.maxHitsPerPeriod || 500; - this.banned = new Set(); - this.bannedUrls = new Set(); - this.whitelist = options.whitelist - || /(?!)/; // Matches nothing by default. - setInterval(this.resetHits.bind(this), this.period * 1000); + this.hits = new Map() + this.period = options.period || 200 // 3 min ⅓, in seconds + this.maxHitsPerPeriod = options.maxHitsPerPeriod || 500 + this.banned = new Set() + this.bannedUrls = new Set() + this.whitelist = options.whitelist || /(?!)/ // Matches nothing by default. + setInterval(this.resetHits.bind(this), this.period * 1000) } resetHits() { - this.hits.clear(); - this.banned.clear(); - this.bannedUrls.clear(); + this.hits.clear() + this.banned.clear() + this.bannedUrls.clear() } isBanned(reqParam, req, res) { - const hitsInCurrentPeriod = this.hits.get(reqParam) || 0; - if ((reqParam != null) && !this.whitelist.test(reqParam) - && (hitsInCurrentPeriod > this.maxHitsPerPeriod)) { - this.banned.add(reqParam); + const hitsInCurrentPeriod = this.hits.get(reqParam) || 0 + if ( + reqParam != null && + !this.whitelist.test(reqParam) && + hitsInCurrentPeriod > this.maxHitsPerPeriod + ) { + this.banned.add(reqParam) } if (this.banned.has(reqParam)) { - res.statusCode = 429; - res.setHeader('Retry-After', String(this.period)); - res.end(`Exceeded limit ${this.maxHitsPerPeriod} requests ` + - `per ${this.period} seconds`); - this.bannedUrls.add(req.url); - return true; + res.statusCode = 429 + res.setHeader('Retry-After', String(this.period)) + res.end( + `Exceeded limit ${this.maxHitsPerPeriod} requests ` + + `per ${this.period} seconds` + ) + this.bannedUrls.add(req.url) + return true } - this.hits.set(reqParam, hitsInCurrentPeriod + 1); - return false; + this.hits.set(reqParam, hitsInCurrentPeriod + 1) + return false } toJSON() { @@ -46,6 +50,6 @@ module.exports = class RateLimit { banned: [...this.banned], hits: [...this.hits], urls: [...this.bannedUrls], - }; + } } } diff --git a/lib/sys/secret-is-valid.js b/lib/sys/secret-is-valid.js index 57726a7af0910a1edd0f35ef5e4472081a26e94f..0e48081633aee1688632df38c814694807812094 100644 --- a/lib/sys/secret-is-valid.js +++ b/lib/sys/secret-is-valid.js @@ -1,18 +1,20 @@ -'use strict'; +'use strict' -const serverSecrets = require('../server-secrets'); +const serverSecrets = require('../server-secrets') function secretIsValid(secret = '') { - return constEq(secret, serverSecrets.shieldsSecret); + return constEq(secret, serverSecrets.shieldsSecret) } function constEq(a, b) { - if (a.length !== b.length) { return false; } - let zero = 0; + if (a.length !== b.length) { + return false + } + let zero = 0 for (let i = 0; i < a.length; i++) { - zero |= a.charCodeAt(i) ^ b.charCodeAt(i); + zero |= a.charCodeAt(i) ^ b.charCodeAt(i) } - return (zero === 0); + return zero === 0 } -module.exports = secretIsValid; +module.exports = secretIsValid diff --git a/lib/teamcity-badge-helpers.js b/lib/teamcity-badge-helpers.js index 61095c49882544d4b82c271f39b5360e9ca96dcb..3ee34077302c1935c3ab0fa2751ae1db4f5e3358 100644 --- a/lib/teamcity-badge-helpers.js +++ b/lib/teamcity-badge-helpers.js @@ -1,36 +1,47 @@ -'use strict'; +'use strict' -const { makeBadgeData: getBadgeData } = require('./badge-data'); +const { makeBadgeData: getBadgeData } = require('./badge-data') -function teamcityBadge(url, buildId, advanced, format, data, sendBadge, request) { - const apiUrl = url + '/app/rest/builds/buildType:(id:' + buildId + ')?guest=1'; - const badgeData = getBadgeData('build', data); - request(apiUrl, { headers: { 'Accept': 'application/json' } }, function(err, res, buffer) { +function teamcityBadge( + url, + buildId, + advanced, + format, + data, + sendBadge, + request +) { + const apiUrl = url + '/app/rest/builds/buildType:(id:' + buildId + ')?guest=1' + const badgeData = getBadgeData('build', data) + request(apiUrl, { headers: { Accept: 'application/json' } }, function( + err, + res, + buffer + ) { if (err != null) { - badgeData.text[1] = 'inaccessible'; - sendBadge(format, badgeData); - return; + badgeData.text[1] = 'inaccessible' + sendBadge(format, badgeData) + return } try { - const data = JSON.parse(buffer); + const data = JSON.parse(buffer) if (advanced) - badgeData.text[1] = (data.statusText || data.status || '').toLowerCase(); - else - badgeData.text[1] = (data.status || '').toLowerCase(); + badgeData.text[1] = (data.statusText || data.status || '').toLowerCase() + else badgeData.text[1] = (data.status || '').toLowerCase() if (data.status === 'SUCCESS') { - badgeData.colorscheme = 'brightgreen'; - badgeData.text[1] = 'passing'; + badgeData.colorscheme = 'brightgreen' + badgeData.text[1] = 'passing' } else { - badgeData.colorscheme = 'red'; + badgeData.colorscheme = 'red' } - sendBadge(format, badgeData); - } catch(e) { - badgeData.text[1] = 'invalid'; - sendBadge(format, badgeData); + sendBadge(format, badgeData) + } catch (e) { + badgeData.text[1] = 'invalid' + sendBadge(format, badgeData) } - }); + }) } module.exports = { - teamcityBadge -}; + teamcityBadge, +} diff --git a/lib/test-config.js b/lib/test-config.js index 2daaed0643fb86e071f9d068bd170f3c95ea9c24..50206d4a629f5421b6f372cde1b40563e1c6695a 100644 --- a/lib/test-config.js +++ b/lib/test-config.js @@ -1,5 +1,5 @@ -'use strict'; +'use strict' module.exports = { - port: 1111 -}; + port: 1111, +} diff --git a/lib/text-formatters.js b/lib/text-formatters.js index fa42a93f4cccd53c6260b9bd0ba6b5102b0934ac..e38f71bf918d9c975fee9721086f6f773be77af8 100644 --- a/lib/text-formatters.js +++ b/lib/text-formatters.js @@ -2,100 +2,113 @@ * Commonly-used functions for formatting text in badge labels. Includes * ordinal numbers, currency codes, star ratings, versions, etc. */ -'use strict'; +'use strict' -const moment = require('moment'); -moment().format(); +const moment = require('moment') +moment().format() function starRating(rating) { - const flooredRating = Math.floor(rating); - let stars = ''; - while (stars.length < flooredRating) { stars += '★'; } - const decimal = rating - flooredRating; - if (decimal >= 0.875) { stars += '★'; } - else if (decimal >= 0.625) { stars += '¾'; } - else if (decimal >= 0.375) { stars += '½'; } - else if (decimal >= 0.125) { stars += '¼'; } - while (stars.length < 5) { stars += '☆'; } - return stars; + const flooredRating = Math.floor(rating) + let stars = '' + while (stars.length < flooredRating) { + stars += '★' + } + const decimal = rating - flooredRating + if (decimal >= 0.875) { + stars += '★' + } else if (decimal >= 0.625) { + stars += '¾' + } else if (decimal >= 0.375) { + stars += '½' + } else if (decimal >= 0.125) { + stars += '¼' + } + while (stars.length < 5) { + stars += '☆' + } + return stars } // Convert ISO 4217 code to unicode string. function currencyFromCode(code) { - return ({ - CNY: '¥', - EUR: '€', - GBP: '₤', - USD: '$', - })[code] || code; + return ( + { + CNY: '¥', + EUR: '€', + GBP: '₤', + USD: '$', + }[code] || code + ) } function ordinalNumber(n) { - const s=['ᵗʰ','ˢᵗ','ⁿᵈ','ʳᵈ'], v=n%100; - return n+(s[(v-20)%10]||s[v]||s[0]); + const s = ['ᵗʰ', 'ˢᵗ', 'ⁿᵈ', 'ʳᵈ'], + v = n % 100 + return n + (s[(v - 20) % 10] || s[v] || s[0]) } // Given a number, string with appropriate unit in the metric system, SI. // Note: numbers beyond the peta- cannot be represented as integers in JS. -const metricPrefix = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; -const metricPower = metricPrefix - .map(function(a, i) { return Math.pow(1000, i + 1); }); +const metricPrefix = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] +const metricPower = metricPrefix.map(function(a, i) { + return Math.pow(1000, i + 1) +}) function metric(n) { for (let i = metricPrefix.length - 1; i >= 0; i--) { - const limit = metricPower[i]; + const limit = metricPower[i] if (n >= limit) { - n = Math.round(n / limit); + n = Math.round(n / limit) if (n < 1000) { - return '' + n + metricPrefix[i]; + return '' + n + metricPrefix[i] } else { - return '1' + metricPrefix[i + 1]; + return '1' + metricPrefix[i + 1] } } } - return '' + n; + return '' + n } // Remove the starting v in a string. function omitv(version) { if (version.charCodeAt(0) === 118) { - return version.slice(1); + return version.slice(1) } - return version; + return version } // Add a starting v to the version unless: // - it does not start with a digit // - it is a date (yyyy-mm-dd) -const ignoredVersionPatterns = /^[^0-9]|[0-9]{4}-[0-9]{2}-[0-9]{2}/; +const ignoredVersionPatterns = /^[^0-9]|[0-9]{4}-[0-9]{2}-[0-9]{2}/ function addv(version) { - version = '' + version; + version = '' + version if (version.startsWith('v') || ignoredVersionPatterns.test(version)) { - return version; + return version } else { - return `v${version}`; + return `v${version}` } } function maybePluralize(singular, countable, plural) { - plural = plural || `${singular}s`; + plural = plural || `${singular}s` if (countable && countable.length === 1) { - return singular; + return singular } else { - return plural; + return plural } } function formatDate(d) { - const date = moment(d); + const date = moment(d) const dateString = date.calendar(null, { lastDay: '[yesterday]', sameDay: '[today]', lastWeek: '[last] dddd', - sameElse: 'MMMM YYYY' - }); + sameElse: 'MMMM YYYY', + }) // Trim current year from date string - return dateString.replace(` ${moment().year()}`, '').toLowerCase(); + return dateString.replace(` ${moment().year()}`, '').toLowerCase() } module.exports = { @@ -106,5 +119,5 @@ module.exports = { omitv, addv, maybePluralize, - formatDate -}; + formatDate, +} diff --git a/lib/text-formatters.spec.js b/lib/text-formatters.spec.js index 0a5d4d7ba36a0d5819b51456f2bdcdfb07889063..a4885dd658919d53f3d96ca950fe805ac7070c9c 100644 --- a/lib/text-formatters.spec.js +++ b/lib/text-formatters.spec.js @@ -1,7 +1,7 @@ -'use strict'; +'use strict' -const { test, given } = require('sazerac'); -const sinon = require('sinon'); +const { test, given } = require('sazerac') +const sinon = require('sinon') const { starRating, currencyFromCode, @@ -10,83 +10,87 @@ const { omitv, addv, maybePluralize, - formatDate -} = require('./text-formatters'); + formatDate, +} = require('./text-formatters') describe('Text formatters', function() { test(starRating, () => { - given(4.9).expect('★★★★★'); - given(3.7).expect('★★★¾☆'); - given(2.566).expect('★★½☆☆'); - given(2.2).expect('★★¼☆☆'); - given(3).expect('★★★☆☆'); - }); + given(4.9).expect('★★★★★') + given(3.7).expect('★★★¾☆') + given(2.566).expect('★★½☆☆') + given(2.2).expect('★★¼☆☆') + given(3).expect('★★★☆☆') + }) test(currencyFromCode, () => { - given('CNY').expect('¥'); - given('EUR').expect('€'); - given('GBP').expect('₤'); - given('USD').expect('$'); - given('AUD').expect('AUD'); - }); + given('CNY').expect('¥') + given('EUR').expect('€') + given('GBP').expect('₤') + given('USD').expect('$') + given('AUD').expect('AUD') + }) test(ordinalNumber, () => { - given(2).expect('2ⁿᵈ'); - given(11).expect('11ᵗʰ'); - given(23).expect('23ʳᵈ'); - given(131).expect('131ˢᵗ'); - }); + given(2).expect('2ⁿᵈ') + given(11).expect('11ᵗʰ') + given(23).expect('23ʳᵈ') + given(131).expect('131ˢᵗ') + }) test(metric, () => { - given(999).expect('999'); - given(1000).expect('1k'); - given(999499).expect('999k'); - given(999500).expect('1M'); - given(1578896212).expect('2G'); - given(80000000000000).expect('80T'); - given(4000000000000001).expect('4P'); - given(71007000100580002000).expect('71E'); - given(1000000000000000000000).expect('1Z'); - given(2222222222222222222222222).expect('2Y'); - }); + given(999).expect('999') + given(1000).expect('1k') + given(999499).expect('999k') + given(999500).expect('1M') + given(1578896212).expect('2G') + given(80000000000000).expect('80T') + given(4000000000000001).expect('4P') + given(71007000100580002000).expect('71E') + given(1000000000000000000000).expect('1Z') + given(2222222222222222222222222).expect('2Y') + }) test(omitv, () => { - given('hello').expect('hello'); - given('v1.0.1').expect('1.0.1'); - }); + given('hello').expect('hello') + given('v1.0.1').expect('1.0.1') + }) test(addv, () => { - given(9).expect('v9'); - given(0.1).expect('v0.1'); - given('1.0.0').expect('v1.0.0'); - given('v0.6').expect('v0.6'); - given('hello').expect('hello'); - given('2017-05-05-Release-2.3.17').expect('2017-05-05-Release-2.3.17'); - }); + given(9).expect('v9') + given(0.1).expect('v0.1') + given('1.0.0').expect('v1.0.0') + given('v0.6').expect('v0.6') + given('hello').expect('hello') + given('2017-05-05-Release-2.3.17').expect('2017-05-05-Release-2.3.17') + }) test(maybePluralize, () => { - given('foo', []).expect('foos'); - given('foo', [123]).expect('foo'); - given('foo', [123, 456]).expect('foos'); - given('foo', undefined).expect('foos'); + given('foo', []).expect('foos') + given('foo', [123]).expect('foo') + given('foo', [123, 456]).expect('foos') + given('foo', undefined).expect('foos') - given('box', [], 'boxes').expect('boxes'); - given('box', [123], 'boxes').expect('box'); - given('box', [123, 456], 'boxes').expect('boxes'); - given('box', undefined, 'boxes').expect('boxes'); - }); + given('box', [], 'boxes').expect('boxes') + given('box', [123], 'boxes').expect('box') + given('box', [123, 456], 'boxes').expect('boxes') + given('box', undefined, 'boxes').expect('boxes') + }) test(formatDate, () => { - given(1465513200000).describe('when given a timestamp in june 2016').expect('june 2016'); - }); + given(1465513200000) + .describe('when given a timestamp in june 2016') + .expect('june 2016') + }) - context('in october', function () { - beforeEach(function () { - sinon.useFakeTimers(new Date(2017, 9, 15).getTime()); - }); + context('in october', function() { + beforeEach(function() { + sinon.useFakeTimers(new Date(2017, 9, 15).getTime()) + }) test(formatDate, () => { - given(new Date(2017, 0, 1).getTime()).describe('when given the beginning of this year').expect('january'); - }); - }); -}); + given(new Date(2017, 0, 1).getTime()) + .describe('when given the beginning of this year') + .expect('january') + }) + }) +}) diff --git a/lib/text-measurer.js b/lib/text-measurer.js index 916ddeba8538f27d411fd4a0a40894e494401f7e..dae729a6a581c434495de4bf5effb6c3faf7da99 100644 --- a/lib/text-measurer.js +++ b/lib/text-measurer.js @@ -1,26 +1,30 @@ -'use strict'; +'use strict' -const PDFDocument = require('pdfkit'); +const PDFDocument = require('pdfkit') class PDFKitTextMeasurer { constructor(fontPath, fallbackFontPath) { - this.document = new PDFDocument({ size: 'A4', layout: 'landscape' }) - .fontSize(11); + this.document = new PDFDocument({ + size: 'A4', + layout: 'landscape', + }).fontSize(11) try { - this.document.font(fontPath); - } catch(e) { + this.document.font(fontPath) + } catch (e) { if (fallbackFontPath) { - console.error(`Text-width computation may be incorrect. Unable to load font at ${fontPath}. Using fallback font ${fallbackFontPath} instead.`); - this.document.font(fallbackFontPath); + console.error( + `Text-width computation may be incorrect. Unable to load font at ${fontPath}. Using fallback font ${fallbackFontPath} instead.` + ) + this.document.font(fallbackFontPath) } else { - console.error('No fallback font set.'); - throw e; + console.error('No fallback font set.') + throw e } } } widthOf(str) { - return this.document.widthOfString(str); + return this.document.widthOfString(str) } } @@ -29,61 +33,62 @@ class QuickTextMeasurer { this.baseMeasurer = new PDFKitTextMeasurer(fontPath, fallbackFontPath) // This will be a Map of characters -> numbers. - this.characterWidths = new Map(); + this.characterWidths = new Map() // This will be Map of Maps of characters -> numbers. - this.kerningPairs = new Map(); - this._prepare(); + this.kerningPairs = new Map() + this._prepare() } static printableAsciiCharacters() { - const printableRange = [32, 126]; - const length = printableRange[1] - printableRange[0] + 1; - return Array - .from({ length }, (value, i) => printableRange[0] + i) - .map(charCode => String.fromCharCode(charCode)); + const printableRange = [32, 126] + const length = printableRange[1] - printableRange[0] + 1 + return Array.from({ length }, (value, i) => printableRange[0] + i).map( + charCode => String.fromCharCode(charCode) + ) } _prepare() { - const charactersToCache = this.constructor.printableAsciiCharacters(); + const charactersToCache = this.constructor.printableAsciiCharacters() charactersToCache.forEach(char => { - this.characterWidths.set(char, this.baseMeasurer.widthOf(char)); - this.kerningPairs.set(char, new Map()); - }); + this.characterWidths.set(char, this.baseMeasurer.widthOf(char)) + this.kerningPairs.set(char, new Map()) + }) charactersToCache.forEach(first => { charactersToCache.forEach(second => { - const individually = this.characterWidths.get(first) + this.characterWidths.get(second); - const asPair = this.baseMeasurer.widthOf(`${first}${second}`); - const kerningAdjustment = asPair - individually; - this.kerningPairs.get(first).set(second, kerningAdjustment); - }); - }); + const individually = + this.characterWidths.get(first) + this.characterWidths.get(second) + const asPair = this.baseMeasurer.widthOf(`${first}${second}`) + const kerningAdjustment = asPair - individually + this.kerningPairs.get(first).set(second, kerningAdjustment) + }) + }) } widthOf(str) { - const { characterWidths, kerningPairs } = this; + const { characterWidths, kerningPairs } = this - let result = 0; - let previous = null; + let result = 0 + let previous = null for (const character of str) { if (!characterWidths.has(character)) { // Bail. - return this.baseMeasurer.widthOf(str); + return this.baseMeasurer.widthOf(str) } - result += characterWidths.get(character); + result += characterWidths.get(character) if (previous !== null) { - result += kerningPairs.get(previous).get(character); + result += kerningPairs.get(previous).get(character) } - previous = character; + previous = character } - return result; + return result } } module.exports = { PDFKitTextMeasurer, QuickTextMeasurer, -}; +} diff --git a/lib/text-measurer.spec.js b/lib/text-measurer.spec.js index 6f944ea0603279ebdb1664acafbdf8a50b374680..1ea58ea22868abc977090eeddca2756e7685a61c 100644 --- a/lib/text-measurer.spec.js +++ b/lib/text-measurer.spec.js @@ -1,142 +1,154 @@ -'use strict'; - -const { expect } = require('chai'); -const path = require('path'); -const fs = require('fs'); -const sinon = require('sinon'); -const { PDFKitTextMeasurer, QuickTextMeasurer } = require('./text-measurer'); -const { starRating } = require('./text-formatters'); -const defaults = require('./defaults'); -const testHelpers = require('./make-badge-test-helpers'); -const almostEqual = require('almost-equal'); - -const EPSILON_PIXELS = 1e-3; - -describe('PDFKitTextMeasurer with DejaVu Sans', function () { - it('should produce the same length as before', function () { - const measurer = new PDFKitTextMeasurer(testHelpers.font.path); - expect(measurer.widthOf('This is the dawning of the Age of Aquariums')) - .to.equal(243.546875); - }); -}); +'use strict' + +const { expect } = require('chai') +const path = require('path') +const fs = require('fs') +const sinon = require('sinon') +const { PDFKitTextMeasurer, QuickTextMeasurer } = require('./text-measurer') +const { starRating } = require('./text-formatters') +const defaults = require('./defaults') +const testHelpers = require('./make-badge-test-helpers') +const almostEqual = require('almost-equal') + +const EPSILON_PIXELS = 1e-3 + +describe('PDFKitTextMeasurer with DejaVu Sans', function() { + it('should produce the same length as before', function() { + const measurer = new PDFKitTextMeasurer(testHelpers.font.path) + expect( + measurer.widthOf('This is the dawning of the Age of Aquariums') + ).to.equal(243.546875) + }) +}) function registerTests(fontPath, skip) { // Invoke `.skip()` within the `it`'s so we get logging of the skipped tests. - const displayName = path.basename(fontPath, path.extname(fontPath)); + const displayName = path.basename(fontPath, path.extname(fontPath)) - describe(`QuickTextMeasurer with ${displayName}`, function () { - let quickMeasurer; - if (! skip) { - before(function () { + describe(`QuickTextMeasurer with ${displayName}`, function() { + let quickMeasurer + if (!skip) { + before(function() { // Since this is slow, share it across all tests. - quickMeasurer = new QuickTextMeasurer(fontPath); - }); + quickMeasurer = new QuickTextMeasurer(fontPath) + }) } - let sandbox; - let pdfKitWidthOf; - let pdfKitMeasurer; - if (! skip) { + let sandbox + let pdfKitWidthOf + let pdfKitMeasurer + if (!skip) { // Boo, the sandbox doesn't get cleaned up after a skipped test. - beforeEach(function () { + beforeEach(function() { // This often times out: https://circleci.com/gh/badges/shields/2786 - this.timeout(5000); - sandbox = sinon.createSandbox(); - pdfKitWidthOf = sandbox.spy(PDFKitTextMeasurer.prototype, 'widthOf'); - pdfKitMeasurer = new PDFKitTextMeasurer(fontPath); - }); + this.timeout(5000) + sandbox = sinon.createSandbox() + pdfKitWidthOf = sandbox.spy(PDFKitTextMeasurer.prototype, 'widthOf') + pdfKitMeasurer = new PDFKitTextMeasurer(fontPath) + }) - afterEach(function () { + afterEach(function() { if (sandbox) { - sandbox.restore(); - sandbox = null; + sandbox.restore() + sandbox = null } - }); + }) } - context('when given ASCII strings', function () { + context('when given ASCII strings', function() { const strings = [ 'This is the dawning of the Age of Aquariums', 'v1.2.511', '5 passed, 2 failed, 1 skipped', '[prismic "1.1"]', - ]; - - strings.forEach(function (str) { - it(`should measure '${str}' in parity with PDFKit`, function () { - if (skip) { this.skip(); } - expect(quickMeasurer.widthOf(str)) - .to.be.closeTo(pdfKitMeasurer.widthOf(str), EPSILON_PIXELS); - }); - }); - - strings.forEach(function (str) { - it(`should measure '${str}' without invoking PDFKit`, function () { - if (skip) { this.skip(); } - quickMeasurer.widthOf(str); - expect(pdfKitWidthOf).not.to.have.been.called; - }); - }); - - context('when the font includes a kerning pair', function () { + ] + + strings.forEach(function(str) { + it(`should measure '${str}' in parity with PDFKit`, function() { + if (skip) { + this.skip() + } + expect(quickMeasurer.widthOf(str)).to.be.closeTo( + pdfKitMeasurer.widthOf(str), + EPSILON_PIXELS + ) + }) + }) + + strings.forEach(function(str) { + it(`should measure '${str}' without invoking PDFKit`, function() { + if (skip) { + this.skip() + } + quickMeasurer.widthOf(str) + expect(pdfKitWidthOf).not.to.have.been.called + }) + }) + + context('when the font includes a kerning pair', function() { const stringsWithKerningPairs = [ 'Q-tips', // In DejaVu, Q- is a kerning pair. 'B-flat', // In Verdana, B- is a kerning pair. - ]; + ] function widthByMeasuringCharacters(str) { - let result = 0; + let result = 0 for (const char of str) { - result += pdfKitMeasurer.widthOf(char); + result += pdfKitMeasurer.widthOf(char) } - return result; + return result } - it(`should apply a width correction`, function () { - if (skip) { this.skip(); } + it(`should apply a width correction`, function() { + if (skip) { + this.skip() + } - const adjustedStrings = []; + const adjustedStrings = [] stringsWithKerningPairs.forEach(str => { - const actual = quickMeasurer.widthOf(str); - const unadjusted = widthByMeasuringCharacters(str); + const actual = quickMeasurer.widthOf(str) + const unadjusted = widthByMeasuringCharacters(str) if (!almostEqual(actual, unadjusted, EPSILON_PIXELS)) { - adjustedStrings.push(str); + adjustedStrings.push(str) } - }); + }) - expect(adjustedStrings).to.be.an('array').that.is.not.empty; - }); - }); - }); + expect(adjustedStrings).to.be.an('array').that.is.not.empty + }) + }) + }) - context('when given non-ASCII strings', function () { - const strings = [ - starRating(3.5), - '\u2026', - ]; - - strings.forEach(function (str) { - it(`should measure '${str}' in parity with PDFKit`, function () { - if (skip) { this.skip(); } - expect(quickMeasurer.widthOf(str)) - .to.be.closeTo(pdfKitMeasurer.widthOf(str), EPSILON_PIXELS); - }); - }); - - strings.forEach(function (str) { - it(`should invoke the base when measuring '${str}'`, function () { - if (skip) { this.skip(); } - quickMeasurer.widthOf(str); - expect(pdfKitWidthOf).to.have.been.called; - }); - }); - }); - }); -}; + context('when given non-ASCII strings', function() { + const strings = [starRating(3.5), '\u2026'] + + strings.forEach(function(str) { + it(`should measure '${str}' in parity with PDFKit`, function() { + if (skip) { + this.skip() + } + expect(quickMeasurer.widthOf(str)).to.be.closeTo( + pdfKitMeasurer.widthOf(str), + EPSILON_PIXELS + ) + }) + }) + + strings.forEach(function(str) { + it(`should invoke the base when measuring '${str}'`, function() { + if (skip) { + this.skip() + } + quickMeasurer.widthOf(str) + expect(pdfKitWidthOf).to.have.been.called + }) + }) + }) + }) +} // i.e. Verdana -registerTests(defaults.font.path, !fs.existsSync(defaults.font.path)); +registerTests(defaults.font.path, !fs.existsSync(defaults.font.path)) // i.e. DejaVu Sans -registerTests(testHelpers.font.path); +registerTests(testHelpers.font.path) diff --git a/lib/token-pool.js b/lib/token-pool.js index 8ce368efb4274ab16912dc751afbfb27f054ced9..cd22a6ec2c4ea09410821f9432a732506ffc7c25 100644 --- a/lib/token-pool.js +++ b/lib/token-pool.js @@ -1,13 +1,13 @@ -'use strict'; +'use strict' -const PriorityQueue = require('priorityqueuejs'); +const PriorityQueue = require('priorityqueuejs') // Encapsulate a rate-limited token, with a user-provided ID and user-provided data. // // Each token has a notion of the number of uses remaining until exhausted, // and the next reset time, when it can be used again even if it's exhausted. class Token { - constructor (id, data, usesRemaining, nextReset) { + constructor(id, data, usesRemaining, nextReset) { // Use underscores to avoid conflict with property accessors. Object.assign(this, { _id: id, @@ -16,73 +16,86 @@ class Token { _nextReset: nextReset, _isValid: true, _isFrozen: false, - }); + }) } - get id () { return this._id; } - get data () { return this._data; } - get usesRemaining () { return this._usesRemaining; } - get nextReset () { return this._nextReset; } - get isValid () { return this._isValid; } + get id() { + return this._id + } + get data() { + return this._data + } + get usesRemaining() { + return this._usesRemaining + } + get nextReset() { + return this._nextReset + } + get isValid() { + return this._isValid + } - static utcEpochSeconds () { - return Date.now() / 1000 >>> 0; + static utcEpochSeconds() { + return (Date.now() / 1000) >>> 0 } - get hasReset () { - return this.constructor.utcEpochSeconds() >= this.nextReset; + get hasReset() { + return this.constructor.utcEpochSeconds() >= this.nextReset } - get isExhausted () { - return this.usesRemaining <= 0 && ! this.hasReset; + get isExhausted() { + return this.usesRemaining <= 0 && !this.hasReset } // Update the uses remaining and next reset time for a token. - update (usesRemaining, nextReset) { - if (! Number.isInteger(usesRemaining)) { - throw Error('usesRemaining must be an integer'); + update(usesRemaining, nextReset) { + if (!Number.isInteger(usesRemaining)) { + throw Error('usesRemaining must be an integer') } - if (! Number.isInteger(nextReset)) { - throw Error('nextReset must be an integer'); + if (!Number.isInteger(nextReset)) { + throw Error('nextReset must be an integer') } if (this._isFrozen) { - return; + return } // Since the token pool will typically return the same token for many uses // before moving on to another, `update()` may be called many times. Since // the sequence of responses may be indeterminate, keep the "worst case" // value for uses remaining. - if (this._nextReset === this.constructor.nextResetNever || nextReset > this._nextReset) { - this._nextReset = nextReset; - this._usesRemaining = usesRemaining; + if ( + this._nextReset === this.constructor.nextResetNever || + nextReset > this._nextReset + ) { + this._nextReset = nextReset + this._usesRemaining = usesRemaining } else if (nextReset === this._nextReset) { - this._usesRemaining = Math.min(this._usesRemaining, usesRemaining); + this._usesRemaining = Math.min(this._usesRemaining, usesRemaining) } else { // Discard the new update; it's older than the values we have. } } // Indicate that the token should no longer be used. - invalidate () { - this._isValid = false; + invalidate() { + this._isValid = false } // Freeze the uses remaining and next reset values. Helpful for keeping // stable ordering for a valid priority queue. - freeze () { - this._isFrozen = true; + freeze() { + this._isFrozen = true } // Unfreeze the uses remaining and next reset values. - unfreeze () { - this._isFrozen = false; + unfreeze() { + this._isFrozen = false } } // Large sentinel value which means "never reset". -Token.nextResetNever = Number.MAX_SAFE_INTEGER; +Token.nextResetNever = Number.MAX_SAFE_INTEGER // Encapsulate a collection of rate-limited tokens and choose the next // available token when one is needed. @@ -92,22 +105,22 @@ Token.nextResetNever = Number.MAX_SAFE_INTEGER; class TokenPool { // batchSize: The maximum number of times we use each token before moving // on to the next one. - constructor (batchSize) { - this.batchSize = batchSize; + constructor(batchSize) { + this.batchSize = batchSize - this.currentBatch = { currentToken: null, remaining: 0 }; + this.currentBatch = { currentToken: null, remaining: 0 } // A set of IDs used for deduplication. - this.tokenIds = new Set(); + this.tokenIds = new Set() // See discussion on the FIFO and priority queues in `next()`. - this.fifoQueue = []; - this.priorityQueue = new PriorityQueue(this.constructor.compareTokens); + this.fifoQueue = [] + this.priorityQueue = new PriorityQueue(this.constructor.compareTokens) } // Use the token whose current rate allotment is expiring soonest. - static compareTokens (first, second) { - return second.nextReset - first.nextReset; + static compareTokens(first, second) { + return second.nextReset - first.nextReset } // Add a token with user-provided ID and data. @@ -115,54 +128,54 @@ class TokenPool { // The ID can be a primitive value or an object reference, and is used (with // `Set`) for deduplication. If a token already exists with a given id, it // will be ignored. - add (id, data, usesRemaining, nextReset) { + add(id, data, usesRemaining, nextReset) { if (this.tokenIds.has(id)) { - return false; + return false } - this.tokenIds.add(id); + this.tokenIds.add(id) - usesRemaining = usesRemaining === undefined ? this.batchSize : usesRemaining; - nextReset = nextReset === undefined ? Token.nextResetNever : nextReset; + usesRemaining = usesRemaining === undefined ? this.batchSize : usesRemaining + nextReset = nextReset === undefined ? Token.nextResetNever : nextReset - const token = new Token(id, data, usesRemaining, nextReset); - this.fifoQueue.push(token); + const token = new Token(id, data, usesRemaining, nextReset) + this.fifoQueue.push(token) - return true; + return true } // Prepare to start a new batch by obtaining and returning the next usable // token. - _nextBatch () { - let next; + _nextBatch() { + let next while ((next = this.fifoQueue.shift())) { - if (! next.isValid) { + if (!next.isValid) { // Discard, and - continue; + continue } else if (next.isExhausted) { - next.freeze(); - this.priorityQueue.enq(next); + next.freeze() + this.priorityQueue.enq(next) } else { - return next; + return next } } while ((next = this.priorityQueue.peek())) { - if (! next.isValid) { + if (!next.isValid) { // Discard, and - continue; + continue } else if (next.isExhausted) { // No need to check any more tokens, since they all reset after this // one. - break; + break } else { - this.priorityQueue.deq(); // deq next - next.unfreeze(); - return next; + this.priorityQueue.deq() // deq next + next.unfreeze() + return next } } - throw Error('Token pool is exhausted'); + throw Error('Token pool is exhausted') } // Obtain the next available token, returning `null` if no tokens are @@ -190,38 +203,40 @@ class TokenPool { // After obtaining a token using `next()`, invoke `update()` on it to set a // new use-remaining count and next-reset time. Invoke `invalidate()` to // indicate it should not be reused. - next () { - let token = this.currentBatch.token; - const remaining = this.currentBatch.remaining; + next() { + let token = this.currentBatch.token + const remaining = this.currentBatch.remaining - if (remaining <= 0 || ! token.isValid || token.isExhausted) { - token = this._nextBatch(); + if (remaining <= 0 || !token.isValid || token.isExhausted) { + token = this._nextBatch() this.currentBatch = { token, - remaining: token.hasReset ? this.batchSize : Math.min(this.batchSize, token.usesRemaining), - }; - this.fifoQueue.push(token); + remaining: token.hasReset + ? this.batchSize + : Math.min(this.batchSize, token.usesRemaining), + } + this.fifoQueue.push(token) } - this.currentBatch.remaining -= 1; + this.currentBatch.remaining -= 1 - return token; + return token } // Iterate over all valid tokens. - forEach (callback) { - function visit (item) { + forEach(callback) { + function visit(item) { if (item.isValid) { - callback(item); - }; + callback(item) + } } - this.fifoQueue.forEach(visit); - this.priorityQueue.forEach(visit); + this.fifoQueue.forEach(visit) + this.priorityQueue.forEach(visit) } } module.exports = { Token, TokenPool, -}; +} diff --git a/lib/token-pool.spec.js b/lib/token-pool.spec.js index c5d9c07709ca863c610e26b17a42163cf06569c9..9a2df2d9ca60c023aeb9cec86d9d9010f8589279 100644 --- a/lib/token-pool.spec.js +++ b/lib/token-pool.spec.js @@ -1,107 +1,121 @@ -'use strict'; +'use strict' -const { expect } = require('chai'); -const sinon = require('sinon'); -const times = require('lodash.times'); -const { Token, TokenPool } = require('./token-pool'); +const { expect } = require('chai') +const sinon = require('sinon') +const times = require('lodash.times') +const { Token, TokenPool } = require('./token-pool') -function expectPoolToBeExhausted (pool) { +function expectPoolToBeExhausted(pool) { expect(() => { - pool.next(); - }).to.throw(Error, /^Token pool is exhausted$/); + pool.next() + }).to.throw(Error, /^Token pool is exhausted$/) } -describe('The token pool', function () { - const ids = [1, 2, 3, 4, 5]; - const batchSize = 3; +describe('The token pool', function() { + const ids = [1, 2, 3, 4, 5] + const batchSize = 3 - let tokenPool; - beforeEach(function () { + let tokenPool + beforeEach(function() { // Set up. - tokenPool = new TokenPool(batchSize); - ids.forEach(id => tokenPool.add(id)); - }); - - it('should yield the expected tokens', function () { - ids.forEach(id => times(batchSize, () => expect(tokenPool.next().id).to.equal(id))); - }); - - it('should repeat when reaching the end', function () { - ids.forEach(id => times(batchSize, () => expect(tokenPool.next().id).to.equal(id))); - ids.forEach(id => times(batchSize, () => expect(tokenPool.next().id).to.equal(id))); - }); - - context('tokens are marked exhausted immediately', function () { - it('should be exhausted', function () { + tokenPool = new TokenPool(batchSize) + ids.forEach(id => tokenPool.add(id)) + }) + + it('should yield the expected tokens', function() { + ids.forEach(id => + times(batchSize, () => expect(tokenPool.next().id).to.equal(id)) + ) + }) + + it('should repeat when reaching the end', function() { + ids.forEach(id => + times(batchSize, () => expect(tokenPool.next().id).to.equal(id)) + ) + ids.forEach(id => + times(batchSize, () => expect(tokenPool.next().id).to.equal(id)) + ) + }) + + context('tokens are marked exhausted immediately', function() { + it('should be exhausted', function() { ids.forEach(() => { - const token = tokenPool.next(); - token.update(0, Token.nextResetNever); - }); + const token = tokenPool.next() + token.update(0, Token.nextResetNever) + }) - expectPoolToBeExhausted(tokenPool); - }); - }); + expectPoolToBeExhausted(tokenPool) + }) + }) - context('tokens are marked after the last request', function () { - it('should be exhausted', function () { + context('tokens are marked after the last request', function() { + it('should be exhausted', function() { ids.forEach(() => { - const token = times(batchSize, () => tokenPool.next()).pop(); - token.update(0, Token.nextResetNever); - }); + const token = times(batchSize, () => tokenPool.next()).pop() + token.update(0, Token.nextResetNever) + }) - expectPoolToBeExhausted(tokenPool); - }); - }); + expectPoolToBeExhausted(tokenPool) + }) + }) - context('tokens are renewed', function () { - it('should keep using them', function () { - const tokensToRenew = [2, 4]; - const renewalCount = 3; + context('tokens are renewed', function() { + it('should keep using them', function() { + const tokensToRenew = [2, 4] + const renewalCount = 3 ids.forEach(id => { - const token = times(batchSize, () => tokenPool.next()).pop(); - const usesRemaining = tokensToRenew.includes(token.id) ? renewalCount : 0; - token.update(usesRemaining, Token.nextResetNever); - }); + const token = times(batchSize, () => tokenPool.next()).pop() + const usesRemaining = tokensToRenew.includes(token.id) + ? renewalCount + : 0 + token.update(usesRemaining, Token.nextResetNever) + }) tokensToRenew.forEach(id => { - let token; + let token times(renewalCount, () => { - token = tokenPool.next(); - expect(token.id).to.equal(id); - }).pop(); - token.update(0, Token.nextResetNever); - }); - - expectPoolToBeExhausted(tokenPool); - }); - }); - - context('tokens reset', function () { - let clock; - beforeEach(function () { clock = sinon.useFakeTimers(); }); - afterEach(function () { clock.restore(); }); - - it('should start using them', function () { - const tokensToReset = [2, 4]; - const futureTime = 1440; + token = tokenPool.next() + expect(token.id).to.equal(id) + }).pop() + token.update(0, Token.nextResetNever) + }) + + expectPoolToBeExhausted(tokenPool) + }) + }) + + context('tokens reset', function() { + let clock + beforeEach(function() { + clock = sinon.useFakeTimers() + }) + afterEach(function() { + clock.restore() + }) + + it('should start using them', function() { + const tokensToReset = [2, 4] + const futureTime = 1440 ids.forEach(id => { - const token = times(batchSize, () => tokenPool.next()).pop(); - const nextReset = tokensToReset.includes(token.id) ? futureTime : Token.nextResetNever; - token.update(0, nextReset); - }); + const token = times(batchSize, () => tokenPool.next()).pop() + const nextReset = tokensToReset.includes(token.id) + ? futureTime + : Token.nextResetNever + token.update(0, nextReset) + }) - expectPoolToBeExhausted(tokenPool); + expectPoolToBeExhausted(tokenPool) - clock.tick(1000 * futureTime); + clock.tick(1000 * futureTime) tokensToReset.forEach(id => { - const token = times(batchSize, () => tokenPool.next()).pop(); - token.update(0, Token.nextResetNever); - }); - - expectPoolToBeExhausted(tokenPool); - }); - }); -}); + const token = times(batchSize, () => tokenPool.next()).pop() + token.update(0, Token.nextResetNever) + }) + + expectPoolToBeExhausted(tokenPool) + }) + }) +}) diff --git a/lib/token-provider.js b/lib/token-provider.js index c5dee6ff979799f0fcbe63e0b409e6e274464c73..46a06aa1cc44bb42ac1a1a1ac51aba3a6df2f429 100644 --- a/lib/token-provider.js +++ b/lib/token-provider.js @@ -1,28 +1,27 @@ -'use strict'; +'use strict' -const { - Token, - TokenPool, -} = require('./token-pool'); +const { Token, TokenPool } = require('./token-pool') class StaticTokenProvider { - constructor (tokenValidator, tokenString) { + constructor(tokenValidator, tokenString) { if (typeof tokenValidator !== 'function') { - throw Error('tokenValidator is not a function'); + throw Error('tokenValidator is not a function') } - if (! tokenValidator(tokenString)) { - throw Error(`Not a valid token: ${tokenString}`); + if (!tokenValidator(tokenString)) { + throw Error(`Not a valid token: ${tokenString}`) } - this.staticToken = new Token(tokenString, null); + this.staticToken = new Token(tokenString, null) } - addToken () { - throw Error('When using token persistence, do not provide a static gh_token'); + addToken() { + throw Error( + 'When using token persistence, do not provide a static gh_token' + ) } - nextToken () { - return this.staticToken; + nextToken() { + return this.staticToken } } @@ -30,45 +29,45 @@ class PoolingTokenProvider { /* tokenValidator: A function which returns true if the argument is a valid token. */ - constructor (tokenValidator) { + constructor(tokenValidator) { if (typeof tokenValidator !== 'function') { - throw Error('tokenValidator is not a function'); + throw Error('tokenValidator is not a function') } Object.assign(this, { tokenValidator, batchSize: 25, searchBatchSize: 5, - }); + }) - this.tokenPool = new TokenPool(this.batchSize); + this.tokenPool = new TokenPool(this.batchSize) } - addToken (tokenString) { - if (! this.tokenValidator(tokenString)) { - throw Error(`Not a valid token: ${tokenString}`); + addToken(tokenString) { + if (!this.tokenValidator(tokenString)) { + throw Error(`Not a valid token: ${tokenString}`) } - this.tokenPool.add(tokenString, null, this.batchSize); + this.tokenPool.add(tokenString, null, this.batchSize) } - nextToken () { - return this.tokenPool.next(); + nextToken() { + return this.tokenPool.next() } // Return an array of token strings. - toNative () { - const result = []; + toNative() { + const result = [] this.tokenPool.forEach(token => { - result.push(token.id); - }); + result.push(token.id) + }) - return result; + return result } } module.exports = { StaticTokenProvider, - PoolingTokenProvider -}; + PoolingTokenProvider, +} diff --git a/lib/token-provider.spec.js b/lib/token-provider.spec.js index 29f0d61f1081697270e01071c89d75c27ee5c2c1..606042667de87b569653598b352d9f7e87ad5f66 100644 --- a/lib/token-provider.spec.js +++ b/lib/token-provider.spec.js @@ -1,19 +1,19 @@ -'use strict'; +'use strict' -const assert = require('assert'); -const { PoolingTokenProvider } = require('./token-provider'); +const assert = require('assert') +const { PoolingTokenProvider } = require('./token-provider') const isValidGithubToken = require('./github-auth/is-valid-token') -describe('The token provider', function () { - describe('toNative', function () { - it('should return the expected value', function () { - const tokens = ['1', '2', '3', '4', '5'].map(c => c.repeat(40)); - const provider = new PoolingTokenProvider(isValidGithubToken); - tokens.forEach(t => provider.addToken(t)); +describe('The token provider', function() { + describe('toNative', function() { + it('should return the expected value', function() { + const tokens = ['1', '2', '3', '4', '5'].map(c => c.repeat(40)) + const provider = new PoolingTokenProvider(isValidGithubToken) + tokens.forEach(t => provider.addToken(t)) assert.deepStrictEqual( provider.toNative().sort(), Array.from(tokens).sort() - ); - }); - }); -}); + ) + }) + }) +}) diff --git a/lib/unhandled-rejection.spec.js b/lib/unhandled-rejection.spec.js index ec8b621c8934ee59b540c8c073f1ac2b9f609eaf..b155793b9b04f0caa45853cbc948e72efea41441 100644 --- a/lib/unhandled-rejection.spec.js +++ b/lib/unhandled-rejection.spec.js @@ -1,5 +1,7 @@ -'use strict'; +'use strict' // Cause unhandled promise rejections to fail unit tests, and print with stack // traces. -process.on('unhandledRejection', error => { throw error; }); +process.on('unhandledRejection', error => { + throw error +}) diff --git a/lib/version.js b/lib/version.js index 1c83e068e13aeb05b8f3700bebe0324b65b80547..65f303b27f97792c6eceb72cadbe3fe7e0a13a67 100644 --- a/lib/version.js +++ b/lib/version.js @@ -5,56 +5,61 @@ * * For utilities specific to PHP version ranges, see php-version.js. */ -'use strict'; +'use strict' -const semver = require('semver'); +const semver = require('semver') // Given a list of versions (as strings), return the latest version. // Return undefined if no version could be found. function latest(versions, { pre = false } = {}) { - let version = ''; - let origVersions = versions; + let version = '' + let origVersions = versions // return all results that are likely semver compatible versions versions = origVersions.filter(function(version) { - return (/\d+\.\d+/).test(version); - }); + return /\d+\.\d+/.test(version) + }) // If no semver versions then look for single numbered versions - if (!versions.length){ + if (!versions.length) { versions = origVersions.filter(function(version) { - return (/\d+/).test(version); - }); + return /\d+/.test(version) + }) } - if (!pre){ + if (!pre) { // remove pre-releases from array versions = versions.filter(function(version) { - return !(/\d+-\w+/).test(version); - }); + return !/\d+-\w+/.test(version) + }) } try { version = versions.sort((a, b) => { // coerce to string then lowercase otherwise alpha > RC - return semver.rcompare((''+a).toLowerCase(), (''+b).toLowerCase(), /* loose */ true); - })[0]; - } catch(e) { - version = latestDottedVersion(versions); + return semver.rcompare( + ('' + a).toLowerCase(), + ('' + b).toLowerCase(), + /* loose */ true + ) + })[0] + } catch (e) { + version = latestDottedVersion(versions) } if (version === undefined || version === null) { - origVersions = origVersions.sort(); - version = origVersions[origVersions.length - 1]; + origVersions = origVersions.sort() + version = origVersions[origVersions.length - 1] } - return version; + return version } function listCompare(a, b) { - const alen = a.length, blen = b.length; + const alen = a.length, + blen = b.length for (let i = 0; i < alen; i++) { if (a[i] < b[i]) { - return -1; + return -1 } else if (a[i] > b[i]) { - return 1; + return 1 } } - return alen - blen; + return alen - blen } // === Private helper functions === @@ -62,77 +67,86 @@ function listCompare(a, b) { // Take a list of string versions. // Return the latest, or undefined, if there are none. function latestDottedVersion(versions) { - const len = versions.length; - if (len === 0) { return; } - let version = versions[0]; + const len = versions.length + if (len === 0) { + return + } + let version = versions[0] for (let i = 1; i < len; i++) { if (compareDottedVersion(version, versions[i]) < 0) { - version = versions[i]; + version = versions[i] } } - return version; + return version } // Take string versions. // -1 if v1 < v2, 1 if v1 > v2, 0 otherwise. function compareDottedVersion(v1, v2) { - const parts1 = /([0-9.]+)(.*)$/.exec(v1); - const parts2 = /([0-9.]+)(.*)$/.exec(v2); + const parts1 = /([0-9.]+)(.*)$/.exec(v1) + const parts2 = /([0-9.]+)(.*)$/.exec(v2) if (parts1 != null && parts2 != null) { - const numbers1 = parts1[1]; - const numbers2 = parts2[1]; - const distinguisher1 = parts1[2]; - const distinguisher2 = parts2[2]; - const numlist1 = numbers1.split('.').map(function(e) { return +e; }); - const numlist2 = numbers2.split('.').map(function(e) { return +e; }); - const cmp = listCompare(numlist1, numlist2); + const numbers1 = parts1[1] + const numbers2 = parts2[1] + const distinguisher1 = parts1[2] + const distinguisher2 = parts2[2] + const numlist1 = numbers1.split('.').map(function(e) { + return +e + }) + const numlist2 = numbers2.split('.').map(function(e) { + return +e + }) + const cmp = listCompare(numlist1, numlist2) if (cmp !== 0) { - return cmp; + return cmp } else { - return distinguisher1 < distinguisher2? -1: - distinguisher1 > distinguisher2? 1: 0; + return distinguisher1 < distinguisher2 + ? -1 + : distinguisher1 > distinguisher2 + ? 1 + : 0 } } - return v1 < v2? -1: v1 > v2? 1: 0; + return v1 < v2 ? -1 : v1 > v2 ? 1 : 0 } // Slice the specified number of dotted parts from the given semver version. // e.g. slice('2.4.7', 'minor') -> '2.4' function slice(v, releaseType) { - if (! semver.valid(v, /* loose */ true)) { - return null; + if (!semver.valid(v, /* loose */ true)) { + return null } - const major = semver.major(v, /* loose */ true); - const minor = semver.minor(v, /* loose */ true); - const patch = semver.patch(v, /* loose */ true); - const prerelease = semver.prerelease(v, /* loose */ true); + const major = semver.major(v, /* loose */ true) + const minor = semver.minor(v, /* loose */ true) + const patch = semver.patch(v, /* loose */ true) + const prerelease = semver.prerelease(v, /* loose */ true) const dottedParts = { major: [major], minor: [major, minor], patch: [major, minor, patch], - }[releaseType]; + }[releaseType] if (dottedParts === undefined) { - throw Error(`Unknown releaseType: ${releaseType}`); + throw Error(`Unknown releaseType: ${releaseType}`) } - const dotted = dottedParts.join('.'); + const dotted = dottedParts.join('.') if (prerelease) { - return `${dotted}-${prerelease.join('.')}`; + return `${dotted}-${prerelease.join('.')}` } else { - return dotted; + return dotted } } function minor(v) { - return slice(v, 'minor'); + return slice(v, 'minor') } function rangeStart(v) { - const range = new semver.Range(v, /* loose */ true); - return range.set[0][0].semver.version; + const range = new semver.Range(v, /* loose */ true) + return range.set[0][0].semver.version } module.exports = { @@ -141,4 +155,4 @@ module.exports = { slice, minor, rangeStart, -}; +} diff --git a/lib/version.spec.js b/lib/version.spec.js index 831a9852ab4533749b8123ebc07f657ce7956536..970714b89ed8dac856c7487fa8bd6937fb9eb518 100644 --- a/lib/version.spec.js +++ b/lib/version.spec.js @@ -1,67 +1,122 @@ -'use strict'; +'use strict' -const { test, given } = require('sazerac'); -const { latest, slice, rangeStart } = require('./version'); -const includePre = true; +const { test, given } = require('sazerac') +const { latest, slice, rangeStart } = require('./version') +const includePre = true -describe('Version helpers', function () { +describe('Version helpers', function() { test(latest, () => { // semver-compatible versions. - given(['1.0.0', '1.0.2', '1.0.1']).expect('1.0.2'); - given(['1.0.0', '2.0.0', '3.0.0']).expect('3.0.0'); - given(['0.0.1', '0.0.10', '0.0.2', '0.0.20']).expect('0.0.20'); + given(['1.0.0', '1.0.2', '1.0.1']).expect('1.0.2') + given(['1.0.0', '2.0.0', '3.0.0']).expect('3.0.0') + given(['0.0.1', '0.0.10', '0.0.2', '0.0.20']).expect('0.0.20') // "not-quite-valid" semver versions - given(['1.0.00', '1.0.02', '1.0.01']).expect('1.0.02'); - given(['1.0.05', '2.0.05', '3.0.05']).expect('3.0.05'); - given(['0.0.01', '0.0.010', '0.0.02', '0.0.020']).expect('0.0.020'); + given(['1.0.00', '1.0.02', '1.0.01']).expect('1.0.02') + given(['1.0.05', '2.0.05', '3.0.05']).expect('3.0.05') + given(['0.0.01', '0.0.010', '0.0.02', '0.0.020']).expect('0.0.020') // Mixed style versions. - include pre-releases - given(['1.0.0', 'v1.0.2', 'r1.0.1', 'release-2.0.0', 'v1.0.1-alpha.1'], { pre: includePre }).expect('release-2.0.0'); - given(['1.0.0', 'v2.0.0', 'r1.0.1', 'release-1.0.3', 'v1.0.1-alpha.1'], { pre: includePre }).expect('v2.0.0'); - given(['2.0.0', 'v1.0.3', 'r1.0.1', 'release-1.0.3', 'v1.0.1-alpha.1'], { pre: includePre }).expect('2.0.0'); - given(['1.0.0', 'v1.0.2', 'r2.0.0', 'release-1.0.3', 'v1.0.1-alpha.1'], { pre: includePre }).expect('r2.0.0'); - given(['1.0.0', 'v1.0.2', 'r2.0.0', 'release-1.0.3', 'v2.0.1-alpha.1'], { pre: includePre }).expect('v2.0.1-alpha.1'); + given(['1.0.0', 'v1.0.2', 'r1.0.1', 'release-2.0.0', 'v1.0.1-alpha.1'], { + pre: includePre, + }).expect('release-2.0.0') + given(['1.0.0', 'v2.0.0', 'r1.0.1', 'release-1.0.3', 'v1.0.1-alpha.1'], { + pre: includePre, + }).expect('v2.0.0') + given(['2.0.0', 'v1.0.3', 'r1.0.1', 'release-1.0.3', 'v1.0.1-alpha.1'], { + pre: includePre, + }).expect('2.0.0') + given(['1.0.0', 'v1.0.2', 'r2.0.0', 'release-1.0.3', 'v1.0.1-alpha.1'], { + pre: includePre, + }).expect('r2.0.0') + given(['1.0.0', 'v1.0.2', 'r2.0.0', 'release-1.0.3', 'v2.0.1-alpha.1'], { + pre: includePre, + }).expect('v2.0.1-alpha.1') // Versions with 'v' prefix. - given(['v1.0.0', 'v1.0.2', 'v1.0.1']).expect('v1.0.2'); - given(['v1.0.0', 'v3.0.0', 'v2.0.0']).expect('v3.0.0'); + given(['v1.0.0', 'v1.0.2', 'v1.0.1']).expect('v1.0.2') + given(['v1.0.0', 'v3.0.0', 'v2.0.0']).expect('v3.0.0') // Simple (2 number) versions. - given(['0.1', '0.3', '0.2']).expect('0.3'); - given(['0.1', '0.5', '0.12', '0.21']).expect('0.21'); - given(['1.0', '2.0', '3.0']).expect('3.0'); + given(['0.1', '0.3', '0.2']).expect('0.3') + given(['0.1', '0.5', '0.12', '0.21']).expect('0.21') + given(['1.0', '2.0', '3.0']).expect('3.0') // Simple (one-number) versions - given(['2', '10', '1']).expect('10'); + given(['2', '10', '1']).expect('10') // Include pre-releases - given(['v1.0.1-alpha.2', 'v1.0.1-alpha.1', 'v1.0.1-beta.3', 'v1.0.1-beta.1', 'v1.0.1-RC.1', 'v1.0.1-RC.2', 'v1.0.0'], { pre: includePre }).expect('v1.0.1-RC.2'); - given(['v1.0.1-alpha.2', 'v1.0.1-alpha.1', 'v1.0.1-beta.3', 'v1.0.1-beta.1', 'v1.0.1-RC.1', 'v1.0.1-RC.2','v1.0.1'], { pre: includePre }).expect('v1.0.1'); + given( + [ + 'v1.0.1-alpha.2', + 'v1.0.1-alpha.1', + 'v1.0.1-beta.3', + 'v1.0.1-beta.1', + 'v1.0.1-RC.1', + 'v1.0.1-RC.2', + 'v1.0.0', + ], + { pre: includePre } + ).expect('v1.0.1-RC.2') + given( + [ + 'v1.0.1-alpha.2', + 'v1.0.1-alpha.1', + 'v1.0.1-beta.3', + 'v1.0.1-beta.1', + 'v1.0.1-RC.1', + 'v1.0.1-RC.2', + 'v1.0.1', + ], + { pre: includePre } + ).expect('v1.0.1') // Exclude pre-releases - given(['v1.0.1-alpha.2', 'v1.0.1-alpha.1', 'v1.0.1-beta.3', 'v1.0.1-beta.1', 'v1.0.1-RC.1', 'v1.0.1-RC.2', 'v1.0.0']).expect('v1.0.0'); - given(['v1.0.1-alpha.2', 'v1.0.1-alpha.1', 'v1.0.1-beta.3', 'v1.0.1-beta.1', 'v1.0.1-RC.1', 'v1.0.1-RC.2','v1.0.1']).expect('v1.0.1'); + given([ + 'v1.0.1-alpha.2', + 'v1.0.1-alpha.1', + 'v1.0.1-beta.3', + 'v1.0.1-beta.1', + 'v1.0.1-RC.1', + 'v1.0.1-RC.2', + 'v1.0.0', + ]).expect('v1.0.0') + given([ + 'v1.0.1-alpha.2', + 'v1.0.1-alpha.1', + 'v1.0.1-beta.3', + 'v1.0.1-beta.1', + 'v1.0.1-RC.1', + 'v1.0.1-RC.2', + 'v1.0.1', + ]).expect('v1.0.1') // Versions with 'release-' prefix - given(['release-1.0.0', 'release-1.0.2', 'release-1.0.20', 'release-1.0.3']).expect('release-1.0.20'); + given([ + 'release-1.0.0', + 'release-1.0.2', + 'release-1.0.20', + 'release-1.0.3', + ]).expect('release-1.0.20') // Semver mixed with non semver versions - given(['1.0.0', '1.0.2', '1.1', '1.0', 'notaversion2', '12bcde4']).expect('1.1'); - }); + given(['1.0.0', '1.0.2', '1.1', '1.0', 'notaversion2', '12bcde4']).expect( + '1.1' + ) + }) test(slice, () => { - given('2.4.7', 'major').expect('2'); - given('2.4.7', 'minor').expect('2.4'); - given('2.4.7', 'patch').expect('2.4.7'); - given('02.4.7', 'major').expect('2'); - given('2.04.7', 'minor').expect('2.4'); - given('2.4.07', 'patch').expect('2.4.7'); - given('2.4.7-alpha.1', 'major').expect('2-alpha.1'); - given('2.4.7-alpha.1', 'minor').expect('2.4-alpha.1'); - given('2.4.7-alpha.1', 'patch').expect('2.4.7-alpha.1'); - }); + given('2.4.7', 'major').expect('2') + given('2.4.7', 'minor').expect('2.4') + given('2.4.7', 'patch').expect('2.4.7') + given('02.4.7', 'major').expect('2') + given('2.04.7', 'minor').expect('2.4') + given('2.4.07', 'patch').expect('2.4.7') + given('2.4.7-alpha.1', 'major').expect('2-alpha.1') + given('2.4.7-alpha.1', 'minor').expect('2.4-alpha.1') + given('2.4.7-alpha.1', 'patch').expect('2.4.7-alpha.1') + }) test(rangeStart, () => { - given('^2.4.7').expect('2.4.7'); - }); -}); + given('^2.4.7').expect('2.4.7') + }) +}) diff --git a/lib/vscode-badge-helpers.js b/lib/vscode-badge-helpers.js index 9d5180fad5a6840086936436f80fce480294e153..3353cbbf535ee51f7d517b071b6daa4ffec289e5 100644 --- a/lib/vscode-badge-helpers.js +++ b/lib/vscode-badge-helpers.js @@ -1,39 +1,41 @@ -'use strict'; +'use strict' //To generate API request Options for VS Code marketplace function getVscodeApiReqOptions(packageName) { return { method: 'POST', - url: 'https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery/', - headers: - { - 'accept': 'application/json;api-version=3.0-preview.1', - 'content-type': 'application/json' + url: + 'https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery/', + headers: { + accept: 'application/json;api-version=3.0-preview.1', + 'content-type': 'application/json', }, - body: - { - filters: [{ - criteria: [ - { filterType: 7, value: packageName }] - }], - flags: 914 + body: { + filters: [ + { + criteria: [{ filterType: 7, value: packageName }], + }, + ], + flags: 914, }, - json: true - }; + json: true, + } } //To extract Statistics (Install/Rating/RatingCount) from respose object for vscode marketplace function getVscodeStatistic(data, statisticName) { - const statistics = data.results[0].extensions[0].statistics; + const statistics = data.results[0].extensions[0].statistics try { - const statistic = statistics.find(x => x.statisticName.toLowerCase() === statisticName.toLowerCase()); - return statistic.value; + const statistic = statistics.find( + x => x.statisticName.toLowerCase() === statisticName.toLowerCase() + ) + return statistic.value } catch (err) { - return 0; //In case required statistic is not found means ZERO. + return 0 //In case required statistic is not found means ZERO. } } module.exports = { getVscodeApiReqOptions, - getVscodeStatistic -}; + getVscodeStatistic, +} diff --git a/pages/index.js b/pages/index.js index a23261aa3baf7ae87e262c293063b79ca4bc6738..453672773eabe9ea20690c5f5d529c61bef1bbba 100644 --- a/pages/index.js +++ b/pages/index.js @@ -1,26 +1,23 @@ -import React from 'react'; -import { HashRouter, StaticRouter, Route } from "react-router-dom"; -import ExamplesPage from '../frontend/components/examples-page'; - +import React from 'react' +import { HashRouter, StaticRouter, Route } from 'react-router-dom' +import ExamplesPage from '../frontend/components/examples-page' export default class Router extends React.Component { - render() { const router = ( <div> <Route path="/" exact component={ExamplesPage} /> <Route path="/examples/:id" component={ExamplesPage} /> </div> - ); + ) if (typeof window !== 'undefined') { // browser - return (<HashRouter>{ router }</HashRouter>); + return <HashRouter>{router}</HashRouter> } else { // server-side rendering - const context = {}; - return (<StaticRouter context={context}>{ router }</StaticRouter>); + const context = {} + return <StaticRouter context={context}>{router}</StaticRouter> } } - } diff --git a/services/amo/amo.tester.js b/services/amo/amo.tester.js index 33ba7df665dcb4806bd3441a913f839adeb402df..b789210a01782ea69da6c09be9d2aad5adc37e8a 100644 --- a/services/amo/amo.tester.js +++ b/services/amo/amo.tester.js @@ -1,57 +1,65 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') const { isMetric, isStarRating, isVPlusDottedVersionAtLeastOne, -} = require('../test-validators'); +} = require('../test-validators') -const t = new ServiceTester({ id: 'amo', title: 'Mozilla Addons' }); -module.exports = t; +const t = new ServiceTester({ id: 'amo', title: 'Mozilla Addons' }) +module.exports = t t.create('Downloads') .get('/d/IndieGala-Helper.json') - .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })); + .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })) t.create('Version') .get('/v/IndieGala-Helper.json') - .expectJSONTypes(Joi.object().keys({ - name: 'mozilla add-on', - value: isVPlusDottedVersionAtLeastOne - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'mozilla add-on', + value: isVPlusDottedVersionAtLeastOne, + }) + ) t.create('Version - Custom label') .get('/v/IndieGala-Helper.json?label=IndieGala Helper') - .expectJSONTypes(Joi.object().keys({ - name: 'IndieGala Helper', - value: isVPlusDottedVersionAtLeastOne - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'IndieGala Helper', + value: isVPlusDottedVersionAtLeastOne, + }) + ) t.create('Users') .get('/users/IndieGala-Helper.json') - .expectJSONTypes(Joi.object().keys({ - name: 'users', - value: Joi.string().regex(/^\d+$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'users', + value: Joi.string().regex(/^\d+$/), + }) + ) t.create('Rating') .get('/rating/IndieGala-Helper.json') - .expectJSONTypes(Joi.object().keys({ - name: 'rating', - value: Joi.string().regex(/^\d\/\d$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'rating', + value: Joi.string().regex(/^\d\/\d$/), + }) + ) t.create('Stars') .get('/stars/IndieGala-Helper.json') - .expectJSONTypes(Joi.object().keys({ name: 'stars', value: isStarRating })); + .expectJSONTypes(Joi.object().keys({ name: 'stars', value: isStarRating })) t.create('Invalid addon') .get('/d/invalid-name-of-addon.json') - .expectJSON({ name: 'mozilla add-on', value: 'invalid' }); + .expectJSON({ name: 'mozilla add-on', value: 'invalid' }) t.create('No connection') .get('/v/IndieGala-Helper.json') .networkOff() - .expectJSON({ name: 'mozilla add-on', value: 'inaccessible' }); + .expectJSON({ name: 'mozilla add-on', value: 'inaccessible' }) diff --git a/services/ansible/ansible.tester.js b/services/ansible/ansible.tester.js index bced90e3b06917fd3dcf96e7f6da403846219721..a8467d0286fc7224626482c588f0456ecb349306 100644 --- a/services/ansible/ansible.tester.js +++ b/services/ansible/ansible.tester.js @@ -1,33 +1,36 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isMetric } = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isMetric } = require('../test-validators') -const t = new ServiceTester({ id: 'ansible', title: 'Ansible Galaxy' }); -module.exports = t; +const t = new ServiceTester({ id: 'ansible', title: 'Ansible Galaxy' }) +module.exports = t t.create('ansible role name') - .get('/role/14542.json') - .expectJSON({ name: 'role', value: 'openwisp.openwisp2' }); + .get('/role/14542.json') + .expectJSON({ name: 'role', value: 'openwisp.openwisp2' }) t.create('ansible role download counts') - .get('/role/d/14542.json') - .expectJSONTypes(Joi.object().keys({ name: 'role downloads', value: isMetric })); + .get('/role/d/14542.json') + .expectJSONTypes( + Joi.object().keys({ name: 'role downloads', value: isMetric }) + ) t.create('unkown role') - .get('/role/000.json') - .expectJSON({ name: 'role', value: 'not found' }); + .get('/role/000.json') + .expectJSON({ name: 'role', value: 'not found' }) t.create('connection error') .get('/role/14542.json') .networkOff() - .expectJSON({ name: 'role', value: 'errored' }); + .expectJSON({ name: 'role', value: 'errored' }) t.create('no response data') .get('/role/14542.json') - .intercept(nock => nock('https://galaxy.ansible.com') - .get('/api/v1/roles/14542/') - .reply(200) + .intercept(nock => + nock('https://galaxy.ansible.com') + .get('/api/v1/roles/14542/') + .reply(200) ) - .expectJSON({ name: 'role', value: 'not found' }); + .expectJSON({ name: 'role', value: 'not found' }) diff --git a/services/apm/apm.service.js b/services/apm/apm.service.js index 9c3d36a01fdc0eeba17d6e84c4fbcdd99220f73b..3f4ccbea254415d1a63f5662879242a1e260733b 100644 --- a/services/apm/apm.service.js +++ b/services/apm/apm.service.js @@ -1,137 +1,130 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const { BaseJsonService } = require('../base'); -const { InvalidResponse } = require('../errors'); -const { version: versionColor } = require('../../lib/color-formatters'); -const { - metric, - addv -} = require('../../lib/text-formatters'); +const Joi = require('joi') +const { BaseJsonService } = require('../base') +const { InvalidResponse } = require('../errors') +const { version: versionColor } = require('../../lib/color-formatters') +const { metric, addv } = require('../../lib/text-formatters') class BaseAPMService extends BaseJsonService { - async fetch(repo) { return this._requestJson({ schema: Joi.object(), url: `https://atom.io/api/packages/${repo}`, - notFoundMessage: 'package not found' - }); + notFoundMessage: 'package not found', + }) } static get defaultBadgeData() { - return { label: 'apm' }; + return { label: 'apm' } } - } class APMDownloads extends BaseAPMService { - async handle({repo}) { - const json = await this.fetch(repo); + async handle({ repo }) { + const json = await this.fetch(repo) - const downloads = json.downloads; - return {message: metric(downloads), color: 'green'}; + const downloads = json.downloads + return { message: metric(downloads), color: 'green' } } static get category() { - return 'downloads'; + return 'downloads' } static get defaultBadgeData() { - return { label: 'downloads' }; + return { label: 'downloads' } } static get url() { return { base: 'apm/dm', format: '(.+)', - capture: ['repo'] - }; + capture: ['repo'], + } } static get examples() { return [ { previewUrl: 'dm/vim-mode', - keywords: [ - 'atom' - ] + keywords: ['atom'], }, - ]; + ] } -}; +} class APMVersion extends BaseAPMService { - async handle({repo}) { - const json = await this.fetch(repo); + async handle({ repo }) { + const json = await this.fetch(repo) - const version = json.releases.latest; + const version = json.releases.latest if (!version) - throw new InvalidResponse({ underlyingError: new Error('version is invalid') }); - return {message: addv(version), color: versionColor(version)}; + throw new InvalidResponse({ + underlyingError: new Error('version is invalid'), + }) + return { message: addv(version), color: versionColor(version) } } static get category() { - return 'version'; + return 'version' } static get url() { return { base: 'apm/v', format: '(.+)', - capture: ['repo'] - }; + capture: ['repo'], + } } static get examples() { return [ { previewUrl: 'v/vim-mode', - keywords: [ - 'atom' - ] + keywords: ['atom'], }, - ]; + ] } -}; +} class APMLicense extends BaseAPMService { - async handle({repo}) { - const json = await this.fetch(repo); + async handle({ repo }) { + const json = await this.fetch(repo) - const license = json.metadata.license; + const license = json.metadata.license if (!license) - throw new InvalidResponse({ underlyingError: new Error('licence is invalid') }); - return {message: license, color: 'blue'}; + throw new InvalidResponse({ + underlyingError: new Error('licence is invalid'), + }) + return { message: license, color: 'blue' } } static get defaultBadgeData() { - return { label: 'license' }; + return { label: 'license' } } static get category() { - return 'license'; + return 'license' } static get url() { return { base: 'apm/l', format: '(.+)', - capture: ['repo'] - }; + capture: ['repo'], + } } static get examples() { return [ { previewUrl: 'l/vim-mode', - keywords: [ - 'atom' - ] + keywords: ['atom'], }, - ]; + ] } -}; +} module.exports = { APMDownloads, diff --git a/services/apm/apm.tester.js b/services/apm/apm.tester.js index 048a6dc2672246446c1af965029fceb00dd36538..78cb27c09e1181f8de7579f5cbf4e128306f8161 100644 --- a/services/apm/apm.tester.js +++ b/services/apm/apm.tester.js @@ -1,70 +1,66 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const t = new ServiceTester({ id: 'apm', title: 'Atom Package Manager' }); -const { invalidJSON } = require('../response-fixtures'); -const { - isMetric, - isVPlusTripleDottedVersion -} = require('../test-validators'); -module.exports = t; +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const t = new ServiceTester({ id: 'apm', title: 'Atom Package Manager' }) +const { invalidJSON } = require('../response-fixtures') +const { isMetric, isVPlusTripleDottedVersion } = require('../test-validators') +module.exports = t t.create('Downloads') .get('/dm/vim-mode.json') - .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })); + .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })) t.create('Version') .get('/v/vim-mode.json') - .expectJSONTypes(Joi.object().keys({ name: 'apm', value: isVPlusTripleDottedVersion })); + .expectJSONTypes( + Joi.object().keys({ name: 'apm', value: isVPlusTripleDottedVersion }) + ) t.create('License') .get('/l/vim-mode.json') - .expectJSON({ name: 'license', value: 'MIT' }); + .expectJSON({ name: 'license', value: 'MIT' }) t.create('Downloads | Package not found') .get('/dm/notapackage.json') - .expectJSON({ name: 'downloads', value: 'package not found' }); + .expectJSON({ name: 'downloads', value: 'package not found' }) t.create('Version | Package not found') .get('/v/notapackage.json') - .expectJSON({ name: 'apm', value: 'package not found' }); + .expectJSON({ name: 'apm', value: 'package not found' }) t.create('License | Package not found') .get('/l/notapackage.json') - .expectJSON({ name: 'license', value: 'package not found' }); + .expectJSON({ name: 'license', value: 'package not found' }) t.create('Connection error') .get('/v/vim-mode.json') .networkOff() - .expectJSON({ name: 'apm', value: 'inaccessible' }); + .expectJSON({ name: 'apm', value: 'inaccessible' }) t.create('Invalid version') .get('/dm/vim-mode.json') - .intercept(nock => nock('https://atom.io') - .get('/api/packages/vim-mode') - .reply([ - 200, - '{"releases":{}}' - ]) + .intercept(nock => + nock('https://atom.io') + .get('/api/packages/vim-mode') + .reply([200, '{"releases":{}}']) ) - .expectJSON({name: 'downloads', value: 'unparseable json response'}); + .expectJSON({ name: 'downloads', value: 'unparseable json response' }) t.create('Invalid License') .get('/l/vim-mode.json') - .intercept(nock => nock('https://atom.io') - .get('/api/packages/vim-mode') - .reply([ - 200, - '{"metadata":{}}' - ]) + .intercept(nock => + nock('https://atom.io') + .get('/api/packages/vim-mode') + .reply([200, '{"metadata":{}}']) ) - .expectJSON({name: 'license', value: 'unparseable json response'}); + .expectJSON({ name: 'license', value: 'unparseable json response' }) t.create('Unexpected response') .get('/dm/vim-mode.json') - .intercept(nock => nock('https://atom.io') - .get('/api/packages/vim-mode') - .reply(invalidJSON) + .intercept(nock => + nock('https://atom.io') + .get('/api/packages/vim-mode') + .reply(invalidJSON) ) - .expectJSON({name: 'downloads', value: 'unparseable json response'}); + .expectJSON({ name: 'downloads', value: 'unparseable json response' }) diff --git a/services/appveyor/appveyor.service.js b/services/appveyor/appveyor.service.js index 820231c91c3bfecfd3dd8c8852360ccb11b1f706..220cc3d2b280ea03b56e056a580666a47fe7d17b 100644 --- a/services/appveyor/appveyor.service.js +++ b/services/appveyor/appveyor.service.js @@ -1,40 +1,42 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const { BaseJsonService } = require('../base'); +const Joi = require('joi') +const { BaseJsonService } = require('../base') module.exports = class AppVeyor extends BaseJsonService { - async handle({repo, branch}) { - let url = `https://ci.appveyor.com/api/projects/${repo}`; + async handle({ repo, branch }) { + let url = `https://ci.appveyor.com/api/projects/${repo}` if (branch != null) { - url += `/branch/${branch}`; + url += `/branch/${branch}` } - const { build: { status } } = await this._requestJson({ + const { + build: { status }, + } = await this._requestJson({ schema: Joi.object(), url, notFoundMessage: 'project not found or access denied', - }); + }) if (status === 'success') { - return {message: 'passing', color: 'brightgreen'}; + return { message: 'passing', color: 'brightgreen' } } else if (status !== 'running' && status !== 'queued') { - return {message: 'failing', color: 'red'}; + return { message: 'failing', color: 'red' } } else { - return {message: status}; + return { message: status } } } // Metadata static get category() { - return 'build'; + return 'build' } static get url() { return { base: 'appveyor/ci', format: '([^/]+/[^/]+)(?:/(.+))?', - capture: ['repo', 'branch'] - }; + capture: ['repo', 'branch'], + } } static get examples() { @@ -46,6 +48,6 @@ module.exports = class AppVeyor extends BaseJsonService { title: `${this.name} branch`, previewUrl: 'gruntjs/grunt/master', }, - ]; + ] } -}; +} diff --git a/services/appveyor/appveyor.tester.js b/services/appveyor/appveyor.tester.js index 5e2299e14d385acaaa1606c13f9069a6bc6860cb..7be77fd216838093dc71362fa418cf44f47ff69b 100644 --- a/services/appveyor/appveyor.tester.js +++ b/services/appveyor/appveyor.tester.js @@ -1,46 +1,51 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') -const { isBuildStatus } = require('../test-validators'); -const isAppveyorTestTotals = - Joi.string().regex(/^(?:[0-9]+ (?:passed|skipped|failed)(?:, )?)+$/); +const { isBuildStatus } = require('../test-validators') +const isAppveyorTestTotals = Joi.string().regex( + /^(?:[0-9]+ (?:passed|skipped|failed)(?:, )?)+$/ +) -const t = new ServiceTester({ id: 'appveyor', title: 'AppVeyor' }); -module.exports = t; +const t = new ServiceTester({ id: 'appveyor', title: 'AppVeyor' }) +module.exports = t // Test AppVeyor build status badge t.create('CI build status') .get('/ci/gruntjs/grunt.json') - .expectJSONTypes(Joi.object().keys({ name: 'build', value: isBuildStatus })); + .expectJSONTypes(Joi.object().keys({ name: 'build', value: isBuildStatus })) // Test AppVeyor branch build status badge t.create('CI build status on master branch') .get('/ci/gruntjs/grunt/master.json') - .expectJSONTypes(Joi.object().keys({ name: 'build', value: isBuildStatus })); + .expectJSONTypes(Joi.object().keys({ name: 'build', value: isBuildStatus })) // Test AppVeyor build status badge on a non-existing project t.create('CI 404') -.get('/ci/somerandomproject/thatdoesntexits.json') - .expectJSON({ name: 'build', value: 'project not found or access denied' }); + .get('/ci/somerandomproject/thatdoesntexits.json') + .expectJSON({ name: 'build', value: 'project not found or access denied' }) t.create('CI (connection error)') .get('/ci/this-one/is-not-real-either.json') .networkOff() - .expectJSON({ name: 'build', value: 'inaccessible' }); + .expectJSON({ name: 'build', value: 'inaccessible' }) // Test AppVeyor tests status badge t.create('tests status') .get('/tests/NZSmartie/coap-net-iu0to.json') - .expectJSONTypes(Joi.object().keys({ name: 'tests', value: isAppveyorTestTotals })); + .expectJSONTypes( + Joi.object().keys({ name: 'tests', value: isAppveyorTestTotals }) + ) // Test AppVeyor branch tests status badge t.create('tests status on master branch') .get('/tests/NZSmartie/coap-net-iu0to/master.json') - .expectJSONTypes(Joi.object().keys({ name: 'tests', value: isAppveyorTestTotals })); + .expectJSONTypes( + Joi.object().keys({ name: 'tests', value: isAppveyorTestTotals }) + ) // Test AppVeyor tests status badge for a non-existing project t.create('tests 404') .get('/tests/somerandomproject/thatdoesntexits.json') - .expectJSON({ name: 'tests', value: 'project not found or access denied' }); + .expectJSON({ name: 'tests', value: 'project not found or access denied' }) diff --git a/services/aur/aur.tester.js b/services/aur/aur.tester.js index 5e7e3a0647ec1852ef82e43e41be5976771dd035..4c8d109f97d67132f13c32c0606f9ecb4eb4add2 100644 --- a/services/aur/aur.tester.js +++ b/services/aur/aur.tester.js @@ -1,121 +1,131 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') const { isVPlusDottedVersionNClausesWithOptionalSuffix, -} = require('../test-validators'); -const { invalidJSON } = require('../response-fixtures'); - -const t = new ServiceTester({ id: 'aur', title: 'Arch Linux AUR' }); -module.exports = t; +} = require('../test-validators') +const { invalidJSON } = require('../response-fixtures') +const t = new ServiceTester({ id: 'aur', title: 'Arch Linux AUR' }) +module.exports = t // version tests t.create('version (valid)') .get('/version/yaourt.json?style=_shields_test') - .expectJSONTypes(Joi.object().keys({ - name: 'AUR', - value: isVPlusDottedVersionNClausesWithOptionalSuffix, - colorB: '#007ec6', - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'AUR', + value: isVPlusDottedVersionNClausesWithOptionalSuffix, + colorB: '#007ec6', + }) + ) t.create('version (valid, out of date)') .get('/version/gog-gemini-rue.json?style=_shields_test') - .expectJSONTypes(Joi.object().keys({ - name: 'AUR', - value: isVPlusDottedVersionNClausesWithOptionalSuffix, - colorB: '#fe7d37', - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'AUR', + value: isVPlusDottedVersionNClausesWithOptionalSuffix, + colorB: '#fe7d37', + }) + ) t.create('version (not found)') .get('/version/not-a-package.json') - .expectJSON({name: 'AUR', value: 'not found'}); + .expectJSON({ name: 'AUR', value: 'not found' }) t.create('version (connection error)') .get('/version/yaourt.json') .networkOff() - .expectJSON({name: 'AUR', value: 'inaccessible'}); + .expectJSON({ name: 'AUR', value: 'inaccessible' }) t.create('version (unexpected response)') .get('/version/yaourt.json') - .intercept(nock => nock('https://aur.archlinux.org') - .get('/rpc.php?type=info&arg=yaourt') - .reply(invalidJSON) + .intercept(nock => + nock('https://aur.archlinux.org') + .get('/rpc.php?type=info&arg=yaourt') + .reply(invalidJSON) ) - .expectJSON({name: 'AUR', value: 'invalid'}); + .expectJSON({ name: 'AUR', value: 'invalid' }) t.create('version (error response)') .get('/version/yaourt.json') - .intercept(nock => nock('https://aur.archlinux.org') - .get('/rpc.php?type=info&arg=yaourt') - .reply(500, '{"error":"oh noes!!"}') + .intercept(nock => + nock('https://aur.archlinux.org') + .get('/rpc.php?type=info&arg=yaourt') + .reply(500, '{"error":"oh noes!!"}') ) - .expectJSON({name: 'AUR', value: 'invalid'}); + .expectJSON({ name: 'AUR', value: 'invalid' }) // votes tests t.create('votes (valid)') .get('/votes/yaourt.json') - .expectJSONTypes(Joi.object().keys({ - name: 'votes', - value: Joi.number().integer(), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'votes', + value: Joi.number().integer(), + }) + ) t.create('votes (not found)') .get('/votes/not-a-package.json') - .expectJSON({name: 'AUR', value: 'not found'}); + .expectJSON({ name: 'AUR', value: 'not found' }) t.create('votes (connection error)') .get('/votes/yaourt.json') .networkOff() - .expectJSON({name: 'AUR', value: 'inaccessible'}); + .expectJSON({ name: 'AUR', value: 'inaccessible' }) t.create('votes (unexpected response)') .get('/votes/yaourt.json') - .intercept(nock => nock('https://aur.archlinux.org') - .get('/rpc.php?type=info&arg=yaourt') - .reply(invalidJSON) + .intercept(nock => + nock('https://aur.archlinux.org') + .get('/rpc.php?type=info&arg=yaourt') + .reply(invalidJSON) ) - .expectJSON({name: 'AUR', value: 'invalid'}); + .expectJSON({ name: 'AUR', value: 'invalid' }) t.create('votes (error response)') .get('/votes/yaourt.json') - .intercept(nock => nock('https://aur.archlinux.org') - .get('/rpc.php?type=info&arg=yaourt') - .reply(500, '{"error":"oh noes!!"}') + .intercept(nock => + nock('https://aur.archlinux.org') + .get('/rpc.php?type=info&arg=yaourt') + .reply(500, '{"error":"oh noes!!"}') ) - .expectJSON({name: 'AUR', value: 'invalid'}); - + .expectJSON({ name: 'AUR', value: 'invalid' }) // license tests t.create('license (valid)') .get('/license/yaourt.json') - .expectJSON({name: 'license', value: 'GPL'}); + .expectJSON({ name: 'license', value: 'GPL' }) t.create('license (not found)') .get('/license/not-a-package.json') - .expectJSON({name: 'AUR', value: 'not found'}); + .expectJSON({ name: 'AUR', value: 'not found' }) t.create('license (connection error)') .get('/license/yaourt.json') .networkOff() - .expectJSON({name: 'AUR', value: 'inaccessible'}); + .expectJSON({ name: 'AUR', value: 'inaccessible' }) t.create('license (unexpected response)') .get('/license/yaourt.json') - .intercept(nock => nock('https://aur.archlinux.org') - .get('/rpc.php?type=info&arg=yaourt') - .reply(invalidJSON) + .intercept(nock => + nock('https://aur.archlinux.org') + .get('/rpc.php?type=info&arg=yaourt') + .reply(invalidJSON) ) - .expectJSON({name: 'AUR', value: 'invalid'}); + .expectJSON({ name: 'AUR', value: 'invalid' }) t.create('license (error response)') .get('/license/yaourt.json') - .intercept(nock => nock('https://aur.archlinux.org') - .get('/rpc.php?type=info&arg=yaourt') - .reply(500, '{"error":"oh noes!!"}') + .intercept(nock => + nock('https://aur.archlinux.org') + .get('/rpc.php?type=info&arg=yaourt') + .reply(500, '{"error":"oh noes!!"}') ) - .expectJSON({name: 'AUR', value: 'invalid'}); + .expectJSON({ name: 'AUR', value: 'invalid' }) diff --git a/services/base-json.spec.js b/services/base-json.spec.js index d030b7f7d08c2dd8b6e02bb7c810bb10f8b7cf56..012e718da0a73d0c0a18b6dcec6d22c97cfb4a37 100644 --- a/services/base-json.spec.js +++ b/services/base-json.spec.js @@ -1,28 +1,28 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const chai = require('chai'); -const { expect } = chai; +const Joi = require('joi') +const chai = require('chai') +const { expect } = chai -const { BaseJsonService } = require('./base'); -const { invalidJSON } = require('./response-fixtures'); +const { BaseJsonService } = require('./base') +const { invalidJSON } = require('./response-fixtures') -chai.use(require('chai-as-promised')); +chai.use(require('chai-as-promised')) class DummyJsonService extends BaseJsonService { static get category() { - return 'cat'; + return 'cat' } static get url() { return { base: 'foo', - }; + } } async handle() { - const { value } = await this._requestJson({ schema: Joi.any() }); - return { message: value }; + const { value } = await this._requestJson({ schema: Joi.any() }) + return { message: value } } } @@ -31,30 +31,30 @@ describe('BaseJsonService', () => { const sendAndCacheRequest = async () => ({ buffer: invalidJSON, res: { statusCode: 200 }, - }); + }) const serviceInstance = new DummyJsonService( { sendAndCacheRequest }, { handleInternalErrors: false } - ); + ) const serviceData = await serviceInstance.invokeHandler( { schema: Joi.any() }, {} - ); + ) expect(serviceData).to.deep.equal({ color: 'lightgray', message: 'unparseable json response', - }); - }); + }) + }) context('a schema is not provided', function() { it('throws the expected error', async function() { const serviceInstance = new DummyJsonService( {}, { handleInternalErrors: false } - ); + ) expect( serviceInstance._requestJson({ schema: undefined }) - ).to.be.rejectedWith('A Joi schema is required'); - }); - }); -}); + ).to.be.rejectedWith('A Joi schema is required') + }) + }) +}) diff --git a/services/base.js b/services/base.js index adce94feed2c75935e8d6e3363413f2ecc054548..a5450c6ea6a0da58efbd6592bd1ca8f209c69a88 100644 --- a/services/base.js +++ b/services/base.js @@ -1,34 +1,26 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const { - NotFound, - InvalidResponse, - Inaccessible, -} = require('./errors'); -const queryString = require('query-string'); +const Joi = require('joi') +const { NotFound, InvalidResponse, Inaccessible } = require('./errors') +const queryString = require('query-string') const { makeLogo, toArray, makeColor, setBadgeColor, -} = require('../lib/badge-data'); -const { - checkErrorResponse, - asJson, -} = require('../lib/error-helper'); - +} = require('../lib/badge-data') +const { checkErrorResponse, asJson } = require('../lib/error-helper') class BaseService { constructor({ sendAndCacheRequest }, { handleInternalErrors }) { - this._sendAndCacheRequest = sendAndCacheRequest; - this._handleInternalErrors = handleInternalErrors; + this._sendAndCacheRequest = sendAndCacheRequest + this._handleInternalErrors = handleInternalErrors } static render(props) { throw new Error( `render() function not implemented for ${this.constructor.name}` - ); + ) } /** @@ -37,9 +29,7 @@ class BaseService { * `this._sendAndCacheRequest`, and returns the badge data. */ async handle(namedParams, queryParams) { - throw new Error( - `Handler not implemented for ${this.constructor.name}` - ); + throw new Error(`Handler not implemented for ${this.constructor.name}`) } // Metadata @@ -49,7 +39,7 @@ class BaseService { * the badges on the main shields.io website. */ static get category() { - return 'unknown'; + return 'unknown' } /** @@ -65,7 +55,7 @@ class BaseService { * parameters will be passed to the handler. */ static get url() { - throw new Error(`URL not defined for ${this.name}`); + throw new Error(`URL not defined for ${this.name}`) } /** @@ -74,7 +64,7 @@ class BaseService { * by either the handler or by the user via URL parameters. */ static get defaultBadgeData() { - return {}; + return {} } /** @@ -83,11 +73,11 @@ class BaseService { * this service. */ static get examples() { - return []; + return [] } static _makeFullUrl(partialUrl) { - return '/' + [this.url.base, partialUrl].filter(Boolean).join('/'); + return '/' + [this.url.base, partialUrl].filter(Boolean).join('/') } /** @@ -102,13 +92,11 @@ class BaseService { return this.examples.map( ({ title, previewUrl, query, exampleUrl, documentation }) => { if (!previewUrl) { - throw Error( - `Example for ${this.name} is missing required previewUrl` - ); + throw Error(`Example for ${this.name} is missing required previewUrl`) } - const stringified = queryString.stringify(query); - const suffix = stringified ? `?${stringified}` : ''; + const stringified = queryString.stringify(query) + const suffix = stringified ? `?${stringified}` : '' return { title: title ? `${title}` : this.name, @@ -117,61 +105,67 @@ class BaseService { ? `${this._makeFullUrl(exampleUrl, query)}.svg${suffix}` : undefined, documentation, - }; + } } - ); + ) } static get _regex() { // Regular expressions treat "/" specially, so we need to escape them - const escapedPath = this.url.format.replace(/\//g, '\\/'); - const fullRegex = `^${this._makeFullUrl(escapedPath)}.(svg|png|gif|jpg|json)$`; - return new RegExp(fullRegex); + const escapedPath = this.url.format.replace(/\//g, '\\/') + const fullRegex = `^${this._makeFullUrl( + escapedPath + )}.(svg|png|gif|jpg|json)$` + return new RegExp(fullRegex) } static _namedParamsForMatch(match) { // Assume the last match is the format, and drop match[0], which is the // entire match. - const captures = match.slice(1, -1); + const captures = match.slice(1, -1) if (this.url.capture.length !== captures.length) { throw new Error( - `Service ${this.constructor.name} declares incorrect number of capture groups `+ - `(expected ${this.url.capture.length}, got ${captures.length})` - ); + `Service ${ + this.constructor.name + } declares incorrect number of capture groups ` + + `(expected ${this.url.capture.length}, got ${captures.length})` + ) } - const result = {}; + const result = {} this.url.capture.forEach((name, index) => { - result[name] = captures[index]; - }); - return result; + result[name] = captures[index] + }) + return result } async invokeHandler(namedParams, queryParams) { try { - return await this.handle(namedParams, queryParams); + return await this.handle(namedParams, queryParams) } catch (error) { if (error instanceof NotFound) { return { message: error.prettyMessage, color: 'red', - }; - } else if (error instanceof InvalidResponse || - error instanceof Inaccessible) { + } + } else if ( + error instanceof InvalidResponse || + error instanceof Inaccessible + ) { return { message: error.prettyMessage, color: 'lightgray', - }; + } } else if (this._handleInternalErrors) { - console.log(error); + console.log(error) return { label: 'shields', message: 'internal error', color: 'lightgray', - }; + } } else { - throw error; + throw error } } } @@ -185,20 +179,20 @@ class BaseService { link: overrideLink, colorA: overrideColorA, colorB: overrideColorB, - } = overrides; + } = overrides const { label: serviceLabel, message: serviceMessage, color: serviceColor, link: serviceLink, - } = serviceData; + } = serviceData const { color: defaultColor, logo: defaultLogo, label: defaultLabel, - } = this.defaultBadgeData; + } = this.defaultBadgeData const badgeData = { text: [ @@ -206,35 +200,46 @@ class BaseService { serviceMessage || 'n/a', ], template: style, - logo: makeLogo(style === 'social' ? defaultLogo : undefined, { logo: overrideLogo }), + logo: makeLogo(style === 'social' ? defaultLogo : undefined, { + logo: overrideLogo, + }), logoWidth: +overrideLogoWidth, links: toArray(overrideLink || serviceLink), colorA: makeColor(overrideColorA), - }; - const color = overrideColorB || serviceColor || defaultColor || 'lightgrey'; - setBadgeColor(badgeData, color); + } + const color = overrideColorB || serviceColor || defaultColor || 'lightgrey' + setBadgeColor(badgeData, color) - return badgeData; + return badgeData } static register(camp, handleRequest, { handleInternalErrors }) { - const ServiceClass = this; // In a static context, "this" is the class. + const ServiceClass = this // In a static context, "this" is the class. - camp.route(this._regex, handleRequest({ - queryParams: this.url.queryParams, - handler: async (queryParams, match, sendBadge, request) => { - const namedParams = this._namedParamsForMatch(match); - const serviceInstance = new ServiceClass({ - sendAndCacheRequest: request.asPromise, - }, { handleInternalErrors }); - const serviceData = await serviceInstance.invokeHandler(namedParams, queryParams); - const badgeData = this._makeBadgeData(queryParams, serviceData); + camp.route( + this._regex, + handleRequest({ + queryParams: this.url.queryParams, + handler: async (queryParams, match, sendBadge, request) => { + const namedParams = this._namedParamsForMatch(match) + const serviceInstance = new ServiceClass( + { + sendAndCacheRequest: request.asPromise, + }, + { handleInternalErrors } + ) + const serviceData = await serviceInstance.invokeHandler( + namedParams, + queryParams + ) + const badgeData = this._makeBadgeData(queryParams, serviceData) - // Assumes the final capture group is the extension - const format = match.slice(-1)[0]; - sendBadge(format, badgeData); - }, - })); + // Assumes the final capture group is the extension + const format = match.slice(-1)[0] + sendBadge(format, badgeData) + }, + }) + ) } } @@ -243,33 +248,36 @@ class BaseJsonService extends BaseService { const { error, value } = Joi.validate(json, schema, { allowUnknown: true, stripUnknown: true, - }); + }) if (error) { throw new InvalidResponse({ prettyMessage: 'invalid json response', underlyingError: error, - }); + }) } else { - return value; + return value } } async _requestJson({ schema, url, options = {}, notFoundMessage }) { - if (! schema || ! schema.isJoi) { - throw Error('A Joi schema is required'); + if (!schema || !schema.isJoi) { + throw Error('A Joi schema is required') } - return this._sendAndCacheRequest(url, - {...{ 'headers': { 'Accept': 'application/json' } }, ...options} - ).then( - checkErrorResponse.asPromise( - notFoundMessage ? { notFoundMessage: notFoundMessage } : undefined + return this._sendAndCacheRequest(url, { + ...{ headers: { Accept: 'application/json' } }, + ...options, + }) + .then( + checkErrorResponse.asPromise( + notFoundMessage ? { notFoundMessage: notFoundMessage } : undefined + ) ) - ).then(asJson) - .then(json => this.constructor._validate(json, schema)); + .then(asJson) + .then(json => this.constructor._validate(json, schema)) } } module.exports = { BaseService, BaseJsonService, -}; +} diff --git a/services/base.spec.js b/services/base.spec.js index 3ddbbc723b7a4869e3bf3383780d5c46d514b555..b553f43f6af6a8a548fc21d8d25af7092415058e 100644 --- a/services/base.spec.js +++ b/services/base.spec.js @@ -1,24 +1,26 @@ -'use strict'; +'use strict' -const { expect } = require('chai'); -const { test, given, forCases } = require('sazerac'); -const sinon = require('sinon'); +const { expect } = require('chai') +const { test, given, forCases } = require('sazerac') +const sinon = require('sinon') -const { BaseService } = require('./base'); +const { BaseService } = require('./base') -require('../lib/register-chai-plugins.spec'); +require('../lib/register-chai-plugins.spec') class DummyService extends BaseService { async handle({ namedParamA }, { queryParamA }) { - return { message: `Hello ${namedParamA}${queryParamA}` }; + return { message: `Hello ${namedParamA}${queryParamA}` } } - static get category() { return 'cat'; } + static get category() { + return 'cat' + } static get examples() { return [ { previewUrl: 'World' }, { previewUrl: 'World', query: { queryParamA: '!!!' } }, - ]; + ] } static get url() { return { @@ -26,30 +28,30 @@ class DummyService extends BaseService { format: '([^/]+)', capture: ['namedParamA'], queryParams: ['queryParamA'], - }; + } } } describe('BaseService', () => { - const defaultConfig = { handleInternalErrors: false }; + const defaultConfig = { handleInternalErrors: false } - describe('URL pattern matching', function () { - const regexExec = str => DummyService._regex.exec(str); + describe('URL pattern matching', function() { + const regexExec = str => DummyService._regex.exec(str) const getNamedParamA = str => { - const [, namedParamA] = regexExec(str); - return namedParamA; - }; + const [, namedParamA] = regexExec(str) + return namedParamA + } const namedParams = str => { - const match = regexExec(str); - return DummyService._namedParamsForMatch(match); - }; + const match = regexExec(str) + return DummyService._namedParamsForMatch(match) + } test(regexExec, () => { forCases([ given('/foo/bar.bar.bar.zip'), given('/foo/bar/bar.svg'), - ]).expect(null); - }); + ]).expect(null) + }) test(getNamedParamA, () => { forCases([ @@ -58,8 +60,8 @@ describe('BaseService', () => { given('/foo/bar.bar.bar.gif'), given('/foo/bar.bar.bar.jpg'), given('/foo/bar.bar.bar.json'), - ]).expect('bar.bar.bar'); - }); + ]).expect('bar.bar.bar') + }) test(namedParams, () => { forCases([ @@ -68,132 +70,143 @@ describe('BaseService', () => { given('/foo/bar.bar.bar.gif'), given('/foo/bar.bar.bar.jpg'), given('/foo/bar.bar.bar.json'), - ]).expect({ namedParamA: 'bar.bar.bar' }); - }); - }); + ]).expect({ namedParamA: 'bar.bar.bar' }) + }) + }) - it('Invokes the handler as expected', async function () { - const serviceInstance = new DummyService({}, defaultConfig); + it('Invokes the handler as expected', async function() { + const serviceInstance = new DummyService({}, defaultConfig) const serviceData = await serviceInstance.invokeHandler( { namedParamA: 'bar.bar.bar' }, - { queryParamA: '!' }); - expect(serviceData).to.deep.equal({ message: 'Hello bar.bar.bar!' }); - }); - - describe('Error handling', function () { - it('Handles internal errors', async function () { - const serviceInstance = new DummyService({}, { handleInternalErrors: true }); - serviceInstance.handle = () => { throw Error("I've made a huge mistake"); }; - const serviceData = await serviceInstance.invokeHandler({ namedParamA: 'bar.bar.bar' }); + { queryParamA: '!' } + ) + expect(serviceData).to.deep.equal({ message: 'Hello bar.bar.bar!' }) + }) + + describe('Error handling', function() { + it('Handles internal errors', async function() { + const serviceInstance = new DummyService( + {}, + { handleInternalErrors: true } + ) + serviceInstance.handle = () => { + throw Error("I've made a huge mistake") + } + const serviceData = await serviceInstance.invokeHandler({ + namedParamA: 'bar.bar.bar', + }) expect(serviceData).to.deep.equal({ color: 'lightgray', label: 'shields', message: 'internal error', - }); - }); - }); - - describe('_makeBadgeData', function () { - describe('Overrides', function () { - it('overrides the label', function () { - const badgeData = DummyService._makeBadgeData({ label: 'purr count' }, { label: 'purrs' }); - expect(badgeData.text).to.deep.equal(['purr count', 'n/a']); - }); - - it('overrides the color', function () { - const badgeData = DummyService._makeBadgeData({ colorB: '10ADED' }, { color: 'red' }); - expect(badgeData.colorB).to.equal('#10ADED'); - }); - }); - - describe('Service data', function () { - it('applies the service message', function () { - const badgeData = DummyService._makeBadgeData({}, { message: '10k' }); - expect(badgeData.text).to.deep.equal(['cat', '10k']); - }); - - it('applies the service color', function () { - const badgeData = DummyService._makeBadgeData({}, { color: 'red' }); - expect(badgeData.colorscheme).to.equal('red'); - }); - }); - - describe('Defaults', function () { - it('uses the default label', function () { - const badgeData = DummyService._makeBadgeData({}, {}); - expect(badgeData.text).to.deep.equal(['cat', 'n/a']); - }); - - it('uses the default color', function () { - const badgeData = DummyService._makeBadgeData({}, {}); - expect(badgeData.colorscheme).to.equal('lightgrey'); - }); - }); - }); - - describe('ScoutCamp integration', function () { - const expectedRouteRegex = /^\/foo\/([^/]+).(svg|png|gif|jpg|json)$/; - - let mockCamp; - let mockHandleRequest; + }) + }) + }) + + describe('_makeBadgeData', function() { + describe('Overrides', function() { + it('overrides the label', function() { + const badgeData = DummyService._makeBadgeData( + { label: 'purr count' }, + { label: 'purrs' } + ) + expect(badgeData.text).to.deep.equal(['purr count', 'n/a']) + }) + + it('overrides the color', function() { + const badgeData = DummyService._makeBadgeData( + { colorB: '10ADED' }, + { color: 'red' } + ) + expect(badgeData.colorB).to.equal('#10ADED') + }) + }) + + describe('Service data', function() { + it('applies the service message', function() { + const badgeData = DummyService._makeBadgeData({}, { message: '10k' }) + expect(badgeData.text).to.deep.equal(['cat', '10k']) + }) + + it('applies the service color', function() { + const badgeData = DummyService._makeBadgeData({}, { color: 'red' }) + expect(badgeData.colorscheme).to.equal('red') + }) + }) + + describe('Defaults', function() { + it('uses the default label', function() { + const badgeData = DummyService._makeBadgeData({}, {}) + expect(badgeData.text).to.deep.equal(['cat', 'n/a']) + }) + + it('uses the default color', function() { + const badgeData = DummyService._makeBadgeData({}, {}) + expect(badgeData.colorscheme).to.equal('lightgrey') + }) + }) + }) + + describe('ScoutCamp integration', function() { + const expectedRouteRegex = /^\/foo\/([^/]+).(svg|png|gif|jpg|json)$/ + + let mockCamp + let mockHandleRequest beforeEach(() => { mockCamp = { route: sinon.spy(), - }; - mockHandleRequest = sinon.spy(); - DummyService.register(mockCamp, mockHandleRequest, defaultConfig); - }); + } + mockHandleRequest = sinon.spy() + DummyService.register(mockCamp, mockHandleRequest, defaultConfig) + }) it('registers the service', () => { - expect(mockCamp.route).to.have.been.calledOnce; - expect(mockCamp.route).to.have.been.calledWith(expectedRouteRegex); - }); + expect(mockCamp.route).to.have.been.calledOnce + expect(mockCamp.route).to.have.been.calledWith(expectedRouteRegex) + }) it('handles the request', async () => { - expect(mockHandleRequest).to.have.been.calledOnce; - const { handler: requestHandler } = mockHandleRequest.getCall(0).args[0]; + expect(mockHandleRequest).to.have.been.calledOnce + const { handler: requestHandler } = mockHandleRequest.getCall(0).args[0] - const mockSendBadge = sinon.spy(); + const mockSendBadge = sinon.spy() const mockRequest = { asPromise: sinon.spy(), - }; - const queryParams = { queryParamA: '?' }; - const match = '/foo/bar.svg'.match(expectedRouteRegex); - await requestHandler(queryParams, match, mockSendBadge, mockRequest); - - const expectedFormat = 'svg'; - expect(mockSendBadge).to.have.been.calledOnce; - expect(mockSendBadge).to.have.been.calledWith( - expectedFormat, - { - text: ['cat', 'Hello bar?'], - colorscheme: 'lightgrey', - template: undefined, - logo: undefined, - logoWidth: NaN, - links: [], - colorA: undefined, - } - ); - }); - }); + } + const queryParams = { queryParamA: '?' } + const match = '/foo/bar.svg'.match(expectedRouteRegex) + await requestHandler(queryParams, match, mockSendBadge, mockRequest) + + const expectedFormat = 'svg' + expect(mockSendBadge).to.have.been.calledOnce + expect(mockSendBadge).to.have.been.calledWith(expectedFormat, { + text: ['cat', 'Hello bar?'], + colorscheme: 'lightgrey', + template: undefined, + logo: undefined, + logoWidth: NaN, + links: [], + colorA: undefined, + }) + }) + }) describe('prepareExamples', function() { it('returns the expected result', function() { - const [first, second] = DummyService.prepareExamples(); + const [first, second] = DummyService.prepareExamples() expect(first).to.deep.equal({ title: 'DummyService', previewUri: '/foo/World.svg', exampleUri: undefined, documentation: undefined, - }); + }) expect(second).to.deep.equal({ title: 'DummyService', previewUri: '/foo/World.svg?queryParamA=%21%21%21', exampleUri: undefined, documentation: undefined, - }); - }); - }); -}); + }) + }) + }) +}) diff --git a/services/bitbucket/bitbucket.tester.js b/services/bitbucket/bitbucket.tester.js index 90553f20aa31809031229623459a7a3a037f2fcf..9cdf09a2b37b8e6204e71ae86b1a8c3f9c2648ed 100644 --- a/services/bitbucket/bitbucket.tester.js +++ b/services/bitbucket/bitbucket.tester.js @@ -1,140 +1,151 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') const { isBuildStatus, isMetric, - isMetricOpenIssues -} = require('../test-validators'); -const { invalidJSON } = require('../response-fixtures'); - -const t = new ServiceTester({ id: 'bitbucket', title: 'Bitbucket badges' }); -module.exports = t; + isMetricOpenIssues, +} = require('../test-validators') +const { invalidJSON } = require('../response-fixtures') +const t = new ServiceTester({ id: 'bitbucket', title: 'Bitbucket badges' }) +module.exports = t // tests for issues endpoints t.create('issues-raw (valid)') .get('/issues-raw/atlassian/python-bitbucket.json') - .expectJSONTypes(Joi.object().keys({ - name: 'issues', - value: isMetric - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'issues', + value: isMetric, + }) + ) t.create('issues-raw (not found)') .get('/issues-raw/atlassian/not-a-repo.json') - .expectJSON({ name: 'issues', value: 'not found' }); + .expectJSON({ name: 'issues', value: 'not found' }) t.create('issues-raw (invalid)') .get('/issues-raw/chris48s/example-private-repo.json') - .expectJSON({ name: 'issues', value: 'invalid' }); + .expectJSON({ name: 'issues', value: 'invalid' }) t.create('issues-raw (connection error)') .get('/issues-raw/atlassian/python-bitbucket.json') .networkOff() - .expectJSON({ name: 'issues', value: 'inaccessible' }); + .expectJSON({ name: 'issues', value: 'inaccessible' }) t.create('issues (valid)') .get('/issues/atlassian/python-bitbucket.json') - .expectJSONTypes(Joi.object().keys({ - name: 'issues', - value: isMetricOpenIssues - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'issues', + value: isMetricOpenIssues, + }) + ) t.create('issues (not found)') .get('/issues/atlassian/not-a-repo.json') - .expectJSON({ name: 'issues', value: 'not found' }); + .expectJSON({ name: 'issues', value: 'not found' }) t.create('issues (invalid)') .get('/issues/chris48s/example-private-repo.json') - .expectJSON({ name: 'issues', value: 'invalid' }); + .expectJSON({ name: 'issues', value: 'invalid' }) t.create('issues (connection error)') .get('/issues/atlassian/python-bitbucket.json') .networkOff() - .expectJSON({ name: 'issues', value: 'inaccessible' }); - + .expectJSON({ name: 'issues', value: 'inaccessible' }) // tests for pull requests endpoints t.create('pr-raw (valid)') .get('/pr-raw/atlassian/python-bitbucket.json') - .expectJSONTypes(Joi.object().keys({ - name: 'pull requests', - value: isMetric - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'pull requests', + value: isMetric, + }) + ) t.create('pr-raw (not found)') .get('/pr-raw/atlassian/not-a-repo.json') - .expectJSON({ name: 'pull requests', value: 'not found' }); + .expectJSON({ name: 'pull requests', value: 'not found' }) t.create('pr-raw (invalid)') .get('/pr-raw/chris48s/example-private-repo.json') - .expectJSON({ name: 'pull requests', value: 'invalid' }); + .expectJSON({ name: 'pull requests', value: 'invalid' }) t.create('pr-raw (connection error)') .get('/pr-raw/atlassian/python-bitbucket.json') .networkOff() - .expectJSON({ name: 'pull requests', value: 'inaccessible' }); + .expectJSON({ name: 'pull requests', value: 'inaccessible' }) t.create('pr (valid)') .get('/pr/atlassian/python-bitbucket.json') - .expectJSONTypes(Joi.object().keys({ - name: 'pull requests', - value: isMetricOpenIssues - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'pull requests', + value: isMetricOpenIssues, + }) + ) t.create('pr (not found)') .get('/pr/atlassian/not-a-repo.json') - .expectJSON({ name: 'pull requests', value: 'not found' }); + .expectJSON({ name: 'pull requests', value: 'not found' }) t.create('pr (invalid)') .get('/pr/chris48s/example-private-repo.json') - .expectJSON({ name: 'pull requests', value: 'invalid' }); + .expectJSON({ name: 'pull requests', value: 'invalid' }) t.create('pr (connection error)') .get('/pr/atlassian/python-bitbucket.json') .networkOff() - .expectJSON({ name: 'pull requests', value: 'inaccessible' }); - + .expectJSON({ name: 'pull requests', value: 'inaccessible' }) // tests for Bitbucket Pipelines function bitbucketApiResponse(status) { return JSON.stringify({ - "values": [ + values: [ { - "state": { - "type": "pipeline_state_completed", - "name": "COMPLETED", - "result": { - "type": "pipeline_state_completed_xyz", - "name": status - } - } - } - ] - }); + state: { + type: 'pipeline_state_completed', + name: 'COMPLETED', + result: { + type: 'pipeline_state_completed_xyz', + name: status, + }, + }, + }, + ], + }) } t.create('master build result (valid)') .get('/pipelines/atlassian/adf-builder-javascript.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: isBuildStatus - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: isBuildStatus, + }) + ) t.create('master build result (not found)') .get('/pipelines/atlassian/not-a-repo.json') .expectJSON({ name: 'build', value: 'not found' }) t.create('branch build result (valid)') - .get('/pipelines/atlassian/adf-builder-javascript/shields-test-dont-remove.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: isBuildStatus - })); + .get( + '/pipelines/atlassian/adf-builder-javascript/shields-test-dont-remove.json' + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: isBuildStatus, + }) + ) t.create('branch build result (not found)') .get('/pipelines/atlassian/not-a-repo/some-branch.json') @@ -146,69 +157,77 @@ t.create('branch build result (never built)') t.create('build result (passing)') .get('/pipelines/atlassian/adf-builder-javascript.json') - .intercept(nock => nock('https://api.bitbucket.org') - .get(/^\/2.0\/.*/) - .reply(200, bitbucketApiResponse('SUCCESSFUL')) + .intercept(nock => + nock('https://api.bitbucket.org') + .get(/^\/2.0\/.*/) + .reply(200, bitbucketApiResponse('SUCCESSFUL')) ) .expectJSON({ name: 'build', value: 'passing' }) t.create('build result (failing)') .get('/pipelines/atlassian/adf-builder-javascript.json') - .intercept(nock => nock('https://api.bitbucket.org') - .get(/^\/2.0\/.*/) - .reply(200, bitbucketApiResponse('FAILED')) + .intercept(nock => + nock('https://api.bitbucket.org') + .get(/^\/2.0\/.*/) + .reply(200, bitbucketApiResponse('FAILED')) ) .expectJSON({ name: 'build', value: 'failing' }) t.create('build result (error)') .get('/pipelines/atlassian/adf-builder-javascript.json') - .intercept(nock => nock('https://api.bitbucket.org') - .get(/^\/2.0\/.*/) - .reply(200, bitbucketApiResponse('ERROR')) + .intercept(nock => + nock('https://api.bitbucket.org') + .get(/^\/2.0\/.*/) + .reply(200, bitbucketApiResponse('ERROR')) ) .expectJSON({ name: 'build', value: 'error' }) t.create('build result (stopped)') .get('/pipelines/atlassian/adf-builder-javascript.json') - .intercept(nock => nock('https://api.bitbucket.org') - .get(/^\/2.0\/.*/) - .reply(200, bitbucketApiResponse('STOPPED')) + .intercept(nock => + nock('https://api.bitbucket.org') + .get(/^\/2.0\/.*/) + .reply(200, bitbucketApiResponse('STOPPED')) ) .expectJSON({ name: 'build', value: 'stopped' }) t.create('build result (expired)') .get('/pipelines/atlassian/adf-builder-javascript.json') - .intercept(nock => nock('https://api.bitbucket.org') - .get(/^\/2.0\/.*/) - .reply(200, bitbucketApiResponse('EXPIRED')) + .intercept(nock => + nock('https://api.bitbucket.org') + .get(/^\/2.0\/.*/) + .reply(200, bitbucketApiResponse('EXPIRED')) ) .expectJSON({ name: 'build', value: 'expired' }) t.create('build result (unknown)') .get('/pipelines/atlassian/adf-builder-javascript.json') - .intercept(nock => nock('https://api.bitbucket.org') - .get(/^\/2.0\/.*/) - .reply(200, bitbucketApiResponse('NEW_AND_UNEXPECTED')) + .intercept(nock => + nock('https://api.bitbucket.org') + .get(/^\/2.0\/.*/) + .reply(200, bitbucketApiResponse('NEW_AND_UNEXPECTED')) ) .expectJSON({ name: 'build', value: 'unknown' }) t.create('build result (empty json)') .get('/pipelines/atlassian/adf-builder-javascript.json') - .intercept(nock => nock('https://api.bitbucket.org') - .get(/^\/2.0\/.*/) - .reply(200, '{}') + .intercept(nock => + nock('https://api.bitbucket.org') + .get(/^\/2.0\/.*/) + .reply(200, '{}') ) .expectJSON({ name: 'build', value: 'invalid' }) t.create('build result (invalid json)') .get('/pipelines/atlassian/adf-builder-javascript.json') - .intercept(nock => nock('https://api.bitbucket.org') - .get(/^\/2.0\/.*/) - .reply(invalidJSON) + .intercept(nock => + nock('https://api.bitbucket.org') + .get(/^\/2.0\/.*/) + .reply(invalidJSON) ) .expectJSON({ name: 'build', value: 'invalid' }) t.create('build result (network error)') .get('/pipelines/atlassian/adf-builder-javascript.json') .networkOff() - .expectJSON({ name: 'build', value: 'inaccessible' }); + .expectJSON({ name: 'build', value: 'inaccessible' }) diff --git a/services/bithound/bithound.tester.js b/services/bithound/bithound.tester.js index f2a2840a1bca89824d1148a470caf3486dbfface..13510764d2ae0e0b1d79d8e88c5c432fa28ef379 100644 --- a/services/bithound/bithound.tester.js +++ b/services/bithound/bithound.tester.js @@ -1,27 +1,27 @@ -'use strict'; +'use strict' -const ServiceTester = require('../service-tester'); +const ServiceTester = require('../service-tester') -const t = new ServiceTester({ id: 'bithound', title: 'BitHound' }); -module.exports = t; +const t = new ServiceTester({ id: 'bithound', title: 'BitHound' }) +module.exports = t t.create('no longer available (code)') .get('/code/github/rexxars/sse-channel.json') .expectJSON({ name: 'bithound', - value: 'no longer available' - }); + value: 'no longer available', + }) t.create('no longer available (dependencies)') .get('/dependencies/github/rexxars/sse-channel.json') .expectJSON({ name: 'bithound', - value: 'no longer available' - }); + value: 'no longer available', + }) t.create('no longer available (devDpendencies)') .get('/devDependencies/github/rexxars/sse-channel.json') .expectJSON({ name: 'bithound', - value: 'no longer available' - }); + value: 'no longer available', + }) diff --git a/services/bitrise/bitrise.tester.js b/services/bitrise/bitrise.tester.js index 5c47e89c73f30d2b908b5ad5052b491c4e2d1099..e688630f808c1663988fe90304eccd6464f822f7 100644 --- a/services/bitrise/bitrise.tester.js +++ b/services/bitrise/bitrise.tester.js @@ -1,53 +1,51 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') -const t = new ServiceTester({ id: 'bitrise', title: 'Bitrise' }); -module.exports = t; +const t = new ServiceTester({ id: 'bitrise', title: 'Bitrise' }) +module.exports = t t.create('deploy status') .get('/cde737473028420d/master.json?token=GCIdEzacE4GW32jLVrZb7A') - .expectJSONTypes(Joi.object().keys({ - name: 'bitrise', - value: Joi.equal( - 'success', - 'error', - 'unknown' - ) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'bitrise', + value: Joi.equal('success', 'error', 'unknown'), + }) + ) t.create('deploy status without branch') .get('/cde737473028420d.json?token=GCIdEzacE4GW32jLVrZb7A') - .expectJSONTypes(Joi.object().keys({ - name: 'bitrise', - value: Joi.equal( - 'success', - 'error', - 'unknown' - ) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'bitrise', + value: Joi.equal('success', 'error', 'unknown'), + }) + ) t.create('unknown branch') .get('/cde737473028420d/unknown.json?token=GCIdEzacE4GW32jLVrZb7A') - .expectJSON({ name: 'bitrise', value: 'unknown' }); + .expectJSON({ name: 'bitrise', value: 'unknown' }) t.create('invalid token') .get('/cde737473028420d/unknown.json?token=token') - .expectJSON({ name: 'bitrise', value: 'inaccessible' }); + .expectJSON({ name: 'bitrise', value: 'inaccessible' }) t.create('invalid App ID') .get('/invalid/master.json?token=GCIdEzacE4GW32jLVrZb7A') - .expectJSON({ name: 'bitrise', value: 'inaccessible' }); + .expectJSON({ name: 'bitrise', value: 'inaccessible' }) t.create('server error') .get('/AppID/branch.json?token=token') - .intercept(nock => nock('https://app.bitrise.io') - .get('/app/AppID/status.json?token=token&branch=branch') - .reply(500, 'Something went wrong')) - .expectJSON({ name: 'bitrise', value: 'inaccessible' }); + .intercept(nock => + nock('https://app.bitrise.io') + .get('/app/AppID/status.json?token=token&branch=branch') + .reply(500, 'Something went wrong') + ) + .expectJSON({ name: 'bitrise', value: 'inaccessible' }) t.create('connection error') .get('/AppID/branch.json?token=token') .networkOff() - .expectJSON({ name: 'bitrise', value: 'inaccessible' }); + .expectJSON({ name: 'bitrise', value: 'inaccessible' }) diff --git a/services/bountysource/bountysource.tester.js b/services/bountysource/bountysource.tester.js index f8a83e303d1a75c3ee7cb7d2ce31a4de16c77560..af0b651a913333d45c486e9f1c3c699871f8ae20 100644 --- a/services/bountysource/bountysource.tester.js +++ b/services/bountysource/bountysource.tester.js @@ -1,47 +1,52 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { invalidJSON } = require('../response-fixtures'); - -const t = new ServiceTester({ id: 'bountysource', title: 'Bountysource' }); -module.exports = t; +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { invalidJSON } = require('../response-fixtures') +const t = new ServiceTester({ id: 'bountysource', title: 'Bountysource' }) +module.exports = t t.create('bounties (valid)') .get('/team/mozilla-core/activity.json') - .expectJSONTypes(Joi.object().keys({ - name: 'bounties', - value: Joi.number().integer().positive() - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'bounties', + value: Joi.number() + .integer() + .positive(), + }) + ) t.create('bounties (invalid team)') .get('/team/not-a-real-team/activity.json') .expectJSON({ name: 'bounties', - value: 'not found' - }); + value: 'not found', + }) t.create('bounties (connection error)') .get('/team/mozilla-core/activity.json') .networkOff() - .expectJSON({name: 'bounties', value: 'inaccessible'}); + .expectJSON({ name: 'bounties', value: 'inaccessible' }) t.create('bounties (unexpected response)') .get('/team/mozilla-core/activity.json') - .intercept(nock => nock('https://api.bountysource.com') - .get('/teams/mozilla-core') - .reply(invalidJSON) + .intercept(nock => + nock('https://api.bountysource.com') + .get('/teams/mozilla-core') + .reply(invalidJSON) ) - .expectJSON({name: 'bounties', value: 'invalid'}); + .expectJSON({ name: 'bounties', value: 'invalid' }) t.create('bounties (error response)') .get('/team/mozilla-core/activity.json') - .intercept(nock => nock('https://api.bountysource.com') - .get('/teams/mozilla-core') - .reply(500, '{"error":"oh noes!!"}') + .intercept(nock => + nock('https://api.bountysource.com') + .get('/teams/mozilla-core') + .reply(500, '{"error":"oh noes!!"}') ) .expectJSON({ name: 'bounties', - value: 'invalid' - }); + value: 'invalid', + }) diff --git a/services/bower/bower.tester.js b/services/bower/bower.tester.js index 6d720b1a426c5e675721e2482907d21f4215e251..b47238db335c984670f41ea4335f672d39a90e24 100644 --- a/services/bower/bower.tester.js +++ b/services/bower/bower.tester.js @@ -1,67 +1,77 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isVPlusDottedVersionAtLeastOne } = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isVPlusDottedVersionAtLeastOne } = require('../test-validators') -const isBowerPrereleaseVersion = Joi.string().regex(/^v\d+(\.\d+)?(\.\d+)?(-?[.\w\d])+?$/); +const isBowerPrereleaseVersion = Joi.string().regex( + /^v\d+(\.\d+)?(\.\d+)?(-?[.\w\d])+?$/ +) -const t = new ServiceTester({ id: 'bower', title: 'Bower' }); -module.exports = t; +const t = new ServiceTester({ id: 'bower', title: 'Bower' }) +module.exports = t t.create('licence') .get('/l/bootstrap.json') - .expectJSON({ name: 'bower', value: 'MIT' }); + .expectJSON({ name: 'bower', value: 'MIT' }) t.create('custom label for licence') .get('/l/bootstrap.json?label=my licence') - .expectJSON({ name: 'my licence', value: 'MIT' }); + .expectJSON({ name: 'my licence', value: 'MIT' }) t.create('version') .get('/v/bootstrap.json') - .expectJSONTypes(Joi.object().keys({ - name: 'bower', - value: isVPlusDottedVersionAtLeastOne - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'bower', + value: isVPlusDottedVersionAtLeastOne, + }) + ) t.create('custom label for version') .get('/v/bootstrap.json?label=my version') - .expectJSONTypes(Joi.object().keys({ - name: 'my version', - value: isVPlusDottedVersionAtLeastOne - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'my version', + value: isVPlusDottedVersionAtLeastOne, + }) + ) t.create('pre version') // e.g. bower|v0.2.5-alpha-rc-pre .get('/vpre/bootstrap.json') - .expectJSONTypes(Joi.object().keys({ - name: 'bower', - value: isBowerPrereleaseVersion - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'bower', + value: isBowerPrereleaseVersion, + }) + ) t.create('custom label for pre version') // e.g. pre version|v0.2.5-alpha-rc-pre .get('/vpre/bootstrap.json?label=pre version') - .expectJSONTypes(Joi.object().keys({ - name: 'pre version', - value: isBowerPrereleaseVersion - })); - + .expectJSONTypes( + Joi.object().keys({ + name: 'pre version', + value: isBowerPrereleaseVersion, + }) + ) t.create('Version for Invaild Package') .get('/v/it-is-a-invalid-package-should-error.json') - .expectJSON({ name: 'bower', value: 'invalid' }); + .expectJSON({ name: 'bower', value: 'invalid' }) t.create('Pre Version for Invaild Package') .get('/vpre/it-is-a-invalid-package-should-error.json') - .expectJSON({ name: 'bower', value: 'invalid' }); + .expectJSON({ name: 'bower', value: 'invalid' }) t.create('licence for Invaild Package') .get('/l/it-is-a-invalid-package-should-error.json') - .expectJSON({ name: 'bower', value: 'invalid' }); - + .expectJSON({ name: 'bower', value: 'invalid' }) t.create('Version label should be `no releases` if no official version') .get('/v/bootstrap.json') - .intercept(nock => nock('https://libraries.io') - .get('/api/bower/bootstrap') - .reply(200, { latest_stable_release: { name: null } })) // or just `{}` - .expectJSON({ name: 'bower', value: 'no releases' }); + .intercept(nock => + nock('https://libraries.io') + .get('/api/bower/bootstrap') + .reply(200, { latest_stable_release: { name: null } }) + ) // or just `{}` + .expectJSON({ name: 'bower', value: 'no releases' }) diff --git a/services/bugzilla/bugzilla.tester.js b/services/bugzilla/bugzilla.tester.js index a2abb486d4e8f876f5c3d11bddc23a799caa1458..547ffd0a34c3f95f4b0b5a54b6413c5d3c10488d 100644 --- a/services/bugzilla/bugzilla.tester.js +++ b/services/bugzilla/bugzilla.tester.js @@ -1,7 +1,7 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') const bzBugStatus = Joi.equal( 'unconfirmed', @@ -9,27 +9,29 @@ const bzBugStatus = Joi.equal( 'assigned', 'fixed', 'invalid', - 'won\'t fix', + "won't fix", 'duplicate', 'works for me', 'incomplete' -); +) -const t = new ServiceTester({ id: 'bugzilla', title: 'Bugzilla' }); -module.exports = t; +const t = new ServiceTester({ id: 'bugzilla', title: 'Bugzilla' }) +module.exports = t t.create('Bugzilla valid bug status') .get('/996038.json') - .expectJSONTypes(Joi.object().keys({ - name: 'bug 996038', - value: bzBugStatus - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'bug 996038', + value: bzBugStatus, + }) + ) t.create('Bugzilla invalid bug status') .get('/83548978974387943879.json') - .expectJSON({ name: 'bug 83548978974387943879', value: 'not found' }); + .expectJSON({ name: 'bug 83548978974387943879', value: 'not found' }) t.create('Bugzilla failed request bug status') .get('/996038.json') .networkOff() - .expectJSON({ name: 'bug 996038', value: 'inaccessible' }); + .expectJSON({ name: 'bug 996038', value: 'inaccessible' }) diff --git a/services/buildkite/buildkite.tester.js b/services/buildkite/buildkite.tester.js index 8ff31cdd39b4ce0864fca657968b73a4895f5f78..fbc64881b06bcec8a252d5bb876af02cc6788042 100644 --- a/services/buildkite/buildkite.tester.js +++ b/services/buildkite/buildkite.tester.js @@ -1,43 +1,52 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const t = new ServiceTester({ id: 'buildkite', title: 'Buildkite Builds' }); -const { invalidJSON } = require('../response-fixtures'); -const { isBuildStatus } = require('../test-validators'); -module.exports = t; +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const t = new ServiceTester({ id: 'buildkite', title: 'Buildkite Builds' }) +const { invalidJSON } = require('../response-fixtures') +const { isBuildStatus } = require('../test-validators') +module.exports = t t.create('buildkite invalid pipeline') .get('/unknown-identifier/unknown-branch.json') - .expectJSON({ name: 'build', value: 'not found' }); + .expectJSON({ name: 'build', value: 'not found' }) t.create('buildkite valid pipeline') .get('/3826789cf8890b426057e6fe1c4e683bdf04fa24d498885489/master.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), + }) + ) t.create('buildkite valid pipeline skipping branch') .get('/3826789cf8890b426057e6fe1c4e683bdf04fa24d498885489.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), + }) + ) t.create('buildkite unknown branch') - .get('/3826789cf8890b426057e6fe1c4e683bdf04fa24d498885489/unknown-branch.json') - .expectJSON({ name: 'build', value: 'unknown' }); + .get( + '/3826789cf8890b426057e6fe1c4e683bdf04fa24d498885489/unknown-branch.json' + ) + .expectJSON({ name: 'build', value: 'unknown' }) t.create('buildkite connection error') .get('/_.json') .networkOff() - .expectJSON({ name: 'build', value: 'inaccessible' }); + .expectJSON({ name: 'build', value: 'inaccessible' }) t.create('buildkite unexpected response') .get('/3826789cf8890b426057e6fe1c4e683bdf04fa24d498885489.json') - .intercept(nock => nock('https://badge.buildkite.com') - .get('/3826789cf8890b426057e6fe1c4e683bdf04fa24d498885489.json?branch=master') - .reply(invalidJSON) + .intercept(nock => + nock('https://badge.buildkite.com') + .get( + '/3826789cf8890b426057e6fe1c4e683bdf04fa24d498885489.json?branch=master' + ) + .reply(invalidJSON) ) - .expectJSON({name: 'build', value: 'invalid'}); + .expectJSON({ name: 'build', value: 'invalid' }) diff --git a/services/bundlephobia/bundlephobia.tester.js b/services/bundlephobia/bundlephobia.tester.js index f72561e1772feccc17ed2a8af11dec36a71c25e9..e0548943480ebc40cd3ec57dc6173748e535fd82 100644 --- a/services/bundlephobia/bundlephobia.tester.js +++ b/services/bundlephobia/bundlephobia.tester.js @@ -5,7 +5,8 @@ const ServiceTester = require('../service-tester') const { isFileSize } = require('../test-validators') const t = new ServiceTester({ - id: 'bundlephobia', title: 'NPM package bundle size', + id: 'bundlephobia', + title: 'NPM package bundle size', }) module.exports = t @@ -67,12 +68,11 @@ const data = [ format: formats.C, get: '/min/@some-no-exist/some-no-exist.json', expect: { name: 'minified size', value: 'package not found error' }, - } + }, ] -data.forEach( ({format, get, expect }) => { +data.forEach(({ format, get, expect }) => { t.create(`Testing format '${format}' against '${get}'`) .get(get) .expectJSONTypes(Joi.object().keys(expect)) - } -) +}) diff --git a/services/cauditor/cauditor.tester.js b/services/cauditor/cauditor.tester.js index d2fa3d92e9ba7ec57fdfb4719a105cfda23051c5..84f7bf6d5594f38af6127fe57d01b1531d838243 100644 --- a/services/cauditor/cauditor.tester.js +++ b/services/cauditor/cauditor.tester.js @@ -1,9 +1,9 @@ -'use strict'; +'use strict' -const ServiceTester = require('../service-tester'); +const ServiceTester = require('../service-tester') -const t = new ServiceTester({ id: 'cauditor', title: 'Cauditor' }); -module.exports = t; +const t = new ServiceTester({ id: 'cauditor', title: 'Cauditor' }) +module.exports = t t.create('no longer available') .get('/mi/matthiasmullie/scrapbook/master.json?style=_shields_test') @@ -11,4 +11,4 @@ t.create('no longer available') name: 'cauditor', value: 'no longer available', colorB: '#9f9f9f', - }); \ No newline at end of file + }) diff --git a/services/cdnjs/cdnjs.service.js b/services/cdnjs/cdnjs.service.js index 47f7151d9a11262c94e6cbd440455d36cd45b62c..52861c82754db77d007b2468dedbae15ce89696a 100644 --- a/services/cdnjs/cdnjs.service.js +++ b/services/cdnjs/cdnjs.service.js @@ -1,58 +1,55 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const { BaseJsonService } = require('../base'); -const { NotFound } = require('../errors'); -const { addv: versionText } = require('../../lib/text-formatters'); -const { version: versionColor} = require('../../lib/color-formatters'); +const Joi = require('joi') +const { BaseJsonService } = require('../base') +const { NotFound } = require('../errors') +const { addv: versionText } = require('../../lib/text-formatters') +const { version: versionColor } = require('../../lib/color-formatters') module.exports = class Cdnjs extends BaseJsonService { - async handle({library}) { - const url = `https://api.cdnjs.com/libraries/${library}?fields=version`; + async handle({ library }) { + const url = `https://api.cdnjs.com/libraries/${library}?fields=version` const json = await this._requestJson({ url, schema: Joi.any(), - }); + }) if (Object.keys(json).length === 0) { /* Note the 'not found' response from cdnjs is: status code = 200, body = {} */ - throw new NotFound(); + throw new NotFound() } - const version = json.version || 0; + const version = json.version || 0 return { message: versionText(version), - color: versionColor(version) - }; + color: versionColor(version), + } } // Metadata static get defaultBadgeData() { - return { label: 'cdnjs' }; + return { label: 'cdnjs' } } static get category() { - return 'version'; + return 'version' } static get url() { return { base: 'cdnjs/v', format: '(.+)', - capture: ['library'] - }; + capture: ['library'], + } } static get examples() { return [ { previewUrl: 'jquery', - keywords: [ - 'cdn', - 'cdnjs' - ] - } - ]; + keywords: ['cdn', 'cdnjs'], + }, + ] } -}; +} diff --git a/services/cdnjs/cdnjs.tester.js b/services/cdnjs/cdnjs.tester.js index 1001091809572fa3125aa2c936248e5abc9c7fad..02991999523154b221fdf0cfeccff3813542eb26 100644 --- a/services/cdnjs/cdnjs.tester.js +++ b/services/cdnjs/cdnjs.tester.js @@ -1,33 +1,35 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isVPlusTripleDottedVersion } = require('../test-validators'); - -const t = new ServiceTester({ id: 'cdnjs', title: 'CDNJs' }); -module.exports = t; +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isVPlusTripleDottedVersion } = require('../test-validators') +const t = new ServiceTester({ id: 'cdnjs', title: 'CDNJs' }) +module.exports = t t.create('cdnjs (valid)') .get('/v/jquery.json') - .expectJSONTypes(Joi.object().keys({ - name: 'cdnjs', - value: isVPlusTripleDottedVersion, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'cdnjs', + value: isVPlusTripleDottedVersion, + }) + ) t.create('cdnjs (not found)') .get('/v/not-a-library.json') - .expectJSON({name: 'cdnjs', value: 'not found'}); + .expectJSON({ name: 'cdnjs', value: 'not found' }) t.create('cdnjs (connection error)') .get('/v/jquery.json') .networkOff() - .expectJSON({name: 'cdnjs', value: 'inaccessible'}); + .expectJSON({ name: 'cdnjs', value: 'inaccessible' }) t.create('cdnjs (error response)') .get('/v/jquery.json') - .intercept(nock => nock('https://api.cdnjs.com') - .get('/libraries/jquery?fields=version') - .reply(500, '{"error":"oh noes!!"}') + .intercept(nock => + nock('https://api.cdnjs.com') + .get('/libraries/jquery?fields=version') + .reply(500, '{"error":"oh noes!!"}') ) - .expectJSON({name: 'cdnjs', value: 'invalid'}); + .expectJSON({ name: 'cdnjs', value: 'invalid' }) diff --git a/services/chocolatey/chocolatey.tester.js b/services/chocolatey/chocolatey.tester.js index c4a01b7b8b6edf8fabe75a403197efdec6eacb6b..591c6710d6d32051cb32d8a2004e9b61e753f8eb 100644 --- a/services/chocolatey/chocolatey.tester.js +++ b/services/chocolatey/chocolatey.tester.js @@ -1,172 +1,202 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') const { isMetric, isVPlusDottedVersionNClauses, isVPlusDottedVersionNClausesWithOptionalSuffix, -} = require('../test-validators'); -const colorscheme = require('../../lib/colorscheme.json'); +} = require('../test-validators') +const colorscheme = require('../../lib/colorscheme.json') const { nuGetV2VersionJsonWithDash, nuGetV2VersionJsonFirstCharZero, - nuGetV2VersionJsonFirstCharNotZero -} = require('../nuget-fixtures'); -const { invalidJSON } = require('../response-fixtures'); - -const t = new ServiceTester({ id: 'chocolatey', title: 'Chocolatey' }); -module.exports = t; + nuGetV2VersionJsonFirstCharNotZero, +} = require('../nuget-fixtures') +const { invalidJSON } = require('../response-fixtures') +const t = new ServiceTester({ id: 'chocolatey', title: 'Chocolatey' }) +module.exports = t // downloads t.create('total downloads (valid)') .get('/dt/scriptcs.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetric, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetric, + }) + ) t.create('total downloads (not found)') .get('/dt/not-a-real-package.json') - .expectJSON({name: 'downloads', value: 'not found'}); + .expectJSON({ name: 'downloads', value: 'not found' }) t.create('total downloads (connection error)') .get('/dt/scriptcs.json') .networkOff() - .expectJSON({name: 'downloads', value: 'inaccessible'}); + .expectJSON({ name: 'downloads', value: 'inaccessible' }) t.create('total downloads (unexpected response)') .get('/dt/scriptcs.json') - .intercept(nock => nock('https://www.chocolatey.org') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true") - .reply(invalidJSON) + .intercept(nock => + nock('https://www.chocolatey.org') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true' + ) + .reply(invalidJSON) ) - .expectJSON({name: 'downloads', value: 'invalid'}); - + .expectJSON({ name: 'downloads', value: 'invalid' }) // version t.create('version (valid)') .get('/v/scriptcs.json') - .expectJSONTypes(Joi.object().keys({ - name: 'chocolatey', - value: isVPlusDottedVersionNClauses, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'chocolatey', + value: isVPlusDottedVersionNClauses, + }) + ) t.create('version (mocked, yellow badge)') .get('/v/scriptcs.json?style=_shields_test') - .intercept(nock => nock('https://www.chocolatey.org') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonWithDash) + .intercept(nock => + nock('https://www.chocolatey.org') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonWithDash) ) .expectJSON({ name: 'chocolatey', value: 'v1.2-beta', - colorB: colorscheme.yellow.colorB - }); + colorB: colorscheme.yellow.colorB, + }) t.create('version (mocked, orange badge)') .get('/v/scriptcs.json?style=_shields_test') - .intercept(nock => nock('https://www.chocolatey.org') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonFirstCharZero) + .intercept(nock => + nock('https://www.chocolatey.org') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonFirstCharZero) ) .expectJSON({ name: 'chocolatey', value: 'v0.35', - colorB: colorscheme.orange.colorB - }); + colorB: colorscheme.orange.colorB, + }) t.create('version (mocked, blue badge)') .get('/v/scriptcs.json?style=_shields_test') - .intercept(nock => nock('https://www.chocolatey.org') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonFirstCharNotZero) + .intercept(nock => + nock('https://www.chocolatey.org') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonFirstCharNotZero) ) .expectJSON({ name: 'chocolatey', value: 'v1.2.7', - colorB: colorscheme.blue.colorB - }); + colorB: colorscheme.blue.colorB, + }) t.create('version (not found)') .get('/v/not-a-real-package.json') - .expectJSON({name: 'chocolatey', value: 'not found'}); + .expectJSON({ name: 'chocolatey', value: 'not found' }) t.create('version (connection error)') .get('/v/scriptcs.json') .networkOff() - .expectJSON({name: 'chocolatey', value: 'inaccessible'}); + .expectJSON({ name: 'chocolatey', value: 'inaccessible' }) t.create('version (unexpected response)') .get('/v/scriptcs.json') - .intercept(nock => nock('https://www.chocolatey.org') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true") - .reply(invalidJSON) + .intercept(nock => + nock('https://www.chocolatey.org') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true' + ) + .reply(invalidJSON) ) - .expectJSON({name: 'chocolatey', value: 'invalid'}); - + .expectJSON({ name: 'chocolatey', value: 'invalid' }) // version (pre) t.create('version (pre) (valid)') .get('/vpre/scriptcs.json') - .expectJSONTypes(Joi.object().keys({ - name: 'chocolatey', - value: isVPlusDottedVersionNClausesWithOptionalSuffix, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'chocolatey', + value: isVPlusDottedVersionNClausesWithOptionalSuffix, + }) + ) t.create('version (pre) (mocked, yellow badge)') .get('/vpre/scriptcs.json?style=_shields_test') - .intercept(nock => nock('https://www.chocolatey.org') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonWithDash) + .intercept(nock => + nock('https://www.chocolatey.org') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonWithDash) ) .expectJSON({ name: 'chocolatey', value: 'v1.2-beta', - colorB: colorscheme.yellow.colorB - }); + colorB: colorscheme.yellow.colorB, + }) t.create('version (pre) (mocked, orange badge)') .get('/vpre/scriptcs.json?style=_shields_test') - .intercept(nock => nock('https://www.chocolatey.org') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonFirstCharZero) + .intercept(nock => + nock('https://www.chocolatey.org') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonFirstCharZero) ) .expectJSON({ name: 'chocolatey', value: 'v0.35', - colorB: colorscheme.orange.colorB - }); + colorB: colorscheme.orange.colorB, + }) t.create('version (pre) (mocked, blue badge)') .get('/vpre/scriptcs.json?style=_shields_test') - .intercept(nock => nock('https://www.chocolatey.org') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonFirstCharNotZero) + .intercept(nock => + nock('https://www.chocolatey.org') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonFirstCharNotZero) ) .expectJSON({ name: 'chocolatey', value: 'v1.2.7', - colorB: colorscheme.blue.colorB - }); + colorB: colorscheme.blue.colorB, + }) t.create('version (pre) (not found)') .get('/vpre/not-a-real-package.json') - .expectJSON({name: 'chocolatey', value: 'not found'}); + .expectJSON({ name: 'chocolatey', value: 'not found' }) t.create('version (pre) (connection error)') .get('/vpre/scriptcs.json') .networkOff() - .expectJSON({name: 'chocolatey', value: 'inaccessible'}); + .expectJSON({ name: 'chocolatey', value: 'inaccessible' }) t.create('version (pre) (unexpected response)') .get('/vpre/scriptcs.json') - .intercept(nock => nock('https://www.chocolatey.org') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true") - .reply(invalidJSON) + .intercept(nock => + nock('https://www.chocolatey.org') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true' + ) + .reply(invalidJSON) ) - .expectJSON({name: 'chocolatey', value: 'invalid'}); + .expectJSON({ name: 'chocolatey', value: 'invalid' }) diff --git a/services/chrome-web-store/chrome-web-store.tester.js b/services/chrome-web-store/chrome-web-store.tester.js index 4c35557c680d0a8e4b104fa55dd7895ccbae863d..12053a48c413293d754aa42b0bd90585c1810da0 100644 --- a/services/chrome-web-store/chrome-web-store.tester.js +++ b/services/chrome-web-store/chrome-web-store.tester.js @@ -1,55 +1,64 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') const { isVPlusDottedVersionAtLeastOne, isStarRating, - isMetric -} = require('../test-validators'); + isMetric, +} = require('../test-validators') -const t = new ServiceTester({ id: 'chrome-web-store', title: 'Chrome Web Store' }); -module.exports = t; +const t = new ServiceTester({ + id: 'chrome-web-store', + title: 'Chrome Web Store', +}) +module.exports = t t.create('Downloads (now users)') .get('/d/alhjnofcnnpeaphgeakdhkebafjcpeae.json') - .expectJSONTypes(Joi.object().keys({ name: 'users', value: isMetric })); + .expectJSONTypes(Joi.object().keys({ name: 'users', value: isMetric })) t.create('Users') .get('/users/alhjnofcnnpeaphgeakdhkebafjcpeae.json') - .expectJSONTypes(Joi.object().keys({ name: 'users', value: isMetric })); + .expectJSONTypes(Joi.object().keys({ name: 'users', value: isMetric })) t.create('Version') .get('/v/alhjnofcnnpeaphgeakdhkebafjcpeae.json') - .expectJSONTypes(Joi.object().keys({ - name: 'chrome web store', - value: isVPlusDottedVersionAtLeastOne - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'chrome web store', + value: isVPlusDottedVersionAtLeastOne, + }) + ) t.create('Version - Custom label') .get('/v/alhjnofcnnpeaphgeakdhkebafjcpeae.json?label=IndieGala Helper') - .expectJSONTypes(Joi.object().keys({ - name: 'IndieGala Helper', - value: isVPlusDottedVersionAtLeastOne - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'IndieGala Helper', + value: isVPlusDottedVersionAtLeastOne, + }) + ) t.create('Rating') .get('/rating/alhjnofcnnpeaphgeakdhkebafjcpeae.json') - .expectJSONTypes(Joi.object().keys({ - name: 'rating', - value: Joi.string().regex(/^\d\.?\d+?\/5$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'rating', + value: Joi.string().regex(/^\d\.?\d+?\/5$/), + }) + ) t.create('Stars') .get('/stars/alhjnofcnnpeaphgeakdhkebafjcpeae.json') - .expectJSONTypes(Joi.object().keys({ name: 'rating', value: isStarRating })); + .expectJSONTypes(Joi.object().keys({ name: 'rating', value: isStarRating })) t.create('Invalid addon') .get('/d/invalid-name-of-addon.json') - .expectJSON({ name: 'chrome web store', value: 'invalid' }); + .expectJSON({ name: 'chrome web store', value: 'invalid' }) t.create('No connection') .get('/v/alhjnofcnnpeaphgeakdhkebafjcpeae.json') .networkOff() - .expectJSON({ name: 'chrome web store', value: 'inaccessible' }); + .expectJSON({ name: 'chrome web store', value: 'inaccessible' }) diff --git a/services/circleci/circleci.tester.js b/services/circleci/circleci.tester.js index 5347c0bc2fb578bfe09431bc24d32c1bc5b1dccb..0a7c01dd7aaf679e3efe9c80c514edd381b16a79 100644 --- a/services/circleci/circleci.tester.js +++ b/services/circleci/circleci.tester.js @@ -1,71 +1,80 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { invalidJSON } = require('../response-fixtures'); -const { isBuildStatus } = require('../test-validators'); - -const t = new ServiceTester({ id: 'circleci', title: 'Circle CI' }); -module.exports = t; +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { invalidJSON } = require('../response-fixtures') +const { isBuildStatus } = require('../test-validators') +const t = new ServiceTester({ id: 'circleci', title: 'Circle CI' }) +module.exports = t t.create('circle ci (valid, without branch)') .get('/project/github/RedSparr0w/node-csgo-parser.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: isBuildStatus - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: isBuildStatus, + }) + ) t.create('circle ci (valid, with branch)') .get('/project/github/RedSparr0w/node-csgo-parser/master.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: isBuildStatus - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: isBuildStatus, + }) + ) t.create('circle ci (not found)') .get('/project/github/PyvesB/EmptyRepo.json') - .expectJSON({name: 'build', value: 'project not found'}); + .expectJSON({ name: 'build', value: 'project not found' }) t.create('circle ci (connection error)') .get('/project/github/RedSparr0w/node-csgo-parser.json') .networkOff() - .expectJSON({name: 'build', value: 'inaccessible'}); + .expectJSON({ name: 'build', value: 'inaccessible' }) t.create('circle ci (unexpected response)') .get('/project/github/RedSparr0w/node-csgo-parser.json') - .intercept(nock => nock('https://circleci.com') - .get('/api/v1.1/project/github/RedSparr0w/node-csgo-parser?filter=completed&limit=1') - .reply(invalidJSON) + .intercept(nock => + nock('https://circleci.com') + .get( + '/api/v1.1/project/github/RedSparr0w/node-csgo-parser?filter=completed&limit=1' + ) + .reply(invalidJSON) ) - .expectJSON({name: 'build', value: 'invalid'}); + .expectJSON({ name: 'build', value: 'invalid' }) t.create('circle ci (no response data)') .get('/project/github/RedSparr0w/node-csgo-parser.json') - .intercept(nock => nock('https://circleci.com') - .get('/api/v1.1/project/github/RedSparr0w/node-csgo-parser?filter=completed&limit=1') - .reply(200) + .intercept(nock => + nock('https://circleci.com') + .get( + '/api/v1.1/project/github/RedSparr0w/node-csgo-parser?filter=completed&limit=1' + ) + .reply(200) ) - .expectJSON({ name: 'build', value: 'invalid' }); + .expectJSON({ name: 'build', value: 'invalid' }) t.create('circle ci (multiple pipelines, pass)') .get('/project/github/RedSparr0w/node-csgo-parser.json?style=_shields_test') - .intercept(nock => nock('https://circleci.com') - .get('/api/v1.1/project/github/RedSparr0w/node-csgo-parser?filter=completed&limit=1') - .reply(200, [ - {'status': 'success'}, - {'status': 'fixed'} - ]) + .intercept(nock => + nock('https://circleci.com') + .get( + '/api/v1.1/project/github/RedSparr0w/node-csgo-parser?filter=completed&limit=1' + ) + .reply(200, [{ status: 'success' }, { status: 'fixed' }]) ) - .expectJSON({ name: 'build', value: 'passing', colorB: '#4c1', }); + .expectJSON({ name: 'build', value: 'passing', colorB: '#4c1' }) t.create('circle ci (multiple pipelines, fail)') .get('/project/github/RedSparr0w/node-csgo-parser.json?style=_shields_test') - .intercept(nock => nock('https://circleci.com') - .get('/api/v1.1/project/github/RedSparr0w/node-csgo-parser?filter=completed&limit=1') - .reply(200, [ - {'status': 'success'}, - {'status': 'failed'} - ]) + .intercept(nock => + nock('https://circleci.com') + .get( + '/api/v1.1/project/github/RedSparr0w/node-csgo-parser?filter=completed&limit=1' + ) + .reply(200, [{ status: 'success' }, { status: 'failed' }]) ) - .expectJSON({ name: 'build', value: 'failed', colorB: '#e05d44', }); + .expectJSON({ name: 'build', value: 'failed', colorB: '#e05d44' }) diff --git a/services/clojars/clojars.service.js b/services/clojars/clojars.service.js index 306e7106670a5fa39bc8d202651d3acf1d0e6353..a02509b7277011235ad63054aa0d8541d4fb5083 100644 --- a/services/clojars/clojars.service.js +++ b/services/clojars/clojars.service.js @@ -1,51 +1,48 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const { BaseJsonService } = require('../base'); -const { NotFound } = require('../errors'); -const { version: versionColor } = require('../../lib/color-formatters'); +const Joi = require('joi') +const { BaseJsonService } = require('../base') +const { NotFound } = require('../errors') +const { version: versionColor } = require('../../lib/color-formatters') module.exports = class Clojars extends BaseJsonService { - async handle({clojar}) { - const url = `https://clojars.org/${clojar}/latest-version.json`; + async handle({ clojar }) { + const url = `https://clojars.org/${clojar}/latest-version.json` const json = await this._requestJson({ url, schema: Joi.any(), - }); + }) if (Object.keys(json).length === 0) { /* Note the 'not found' response from clojars is: status code = 200, body = {} */ - throw new NotFound(); + throw new NotFound() } return { - message: "[" + clojar + " \"" + json.version + "\"]", - color: versionColor(json.version) - }; + message: '[' + clojar + ' "' + json.version + '"]', + color: versionColor(json.version), + } } // Metadata static get defaultBadgeData() { - return { label: 'clojars' }; + return { label: 'clojars' } } static get category() { - return 'version'; + return 'version' } static get url() { return { base: 'clojars/v', format: '(.+)', - capture: ['clojar'] - }; + capture: ['clojar'], + } } static get examples() { - return [ - { previewUrl: 'prismic' } - ]; + return [{ previewUrl: 'prismic' }] } - -}; +} diff --git a/services/clojars/clojars.tester.js b/services/clojars/clojars.tester.js index fd4199c16f4bf56525cdf4e6bbf365258d0f902f..cf91fee04a9623a55a148270beda87a8393cf196 100644 --- a/services/clojars/clojars.tester.js +++ b/services/clojars/clojars.tester.js @@ -1,32 +1,34 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); - -const t = new ServiceTester({ id: 'clojars', title: 'clojars' }); -module.exports = t; +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const t = new ServiceTester({ id: 'clojars', title: 'clojars' }) +module.exports = t t.create('clojars (valid)') .get('/v/prismic.json') - .expectJSONTypes(Joi.object().keys({ - name: 'clojars', - value: /^\[prismic "([0-9][.]?)+"\]$/, // note: https://github.com/badges/shields/pull/431 - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'clojars', + value: /^\[prismic "([0-9][.]?)+"\]$/, // note: https://github.com/badges/shields/pull/431 + }) + ) t.create('clojars (not found)') .get('/v/not-a-package.json') - .expectJSON({name: 'clojars', value: 'not found'}); + .expectJSON({ name: 'clojars', value: 'not found' }) t.create('clojars (connection error)') .get('/v/jquery.json') .networkOff() - .expectJSON({name: 'clojars', value: 'inaccessible'}); + .expectJSON({ name: 'clojars', value: 'inaccessible' }) t.create('clojars (error response)') .get('/v/prismic.json') - .intercept(nock => nock('https://clojars.org') - .get('/prismic/latest-version.json') - .reply(500, '{"error":"oh noes!!"}') + .intercept(nock => + nock('https://clojars.org') + .get('/prismic/latest-version.json') + .reply(500, '{"error":"oh noes!!"}') ) - .expectJSON({name: 'clojars', value: 'invalid'}); + .expectJSON({ name: 'clojars', value: 'invalid' }) diff --git a/services/cocoapods/cocoapods.tester.js b/services/cocoapods/cocoapods.tester.js index 2a6dd99685e17dc4f19c5097c128c95f26215b37..143e422f3b16f4b0ab7db2ff79050d0ada23e9cb 100644 --- a/services/cocoapods/cocoapods.tester.js +++ b/services/cocoapods/cocoapods.tester.js @@ -1,211 +1,228 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { invalidJSON } = require('../response-fixtures'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { invalidJSON } = require('../response-fixtures') const { - isIntegerPercentage, - isVPlusDottedVersionAtLeastOne, -} = require('../test-validators'); + isIntegerPercentage, + isVPlusDottedVersionAtLeastOne, +} = require('../test-validators') -const isPlatform = Joi.string().regex(/^(osx|ios|tvos|watchos)( \| (osx|ios|tvos|watchos))*$/); +const isPlatform = Joi.string().regex( + /^(osx|ios|tvos|watchos)( \| (osx|ios|tvos|watchos))*$/ +) // these are deliberately not isMetricOverTimePeriod due to // https://github.com/CocoaPods/cocoapods.org/issues/348 -const isMetricOverTimePeriodAllowZero = Joi - .string() - .regex(/^(0|[1-9][0-9]*)[kMGTPEZY]?\/(year|month|4 weeks|week|day)$/); -const isMetricAllowZero = Joi - .string() - .regex(/^(0|[1-9][0-9]*)[kMGTPEZY]?$/); - -const t = new ServiceTester({ id: 'cocoapods', title: 'Cocoa Pods' }); -module.exports = t; +const isMetricOverTimePeriodAllowZero = Joi.string().regex( + /^(0|[1-9][0-9]*)[kMGTPEZY]?\/(year|month|4 weeks|week|day)$/ +) +const isMetricAllowZero = Joi.string().regex(/^(0|[1-9][0-9]*)[kMGTPEZY]?$/) +const t = new ServiceTester({ id: 'cocoapods', title: 'Cocoa Pods' }) +module.exports = t // version endpoint t.create('version (valid)') .get('/v/AFNetworking.json') - .expectJSONTypes(Joi.object().keys({ - name: 'pod', - value: isVPlusDottedVersionAtLeastOne - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'pod', + value: isVPlusDottedVersionAtLeastOne, + }) + ) t.create('version (not found)') .get('/v/not-a-package.json') - .expectJSON({name: 'pod', value: 'not found'}); + .expectJSON({ name: 'pod', value: 'not found' }) t.create('version (connection error)') .get('/v/AFNetworking.json') .networkOff() - .expectJSON({name: 'pod', value: 'inaccessible'}); + .expectJSON({ name: 'pod', value: 'inaccessible' }) t.create('version (unexpected response)') .get('/v/AFNetworking.json') - .intercept(nock => nock('https://trunk.cocoapods.org') - .get('/api/v1/pods/AFNetworking/specs/latest') - .reply(invalidJSON) + .intercept(nock => + nock('https://trunk.cocoapods.org') + .get('/api/v1/pods/AFNetworking/specs/latest') + .reply(invalidJSON) ) - .expectJSON({name: 'pod', value: 'invalid'}); - + .expectJSON({ name: 'pod', value: 'invalid' }) // platform endpoint t.create('platform (valid)') .get('/p/AFNetworking.json') - .expectJSONTypes(Joi.object().keys({ - name: 'platform', - value: isPlatform - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'platform', + value: isPlatform, + }) + ) t.create('platform (not found)') .get('/p/not-a-package.json') - .expectJSON({name: 'platform', value: 'not found'}); + .expectJSON({ name: 'platform', value: 'not found' }) t.create('platform (connection error)') .get('/p/AFNetworking.json') .networkOff() - .expectJSON({name: 'platform', value: 'inaccessible'}); + .expectJSON({ name: 'platform', value: 'inaccessible' }) t.create('platform (unexpected response)') .get('/p/AFNetworking.json') - .intercept(nock => nock('https://trunk.cocoapods.org') - .get('/api/v1/pods/AFNetworking/specs/latest') - .reply(invalidJSON) + .intercept(nock => + nock('https://trunk.cocoapods.org') + .get('/api/v1/pods/AFNetworking/specs/latest') + .reply(invalidJSON) ) - .expectJSON({name: 'platform', value: 'invalid'}); - + .expectJSON({ name: 'platform', value: 'invalid' }) // license endpoint t.create('license (valid)') .get('/l/AFNetworking.json') - .expectJSON({name: 'license', value: 'MIT'}); + .expectJSON({ name: 'license', value: 'MIT' }) t.create('license (not found)') .get('/l/not-a-package.json') - .expectJSON({name: 'license', value: 'not found'}); + .expectJSON({ name: 'license', value: 'not found' }) t.create('license (connection error)') .get('/l/AFNetworking.json') .networkOff() - .expectJSON({name: 'license', value: 'inaccessible'}); + .expectJSON({ name: 'license', value: 'inaccessible' }) t.create('license (unexpected response)') .get('/l/AFNetworking.json') - .intercept(nock => nock('https://trunk.cocoapods.org') - .get('/api/v1/pods/AFNetworking/specs/latest') - .reply(invalidJSON) + .intercept(nock => + nock('https://trunk.cocoapods.org') + .get('/api/v1/pods/AFNetworking/specs/latest') + .reply(invalidJSON) ) - .expectJSON({name: 'license', value: 'invalid'}); - + .expectJSON({ name: 'license', value: 'invalid' }) // doc percent endpoint t.create('doc percent (valid)') .get('/metrics/doc-percent/AFNetworking.json') - .expectJSONTypes(Joi.object().keys({ - name: 'docs', - value: isIntegerPercentage - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'docs', + value: isIntegerPercentage, + }) + ) t.create('doc percent (null)') .get('/metrics/doc-percent/AFNetworking.json') - .intercept(nock => nock('https://metrics.cocoapods.org') - .get('/api/v1/pods/AFNetworking') - .reply(200, '{"cocoadocs": {"doc_percent": null}}') + .intercept(nock => + nock('https://metrics.cocoapods.org') + .get('/api/v1/pods/AFNetworking') + .reply(200, '{"cocoadocs": {"doc_percent": null}}') ) - .expectJSON({name: 'docs', value: '0%'});; + .expectJSON({ name: 'docs', value: '0%' }) t.create('doc percent (not found)') .get('/metrics/doc-percent/not-a-package.json') - .expectJSON({name: 'docs', value: 'not found'}); + .expectJSON({ name: 'docs', value: 'not found' }) t.create('doc percent (connection error)') .get('/metrics/doc-percent/AFNetworking.json') .networkOff() - .expectJSON({name: 'docs', value: 'inaccessible'}); + .expectJSON({ name: 'docs', value: 'inaccessible' }) t.create('doc percent (unexpected response)') .get('/metrics/doc-percent/AFNetworking.json') - .intercept(nock => nock('https://metrics.cocoapods.org') - .get('/api/v1/pods/AFNetworking') - .reply(invalidJSON) + .intercept(nock => + nock('https://metrics.cocoapods.org') + .get('/api/v1/pods/AFNetworking') + .reply(invalidJSON) ) - .expectJSON({name: 'docs', value: 'invalid'}); - + .expectJSON({ name: 'docs', value: 'invalid' }) // downloads endpoints t.create('downloads (valid, monthly)') .get('/dm/AFNetworking.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetricOverTimePeriodAllowZero - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetricOverTimePeriodAllowZero, + }) + ) t.create('downloads (valid, weekly)') .get('/dw/AFNetworking.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetricOverTimePeriodAllowZero - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetricOverTimePeriodAllowZero, + }) + ) t.create('downloads (valid, total)') .get('/dt/AFNetworking.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetricAllowZero - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetricAllowZero, + }) + ) t.create('downloads (not found)') .get('/dt/not-a-package.json') - .expectJSON({name: 'downloads', value: 'not found'}); + .expectJSON({ name: 'downloads', value: 'not found' }) t.create('downloads (connection error)') .get('/dt/AFNetworking.json') .networkOff() - .expectJSON({name: 'downloads', value: 'inaccessible'}); + .expectJSON({ name: 'downloads', value: 'inaccessible' }) t.create('downloads (unexpected response)') .get('/dt/AFNetworking.json') - .intercept(nock => nock('https://metrics.cocoapods.org') - .get('/api/v1/pods/AFNetworking') - .reply(invalidJSON) + .intercept(nock => + nock('https://metrics.cocoapods.org') + .get('/api/v1/pods/AFNetworking') + .reply(invalidJSON) ) - .expectJSON({name: 'downloads', value: 'invalid'}); - + .expectJSON({ name: 'downloads', value: 'invalid' }) // apps endpoints t.create('apps (valid, weekly)') .get('/aw/AFNetworking.json') - .expectJSONTypes(Joi.object().keys({ - name: 'apps', - value: isMetricOverTimePeriodAllowZero - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'apps', + value: isMetricOverTimePeriodAllowZero, + }) + ) t.create('apps (valid, total)') .get('/at/AFNetworking.json') - .expectJSONTypes(Joi.object().keys({ - name: 'apps', - value: isMetricAllowZero - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'apps', + value: isMetricAllowZero, + }) + ) t.create('apps (not found)') .get('/at/not-a-package.json') - .expectJSON({name: 'apps', value: 'not found'}); + .expectJSON({ name: 'apps', value: 'not found' }) t.create('apps (connection error)') .get('/at/AFNetworking.json') .networkOff() - .expectJSON({name: 'apps', value: 'inaccessible'}); + .expectJSON({ name: 'apps', value: 'inaccessible' }) t.create('apps (unexpected response)') .get('/at/AFNetworking.json') - .intercept(nock => nock('https://metrics.cocoapods.org') - .get('/api/v1/pods/AFNetworking') - .reply(invalidJSON) + .intercept(nock => + nock('https://metrics.cocoapods.org') + .get('/api/v1/pods/AFNetworking') + .reply(invalidJSON) ) - .expectJSON({name: 'apps', value: 'invalid'}); + .expectJSON({ name: 'apps', value: 'invalid' }) diff --git a/services/codeclimate/codeclimate.tester.js b/services/codeclimate/codeclimate.tester.js index 012b883cd78d0184dcfa470c3b79f0a3a57d8b7a..02f907c47a8de12c2b91075c948bcef0ba7afd58 100644 --- a/services/codeclimate/codeclimate.tester.js +++ b/services/codeclimate/codeclimate.tester.js @@ -1,121 +1,140 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { - isIntegerPercentage, -} = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isIntegerPercentage } = require('../test-validators') const t = new ServiceTester({ id: 'codeclimate', title: 'Code Climate' }) // Tests based on Code Climate's test reports endpoint. t.create('test coverage percentage') .get('/c/jekyll/jekyll.json') - .expectJSONTypes(Joi.object().keys({ - name: 'coverage', - value: isIntegerPercentage - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'coverage', + value: isIntegerPercentage, + }) + ) t.create('test coverage percentage alternative coverage URL') .get('/coverage/jekyll/jekyll.json') - .expectJSONTypes(Joi.object().keys({ - name: 'coverage', - value: isIntegerPercentage - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'coverage', + value: isIntegerPercentage, + }) + ) t.create('test coverage percentage alternative top-level URL') .get('/jekyll/jekyll.json') - .expectJSONTypes(Joi.object().keys({ - name: 'coverage', - value: isIntegerPercentage - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'coverage', + value: isIntegerPercentage, + }) + ) t.create('test coverage letter') .get('/c-letter/jekyll/jekyll.json') - .expectJSONTypes(Joi.object().keys({ - name: 'coverage', - value: Joi.equal('A', 'B', 'C', 'D', 'E', 'F') - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'coverage', + value: Joi.equal('A', 'B', 'C', 'D', 'E', 'F'), + }) + ) t.create('test coverage percentage for non-existent repo') .get('/c/unknown/unknown.json') .expectJSON({ name: 'coverage', - value: 'not found' - }); + value: 'not found', + }) t.create('test coverage percentage for repo without test reports') .get('/c/angular/angular.js.json') .expectJSON({ name: 'coverage', - value: 'unknown' - }); + value: 'unknown', + }) // Tests based on Code Climate's snapshots endpoint. t.create('issues count') .get('/issues/angular/angular.js.json') - .expectJSONTypes(Joi.object().keys({ - name: 'issues', - value: Joi.number().integer().positive() - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'issues', + value: Joi.number() + .integer() + .positive(), + }) + ) t.create('technical debt percentage') .get('/tech-debt/angular/angular.js.json') - .expectJSONTypes(Joi.object().keys({ - name: 'technical debt', - value: isIntegerPercentage - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'technical debt', + value: isIntegerPercentage, + }) + ) t.create('maintainability percentage') .get('/maintainability-percentage/angular/angular.js.json') - .expectJSONTypes(Joi.object().keys({ - name: 'maintainability', - value: isIntegerPercentage - })); - + .expectJSONTypes( + Joi.object().keys({ + name: 'maintainability', + value: isIntegerPercentage, + }) + ) t.create('maintainability letter') .get('/maintainability/angular/angular.js.json') - .expectJSONTypes(Joi.object().keys({ - name: 'maintainability', - value: Joi.equal('A', 'B', 'C', 'D', 'E', 'F') - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'maintainability', + value: Joi.equal('A', 'B', 'C', 'D', 'E', 'F'), + }) + ) t.create('maintainability letter for non-existent repo') .get('/maintainability/unknown/unknown.json') .expectJSON({ name: 'maintainability', - value: 'not found' - }); + value: 'not found', + }) t.create('maintainability letter for repo without snapshots') .get('/maintainability/kabisaict/flow.json') .expectJSON({ name: 'maintainability', - value: 'unknown' - }); + value: 'unknown', + }) t.create('malformed response for outer user repos query') .get('/maintainability/angular/angular.js.json') - .intercept(nock => nock('https://api.codeclimate.com') - .get('/v1/repos?github_slug=angular/angular.js') - .reply(200, { - data: [{}] // No relationships in the list of data elements. - })) + .intercept(nock => + nock('https://api.codeclimate.com') + .get('/v1/repos?github_slug=angular/angular.js') + .reply(200, { + data: [{}], // No relationships in the list of data elements. + }) + ) .expectJSON({ name: 'maintainability', - value: 'invalid' - }); + value: 'invalid', + }) t.create('malformed response for inner specific repo query') .get('/maintainability/angular/angular.js.json') - .intercept(nock => nock('https://api.codeclimate.com', {allowUnmocked: true}) - .get(/\/v1\/repos\/[a-z0-9]+\/snapshots\/[a-z0-9]+/) - .reply(200, {})) // No data. + .intercept(nock => + nock('https://api.codeclimate.com', { allowUnmocked: true }) + .get(/\/v1\/repos\/[a-z0-9]+\/snapshots\/[a-z0-9]+/) + .reply(200, {}) + ) // No data. .networkOn() // Combined with allowUnmocked: true, this allows the outer user repos query to go through. .expectJSON({ name: 'maintainability', - value: 'invalid' - }); + value: 'invalid', + }) -module.exports = t; +module.exports = t diff --git a/services/codecov/codecov.tester.js b/services/codecov/codecov.tester.js index 7d55f140d90a0aabdf7c5bcffafba15fafe0b929..5f5b72f713bc9537dc8a954412a90b90aa574685 100644 --- a/services/codecov/codecov.tester.js +++ b/services/codecov/codecov.tester.js @@ -1,22 +1,26 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isIntegerPercentage } = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isIntegerPercentage } = require('../test-validators') -const t = new ServiceTester({ id: 'codecov', title: 'Codecov.io' }); -module.exports = t; +const t = new ServiceTester({ id: 'codecov', title: 'Codecov.io' }) +module.exports = t t.create('gets coverage status') .get('/c/github/codecov/example-python.json') - .expectJSONTypes(Joi.object().keys({ - name: 'coverage', - value: isIntegerPercentage, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'coverage', + value: isIntegerPercentage, + }) + ) t.create('gets coverate status for branch') .get('/c/github/codecov/example-python/master.json') - .expectJSONTypes(Joi.object().keys({ - name: 'coverage', - value: isIntegerPercentage, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'coverage', + value: isIntegerPercentage, + }) + ) diff --git a/services/codeship/codeship.tester.js b/services/codeship/codeship.tester.js index fae9d714323e3fa4ef1ce503ac8e389b70a1c939..70adb3ce5110e7b90ffee78b4ed0c62a8cf281f1 100644 --- a/services/codeship/codeship.tester.js +++ b/services/codeship/codeship.tester.js @@ -1,54 +1,59 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isBuildStatus } = require('../test-validators'); - -const t = new ServiceTester({ id: 'codeship', title: 'codeship' }); -module.exports = t; +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isBuildStatus } = require('../test-validators') +const t = new ServiceTester({ id: 'codeship', title: 'codeship' }) +module.exports = t t.create('codeship (valid, no branch)') .get('/d6c1ddd0-16a3-0132-5f85-2e35c05e22b1.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: isBuildStatus, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: isBuildStatus, + }) + ) t.create('codeship (valid, with branch)') .get('/d6c1ddd0-16a3-0132-5f85-2e35c05e22b1/master.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: isBuildStatus, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: isBuildStatus, + }) + ) t.create('codeship (repo not found)') .get('/not-a-repo.json') - .expectJSON({name: 'build', value: 'not found'}); + .expectJSON({ name: 'build', value: 'not found' }) t.create('codeship (branch not found)') .get('/d6c1ddd0-16a3-0132-5f85-2e35c05e22b1/not-a-branch.json') - .expectJSON({name: 'build', value: 'branch not found'}); + .expectJSON({ name: 'build', value: 'branch not found' }) t.create('codeship (connection error)') .get('/d6c1ddd0-16a3-0132-5f85-2e35c05e22b1.json') .networkOff() - .expectJSON({name: 'build', value: 'inaccessible'}); + .expectJSON({ name: 'build', value: 'inaccessible' }) t.create('codeship (unexpected response header)') .get('/d6c1ddd0-16a3-0132-5f85-2e35c05e22b1.json') - .intercept(nock => nock('https://codeship.com') - .get('/projects/d6c1ddd0-16a3-0132-5f85-2e35c05e22b1/status') - .reply(200, "", { - 'content-disposition': 'foo' - }) + .intercept(nock => + nock('https://codeship.com') + .get('/projects/d6c1ddd0-16a3-0132-5f85-2e35c05e22b1/status') + .reply(200, '', { + 'content-disposition': 'foo', + }) ) - .expectJSON({name: 'build', value: 'unknown'}); + .expectJSON({ name: 'build', value: 'unknown' }) t.create('codeship (unexpected response body)') .get('/d6c1ddd0-16a3-0132-5f85-2e35c05e22b1.json') - .intercept(nock => nock('https://codeship.com') - .get('/projects/d6c1ddd0-16a3-0132-5f85-2e35c05e22b1/status') - .reply(200, "") + .intercept(nock => + nock('https://codeship.com') + .get('/projects/d6c1ddd0-16a3-0132-5f85-2e35c05e22b1/status') + .reply(200, '') ) - .expectJSON({name: 'build', value: 'invalid'}); + .expectJSON({ name: 'build', value: 'invalid' }) diff --git a/services/codetally/codetally.tester.js b/services/codetally/codetally.tester.js index 6ed3167ddac7500633b4ab2ba36752cdf8bb4356..40d6d6839b64fab0aca75df932b441887c944e16 100644 --- a/services/codetally/codetally.tester.js +++ b/services/codetally/codetally.tester.js @@ -1,10 +1,10 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') -const t = new ServiceTester({ id: 'codetally', title: 'Codetally' }); -module.exports = t; +const t = new ServiceTester({ id: 'codetally', title: 'Codetally' }) +module.exports = t // This test will extract the currency value from the // string value response from the server. @@ -15,16 +15,23 @@ module.exports = t; t.create('Codetally') .get('/triggerman722/colorstrap.json') - .expectJSONTypes(Joi.object().keys({ - name: 'codetally', - value: Joi.string().regex(/\b\d+(?:.\d+)?/) - })); - + .expectJSONTypes( + Joi.object().keys({ + name: 'codetally', + value: Joi.string().regex(/\b\d+(?:.\d+)?/), + }) + ) t.create('Empty') .get('/triggerman722/colorstrap.json') - .intercept(nock => nock('http://www.codetally.com') - .get('/formattedshield/triggerman722/colorstrap') - .reply(200, { currency_sign: '$', amount: '0.00', multiplier: '', currency_abbreviation: 'CAD' }) + .intercept(nock => + nock('http://www.codetally.com') + .get('/formattedshield/triggerman722/colorstrap') + .reply(200, { + currency_sign: '$', + amount: '0.00', + multiplier: '', + currency_abbreviation: 'CAD', + }) ) - .expectJSON({ name: 'codetally', value: ' $0.00 '}); + .expectJSON({ name: 'codetally', value: ' $0.00 ' }) diff --git a/services/conda/conda.tester.js b/services/conda/conda.tester.js index 41c8f1312a8d642a46b9cf1e72809bd03a055028..7bdf975fb1d261a84c0221ddd0d9c93fe292d3e1 100644 --- a/services/conda/conda.tester.js +++ b/services/conda/conda.tester.js @@ -1,101 +1,113 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { - isVPlusTripleDottedVersion, - isMetric -} = require('../test-validators'); -const { invalidJSON } = require('../response-fixtures'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isVPlusTripleDottedVersion, isMetric } = require('../test-validators') +const { invalidJSON } = require('../response-fixtures') -const isCondaPlatform = Joi.string().regex(/^\w+-\d+( \| \w+-\d+)*$/); +const isCondaPlatform = Joi.string().regex(/^\w+-\d+( \| \w+-\d+)*$/) -const t = new ServiceTester({id: 'conda', title: 'Conda'}); -module.exports = t; +const t = new ServiceTester({ id: 'conda', title: 'Conda' }) +module.exports = t t.create('version') .get('/v/conda-forge/zlib.json') - .expectJSONTypes(Joi.object().keys({ - name: 'conda|conda-forge', - value: isVPlusTripleDottedVersion - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'conda|conda-forge', + value: isVPlusTripleDottedVersion, + }) + ) t.create('version (relabel)') .get('/v/conda-forge/zlib.json?label=123') - .expectJSONTypes(Joi.object().keys({ - name: '123', - value: isVPlusTripleDottedVersion - })); + .expectJSONTypes( + Joi.object().keys({ + name: '123', + value: isVPlusTripleDottedVersion, + }) + ) t.create('version (skip prefix)') .get('/vn/conda-forge/zlib.json') - .expectJSONTypes(Joi.object().keys({ - name: 'conda-forge', - value: isVPlusTripleDottedVersion - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'conda-forge', + value: isVPlusTripleDottedVersion, + }) + ) t.create('version (skip prefix, relabel)') .get('/vn/conda-forge/zlib.json?label=123') - .expectJSONTypes(Joi.object().keys({ - name: '123', - value: isVPlusTripleDottedVersion - })); + .expectJSONTypes( + Joi.object().keys({ + name: '123', + value: isVPlusTripleDottedVersion, + }) + ) t.create('platform') .get('/p/conda-forge/zlib.json') - .expectJSONTypes(Joi.object().keys({ - name: 'conda|platform', - value: isCondaPlatform - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'conda|platform', + value: isCondaPlatform, + }) + ) t.create('platform (skip prefix)') .get('/pn/conda-forge/zlib.json') - .expectJSONTypes(Joi.object().keys({ - name: 'platform', - value: isCondaPlatform - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'platform', + value: isCondaPlatform, + }) + ) t.create('platform (skip prefix,relabel)') .get('/pn/conda-forge/zlib.json?label=123') - .expectJSONTypes(Joi.object().keys({ name: '123', value: isCondaPlatform })); + .expectJSONTypes(Joi.object().keys({ name: '123', value: isCondaPlatform })) t.create('downloads') .get('/d/conda-forge/zlib.json') - .expectJSONTypes(Joi.object().keys({ - name: 'conda|downloads', - value: isMetric - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'conda|downloads', + value: isMetric, + }) + ) t.create('downloads (skip prefix)') .get('/dn/conda-forge/zlib.json') - .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })); + .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })) t.create('downloads (skip prefix, relabel)') .get('/dn/conda-forge/zlib.json?label=123') - .expectJSONTypes(Joi.object().keys({ name: '123', value: isMetric })); + .expectJSONTypes(Joi.object().keys({ name: '123', value: isMetric })) t.create('unknown package') .get('/d/conda-forge/some-bogus-package-that-never-exists.json') - .expectJSON({ name: 'conda|downloads', value: 'invalid' }); + .expectJSON({ name: 'conda|downloads', value: 'invalid' }) t.create('unknown channel') .get('/d/some-bogus-channel-that-never-exists/zlib.json') - .expectJSON({ name: 'conda|downloads', value: 'invalid' }); + .expectJSON({ name: 'conda|downloads', value: 'invalid' }) t.create('unknown info') .get('/x/conda-forge/zlib.json') .expectStatus(404) - .expectJSON({ name: '404', value: 'badge not found' }); + .expectJSON({ name: '404', value: 'badge not found' }) t.create('connection error') .get('/d/conda-forge/zlib.json') .networkOff() - .expectJSON({ name: 'conda|downloads', value: 'inaccessible' }); + .expectJSON({ name: 'conda|downloads', value: 'inaccessible' }) t.create('unexpected response') .get('/v/conda-forge/zlib.json') - .intercept(nock => nock('https://api.anaconda.org') - .get('/package/conda-forge/zlib') - .reply(invalidJSON) + .intercept(nock => + nock('https://api.anaconda.org') + .get('/package/conda-forge/zlib') + .reply(invalidJSON) ) - .expectJSON({name: 'conda|conda-forge', value: 'invalid'}); + .expectJSON({ name: 'conda|conda-forge', value: 'invalid' }) diff --git a/services/continuousphp/continuousphp.tester.js b/services/continuousphp/continuousphp.tester.js index d3cd85b9746c3cbab16951e7ea9ecda134831c8c..7108eb6c4b347dd67056774c5fc0e2fd20f98797 100644 --- a/services/continuousphp/continuousphp.tester.js +++ b/services/continuousphp/continuousphp.tester.js @@ -1,31 +1,35 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isBuildStatus } = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isBuildStatus } = require('../test-validators') -const t = new ServiceTester({ id: 'continuousphp', title: 'continuousphp' }); -module.exports = t; +const t = new ServiceTester({ id: 'continuousphp', title: 'continuousphp' }) +module.exports = t t.create('build status on default branch') .get('/git-hub/doctrine/dbal.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), + }) + ) t.create('build status on named branch') .get('/git-hub/doctrine/dbal/develop.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), + }) + ) t.create('unknown repo') .get('/git-hub/this-repo/does-not-exist.json') - .expectJSON({ name: 'build', value: 'invalid' }); + .expectJSON({ name: 'build', value: 'invalid' }) t.create('connection error') .get('/git-hub/doctrine/dbal.json') .networkOff() - .expectJSON({ name: 'build', value: 'invalid' }); + .expectJSON({ name: 'build', value: 'invalid' }) diff --git a/services/coveralls/coveralls.tester.js b/services/coveralls/coveralls.tester.js index bc095d3b91fed4e82e74f651af1bd8c53ef4190f..87a08f3219686c06c3b7d12b8bd12b3bed856d96 100644 --- a/services/coveralls/coveralls.tester.js +++ b/services/coveralls/coveralls.tester.js @@ -1,103 +1,152 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isIntegerPercentage } = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isIntegerPercentage } = require('../test-validators') -const t = new ServiceTester({ id: 'coveralls', title: 'Coveralls.io' }); -module.exports = t; +const t = new ServiceTester({ id: 'coveralls', title: 'Coveralls.io' }) +module.exports = t t.create('error status code - location header is missing') .get('/github/not/existed.json') - .intercept(nock => nock('https://coveralls.io') - .head('/repos/github/not/existed/badge.svg') - .reply(404) + .intercept(nock => + nock('https://coveralls.io') + .head('/repos/github/not/existed/badge.svg') + .reply(404) ) - .expectJSON({ name: 'coverage', value: 'invalid' }); + .expectJSON({ name: 'coverage', value: 'invalid' }) t.create('malformed location') .get('/github/user/repository.json') - .intercept(nock => nock('https://coveralls.io') - .head('/repos/github/user/repository/badge.svg') - .reply(302, {}, { - 'Location': 'https://s3.amazonaws.com/assets.coveralls.io/badges/malformedlocation.svg' - }) + .intercept(nock => + nock('https://coveralls.io') + .head('/repos/github/user/repository/badge.svg') + .reply( + 302, + {}, + { + Location: + 'https://s3.amazonaws.com/assets.coveralls.io/badges/malformedlocation.svg', + } + ) ) - .expectJSON({ name: 'coverage', value: 'malformed' }); + .expectJSON({ name: 'coverage', value: 'malformed' }) t.create('NaN percentage in location') .get('/github/user/repository.json') - .intercept(nock => nock('https://coveralls.io') - .head('/repos/github/user/repository/badge.svg') - .reply(302, {}, { - 'Location': 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_notanumber.svg' - }) + .intercept(nock => + nock('https://coveralls.io') + .head('/repos/github/user/repository/badge.svg') + .reply( + 302, + {}, + { + Location: + 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_notanumber.svg', + } + ) ) - .expectJSON({ name: 'coverage', value: 'unknown' }); + .expectJSON({ name: 'coverage', value: 'unknown' }) t.create('connection error') .get('/github/user/repository.json') .networkOff() - .expectJSON({ name: 'coverage', value: 'invalid' }); + .expectJSON({ name: 'coverage', value: 'invalid' }) t.create('show coverage') .get('/github/user/repository.json') - .intercept(nock => nock('https://coveralls.io') - .head('/repos/github/user/repository/badge.svg') - .reply(302, {}, { - 'Location': 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_50.svg' - }) + .intercept(nock => + nock('https://coveralls.io') + .head('/repos/github/user/repository/badge.svg') + .reply( + 302, + {}, + { + Location: + 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_50.svg', + } + ) ) - .expectJSON({ name: 'coverage', value: '50%' }); + .expectJSON({ name: 'coverage', value: '50%' }) t.create('show coverage for legacy github link') .get('/user/repository.json') - .intercept(nock => nock('https://coveralls.io') - .head('/repos/github/user/repository/badge.svg') - .reply(302, {}, { - 'Location': 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_50.svg' - }) + .intercept(nock => + nock('https://coveralls.io') + .head('/repos/github/user/repository/badge.svg') + .reply( + 302, + {}, + { + Location: + 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_50.svg', + } + ) ) - .expectJSON({ name: 'coverage', value: '50%' }); + .expectJSON({ name: 'coverage', value: '50%' }) t.create('show coverage for branch') .get('/github/user/repository/branch.json') - .intercept(nock => nock('https://coveralls.io') - .head('/repos/github/user/repository/badge.svg?branch=branch') - .reply(302, {}, { - 'Location': 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_50.svg' - }) + .intercept(nock => + nock('https://coveralls.io') + .head('/repos/github/user/repository/badge.svg?branch=branch') + .reply( + 302, + {}, + { + Location: + 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_50.svg', + } + ) ) - .expectJSON({ name: 'coverage', value: '50%' }); + .expectJSON({ name: 'coverage', value: '50%' }) t.create('show coverage for bitbucket') .get('/bitbucket/user/repository.json') - .intercept(nock => nock('https://coveralls.io') - .head('/repos/bitbucket/user/repository/badge.svg') - .reply(302, {}, { - 'Location': 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_50.svg' - }) + .intercept(nock => + nock('https://coveralls.io') + .head('/repos/bitbucket/user/repository/badge.svg') + .reply( + 302, + {}, + { + Location: + 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_50.svg', + } + ) ) - .expectJSON({ name: 'coverage', value: '50%' }); + .expectJSON({ name: 'coverage', value: '50%' }) t.create('show coverage for bitbucket with branch') .get('/bitbucket/user/repository/branch.json') - .intercept(nock => nock('https://coveralls.io') - .head('/repos/bitbucket/user/repository/badge.svg?branch=branch') - .reply(302, {}, { - 'Location': 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_50.svg' - }) + .intercept(nock => + nock('https://coveralls.io') + .head('/repos/bitbucket/user/repository/badge.svg?branch=branch') + .reply( + 302, + {}, + { + Location: + 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_50.svg', + } + ) ) - .expectJSON({ name: 'coverage', value: '50%' }); + .expectJSON({ name: 'coverage', value: '50%' }) t.create('github coverage') .get('/github/jekyll/jekyll.json') - .expectJSONTypes(Joi.object().keys({ name: 'coverage', value: isIntegerPercentage })); + .expectJSONTypes( + Joi.object().keys({ name: 'coverage', value: isIntegerPercentage }) + ) t.create('github coverage for legacy link') .get('/jekyll/jekyll.json') - .expectJSONTypes(Joi.object().keys({ name: 'coverage', value: isIntegerPercentage })); + .expectJSONTypes( + Joi.object().keys({ name: 'coverage', value: isIntegerPercentage }) + ) t.create('bitbucket coverage') .get('/bitbucket/pyKLIP/pyklip.json') - .expectJSONTypes(Joi.object().keys({ name: 'coverage', value: isIntegerPercentage })); + .expectJSONTypes( + Joi.object().keys({ name: 'coverage', value: isIntegerPercentage }) + ) diff --git a/services/cran/cran.tester.js b/services/cran/cran.tester.js index f5ed483552c2e56537bad07bd0f3adb38ec8202d..36f5b4ca40f67c0c44893513dd3b60d28e32eee8 100644 --- a/services/cran/cran.tester.js +++ b/services/cran/cran.tester.js @@ -1,47 +1,53 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isVPlusTripleDottedVersion } = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isVPlusTripleDottedVersion } = require('../test-validators') -const t = new ServiceTester({ id: 'cran', title: 'CRAN/METACRAN' }); -module.exports = t; +const t = new ServiceTester({ id: 'cran', title: 'CRAN/METACRAN' }) +module.exports = t t.create('version') .get('/v/devtools.json') - .expectJSONTypes(Joi.object().keys({ - name: 'cran', - value: isVPlusTripleDottedVersion - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'cran', + value: isVPlusTripleDottedVersion, + }) + ) t.create('specified license') .get('/l/devtools.json') - .expectJSON({ name: 'license', value: 'GPL (>= 2)' }); + .expectJSON({ name: 'license', value: 'GPL (>= 2)' }) t.create('unknown package') .get('/l/some-bogus-package.json') - .expectJSON({ name: 'cran', value: 'not found' }); + .expectJSON({ name: 'cran', value: 'not found' }) t.create('unknown info') .get('/z/devtools.json') .expectStatus(404) - .expectJSON({ name: '404', value: 'badge not found' }); + .expectJSON({ name: '404', value: 'badge not found' }) t.create('malformed response') .get('/v/foobar.json') - .intercept(nock => nock('http://crandb.r-pkg.org') - .get('/foobar') - .reply(200)) // JSON without Version. - .expectJSON({ name: 'cran', value: 'invalid' }); + .intercept(nock => + nock('http://crandb.r-pkg.org') + .get('/foobar') + .reply(200) + ) // JSON without Version. + .expectJSON({ name: 'cran', value: 'invalid' }) t.create('connection error') .get('/v/foobar.json') .networkOff() - .expectJSON({ name: 'cran', value: 'inaccessible' }); + .expectJSON({ name: 'cran', value: 'inaccessible' }) t.create('unspecified license') .get('/l/foobar.json') - .intercept(nock => nock('http://crandb.r-pkg.org') - .get('/foobar') - .reply(200, {})) // JSON without License. - .expectJSON({ name: 'license', value: 'unknown' }); + .intercept(nock => + nock('http://crandb.r-pkg.org') + .get('/foobar') + .reply(200, {}) + ) // JSON without License. + .expectJSON({ name: 'license', value: 'unknown' }) diff --git a/services/crates/crates.tester.js b/services/crates/crates.tester.js index c9431d4fc8cce87a4fbe8e508b8afaa5e4e5b157..dc33a5ec744ff437b0a24047d4b65a8b8186f343 100644 --- a/services/crates/crates.tester.js +++ b/services/crates/crates.tester.js @@ -1,14 +1,14 @@ -'use strict'; +'use strict' -const ServiceTester = require('../service-tester'); +const ServiceTester = require('../service-tester') -const t = new ServiceTester({ id: 'crates', title: 'crates.io' }); -module.exports = t; +const t = new ServiceTester({ id: 'crates', title: 'crates.io' }) +module.exports = t t.create('license') .get('/l/libc.json') - .expectJSON({ name: 'license', value: 'MIT/Apache-2.0' }); + .expectJSON({ name: 'license', value: 'MIT/Apache-2.0' }) t.create('license (with version)') .get('/l/libc/0.2.31.json') - .expectJSON({ name: 'license', value: 'MIT/Apache-2.0' }); + .expectJSON({ name: 'license', value: 'MIT/Apache-2.0' }) diff --git a/services/david/david.tester.js b/services/david/david.tester.js index 4f7042c804207469b5def522de2377bf217395d0..7b2f39c9f0c1e19cc98714af26235d0949530353 100644 --- a/services/david/david.tester.js +++ b/services/david/david.tester.js @@ -1,70 +1,84 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { invalidJSON } = require('../response-fixtures'); -const isDependencyStatus = Joi.string().valid('insecure', 'up to date', 'out of date'); - -const t = new ServiceTester({ id: 'david', title: 'David' }); -module.exports = t; +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { invalidJSON } = require('../response-fixtures') +const isDependencyStatus = Joi.string().valid( + 'insecure', + 'up to date', + 'out of date' +) +const t = new ServiceTester({ id: 'david', title: 'David' }) +module.exports = t t.create('david dependencies (valid)') .get('/expressjs/express.json') - .expectJSONTypes(Joi.object().keys({ - name: 'dependencies', - value: isDependencyStatus - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'dependencies', + value: isDependencyStatus, + }) + ) t.create('david dev dependencies (valid)') .get('/dev/expressjs/express.json') - .expectJSONTypes(Joi.object().keys({ - name: 'devDependencies', - value: isDependencyStatus - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'devDependencies', + value: isDependencyStatus, + }) + ) t.create('david optional dependencies (valid)') .get('/optional/elnounch/byebye.json') - .expectJSONTypes(Joi.object().keys({ - name: 'optionalDependencies', - value: isDependencyStatus - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'optionalDependencies', + value: isDependencyStatus, + }) + ) t.create('david peer dependencies (valid)') .get('/peer/webcomponents/generator-element.json') - .expectJSONTypes(Joi.object().keys({ - name: 'peerDependencies', - value: isDependencyStatus - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'peerDependencies', + value: isDependencyStatus, + }) + ) t.create('david dependencies with path (valid)') .get('/babel/babel.json?path=packages/babel-core') - .expectJSONTypes(Joi.object().keys({ - name: 'dependencies', - value: isDependencyStatus - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'dependencies', + value: isDependencyStatus, + }) + ) t.create('david dependencies (none)') .get('/peer/expressjs/express.json') // express does not specify peer dependencies - .expectJSON({name: 'peerDependencies', value: 'none'}); + .expectJSON({ name: 'peerDependencies', value: 'none' }) t.create('david dependencies (repo not found)') .get('/pyvesb/emptyrepo.json') - .expectJSON({name: 'dependencies', value: 'invalid'}); + .expectJSON({ name: 'dependencies', value: 'invalid' }) t.create('david dependencies (path not found') .get('/babel/babel.json?path=invalid/path') - .expectJSON({name: 'dependencies', value: 'invalid'}); + .expectJSON({ name: 'dependencies', value: 'invalid' }) t.create('david dependencies (connection error)') .get('/expressjs/express.json') .networkOff() - .expectJSON({name: 'dependencies', value: 'inaccessible'}); + .expectJSON({ name: 'dependencies', value: 'inaccessible' }) t.create('david dependencies (unexpected response)') .get('/expressjs/express.json') - .intercept(nock => nock('https://david-dm.org') - .get('/expressjs/express/info.json') - .reply(invalidJSON) + .intercept(nock => + nock('https://david-dm.org') + .get('/expressjs/express/info.json') + .reply(invalidJSON) ) - .expectJSON({name: 'dependencies', value: 'invalid'}); + .expectJSON({ name: 'dependencies', value: 'invalid' }) diff --git a/services/dependabot/dependabot.tester.js b/services/dependabot/dependabot.tester.js index ebf8270f82ef9b7023464dee56e88d4dbfc4f3a5..f82f6c8a9faaceabebc7c5c67e616d637ce58e34 100644 --- a/services/dependabot/dependabot.tester.js +++ b/services/dependabot/dependabot.tester.js @@ -1,23 +1,26 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isIntegerPercentage } = require('../test-validators'); -const { invalidJSON } = require('../response-fixtures'); -const colorscheme = require('../../lib/colorscheme.json'); -const mapValues = require('lodash.mapvalues'); -const colorsB = mapValues(colorscheme, 'colorB'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isIntegerPercentage } = require('../test-validators') +const { invalidJSON } = require('../response-fixtures') +const colorscheme = require('../../lib/colorscheme.json') +const mapValues = require('lodash.mapvalues') +const colorsB = mapValues(colorscheme, 'colorB') -const t = new ServiceTester({ id: 'dependabot', title: 'Dependabot' }); -module.exports = t; +const t = new ServiceTester({ id: 'dependabot', title: 'Dependabot' }) +module.exports = t t.create('semver stability (valid)') .get('/semver/bundler/puma.json') - .expectJSONTypes(Joi.object().keys({ - name: 'semver stability', - value: isIntegerPercentage, - link: 'https://dependabot.com/compatibility-score.html?dependency-name=puma&package-manager=bundler&version-scheme=semver', - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'semver stability', + value: isIntegerPercentage, + link: + 'https://dependabot.com/compatibility-score.html?dependency-name=puma&package-manager=bundler&version-scheme=semver', + }) + ) t.create('semver stability (connection error)') .get('/semver/bundler/puma.json?style=_shields_test') @@ -25,31 +28,34 @@ t.create('semver stability (connection error)') .expectJSON({ name: 'semver stability', value: 'inaccessible', - colorB: colorsB.red - }); + colorB: colorsB.red, + }) t.create('semver stability (invalid error)') .get('/semver/invalid-manager/puma.json?style=_shields_test') .expectJSON({ name: 'semver stability', value: 'invalid', - colorB: colorsB.lightgrey - }); + colorB: colorsB.lightgrey, + }) t.create('semver stability (invalid JSON response)') .get('/semver/bundler/puma.json') - .intercept(nock => nock('https://api.dependabot.com') - .get('/badges/compatibility_score?package-manager=bundler&dependency-name=puma&version-scheme=semver') - .reply(invalidJSON) - ) + .intercept(nock => + nock('https://api.dependabot.com') + .get( + '/badges/compatibility_score?package-manager=bundler&dependency-name=puma&version-scheme=semver' + ) + .reply(invalidJSON) + ) .expectJSON({ name: 'semver stability', - value: 'invalid' - }); + value: 'invalid', + }) t.create('semver stability (missing dependency)') .get('/semver/bundler/some-random-missing-dependency.json') .expectJSON({ name: 'semver stability', value: 'unknown', - }); + }) diff --git a/services/depfu/depfu.tester.js b/services/depfu/depfu.tester.js index 291fc51da3a039404d2e151ada30a303d602e55b..6e6786999aa7e6f08320cf5fd98fa22ac8b03e89 100644 --- a/services/depfu/depfu.tester.js +++ b/services/depfu/depfu.tester.js @@ -1,26 +1,32 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') -const isDependencyStatus = Joi.string().valid('insecure', 'latest', 'recent', 'stale'); - -const t = new ServiceTester({ id: 'depfu', title: 'Depfu' }); -module.exports = t; +const isDependencyStatus = Joi.string().valid( + 'insecure', + 'latest', + 'recent', + 'stale' +) +const t = new ServiceTester({ id: 'depfu', title: 'Depfu' }) +module.exports = t t.create('depfu dependencies (valid)') .get('/depfu/example-ruby.json') - .expectJSONTypes(Joi.object().keys({ - name: 'dependencies', - value: isDependencyStatus - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'dependencies', + value: isDependencyStatus, + }) + ) t.create('depfu dependencies (repo not found)') .get('/pyvesb/emptyrepo.json') - .expectJSON({name: 'dependencies', value: 'invalid'}); + .expectJSON({ name: 'dependencies', value: 'invalid' }) t.create('depfu dependencies (connection error)') .get('/depfu/example-ruby.json') .networkOff() - .expectJSON({name: 'dependencies', value: 'inaccessible'}); + .expectJSON({ name: 'dependencies', value: 'inaccessible' }) diff --git a/services/discord/discord.tester.js b/services/discord/discord.tester.js index 5a47dbf27ceb07f76567a19ed4723a6af138db6b..f3aa86782af20559ed9b8ca3279c7069e578c200 100644 --- a/services/discord/discord.tester.js +++ b/services/discord/discord.tester.js @@ -1,42 +1,46 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') -const t = new ServiceTester({ id: 'discord', title: 'Discord' }); -module.exports = t; +const t = new ServiceTester({ id: 'discord', title: 'Discord' }) +module.exports = t t.create('gets status for Reactiflux') .get('/102860784329052160.json') - .expectJSONTypes(Joi.object().keys({ - name: 'chat', - value: Joi.string().regex(/^[0-9]+ online$/), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'chat', + value: Joi.string().regex(/^[0-9]+ online$/), + }) + ) t.create('invalid server ID') .get('/12345.json') - .expectJSON({ name: 'chat', value: 'invalid server' }); + .expectJSON({ name: 'chat', value: 'invalid server' }) t.create('widget disabled') .get('/12345.json') - .intercept(nock => nock('https://discordapp.com/') - .get('/api/guilds/12345/widget.json') - .reply(403, { - code: 50004, - message: 'Widget Disabled' - }) + .intercept(nock => + nock('https://discordapp.com/') + .get('/api/guilds/12345/widget.json') + .reply(403, { + code: 50004, + message: 'Widget Disabled', + }) ) - .expectJSON({ name: 'chat', value: 'widget disabled' }); + .expectJSON({ name: 'chat', value: 'widget disabled' }) t.create('server error') .get('/12345.json') - .intercept(nock => nock('https://discordapp.com/') - .get('/api/guilds/12345/widget.json') - .reply(500, 'Something broke') + .intercept(nock => + nock('https://discordapp.com/') + .get('/api/guilds/12345/widget.json') + .reply(500, 'Something broke') ) - .expectJSON({ name: 'chat', value: 'inaccessible' }); + .expectJSON({ name: 'chat', value: 'inaccessible' }) t.create('connection error') .get('/102860784329052160.json') .networkOff() - .expectJSON({ name: 'chat', value: 'inaccessible' }); + .expectJSON({ name: 'chat', value: 'inaccessible' }) diff --git a/services/discourse/discourse.tester.js b/services/discourse/discourse.tester.js index 33c5cca0de6b225e3818bd3f6302c87128460922..74bca839c0e6239e203bf95ea274631a0124d3b2 100644 --- a/services/discourse/discourse.tester.js +++ b/services/discourse/discourse.tester.js @@ -1,10 +1,10 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') -const t = new ServiceTester({ id: 'discourse', title: 'Discourse' }); -module.exports = t; +const t = new ServiceTester({ id: 'discourse', title: 'Discourse' }) +module.exports = t const data = { topic_count: 22513, @@ -20,106 +20,122 @@ const data = { active_users_30_days: 1495, like_count: 308833, likes_7_days: 3633, - likes_30_days: 13397 -}; + likes_30_days: 13397, +} t.create('Topics') .get('/https/meta.discourse.org/topics.json') - .intercept(nock => nock('https://meta.discourse.org') - .get('/site/statistics.json') - .reply(200, data) + .intercept(nock => + nock('https://meta.discourse.org') + .get('/site/statistics.json') + .reply(200, data) ) - .expectJSON({ name: 'discourse', value: '23k topics' }); + .expectJSON({ name: 'discourse', value: '23k topics' }) t.create('Posts') .get('/https/meta.discourse.org/posts.json') - .intercept(nock => nock('https://meta.discourse.org') - .get('/site/statistics.json') - .reply(200, data) + .intercept(nock => + nock('https://meta.discourse.org') + .get('/site/statistics.json') + .reply(200, data) ) - .expectJSON({ name: 'discourse', value: '338k posts' }); + .expectJSON({ name: 'discourse', value: '338k posts' }) t.create('Users') .get('/https/meta.discourse.org/users.json') - .intercept(nock => nock('https://meta.discourse.org') - .get('/site/statistics.json') - .reply(200, data) + .intercept(nock => + nock('https://meta.discourse.org') + .get('/site/statistics.json') + .reply(200, data) ) - .expectJSON({ name: 'discourse', value: '31k users' }); + .expectJSON({ name: 'discourse', value: '31k users' }) t.create('Likes') .get('/https/meta.discourse.org/likes.json') - .intercept(nock => nock('https://meta.discourse.org') - .get('/site/statistics.json') - .reply(200, data) + .intercept(nock => + nock('https://meta.discourse.org') + .get('/site/statistics.json') + .reply(200, data) ) - .expectJSON({ name: 'discourse', value: '309k likes' }); + .expectJSON({ name: 'discourse', value: '309k likes' }) t.create('Status') .get('/https/meta.discourse.org/status.json') - .intercept(nock => nock('https://meta.discourse.org') - .get('/site/statistics.json') - .reply(200, data) + .intercept(nock => + nock('https://meta.discourse.org') + .get('/site/statistics.json') + .reply(200, data) ) - .expectJSON({ name: 'discourse', value: 'online' }); + .expectJSON({ name: 'discourse', value: 'online' }) - t.create('Status with http (not https)') +t.create('Status with http (not https)') .get('/http/meta.discourse.org/status.json') - .intercept(nock => nock('http://meta.discourse.org') - .get('/site/statistics.json') - .reply(200, data) + .intercept(nock => + nock('http://meta.discourse.org') + .get('/site/statistics.json') + .reply(200, data) ) - .expectJSON({ name: 'discourse', value: 'online' }); + .expectJSON({ name: 'discourse', value: 'online' }) t.create('Invalid Host') .get('/https/some.host/status.json') - .intercept(nock => nock('https://some.host') - .get('/site/statistics.json') - .reply(404, "<h1>Not Found</h1>") + .intercept(nock => + nock('https://some.host') + .get('/site/statistics.json') + .reply(404, '<h1>Not Found</h1>') ) - .expectJSON({ name: 'discourse', value: 'inaccessible' }); + .expectJSON({ name: 'discourse', value: 'inaccessible' }) t.create('Invalid Stat') .get('/https/meta.discourse.org/unknown.json') - .intercept(nock => nock('https://meta.discourse.org') - .get('/site/statistics.json') - .reply(200, data) + .intercept(nock => + nock('https://meta.discourse.org') + .get('/site/statistics.json') + .reply(200, data) ) - .expectJSON({ name: 'discourse', value: 'invalid' }); + .expectJSON({ name: 'discourse', value: 'invalid' }) t.create('Connection Error') .get('/https/meta.discourse.org/status.json') .networkOff() - .expectJSON({ name: 'discourse', value: 'inaccessible' }); + .expectJSON({ name: 'discourse', value: 'inaccessible' }) t.create('Topics (live)') .get('/https/meta.discourse.org/topics.json') - .expectJSONTypes(Joi.object().keys({ - name: 'discourse', - value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? topics$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'discourse', + value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? topics$/), + }) + ) t.create('Posts (live)') .get('/https/meta.discourse.org/posts.json') - .expectJSONTypes(Joi.object().keys({ - name: 'discourse', - value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? posts$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'discourse', + value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? posts$/), + }) + ) t.create('Users (live)') .get('/https/meta.discourse.org/users.json') - .expectJSONTypes(Joi.object().keys({ - name: 'discourse', - value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? users$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'discourse', + value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? users$/), + }) + ) t.create('Likes (live)') .get('/https/meta.discourse.org/likes.json') - .expectJSONTypes(Joi.object().keys({ - name: 'discourse', - value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? likes$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'discourse', + value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? likes$/), + }) + ) t.create('Status (live)') .get('/https/meta.discourse.org/status.json') - .expectJSON({ name: 'discourse', value: 'online' }); \ No newline at end of file + .expectJSON({ name: 'discourse', value: 'online' }) diff --git a/services/dockbit/dockbit.tester.js b/services/dockbit/dockbit.tester.js index 974f4c666efe1528048e96678b4618c10f56c398..8626b096fac0367fce3d363883b955fa91770230 100644 --- a/services/dockbit/dockbit.tester.js +++ b/services/dockbit/dockbit.tester.js @@ -1,44 +1,50 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') -const t = new ServiceTester({ id: 'dockbit', title: 'Dockbit' }); -module.exports = t; +const t = new ServiceTester({ id: 'dockbit', title: 'Dockbit' }) +module.exports = t t.create('deploy status') .get('/DockbitStatus/health.json?token=TvavttxFHJ4qhnKstDxrvBXM') - .expectJSONTypes(Joi.object().keys({ - name: 'deploy', - value: Joi.equal( - 'success', - 'failure', - 'error', - 'working', - 'pending', - 'rejected' - ) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'deploy', + value: Joi.equal( + 'success', + 'failure', + 'error', + 'working', + 'pending', + 'rejected' + ), + }) + ) t.create('unknown pipeline') .get('/DockbitStatus/unknown.json') - .expectJSON({ name: 'deploy', value: 'not found' }); + .expectJSON({ name: 'deploy', value: 'not found' }) t.create('no deploy status') .get('/foo/bar.json?token=123') - .intercept(nock => nock('https://dockbit.com/') - .get('/foo/bar/status/123') - .reply(200, {state: null})) - .expectJSON({ name: 'deploy', value: 'not found' }); + .intercept(nock => + nock('https://dockbit.com/') + .get('/foo/bar/status/123') + .reply(200, { state: null }) + ) + .expectJSON({ name: 'deploy', value: 'not found' }) t.create('server error') .get('/foo/bar.json?token=123') - .intercept(nock => nock('https://dockbit.com/') - .get('/foo/bar/status/123') - .reply(500, 'Something went wrong')) - .expectJSON({ name: 'deploy', value: 'inaccessible' }); + .intercept(nock => + nock('https://dockbit.com/') + .get('/foo/bar/status/123') + .reply(500, 'Something went wrong') + ) + .expectJSON({ name: 'deploy', value: 'inaccessible' }) t.create('connection error') .get('/foo/bar.json') .networkOff() - .expectJSON({ name: 'deploy', value: 'inaccessible' }); + .expectJSON({ name: 'deploy', value: 'inaccessible' }) diff --git a/services/docker/docker.tester.js b/services/docker/docker.tester.js index 2f8c75e55715b8709f5e68edc3e116f27954f9f1..600f2ac25dbb645d218f08d7ff51706abe59f7e8 100644 --- a/services/docker/docker.tester.js +++ b/services/docker/docker.tester.js @@ -1,249 +1,298 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const colorscheme = require('../../lib/colorscheme.json'); -const mapValues = require('lodash.mapvalues'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const colorscheme = require('../../lib/colorscheme.json') +const mapValues = require('lodash.mapvalues') -const { isMetric } = require('../test-validators'); -const { invalidJSON } = require('../response-fixtures'); -const { isBuildStatus } = require('../test-validators'); -const isAutomatedBuildStatus = Joi.string().valid('automated', 'manual'); -const colorsB = mapValues(colorscheme, 'colorB'); - -const t = new ServiceTester({ id: 'docker', title: 'Docker Hub' }); -module.exports = t; +const { isMetric } = require('../test-validators') +const { invalidJSON } = require('../response-fixtures') +const { isBuildStatus } = require('../test-validators') +const isAutomatedBuildStatus = Joi.string().valid('automated', 'manual') +const colorsB = mapValues(colorscheme, 'colorB') +const t = new ServiceTester({ id: 'docker', title: 'Docker Hub' }) +module.exports = t // stars endpoint t.create('docker stars (valid, library)') .get('/stars/_/ubuntu.json?style=_shields_test') - .expectJSONTypes(Joi.object().keys({ - name: 'docker stars', - value: isMetric, - colorB: Joi.any().equal('#066da5').required() - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'docker stars', + value: isMetric, + colorB: Joi.any() + .equal('#066da5') + .required(), + }) + ) t.create('docker stars (override colorB)') .get('/stars/_/ubuntu.json?colorB=fedcba&style=_shields_test') - .expectJSONTypes(Joi.object().keys({ - name: 'docker stars', - value: isMetric, - colorB: Joi.any().equal('#fedcba').required() - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'docker stars', + value: isMetric, + colorB: Joi.any() + .equal('#fedcba') + .required(), + }) + ) t.create('docker stars (valid, user)') .get('/stars/jrottenberg/ffmpeg.json') - .expectJSONTypes(Joi.object().keys({ - name: 'docker stars', - value: isMetric - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'docker stars', + value: isMetric, + }) + ) t.create('docker stars (not found)') .get('/stars/_/not-a-real-repo.json') - .expectJSON({name: 'docker stars', value: 'repo not found'}); + .expectJSON({ name: 'docker stars', value: 'repo not found' }) t.create('docker stars (connection error)') .get('/stars/_/ubuntu.json') .networkOff() - .expectJSON({name: 'docker stars', value: 'inaccessible'}); + .expectJSON({ name: 'docker stars', value: 'inaccessible' }) t.create('docker stars (unexpected response)') .get('/stars/_/ubuntu.json') - .intercept(nock => nock('https://hub.docker.com/') - .get('/v2/repositories/library/ubuntu/stars/count/') - .reply(invalidJSON) + .intercept(nock => + nock('https://hub.docker.com/') + .get('/v2/repositories/library/ubuntu/stars/count/') + .reply(invalidJSON) ) - .expectJSON({name: 'docker stars', value: 'invalid'}); - + .expectJSON({ name: 'docker stars', value: 'invalid' }) // pulls endpoint t.create('docker pulls (valid, library)') .get('/pulls/_/ubuntu.json?style=_shields_test') - .expectJSONTypes(Joi.object().keys({ - name: 'docker pulls', - value: isMetric, - colorB: Joi.any().equal('#066da5').required() - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'docker pulls', + value: isMetric, + colorB: Joi.any() + .equal('#066da5') + .required(), + }) + ) t.create('docker pulls (override colorB)') .get('/pulls/_/ubuntu.json?colorB=fedcba&style=_shields_test') - .expectJSONTypes(Joi.object().keys({ - name: 'docker pulls', - value: isMetric, - colorB: Joi.any().equal('#fedcba').required() - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'docker pulls', + value: isMetric, + colorB: Joi.any() + .equal('#fedcba') + .required(), + }) + ) t.create('docker pulls (valid, user)') .get('/pulls/jrottenberg/ffmpeg.json') - .expectJSONTypes(Joi.object().keys({ - name: 'docker pulls', - value: isMetric - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'docker pulls', + value: isMetric, + }) + ) t.create('docker pulls (not found)') .get('/pulls/_/not-a-real-repo.json') - .expectJSON({name: 'docker pulls', value: 'repo not found'}); + .expectJSON({ name: 'docker pulls', value: 'repo not found' }) t.create('docker pulls (connection error)') .get('/pulls/_/ubuntu.json') .networkOff() - .expectJSON({name: 'docker pulls', value: 'inaccessible'}); + .expectJSON({ name: 'docker pulls', value: 'inaccessible' }) t.create('docker pulls (unexpected response)') .get('/pulls/_/ubuntu.json') - .intercept(nock => nock('https://hub.docker.com/') - .get('/v2/repositories/library/ubuntu') - .reply(invalidJSON) + .intercept(nock => + nock('https://hub.docker.com/') + .get('/v2/repositories/library/ubuntu') + .reply(invalidJSON) ) - .expectJSON({name: 'docker pulls', value: 'invalid'}); - + .expectJSON({ name: 'docker pulls', value: 'invalid' }) // automated build endpoint t.create('docker automated build (valid, library)') .get('/automated/_/ubuntu.json') - .expectJSONTypes(Joi.object().keys({ - name: 'docker build', - value: isAutomatedBuildStatus - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'docker build', + value: isAutomatedBuildStatus, + }) + ) t.create('docker automated build (valid, user)') .get('/automated/jrottenberg/ffmpeg.json') - .expectJSONTypes(Joi.object().keys({ - name: 'docker build', - value: isAutomatedBuildStatus - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'docker build', + value: isAutomatedBuildStatus, + }) + ) t.create('docker automated build (not found)') .get('/automated/_/not-a-real-repo.json') - .expectJSON({name: 'docker build', value: 'repo not found'}); + .expectJSON({ name: 'docker build', value: 'repo not found' }) t.create('docker automated build (connection error)') .get('/automated/_/ubuntu.json') .networkOff() - .expectJSON({name: 'docker build', value: 'inaccessible'}); + .expectJSON({ name: 'docker build', value: 'inaccessible' }) t.create('docker automated build (unexpected response)') .get('/automated/_/ubuntu.json') - .intercept(nock => nock('https://registry.hub.docker.com/') - .get('/v2/repositories/library/ubuntu') - .reply(invalidJSON) + .intercept(nock => + nock('https://registry.hub.docker.com/') + .get('/v2/repositories/library/ubuntu') + .reply(invalidJSON) ) - .expectJSON({name: 'docker build', value: 'invalid'}); + .expectJSON({ name: 'docker build', value: 'invalid' }) t.create('docker automated build - automated') .get('/automated/_/ubuntu.json?style=_shields_test') - .intercept(nock => nock('https://registry.hub.docker.com/') - .get('/v2/repositories/library/ubuntu') - .reply(200, {is_automated: true}) + .intercept(nock => + nock('https://registry.hub.docker.com/') + .get('/v2/repositories/library/ubuntu') + .reply(200, { is_automated: true }) ) - .expectJSON({name: 'docker build', value: 'automated', colorB: '#066da5'}); + .expectJSON({ name: 'docker build', value: 'automated', colorB: '#066da5' }) t.create('docker automated build - manual') .get('/automated/_/ubuntu.json?style=_shields_test') - .intercept(nock => nock('https://registry.hub.docker.com/') - .get('/v2/repositories/library/ubuntu') - .reply(200, {is_automated: false}) + .intercept(nock => + nock('https://registry.hub.docker.com/') + .get('/v2/repositories/library/ubuntu') + .reply(200, { is_automated: false }) ) - .expectJSON({name: 'docker build', value: 'manual', colorB: colorsB.yellow}); + .expectJSON({ name: 'docker build', value: 'manual', colorB: colorsB.yellow }) t.create('docker automated build - colorB override in manual') .get('/automated/_/ubuntu.json?colorB=fedcba&style=_shields_test') - .intercept(nock => nock('https://registry.hub.docker.com/') - .get('/v2/repositories/library/ubuntu') - .reply(200, {is_automated: false}) + .intercept(nock => + nock('https://registry.hub.docker.com/') + .get('/v2/repositories/library/ubuntu') + .reply(200, { is_automated: false }) + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'docker build', + value: isAutomatedBuildStatus, + colorB: Joi.any() + .equal('#fedcba') + .required(), + }) ) - .expectJSONTypes(Joi.object().keys({ - name: 'docker build', - value: isAutomatedBuildStatus, - colorB: Joi.any().equal('#fedcba').required() - })); t.create('docker automated build - colorB override in automated') .get('/automated/_/ubuntu.json?colorB=fedcba&style=_shields_test') - .intercept(nock => nock('https://registry.hub.docker.com/') - .get('/v2/repositories/library/ubuntu') - .reply(200, {is_automated: true}) + .intercept(nock => + nock('https://registry.hub.docker.com/') + .get('/v2/repositories/library/ubuntu') + .reply(200, { is_automated: true }) + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'docker build', + value: isAutomatedBuildStatus, + colorB: Joi.any() + .equal('#fedcba') + .required(), + }) ) - .expectJSONTypes(Joi.object().keys({ - name: 'docker build', - value: isAutomatedBuildStatus, - colorB: Joi.any().equal('#fedcba').required() - })); // build status endpoint t.create('docker build status (valid, user)') .get('/build/jrottenberg/ffmpeg.json') - .expectJSONTypes(Joi.object().keys({ - name: 'docker build', - value: isBuildStatus - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'docker build', + value: isBuildStatus, + }) + ) t.create('docker build status (not found)') .get('/build/_/not-a-real-repo.json') - .expectJSON({name: 'docker build', value: 'repo not found'}); + .expectJSON({ name: 'docker build', value: 'repo not found' }) t.create('docker build status (connection error)') .get('/build/_/ubuntu.json') .networkOff() - .expectJSON({name: 'docker build', value: 'inaccessible'}); + .expectJSON({ name: 'docker build', value: 'inaccessible' }) t.create('docker build status (unexpected response)') .get('/build/_/ubuntu.json') - .intercept(nock => nock('https://registry.hub.docker.com/') - .get('/v2/repositories/library/ubuntu/buildhistory') - .reply(invalidJSON) + .intercept(nock => + nock('https://registry.hub.docker.com/') + .get('/v2/repositories/library/ubuntu/buildhistory') + .reply(invalidJSON) ) - .expectJSON({name: 'docker build', value: 'invalid'}); + .expectJSON({ name: 'docker build', value: 'invalid' }) t.create('docker build status (passing)') .get('/build/_/ubuntu.json?style=_shields_test') - .intercept(nock => nock('https://registry.hub.docker.com/') - .get('/v2/repositories/library/ubuntu/buildhistory') - .reply(200, {results: [{status: 10}]}) + .intercept(nock => + nock('https://registry.hub.docker.com/') + .get('/v2/repositories/library/ubuntu/buildhistory') + .reply(200, { results: [{ status: 10 }] }) ) - .expectJSON({name: 'docker build', value: 'passing', colorB: colorsB.brightgreen}); + .expectJSON({ + name: 'docker build', + value: 'passing', + colorB: colorsB.brightgreen, + }) t.create('docker build status (failing)') .get('/build/_/ubuntu.json?style=_shields_test') - .intercept(nock => nock('https://registry.hub.docker.com/') - .get('/v2/repositories/library/ubuntu/buildhistory') - .reply(200, {results: [{status: -1}]}) + .intercept(nock => + nock('https://registry.hub.docker.com/') + .get('/v2/repositories/library/ubuntu/buildhistory') + .reply(200, { results: [{ status: -1 }] }) ) - .expectJSON({name: 'docker build', value: 'failing', colorB: colorsB.red}); + .expectJSON({ name: 'docker build', value: 'failing', colorB: colorsB.red }) t.create('docker build status (building)') .get('/build/_/ubuntu.json?style=_shields_test') - .intercept(nock => nock('https://registry.hub.docker.com/') - .get('/v2/repositories/library/ubuntu/buildhistory') - .reply(200, {results: [{status: 1}]}) + .intercept(nock => + nock('https://registry.hub.docker.com/') + .get('/v2/repositories/library/ubuntu/buildhistory') + .reply(200, { results: [{ status: 1 }] }) ) - .expectJSON({name: 'docker build', value: 'building', colorB: '#066da5'}); + .expectJSON({ name: 'docker build', value: 'building', colorB: '#066da5' }) t.create('docker build status (override colorB for passing)') .get('/build/_/ubuntu.json?colorB=fedcba&style=_shields_test') - .intercept(nock => nock('https://registry.hub.docker.com/') - .get('/v2/repositories/library/ubuntu/buildhistory') - .reply(200, {results: [{status: 10}]}) + .intercept(nock => + nock('https://registry.hub.docker.com/') + .get('/v2/repositories/library/ubuntu/buildhistory') + .reply(200, { results: [{ status: 10 }] }) ) - .expectJSON({name: 'docker build', value: 'passing', colorB: '#fedcba'}); + .expectJSON({ name: 'docker build', value: 'passing', colorB: '#fedcba' }) t.create('docker build status (override colorB for failing)') .get('/build/_/ubuntu.json?colorB=fedcba&style=_shields_test') - .intercept(nock => nock('https://registry.hub.docker.com/') - .get('/v2/repositories/library/ubuntu/buildhistory') - .reply(200, {results: [{status: -1}]}) + .intercept(nock => + nock('https://registry.hub.docker.com/') + .get('/v2/repositories/library/ubuntu/buildhistory') + .reply(200, { results: [{ status: -1 }] }) ) - .expectJSON({name: 'docker build', value: 'failing', colorB: '#fedcba'}); + .expectJSON({ name: 'docker build', value: 'failing', colorB: '#fedcba' }) t.create('docker build status (override colorB for building)') .get('/build/_/ubuntu.json?colorB=fedcba&style=_shields_test') - .intercept(nock => nock('https://registry.hub.docker.com/') - .get('/v2/repositories/library/ubuntu/buildhistory') - .reply(200, {results: [{status: 1}]}) + .intercept(nock => + nock('https://registry.hub.docker.com/') + .get('/v2/repositories/library/ubuntu/buildhistory') + .reply(200, { results: [{ status: 1 }] }) ) - .expectJSON({name: 'docker build', value: 'building', colorB: '#fedcba'}); + .expectJSON({ name: 'docker build', value: 'building', colorB: '#fedcba' }) diff --git a/services/dotnetstatus/dotnetstatus.tester.js b/services/dotnetstatus/dotnetstatus.tester.js index 1580d1fdcbbfbd4a3cd26cc949b3bd75791a4686..66dd1090e242e8fdd224a40181e62a129b895df6 100644 --- a/services/dotnetstatus/dotnetstatus.tester.js +++ b/services/dotnetstatus/dotnetstatus.tester.js @@ -1,13 +1,13 @@ -'use strict'; +'use strict' -const ServiceTester = require('../service-tester'); +const ServiceTester = require('../service-tester') -const t = new ServiceTester({ id: 'dotnetstatus', title: 'dotnet-status' }); -module.exports = t; +const t = new ServiceTester({ id: 'dotnetstatus', title: 'dotnet-status' }) +module.exports = t t.create('no longer available (previously get package status)') .get('/gh/jaredcnance/dotnet-status/API.json') .expectJSON({ name: 'dotnet status', - value: 'no longer available' - }); + value: 'no longer available', + }) diff --git a/services/dub/dub.tester.js b/services/dub/dub.tester.js index b140d96209514a699d090a703ef97469f87c8052..310d21b25600ca6876f73e403cc6067549e6a37a 100644 --- a/services/dub/dub.tester.js +++ b/services/dub/dub.tester.js @@ -1,124 +1,137 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') -const { invalidJSON } = require('../response-fixtures'); -const colorscheme = require('../../lib/colorscheme.json'); +const { invalidJSON } = require('../response-fixtures') +const colorscheme = require('../../lib/colorscheme.json') const { isVPlusDottedVersionNClausesWithOptionalSuffix, isMetric, - isMetricOverTimePeriod -} = require('../test-validators'); + isMetricOverTimePeriod, +} = require('../test-validators') const isVersionColor = Joi.equal( colorscheme.red.colorB, colorscheme.yellow.colorB, colorscheme.yellowgreen.colorB, colorscheme.green.colorB, colorscheme.brightgreen.colorB -); - -const t = new ServiceTester({ id: 'dub', title: 'Dub' }); -module.exports = t; +) +const t = new ServiceTester({ id: 'dub', title: 'Dub' }) +module.exports = t // downloads t.create('total downloads (valid)') .get('/dt/vibe-d.json?style=_shields_test') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetric, - colorB: isVersionColor - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetric, + colorB: isVersionColor, + }) + ) t.create('total downloads, specific version (valid)') .get('/dt/vibe-d/0.7.27.json?style=_shields_test') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: Joi.string().regex(/^[1-9][0-9]*[kMGTPEZY]? v0.7.27$/), - colorB: isVersionColor - })) - .timeout(15000); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: Joi.string().regex(/^[1-9][0-9]*[kMGTPEZY]? v0.7.27$/), + colorB: isVersionColor, + }) + ) + .timeout(15000) t.create('total downloads, latest version (valid)') .get('/dt/vibe-d/latest.json?style=_shields_test') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: Joi.string().regex(/^[1-9][0-9]*[kMGTPEZY]? latest$/), - colorB: isVersionColor - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: Joi.string().regex(/^[1-9][0-9]*[kMGTPEZY]? latest$/), + colorB: isVersionColor, + }) + ) t.create('daily downloads (valid)') .get('/dd/vibe-d.json?style=_shields_test') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetricOverTimePeriod, - colorB: isVersionColor - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetricOverTimePeriod, + colorB: isVersionColor, + }) + ) t.create('weekly downloads (valid)') .get('/dw/vibe-d.json?style=_shields_test') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetricOverTimePeriod, - colorB: isVersionColor - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetricOverTimePeriod, + colorB: isVersionColor, + }) + ) t.create('monthly downloads (valid)') .get('/dm/vibe-d.json?style=_shields_test') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetricOverTimePeriod, - colorB: isVersionColor - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetricOverTimePeriod, + colorB: isVersionColor, + }) + ) t.create('total downloads (not found)') .get('/dt/not-a-package.json') - .expectJSON({name: 'dub', value: 'not found'}); + .expectJSON({ name: 'dub', value: 'not found' }) t.create('total downloads (connection error)') .get('/dt/vibe-d.json') .networkOff() - .expectJSON({name: 'dub', value: 'inaccessible'}); + .expectJSON({ name: 'dub', value: 'inaccessible' }) t.create('total downloads (unexpected response)') .get('/dt/vibe-d.json') - .intercept(nock => nock('https://code.dlang.org') - .get('/api/packages/vibe-d/stats') - .reply(invalidJSON) + .intercept(nock => + nock('https://code.dlang.org') + .get('/api/packages/vibe-d/stats') + .reply(invalidJSON) ) - .expectJSON({name: 'dub', value: 'invalid'}); - + .expectJSON({ name: 'dub', value: 'invalid' }) // version t.create('version (valid)') .get('/v/vibe-d.json?style=_shields_test') - .expectJSONTypes(Joi.object().keys({ - name: 'dub', - value: isVPlusDottedVersionNClausesWithOptionalSuffix, - colorB: Joi.equal(colorscheme.blue.colorB, colorscheme.orange.colorB) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'dub', + value: isVPlusDottedVersionNClausesWithOptionalSuffix, + colorB: Joi.equal(colorscheme.blue.colorB, colorscheme.orange.colorB), + }) + ) t.create('version (not found)') .get('/v/not-a-package.json') - .expectJSON({name: 'dub', value: 'not found'}); + .expectJSON({ name: 'dub', value: 'not found' }) t.create('version (connection error)') .get('/v/vibe-d.json') .networkOff() - .expectJSON({name: 'dub', value: 'inaccessible'}); + .expectJSON({ name: 'dub', value: 'inaccessible' }) t.create('version (unexpected response)') .get('/v/vibe-d.json') - .intercept(nock => nock('https://code.dlang.org') - .get('/api/packages/vibe-d/latest') - .reply(invalidJSON) + .intercept(nock => + nock('https://code.dlang.org') + .get('/api/packages/vibe-d/latest') + .reply(invalidJSON) ) - .expectJSON({name: 'dub', value: 'invalid'}); - + .expectJSON({ name: 'dub', value: 'invalid' }) // license @@ -127,22 +140,23 @@ t.create('license (valid)') .expectJSON({ name: 'license', value: 'MIT', - colorB: colorscheme.blue.colorB - }); + colorB: colorscheme.blue.colorB, + }) t.create('license (not found)') .get('/l/not-a-package.json') - .expectJSON({name: 'dub', value: 'not found'}); + .expectJSON({ name: 'dub', value: 'not found' }) t.create('license (connection error)') .get('/l/vibe-d.json') .networkOff() - .expectJSON({name: 'dub', value: 'inaccessible'}); + .expectJSON({ name: 'dub', value: 'inaccessible' }) t.create('license (unexpected response)') .get('/l/vibe-d.json') - .intercept(nock => nock('https://code.dlang.org') - .get('/api/packages/vibe-d/latest/info') - .reply(invalidJSON) + .intercept(nock => + nock('https://code.dlang.org') + .get('/api/packages/vibe-d/latest/info') + .reply(invalidJSON) ) - .expectJSON({name: 'dub', value: 'invalid'}); + .expectJSON({ name: 'dub', value: 'invalid' }) diff --git a/services/eclipse-marketplace/eclipse-marketplace.tester.js b/services/eclipse-marketplace/eclipse-marketplace.tester.js index 56504f8cd7bcff989709f719d0da1987e9e28745..c90a06437076be434829fa55b77dabc0273e3f3e 100644 --- a/services/eclipse-marketplace/eclipse-marketplace.tester.js +++ b/services/eclipse-marketplace/eclipse-marketplace.tester.js @@ -1,48 +1,60 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') const { - isFormattedDate, - isMetric, - isMetricOverTimePeriod, - isVPlusDottedVersionAtLeastOne -} = require('../test-validators'); + isFormattedDate, + isMetric, + isMetricOverTimePeriod, + isVPlusDottedVersionAtLeastOne, +} = require('../test-validators') -const t = new ServiceTester({ id: 'eclipse-marketplace', title: 'Eclipse' }); -module.exports = t; +const t = new ServiceTester({ id: 'eclipse-marketplace', title: 'Eclipse' }) +module.exports = t t.create('total marketplace downloads') .get('/dt/notepad4e.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetric, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetric, + }) + ) t.create('monthly marketplace downloads') .get('/dm/notepad4e.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetricOverTimePeriod, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetricOverTimePeriod, + }) + ) t.create('marketplace version') .get('/v/notepad4e.json') - .expectJSONTypes(Joi.object().keys({ - name: 'eclipse marketplace', - value: isVPlusDottedVersionAtLeastOne, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'eclipse marketplace', + value: isVPlusDottedVersionAtLeastOne, + }) + ) t.create('favorites count') .get('/favorites/notepad4e.json') - .expectJSONTypes(Joi.object().keys({ - name: 'favorites', - value: Joi.number().integer().positive(), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'favorites', + value: Joi.number() + .integer() + .positive(), + }) + ) t.create('last update date') .get('/last-update/notepad4e.json') - .expectJSONTypes(Joi.object().keys({ - name: 'updated', - value: isFormattedDate, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'updated', + value: isFormattedDate, + }) + ) diff --git a/services/elm-package/elm-package.tester.js b/services/elm-package/elm-package.tester.js index 392163b626e0698b28e17ca0972f4a64d874d795..3fed5cafc878eee85dbbca8e03fa91c05d0aeaf4 100644 --- a/services/elm-package/elm-package.tester.js +++ b/services/elm-package/elm-package.tester.js @@ -1,16 +1,16 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isSemver } = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isSemver } = require('../test-validators') -const t = new ServiceTester({ id: 'elm-package', title: 'ELM PACKAGE' }); -module.exports = t; +const t = new ServiceTester({ id: 'elm-package', title: 'ELM PACKAGE' }) +module.exports = t t.create('gets the package version of elm-lang/core') .get('/v/elm-lang/core.json') - .expectJSONTypes(Joi.object().keys({ name: 'elm-package', value: isSemver })); + .expectJSONTypes(Joi.object().keys({ name: 'elm-package', value: isSemver })) t.create('invalid package name') .get('/v/elm-community/frodo-is-not-a-package.json') - .expectJSON({ name: 'elm-package', value: 'invalid' }); + .expectJSON({ name: 'elm-package', value: 'invalid' }) diff --git a/services/errors.js b/services/errors.js index 61fc9de89a5c62c195c8e4d01a891ee69d117902..68131efc35837c01b57ba0d669958bb4539544e0 100644 --- a/services/errors.js +++ b/services/errors.js @@ -1,59 +1,71 @@ -'use strict'; +'use strict' class ShieldsRuntimeError extends Error { - - get name() { return 'ShieldsRuntimeError'; } - get defaultPrettyMessage() { throw new Error('Must implement abstract method'); } + get name() { + return 'ShieldsRuntimeError' + } + get defaultPrettyMessage() { + throw new Error('Must implement abstract method') + } constructor(props = {}, message) { - super(message); - this.prettyMessage = props.prettyMessage || this.defaultPrettyMessage; + super(message) + this.prettyMessage = props.prettyMessage || this.defaultPrettyMessage if (props.underlyingError) { - this.stack = props.underlyingError.stack; + this.stack = props.underlyingError.stack } } } - -const defaultNotFoundError = 'not found'; +const defaultNotFoundError = 'not found' class NotFound extends ShieldsRuntimeError { - - get name() { return 'NotFound'; } - get defaultPrettyMessage() { return defaultNotFoundError; } + get name() { + return 'NotFound' + } + get defaultPrettyMessage() { + return defaultNotFoundError + } constructor(props = {}) { - const prettyMessage = props.prettyMessage || defaultNotFoundError; - const message = prettyMessage === defaultNotFoundError - ? 'Not Found' - : `Not Found: ${prettyMessage}`; - super(props, message); + const prettyMessage = props.prettyMessage || defaultNotFoundError + const message = + prettyMessage === defaultNotFoundError + ? 'Not Found' + : `Not Found: ${prettyMessage}` + super(props, message) } } class InvalidResponse extends ShieldsRuntimeError { - - get name() { return 'InvalidResponse'; } - get defaultPrettyMessage() { return 'invalid'; } + get name() { + return 'InvalidResponse' + } + get defaultPrettyMessage() { + return 'invalid' + } constructor(props = {}) { const message = props.underlyingError ? `Invalid Response: ${props.underlyingError.message}` - : 'Invalid Response'; - super(props, message); + : 'Invalid Response' + super(props, message) } } class Inaccessible extends ShieldsRuntimeError { - - get name() { return 'Inaccessible'; } - get defaultPrettyMessage() { return 'inaccessible'; } + get name() { + return 'Inaccessible' + } + get defaultPrettyMessage() { + return 'inaccessible' + } constructor(props = {}) { const message = props.underlyingError ? `Inaccessible: ${props.underlyingError.message}` - : 'Inaccessible'; - super(props, message); + : 'Inaccessible' + super(props, message) } } @@ -61,4 +73,4 @@ module.exports = { NotFound, InvalidResponse, Inaccessible, -}; +} diff --git a/services/gem/gem.service.js b/services/gem/gem.service.js index 54032a8f9aa445a8980184652d095d59901add7d..03a9033b6813f29b50b86a697d704d8df9ab7666 100644 --- a/services/gem/gem.service.js +++ b/services/gem/gem.service.js @@ -1,51 +1,47 @@ -'use strict'; +'use strict' -const semver = require('semver'); -const Joi = require('joi'); +const semver = require('semver') +const Joi = require('joi') -const { BaseJsonService } = require('../base'); -const { InvalidResponse } = require('../errors'); -const { addv: versionText } = require('../../lib/text-formatters'); -const { version: versionColor} = require('../../lib/color-formatters'); +const { BaseJsonService } = require('../base') +const { InvalidResponse } = require('../errors') +const { addv: versionText } = require('../../lib/text-formatters') +const { version: versionColor } = require('../../lib/color-formatters') const { floorCount: floorCountColor, downloadCount: downloadCountColor, -} = require('../../lib/color-formatters'); -const { - metric, - ordinalNumber, -} = require('../../lib/text-formatters'); -const { latest: latestVersion } = require('../../lib/version'); - +} = require('../../lib/color-formatters') +const { metric, ordinalNumber } = require('../../lib/text-formatters') +const { latest: latestVersion } = require('../../lib/version') class GemVersion extends BaseJsonService { - async handle({repo}) { - const url = `https://rubygems.org/api/v1/gems/${repo}.json`; + async handle({ repo }) { + const url = `https://rubygems.org/api/v1/gems/${repo}.json` const { version } = await this._requestJson({ url, schema: Joi.object(), - }); + }) return { message: versionText(version), - color: versionColor(version) - }; + color: versionColor(version), + } } // Metadata static get defaultBadgeData() { - return { label: 'gem' }; + return { label: 'gem' } } static get category() { - return 'version'; + return 'version' } static get url() { return { base: 'gem/v', format: '(.+)', - capture: ['repo'] - }; + capture: ['repo'], + } } static get examples() { @@ -53,106 +49,103 @@ class GemVersion extends BaseJsonService { { title: 'Gem', previewUrl: 'formatador', - keywords: [ - 'ruby' - ] - } - ]; + keywords: ['ruby'], + }, + ] } -}; +} class GemDownloads extends BaseJsonService { - fetch(repo, info) { - const endpoint = info === "dv" ? 'versions/' : 'gems/'; - const url = `https://rubygems.org/api/v1/${endpoint}${repo}.json`; + const endpoint = info === 'dv' ? 'versions/' : 'gems/' + const url = `https://rubygems.org/api/v1/${endpoint}${repo}.json` return this._requestJson({ url, schema: Joi.any(), - }); + }) } _getLabel(version, info) { if (version) { - return 'downloads@' + version; + return 'downloads@' + version } else { - if (info === "dtv") { - return 'downloads@latest'; + if (info === 'dtv') { + return 'downloads@latest' } else { - return 'downloads'; + return 'downloads' } } } - async handle({info, rubygem}) { - const splitRubygem = rubygem.split('/'); - const repo = splitRubygem[0]; - let version = (splitRubygem.length > 1) - ? splitRubygem[splitRubygem.length - 1] - : null; - version = (version === "stable") ? version : semver.valid(version); - const label = this._getLabel(version, info); - const json = await this.fetch(repo, info); - - let downloads; - if (info === "dt") { - downloads = metric(json.downloads); - } else if (info === "dtv") { - downloads = metric(json.version_downloads); - } else if (info === "dv") { - - let versionData; - if (version !== null && version === "stable") { - - const versions = json.filter(function(ver) { - return ver.prerelease === false; - }).map(function(ver) { - return ver.number; - }); + async handle({ info, rubygem }) { + const splitRubygem = rubygem.split('/') + const repo = splitRubygem[0] + let version = + splitRubygem.length > 1 ? splitRubygem[splitRubygem.length - 1] : null + version = version === 'stable' ? version : semver.valid(version) + const label = this._getLabel(version, info) + const json = await this.fetch(repo, info) + + let downloads + if (info === 'dt') { + downloads = metric(json.downloads) + } else if (info === 'dtv') { + downloads = metric(json.version_downloads) + } else if (info === 'dv') { + let versionData + if (version !== null && version === 'stable') { + const versions = json + .filter(function(ver) { + return ver.prerelease === false + }) + .map(function(ver) { + return ver.number + }) // Found latest stable version. - const stableVersion = latestVersion(versions); + const stableVersion = latestVersion(versions) versionData = json.filter(function(ver) { - return ver.number === stableVersion; - })[0]; - downloads = metric(versionData.downloads_count); - + return ver.number === stableVersion + })[0] + downloads = metric(versionData.downloads_count) } else if (version !== null) { - versionData = json.filter(function(ver) { - return ver.number === version; - })[0]; + return ver.number === version + })[0] - downloads = metric(versionData.downloads_count); + downloads = metric(versionData.downloads_count) } else { - throw new InvalidResponse({ underlyingError: new Error('version is null') }); + throw new InvalidResponse({ + underlyingError: new Error('version is null'), + }) } - } else { - throw new InvalidResponse({ underlyingError: new Error('info is invalid') }); + throw new InvalidResponse({ + underlyingError: new Error('info is invalid'), + }) } return { label: label, message: downloads, color: downloadCountColor(downloads), - }; + } } // Metadata static get defaultBadgeData() { - return { label: 'downloads' }; + return { label: 'downloads' } } static get category() { - return 'downloads'; + return 'downloads' } static get url() { return { base: 'gem', format: '(dt|dtv|dv)/(.+)', - capture: ['info', 'rubygem'] - }; + capture: ['info', 'rubygem'], + } } static get examples() { @@ -160,66 +153,57 @@ class GemDownloads extends BaseJsonService { { title: 'Gem', previewUrl: 'dv/rails/stable', - keywords: [ - 'ruby' - ] + keywords: ['ruby'], }, { title: 'Gem', previewUrl: 'dv/rails/4.1.0', - keywords: [ - 'ruby' - ] + keywords: ['ruby'], }, { title: 'Gem', previewUrl: 'dtv/rails', - keywords: [ - 'ruby' - ] + keywords: ['ruby'], }, { title: 'Gem', previewUrl: 'dt/rails', - keywords: [ - 'ruby' - ] + keywords: ['ruby'], }, - ]; + ] } -}; +} class GemOwner extends BaseJsonService { - - async handle({user}) { - const url = `https://rubygems.org/api/v1/owners/${user}/gems.json`; + async handle({ user }) { + const url = `https://rubygems.org/api/v1/owners/${user}/gems.json` const json = await this._requestJson({ url, schema: Joi.array(), - }); - const count = json.length; + }) + const count = json.length return { message: count, - color: floorCountColor(count, 10, 50, 100) - }; + color: floorCountColor(count, 10, 50, 100), + } } // Metadata static get defaultBadgeData() { - return { label: 'gems' }; + return { label: 'gems' } } static get category() { - return 'other'; + return 'other' } static get url() { return { base: 'gem/u', format: '(.+)', - capture: ['user'] - }; + capture: ['user'], + } } static get examples() { @@ -227,66 +211,63 @@ class GemOwner extends BaseJsonService { { title: 'Gems', previewUrl: 'raphink', - keywords: [ - 'ruby' - ] - } - ]; + keywords: ['ruby'], + }, + ] } -}; +} class GemRank extends BaseJsonService { - _getApiUrl(repo, totalRank, dailyRank) { - let endpoint; + let endpoint if (totalRank) { - endpoint = '/total_ranking.json'; + endpoint = '/total_ranking.json' } else if (dailyRank) { - endpoint = '/daily_ranking.json'; + endpoint = '/daily_ranking.json' } - return `http://bestgems.org/api/v1/gems/${repo}${endpoint}`; + return `http://bestgems.org/api/v1/gems/${repo}${endpoint}` } - async handle({info, repo}) { - const totalRank = (info === 'rt'); - const dailyRank = (info === 'rd'); - const url = this._getApiUrl(repo, totalRank, dailyRank); + async handle({ info, repo }) { + const totalRank = info === 'rt' + const dailyRank = info === 'rd' + const url = this._getApiUrl(repo, totalRank, dailyRank) const json = await this._requestJson({ url, schema: Joi.array(), - }); + }) - let rank; + let rank if (totalRank) { - rank = json[0].total_ranking; + rank = json[0].total_ranking } else if (dailyRank) { - rank = json[0].daily_ranking; + rank = json[0].daily_ranking } - const count = Math.floor(100000 / rank); - let message = ordinalNumber(rank); + const count = Math.floor(100000 / rank) + let message = ordinalNumber(rank) message += totalRank ? '' : ' daily' return { message: message, - color: floorCountColor(count, 10, 50, 100) - }; + color: floorCountColor(count, 10, 50, 100), + } } // Metadata static get defaultBadgeData() { - return { label: 'rank' }; + return { label: 'rank' } } static get category() { - return 'rating'; + return 'rating' } static get url() { return { base: 'gem', format: '(rt|rd)/(.+)', - capture: ['info', 'repo'] - }; + capture: ['info', 'repo'], + } } static get examples() { @@ -294,20 +275,16 @@ class GemRank extends BaseJsonService { { title: 'Gems', previewUrl: 'rt/puppet', - keywords: [ - 'ruby' - ] + keywords: ['ruby'], }, { title: 'Gems', previewUrl: 'rd/facter', - keywords: [ - 'ruby' - ] - } - ]; + keywords: ['ruby'], + }, + ] } -}; +} module.exports = { GemVersion, diff --git a/services/gem/gem.tester.js b/services/gem/gem.tester.js index 9801f5bb62f91fe471a228518080a72ab3eb9857..6ee7d62249b275d5e20abdc7bee5a4816cfd1071 100644 --- a/services/gem/gem.tester.js +++ b/services/gem/gem.tester.js @@ -1,149 +1,161 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') const { isVPlusDottedVersionAtLeastOne, - isMetric -} = require('../test-validators'); -const isOrdinalNumber = Joi.string().regex(/^[1-9][0-9]+(ᵗʰ|ˢᵗ|ⁿᵈ|ʳᵈ)$/); -const isOrdinalNumberDaily = Joi.string().regex(/^[1-9][0-9]+(ᵗʰ|ˢᵗ|ⁿᵈ|ʳᵈ) daily$/); - -const t = new ServiceTester({ id: 'gem', title: 'Ruby Gems' }); -module.exports = t; + isMetric, +} = require('../test-validators') +const isOrdinalNumber = Joi.string().regex(/^[1-9][0-9]+(ᵗʰ|ˢᵗ|ⁿᵈ|ʳᵈ)$/) +const isOrdinalNumberDaily = Joi.string().regex( + /^[1-9][0-9]+(ᵗʰ|ˢᵗ|ⁿᵈ|ʳᵈ) daily$/ +) +const t = new ServiceTester({ id: 'gem', title: 'Ruby Gems' }) +module.exports = t // version endpoint t.create('version (valid)') .get('/v/formatador.json') - .expectJSONTypes(Joi.object().keys({ - name: 'gem', - value: isVPlusDottedVersionAtLeastOne - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'gem', + value: isVPlusDottedVersionAtLeastOne, + }) + ) t.create('version (not found)') .get('/v/not-a-package.json') - .expectJSON({name: 'gem', value: 'not found'}); + .expectJSON({ name: 'gem', value: 'not found' }) t.create('version (connection error)') .get('/v/formatador.json') .networkOff() - .expectJSON({name: 'gem', value: 'inaccessible'}); - + .expectJSON({ name: 'gem', value: 'inaccessible' }) // downloads endpoints // total downloads t.create('total downloads (valid)') .get('/dt/rails.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetric - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetric, + }) + ) t.create('total downloads (not found)') .get('/dt/not-a-package.json') - .expectJSON({name: 'downloads', value: 'not found'}); + .expectJSON({ name: 'downloads', value: 'not found' }) t.create('total downloads (connection error)') .get('/dt/rails.json') .networkOff() - .expectJSON({name: 'downloads', value: 'inaccessible'}); - + .expectJSON({ name: 'downloads', value: 'inaccessible' }) // version downloads t.create('version downloads (valid, stable version)') .get('/dv/rails/stable.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads@stable', - value: isMetric - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads@stable', + value: isMetric, + }) + ) t.create('version downloads (valid, specific version)') .get('/dv/rails/4.1.0.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads@4.1.0', - value: isMetric - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads@4.1.0', + value: isMetric, + }) + ) t.create('version downloads (package not found)') .get('/dv/not-a-package/4.1.0.json') - .expectJSON({name: 'downloads', value: 'not found'}); + .expectJSON({ name: 'downloads', value: 'not found' }) t.create('version downloads (valid package, invalid version)') .get('/dv/rails/not-a-version.json') - .expectJSON({name: 'downloads', value: 'invalid'}); + .expectJSON({ name: 'downloads', value: 'invalid' }) t.create('version downloads (valid package, version not specified)') .get('/dv/rails.json') - .expectJSON({name: 'downloads', value: 'invalid'}); + .expectJSON({ name: 'downloads', value: 'invalid' }) t.create('version downloads (connection error)') .get('/dv/rails/4.1.0.json') .networkOff() - .expectJSON({name: 'downloads', value: 'inaccessible'}); - + .expectJSON({ name: 'downloads', value: 'inaccessible' }) // latest version downloads t.create('latest version downloads (valid)') .get('/dtv/rails.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads@latest', - value: isMetric - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads@latest', + value: isMetric, + }) + ) t.create('latest version downloads (not found)') .get('/dtv/not-a-package.json') - .expectJSON({name: 'downloads', value: 'not found'}); + .expectJSON({ name: 'downloads', value: 'not found' }) t.create('latest version downloads (connection error)') .get('/dtv/rails.json') .networkOff() - .expectJSON({name: 'downloads', value: 'inaccessible'}); - + .expectJSON({ name: 'downloads', value: 'inaccessible' }) // users endpoint t.create('users (valid)') .get('/u/raphink.json') - .expectJSONTypes(Joi.object().keys({ - name: 'gems', - value: Joi.string().regex(/^[0-9]+$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'gems', + value: Joi.string().regex(/^[0-9]+$/), + }) + ) t.create('users (not found)') .get('/u/not-a-package.json') - .expectJSON({name: 'gems', value: 'not found'}); + .expectJSON({ name: 'gems', value: 'not found' }) t.create('users (connection error)') .get('/u/raphink.json') .networkOff() - .expectJSON({name: 'gems', value: 'inaccessible'}); - + .expectJSON({ name: 'gems', value: 'inaccessible' }) // rank endpoint t.create('total rank (valid)') .get('/rt/rspec-puppet-facts.json') - .expectJSONTypes(Joi.object().keys({ - name: 'rank', - value: isOrdinalNumber - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'rank', + value: isOrdinalNumber, + }) + ) t.create('daily rank (valid)') .get('/rd/rspec-puppet-facts.json') - .expectJSONTypes(Joi.object().keys({ - name: 'rank', - value: isOrdinalNumberDaily - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'rank', + value: isOrdinalNumberDaily, + }) + ) t.create('rank (not found)') .get('/rt/not-a-package.json') - .expectJSON({name: 'rank', value: 'not found'}); + .expectJSON({ name: 'rank', value: 'not found' }) t.create('rank (connection error)') .get('/rt/rspec-puppet-facts.json') .networkOff() - .expectJSON({name: 'rank', value: 'inaccessible'}); + .expectJSON({ name: 'rank', value: 'inaccessible' }) diff --git a/services/gemnasium/gemnasium.tester.js b/services/gemnasium/gemnasium.tester.js index 21c064626441beee614518bb3225cf30fef4e99c..6f23ffa235e5d96460f191028151b9aaa2f9fb29 100644 --- a/services/gemnasium/gemnasium.tester.js +++ b/services/gemnasium/gemnasium.tester.js @@ -1,21 +1,20 @@ -'use strict'; +'use strict' -const ServiceTester = require('../service-tester'); +const ServiceTester = require('../service-tester') const { expect } = require('chai') -const { isDeprecated } = require('../../lib/deprecation-helpers'); - -const t = new ServiceTester({ id: 'gemnasium', title: 'gemnasium' }); -module.exports = t; +const { isDeprecated } = require('../../lib/deprecation-helpers') +const t = new ServiceTester({ id: 'gemnasium', title: 'gemnasium' }) +module.exports = t t.create('no longer available (previously dependencies)') .get('/mathiasbynens/he.json') .afterJSON(function(badge) { if (isDeprecated('gemnasium')) { - expect(badge.name).to.equal('gemnasium'); - expect(badge.value).to.equal('no longer available'); + expect(badge.name).to.equal('gemnasium') + expect(badge.value).to.equal('no longer available') } else { - expect(badge.name).to.equal('dependencies'); + expect(badge.name).to.equal('dependencies') } - }); + }) diff --git a/services/github/auth/admin.js b/services/github/auth/admin.js index 7a7ee054bab9a1a211ce8b349b5946255e4d8583..01f1d0265ea9cd467bf7c5accf259d2be1cf512e 100644 --- a/services/github/auth/admin.js +++ b/services/github/auth/admin.js @@ -1,8 +1,8 @@ -'use strict'; +'use strict' -const crypto = require('crypto'); -const { serializeDebugInfo } = require('../../../lib/github-auth'); -const serverSecrets = require('../../../lib/server-secrets'); +const crypto = require('crypto') +const { serializeDebugInfo } = require('../../../lib/github-auth') +const serverSecrets = require('../../../lib/server-secrets') function setRoutes(server) { // Allow the admin to obtain the tokens for operational and debugging @@ -21,13 +21,13 @@ function setRoutes(server) { if (!crypto.timingSafeEqual(ask.password, serverSecrets.shieldsSecret)) { // An unknown entity tries to connect. Let the connection linger for a minute. return setTimeout(function() { - end('Invalid secret.'); - }, 10000); + end('Invalid secret.') + }, 10000) } - end(serializeDebugInfo({ sanitize: false })); - }); + end(serializeDebugInfo({ sanitize: false })) + }) } module.exports = { setRoutes, -}; +} diff --git a/services/github/github-api-provider.integration.js b/services/github/github-api-provider.integration.js index d584bedab180a042ce0f68fd95e5aeaa43df2728..27e72cf4d49029ca4335af1ca711538cf09f3bd0 100644 --- a/services/github/github-api-provider.integration.js +++ b/services/github/github-api-provider.integration.js @@ -1,37 +1,37 @@ -'use strict'; +'use strict' -const { expect } = require('chai'); -const GithubApiProvider = require('./github-api-provider'); +const { expect } = require('chai') +const GithubApiProvider = require('./github-api-provider') describe('Github API provider', function() { - const baseUrl = process.env.GITHUB_URL || 'https://api.github.com'; + const baseUrl = process.env.GITHUB_URL || 'https://api.github.com' - let githubApiProvider; + let githubApiProvider before(function() { - githubApiProvider = new GithubApiProvider({ baseUrl }); - }); + githubApiProvider = new GithubApiProvider({ baseUrl }) + }) - const headers = []; + const headers = [] async function performOneRequest() { const { res } = await githubApiProvider.requestAsPromise( require('request'), '/repos/rust-lang/rust', {} - ); - expect(res.statusCode).to.equal(200); - headers.push(res.headers); + ) + expect(res.statusCode).to.equal(200) + headers.push(res.headers) } before('should be able to run 10 requests', async function() { - this.timeout(10000); + this.timeout(10000) for (let i = 0; i < 10; ++i) { - await performOneRequest(); + await performOneRequest() } - }); + }) it('should decrement the limit remaining with each request', function() { - const remaining = headers.map(h => +h['x-ratelimit-remaining']); - const expected = Array.from({ length: 10 }, (e, i) => remaining[0] - i); - expect(remaining).to.deep.equal(expected); - }); -}); + const remaining = headers.map(h => +h['x-ratelimit-remaining']) + const expected = Array.from({ length: 10 }, (e, i) => remaining[0] - i) + expect(remaining).to.deep.equal(expected) + }) +}) diff --git a/services/github/github-api-provider.js b/services/github/github-api-provider.js index b0f6f49e6c3106df3a8d0ee34d34ac44ee7ece01..ef6aac2231c4f1ce20be54a4ff55b4940e75232d 100644 --- a/services/github/github-api-provider.js +++ b/services/github/github-api-provider.js @@ -1,6 +1,6 @@ -'use strict'; +'use strict' -const githubAuth = require('../../lib/github-auth'); +const githubAuth = require('../../lib/github-auth') // Provide an interface to the Github API. Manages the base URL. // @@ -9,36 +9,36 @@ const githubAuth = require('../../lib/github-auth'); // to legacy code. class GithubApiProvider { constructor({ baseUrl }) { - this.baseUrl = baseUrl; + this.baseUrl = baseUrl } // Act like request(), but tweak headers and query to avoid hitting a rate // limit. Inject `request` so we can pass in `cachingRequest` from // `request-handler.js`. request(request, url, query, callback) { - const { baseUrl } = this; + const { baseUrl } = this githubAuth.request( request, `${baseUrl}${url}`, query, (err, res, buffer) => { - callback(err, res, buffer); + callback(err, res, buffer) } - ); + ) } requestAsPromise(request, url, query) { return new Promise((resolve, reject) => { this.request(request, url, query, (err, res, buffer) => { if (err) { - reject(err); + reject(err) } else { - resolve({ res, buffer }); + resolve({ res, buffer }) } - }); - }); + }) + }) } } -module.exports = GithubApiProvider; +module.exports = GithubApiProvider diff --git a/services/github/github-api-provider.spec.js b/services/github/github-api-provider.spec.js index 334652dabffed9154855971f709145933ad8e0b9..94b61d4544c17f26a19c3170b19ef9983833b354 100644 --- a/services/github/github-api-provider.spec.js +++ b/services/github/github-api-provider.spec.js @@ -1,20 +1,20 @@ -'use strict'; +'use strict' -const { expect } = require('chai'); -const GithubApiProvider = require('./github-api-provider'); +const { expect } = require('chai') +const GithubApiProvider = require('./github-api-provider') describe('Github API provider', function() { - const baseUrl = 'https://github-api.example.com'; + const baseUrl = 'https://github-api.example.com' - let provider; + let provider beforeEach(function() { - provider = new GithubApiProvider({ baseUrl }); - }); + provider = new GithubApiProvider({ baseUrl }) + }) context('a valid response', function() { - const rateLimit = 12500; - const remaining = 7955; - const nextReset = 123456789; + const rateLimit = 12500 + const remaining = 7955 + const nextReset = 123456789 const mockResponse = { statusCode: 200, headers: { @@ -22,52 +22,52 @@ describe('Github API provider', function() { 'x-ratelimit-remaining': remaining, 'x-ratelimit-reset': nextReset, }, - }; - const mockBuffer = Buffer.alloc(0); + } + const mockBuffer = Buffer.alloc(0) const mockRequest = (...args) => { - const callback = args.pop(); - callback(null, mockResponse, mockBuffer); - }; + const callback = args.pop() + callback(null, mockResponse, mockBuffer) + } it('should invoke the callback', function(done) { provider.request(mockRequest, '/foo', {}, (err, res, buffer) => { - expect(err).to.equal(null); - expect(Object.is(res, mockResponse)).to.be.true; - expect(Object.is(buffer, mockBuffer)).to.be.true; - done(); - }); - }); - }); + expect(err).to.equal(null) + expect(Object.is(res, mockResponse)).to.be.true + expect(Object.is(buffer, mockBuffer)).to.be.true + done() + }) + }) + }) context('an unauthorized response', function() { - const mockResponse = { statusCode: 401 }; - const mockBuffer = Buffer.alloc(0); + const mockResponse = { statusCode: 401 } + const mockBuffer = Buffer.alloc(0) const mockRequest = (...args) => { - const callback = args.pop(); - callback(null, mockResponse, mockBuffer); - }; + const callback = args.pop() + callback(null, mockResponse, mockBuffer) + } it('should invoke the callback', function(done) { provider.request(mockRequest, '/foo', {}, (err, res, buffer) => { - expect(err).to.equal(null); + expect(err).to.equal(null) // Add more? - done(); - }); - }); - }); + done() + }) + }) + }) context('a connection error', function() { const mockRequest = (...args) => { - const callback = args.pop(); - callback(Error('connection timeout')); - }; + const callback = args.pop() + callback(Error('connection timeout')) + } it('should pass the error to the callback', function(done) { provider.request(mockRequest, '/foo', {}, (err, res, buffer) => { - expect(err).to.be.an.instanceof(Error); - expect(err.message).to.equal('connection timeout'); - done(); - }); - }); - }); -}); + expect(err).to.be.an.instanceof(Error) + expect(err.message).to.equal('connection timeout') + done() + }) + }) + }) +}) diff --git a/services/github/github.tester.js b/services/github/github.tester.js index 42af7fa63909276a552978339f802d075c74afeb..6b030372b8179deb87459a9af178f95432f6bcdf 100644 --- a/services/github/github.tester.js +++ b/services/github/github.tester.js @@ -1,719 +1,902 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') const { isMetric, isMetricOpenIssues, isMetricOverTimePeriod, isFileSize, isFormattedDate, - isVPlusDottedVersionAtLeastOne -} = require('../test-validators'); -const colorscheme = require('../../lib/colorscheme.json'); -const {licenseToColor} = require('../../lib/licenses'); -const {makeColor} = require('../../lib/badge-data'); -const mapValues = require('lodash.mapvalues'); -const { invalidJSON } = require('../response-fixtures'); - -const t = new ServiceTester({ id: 'github', title: 'Github' }); -module.exports = t; -const colorsB = mapValues(colorscheme, 'colorB'); -const publicDomainLicenseColor = makeColor(licenseToColor('CC0-1.0')); -const permissiveLicenseColor = colorsB[licenseToColor('MIT')]; -const copyleftLicenseColor = colorsB[licenseToColor('GPL-3.0')]; -const unknownLicenseColor = colorsB[licenseToColor()]; + isVPlusDottedVersionAtLeastOne, +} = require('../test-validators') +const colorscheme = require('../../lib/colorscheme.json') +const { licenseToColor } = require('../../lib/licenses') +const { makeColor } = require('../../lib/badge-data') +const mapValues = require('lodash.mapvalues') +const { invalidJSON } = require('../response-fixtures') + +const t = new ServiceTester({ id: 'github', title: 'Github' }) +module.exports = t +const colorsB = mapValues(colorscheme, 'colorB') +const publicDomainLicenseColor = makeColor(licenseToColor('CC0-1.0')) +const permissiveLicenseColor = colorsB[licenseToColor('MIT')] +const copyleftLicenseColor = colorsB[licenseToColor('GPL-3.0')] +const unknownLicenseColor = colorsB[licenseToColor()] t.create('Public domain license') .get('/license/github/gitignore.json?style=_shields_test') - .expectJSON({ name: 'license', value: 'CC0-1.0', colorB: publicDomainLicenseColor }); + .expectJSON({ + name: 'license', + value: 'CC0-1.0', + colorB: publicDomainLicenseColor, + }) t.create('Copyleft license') .get('/license/ansible/ansible.json?style=_shields_test') - .expectJSON({ name: 'license', value: 'GPL-3.0', colorB: copyleftLicenseColor }); + .expectJSON({ + name: 'license', + value: 'GPL-3.0', + colorB: copyleftLicenseColor, + }) t.create('Permissive license') .get('/license/atom/atom.json?style=_shields_test') - .expectJSON({ name: 'license', value: 'MIT', colorB: permissiveLicenseColor }); + .expectJSON({ name: 'license', value: 'MIT', colorB: permissiveLicenseColor }) t.create('License for repo without a license') .get('/license/badges/badger.json?style=_shields_test') - .expectJSON({ name: 'license', value: 'missing', colorB: colorsB.red }); + .expectJSON({ name: 'license', value: 'missing', colorB: colorsB.red }) t.create('License for repo with an unrecognized license') .get('/license/philokev/sopel-noblerealms.json?style=_shields_test') - .expectJSON({ name: 'license', value: 'unknown', colorB: unknownLicenseColor }); + .expectJSON({ + name: 'license', + value: 'unknown', + colorB: unknownLicenseColor, + }) t.create('License with SPDX id not appearing in configuration') .get('/license/user1/project-with-EFL-license.json?style=_shields_test') - .intercept(nock => nock('https://api.github.com') - .get('/repos/user1/project-with-EFL-license') - .query(true) - // GitHub API currently returns "other" as a key for repo with EFL license - .reply(200, { - license: { - key: 'efl-1.0', - name: 'Eiffel Forum License v1.0', - spdx_id: 'EFL-1.0', - url: 'https://api.github.com/licenses/efl-1.0', - featured: true - } - })) - .expectJSON({ name: 'license', value: 'EFL-1.0', colorB: unknownLicenseColor }); + .intercept(nock => + nock('https://api.github.com') + .get('/repos/user1/project-with-EFL-license') + .query(true) + // GitHub API currently returns "other" as a key for repo with EFL license + .reply(200, { + license: { + key: 'efl-1.0', + name: 'Eiffel Forum License v1.0', + spdx_id: 'EFL-1.0', + url: 'https://api.github.com/licenses/efl-1.0', + featured: true, + }, + }) + ) + .expectJSON({ + name: 'license', + value: 'EFL-1.0', + colorB: unknownLicenseColor, + }) t.create('License for unknown repo') .get('/license/user1/github-does-not-have-this-repo.json?style=_shields_test') - .expectJSON({ name: 'license', value: 'repo not found', colorB: colorsB.lightgrey }); + .expectJSON({ + name: 'license', + value: 'repo not found', + colorB: colorsB.lightgrey, + }) t.create('License - API rate limit exceeded') .get('/license/user1/repo1.json?style=_shields_test') - .intercept(nock => nock('https://api.github.com') - .get('/repos/user1/repo1') - .query(true) - .reply(403, { - message: "API rate limit exceeded for 123.123.123.123. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)", - documentation_url: "https://developer.github.com/v3/#rate-limiting" - })) - .expectJSON({ name: 'license', value: 'invalid', colorB: colorsB.lightgrey }); + .intercept(nock => + nock('https://api.github.com') + .get('/repos/user1/repo1') + .query(true) + .reply(403, { + message: + "API rate limit exceeded for 123.123.123.123. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)", + documentation_url: 'https://developer.github.com/v3/#rate-limiting', + }) + ) + .expectJSON({ name: 'license', value: 'invalid', colorB: colorsB.lightgrey }) t.create('Contributors') .get('/contributors/cdnjs/cdnjs.json') - .expectJSONTypes(Joi.object().keys({ - name: 'contributors', - value: Joi.string().regex(/^\w+$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'contributors', + value: Joi.string().regex(/^\w+$/), + }) + ) t.create('Contributors (repo not found)') .get('/contributors/badges/helmets.json') .expectJSON({ name: 'contributors', - value: 'repo not found' - }); + value: 'repo not found', + }) t.create('GitHub closed pull requests') .get('/issues-pr-closed/badges/shields.json') - .expectJSONTypes(Joi.object().keys({ - name: 'pull requests', - value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? closed$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'pull requests', + value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? closed$/), + }) + ) t.create('GitHub closed pull requests raw') .get('/issues-pr-closed-raw/badges/shields.json') - .expectJSONTypes(Joi.object().keys({ - name: 'closed pull requests', - value: Joi.string().regex(/^\w+?$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'closed pull requests', + value: Joi.string().regex(/^\w+?$/), + }) + ) t.create('GitHub pull requests') .get('/issues-pr/badges/shields.json') - .expectJSONTypes(Joi.object().keys({ - name: 'pull requests', - value: isMetricOpenIssues - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'pull requests', + value: isMetricOpenIssues, + }) + ) t.create('GitHub pull requests raw') .get('/issues-pr-raw/badges/shields.json') - .expectJSONTypes(Joi.object().keys({ - name: 'open pull requests', - value: isMetric - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'open pull requests', + value: isMetric, + }) + ) t.create('GitHub closed issues') .get('/issues-closed/badges/shields.json') - .expectJSONTypes(Joi.object().keys({ - name: 'issues', - value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? closed$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'issues', + value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? closed$/), + }) + ) t.create('GitHub closed issues raw') .get('/issues-closed-raw/badges/shields.json') - .expectJSONTypes(Joi.object().keys({ - name: 'closed issues', - value: Joi.string().regex(/^\w+\+?$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'closed issues', + value: Joi.string().regex(/^\w+\+?$/), + }) + ) t.create('GitHub open issues') .get('/issues/badges/shields.json') - .expectJSONTypes(Joi.object().keys({ - name: 'issues', - value: isMetricOpenIssues - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'issues', + value: isMetricOpenIssues, + }) + ) t.create('GitHub open issues raw') .get('/issues-raw/badges/shields.json') - .expectJSONTypes(Joi.object().keys({ name: 'open issues', value: isMetric })); + .expectJSONTypes(Joi.object().keys({ name: 'open issues', value: isMetric })) t.create('GitHub open issues by label is > zero') .get('/issues/badges/shields/service-badge.json') - .expectJSONTypes(Joi.object().keys({ - name: 'service-badge issues', - value: isMetricOpenIssues - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'service-badge issues', + value: isMetricOpenIssues, + }) + ) t.create('GitHub open issues by multi-word label is > zero') .get('/issues/Cockatrice/Cockatrice/App%20-%20Cockatrice.json') - .expectJSONTypes(Joi.object().keys({ - name: '"App - Cockatrice" issues', - value: isMetricOpenIssues - })); + .expectJSONTypes( + Joi.object().keys({ + name: '"App - Cockatrice" issues', + value: isMetricOpenIssues, + }) + ) t.create('GitHub open issues by label (raw)') .get('/issues-raw/badges/shields/service-badge.json') - .expectJSONTypes(Joi.object().keys({ - name: 'open service-badge issues', - value: isMetric - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'open service-badge issues', + value: isMetric, + }) + ) t.create('GitHub open issues (repo not found)') .get('/issues-raw/badges/helmets.json') .expectJSON({ name: 'open issues', - value: 'repo not found' - }); + value: 'repo not found', + }) t.create('GitHub open pull requests by label') .get('/issues-pr/badges/shields/service-badge.json') - .expectJSONTypes(Joi.object().keys({ - name: 'service-badge pull requests', - value: isMetricOpenIssues - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'service-badge pull requests', + value: isMetricOpenIssues, + }) + ) t.create('GitHub open pull requests by label (raw)') .get('/issues-pr-raw/badges/shields/service-badge.json') - .expectJSONTypes(Joi.object().keys({ - name: 'open service-badge pull requests', - value: isMetric - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'open service-badge pull requests', + value: isMetric, + }) + ) t.create('Followers') .get('/followers/webcaetano.json') - .expectJSONTypes(Joi.object().keys({ - name: 'followers', - value: Joi.string().regex(/^\w+$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'followers', + value: Joi.string().regex(/^\w+$/), + }) + ) t.create('Followers (user not found)') .get('/followers/PyvesB2.json') .expectJSON({ name: 'followers', - value: 'user not found' - }); + value: 'user not found', + }) t.create('Watchers') .get('/watchers/badges/shields.json') - .expectJSONTypes(Joi.object().keys({ - name: 'watchers', - value: Joi.number().integer().positive() - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'watchers', + value: Joi.number() + .integer() + .positive(), + }) + ) t.create('Watchers (repo not found)') .get('/watchers/badges/helmets.json') .expectJSON({ name: 'watchers', - value: 'repo not found' - }); + value: 'repo not found', + }) t.create('Stars') .get('/stars/badges/shields.json') - .expectJSONTypes(Joi.object().keys({ - name: 'stars', - value: Joi.string().regex(/^\w+$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'stars', + value: Joi.string().regex(/^\w+$/), + }) + ) t.create('Stars (repo not found)') .get('/stars/badges/helmets.json') .expectJSON({ name: 'stars', - value: 'repo not found' - }); + value: 'repo not found', + }) t.create('Forks') .get('/forks/badges/shields.json') - .expectJSONTypes(Joi.object().keys({ - name: 'forks', - value: Joi.number().integer().positive() - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'forks', + value: Joi.number() + .integer() + .positive(), + }) + ) t.create('Forks (repo not found)') .get('/forks/badges/helmets.json') .expectJSON({ name: 'forks', - value: 'repo not found' - }); + value: 'repo not found', + }) t.create('Commits since') - .get('/commits-since/badges/shields/a0663d8da53fb712472c02665e6ff7547ba945b7.json') - .expectJSONTypes(Joi.object().keys({ - name: Joi.string().regex(/^(commits since){1}[\s\S]+$/), - value: Joi.string().regex(/^\w+$/) - })); + .get( + '/commits-since/badges/shields/a0663d8da53fb712472c02665e6ff7547ba945b7.json' + ) + .expectJSONTypes( + Joi.object().keys({ + name: Joi.string().regex(/^(commits since){1}[\s\S]+$/), + value: Joi.string().regex(/^\w+$/), + }) + ) t.create('Commits since by latest release') .get('/commits-since/microsoft/typescript/latest.json') - .expectJSONTypes(Joi.object().keys({ - name: Joi.string().regex(/^(commits since){1}[\s\S]+$/), - value: Joi.string().regex(/^\d+\w?$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: Joi.string().regex(/^(commits since){1}[\s\S]+$/), + value: Joi.string().regex(/^\d+\w?$/), + }) + ) t.create('Release') .get('/release/photonstorm/phaser.json') - .expectJSONTypes(Joi.object().keys({ name: 'release', value: Joi.string() })); + .expectJSONTypes(Joi.object().keys({ name: 'release', value: Joi.string() })) t.create('Release (repo not found)') .get('/release/badges/helmets.json') - .expectJSON({ name: 'release', value: 'repo not found' }); + .expectJSON({ name: 'release', value: 'repo not found' }) t.create('(pre-)Release') .get('/release/photonstorm/phaser/all.json') - .expectJSONTypes(Joi.object().keys({ name: 'release', value: Joi.string() })); + .expectJSONTypes(Joi.object().keys({ name: 'release', value: Joi.string() })) t.create('Release Date. e.g release date|today') -.get('/release-date/microsoft/vscode.json') -.expectJSONTypes(Joi.object().keys({ - name: 'release date', - value: isFormattedDate -})); + .get('/release-date/microsoft/vscode.json') + .expectJSONTypes( + Joi.object().keys({ + name: 'release date', + value: isFormattedDate, + }) + ) t.create('Release Date - Custom Label. e.g myRelease|today') -.get('/release-date/microsoft/vscode.json?label=myRelease') -.expectJSONTypes(Joi.object().keys({ - name: 'myRelease', - value: isFormattedDate -})); - -t.create('Release Date - Should return `no releases or repo not found` for invalid repo') -.get('/release-date/not-valid-name/not-valid-repo.json') -.expectJSON({ name: 'release date', value: 'no releases or repo not found' }); + .get('/release-date/microsoft/vscode.json?label=myRelease') + .expectJSONTypes( + Joi.object().keys({ + name: 'myRelease', + value: isFormattedDate, + }) + ) + +t.create( + 'Release Date - Should return `no releases or repo not found` for invalid repo' +) + .get('/release-date/not-valid-name/not-valid-repo.json') + .expectJSON({ name: 'release date', value: 'no releases or repo not found' }) t.create('(Pre-)Release Date. e.g release date|today') -.get('/release-date-pre/microsoft/vscode.json') -.expectJSONTypes(Joi.object().keys({ - name: 'release date', - value: isFormattedDate -})); + .get('/release-date-pre/microsoft/vscode.json') + .expectJSONTypes( + Joi.object().keys({ + name: 'release date', + value: isFormattedDate, + }) + ) t.create('(Pre-)Release Date - Custom Label. e.g myRelease|today') -.get('/release-date-pre/microsoft/vscode.json?label=myRelease') -.expectJSONTypes(Joi.object().keys({ - name: 'myRelease', - value: isFormattedDate -})); - -t.create('(Pre-)Release Date - Should return `no releases or repo not found` for invalid repo') -.get('/release-date-pre/not-valid-name/not-valid-repo.json') -.expectJSON({ name: 'release date', value: 'no releases or repo not found' }); - + .get('/release-date-pre/microsoft/vscode.json?label=myRelease') + .expectJSONTypes( + Joi.object().keys({ + name: 'myRelease', + value: isFormattedDate, + }) + ) + +t.create( + '(Pre-)Release Date - Should return `no releases or repo not found` for invalid repo' +) + .get('/release-date-pre/not-valid-name/not-valid-repo.json') + .expectJSON({ name: 'release date', value: 'no releases or repo not found' }) t.create('Tag') .get('/tag/photonstorm/phaser.json') - .expectJSONTypes(Joi.object().keys({ name: 'tag', value: Joi.string() })); + .expectJSONTypes(Joi.object().keys({ name: 'tag', value: Joi.string() })) t.create('Tag (repo not found)') .get('/tag/badges/helmets.json') - .expectJSON({ name: 'tag', value: 'repo not found' }); + .expectJSON({ name: 'tag', value: 'repo not found' }) t.create('Package version') .get('/package-json/v/badges/shields.json') - .expectJSONTypes(Joi.object().keys({ - name: 'package', - value: isVPlusDottedVersionAtLeastOne - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'package', + value: isVPlusDottedVersionAtLeastOne, + }) + ) t.create('Package version (repo not found)') .get('/package-json/v/badges/helmets.json') .expectJSON({ name: 'package', - value: 'repo not found' - }); + value: 'repo not found', + }) t.create('Package name') .get('/package-json/n/badges/shields.json') - .expectJSON({ name: 'package name', value: 'gh-badges' }); + .expectJSON({ name: 'package name', value: 'gh-badges' }) t.create('Package name - Custom label') .get('/package-json/name/badges/shields.json?label=Dev Name') - .expectJSON({ name: 'Dev Name', value: 'gh-badges' }); + .expectJSON({ name: 'Dev Name', value: 'gh-badges' }) t.create('Package array') .get('/package-json/keywords/badges/shields.json') - .expectJSONTypes(Joi.object().keys({ - name: 'package keywords', - value: Joi.string().regex(/.*?,/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'package keywords', + value: Joi.string().regex(/.*?,/), + }) + ) t.create('Package object') .get('/package-json/dependencies/badges/shields.json') - .expectJSON({ name: 'package dependencies', value: 'invalid data' }); + .expectJSON({ name: 'package dependencies', value: 'invalid data' }) t.create('Manifest version') .get('/manifest-json/v/RedSparr0w/IndieGala-Helper.json') - .expectJSONTypes(Joi.object().keys({ - name: 'manifest', - value: isVPlusDottedVersionAtLeastOne - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'manifest', + value: isVPlusDottedVersionAtLeastOne, + }) + ) t.create('Manifest name') .get('/manifest-json/n/RedSparr0w/IndieGala-Helper.json') - .expectJSON({ name: 'manifest name', value: 'IndieGala Helper' }); + .expectJSON({ name: 'manifest name', value: 'IndieGala Helper' }) t.create('Manifest array') .get('/manifest-json/permissions/RedSparr0w/IndieGala-Helper.json') - .expectJSONTypes(Joi.object().keys({ - name: 'manifest permissions', - value: Joi.string().regex(/.*?,/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'manifest permissions', + value: Joi.string().regex(/.*?,/), + }) + ) t.create('Manifest object') .get('/manifest-json/background/RedSparr0w/IndieGala-Helper.json') - .expectJSON({ name: 'manifest background', value: 'invalid data' }); + .expectJSON({ name: 'manifest background', value: 'invalid data' }) t.create('Manifest invalid json response') .get('/manifest-json/v/RedSparr0w/not-a-real-project.json') - .expectJSON({ name: 'manifest', value: 'repo not found' }); + .expectJSON({ name: 'manifest', value: 'repo not found' }) t.create('Manifest no network connection') .get('/manifest-json/v/RedSparr0w/IndieGala-Helper.json') .networkOff() - .expectJSON({ name: 'manifest', value: 'inaccessible' }); + .expectJSON({ name: 'manifest', value: 'inaccessible' }) t.create('File size') .get('/size/webcaetano/craft/build/phaser-craft.min.js.json') - .expectJSONTypes(Joi.object().keys({ name: 'size', value: isFileSize })); + .expectJSONTypes(Joi.object().keys({ name: 'size', value: isFileSize })) t.create('File size 404') .get('/size/webcaetano/craft/build/does-not-exist.min.js.json') - .expectJSON({ name: 'size', value: 'repo or file not found' }); + .expectJSON({ name: 'size', value: 'repo or file not found' }) t.create('File size for "not a regular file"') .get('/size/webcaetano/craft/build.json') - .expectJSON({ name: 'size', value: 'not a regular file' }); + .expectJSON({ name: 'size', value: 'not a regular file' }) t.create('Downloads all releases') .get('/downloads/photonstorm/phaser/total.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: Joi.string().regex(/^\w+\s+total$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: Joi.string().regex(/^\w+\s+total$/), + }) + ) t.create('Downloads all releases (repo not found)') .get('/downloads/badges/helmets/total.json') .expectJSON({ name: 'downloads', - value: 'repo or release not found' - }); + value: 'repo or release not found', + }) t.create('downloads for latest release') .get('/downloads/photonstorm/phaser/latest/total.json') - .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })); + .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })) t.create('downloads-pre for latest release') .get('/downloads-pre/photonstorm/phaser/latest/total.json') - .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })); + .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })) t.create('downloads for release without slash') .get('/downloads/atom/atom/v0.190.0/total.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? v0\.190\.0$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? v0\.190\.0$/), + }) + ) t.create('downloads for specific asset without slash') .get('/downloads/atom/atom/v0.190.0/atom-amd64.deb.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? v0\.190\.0 \[atom-amd64\.deb\]$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: Joi.string().regex( + /^[0-9]+[kMGTPEZY]? v0\.190\.0 \[atom-amd64\.deb\]$/ + ), + }) + ) t.create('downloads for specific asset from latest release') .get('/downloads/atom/atom/latest/atom-amd64.deb.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? \[atom-amd64\.deb\]$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? \[atom-amd64\.deb\]$/), + }) + ) t.create('downloads-pre for specific asset from latest release') .get('/downloads-pre/atom/atom/latest/atom-amd64.deb.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? \[atom-amd64\.deb\]$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? \[atom-amd64\.deb\]$/), + }) + ) t.create('downloads for release with slash') .get('/downloads/NHellFire/dban/stable/v2.2.8/total.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? stable\/v2\.2\.8$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? stable\/v2\.2\.8$/), + }) + ) t.create('downloads for specific asset with slash') .get('/downloads/NHellFire/dban/stable/v2.2.8/dban-2.2.8_i586.iso.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: Joi.string().regex(/^[0-9]+[kMGTPEZY]? stable\/v2\.2\.8 \[dban-2\.2\.8_i586\.iso\]$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: Joi.string().regex( + /^[0-9]+[kMGTPEZY]? stable\/v2\.2\.8 \[dban-2\.2\.8_i586\.iso\]$/ + ), + }) + ) t.create('downloads for unknown release') .get('/downloads/atom/atom/does-not-exist/total.json') - .expectJSON({ name: 'downloads', value: 'repo or release not found' }); + .expectJSON({ name: 'downloads', value: 'repo or release not found' }) t.create('hit counter') .get('/search/torvalds/linux/goto.json') - .expectJSONTypes(Joi.object().keys({ name: 'goto counter', value: isMetric })); + .expectJSONTypes(Joi.object().keys({ name: 'goto counter', value: isMetric })) t.create('hit counter for nonexistent repo') .get('/search/torvalds/not-linux/goto.json') - .expectJSON({ name: 'goto counter', value: 'repo not found' }); + .expectJSON({ name: 'goto counter', value: 'repo not found' }) t.create('commit activity (1 year)') .get('/commit-activity/y/eslint/eslint.json') - .expectJSONTypes(Joi.object().keys({ - name: 'commit activity', - value: isMetricOverTimePeriod, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'commit activity', + value: isMetricOverTimePeriod, + }) + ) t.create('commit activity (4 weeks)') .get('/commit-activity/4w/eslint/eslint.json') - .expectJSONTypes(Joi.object().keys({ - name: 'commit activity', - value: isMetricOverTimePeriod, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'commit activity', + value: isMetricOverTimePeriod, + }) + ) t.create('commit activity (1 week)') .get('/commit-activity/w/eslint/eslint.json') - .expectJSONTypes(Joi.object().keys({ - name: 'commit activity', - value: isMetricOverTimePeriod, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'commit activity', + value: isMetricOverTimePeriod, + }) + ) t.create('commit activity (repo not found)') .get('/commit-activity/w/badges/helmets.json') .expectJSON({ name: 'commit activity', value: 'repo not found', - }); + }) t.create('last commit (recent)') .get('/last-commit/eslint/eslint.json') - .expectJSONTypes(Joi.object().keys({ name: 'last commit', value: isFormattedDate })); + .expectJSONTypes( + Joi.object().keys({ name: 'last commit', value: isFormattedDate }) + ) t.create('last commit (ancient)') .get('/last-commit/badges/badgr.co.json') - .expectJSON({ name: 'last commit', value: 'january 2014' }); + .expectJSON({ name: 'last commit', value: 'january 2014' }) t.create('last commit (on branch)') .get('/last-commit/badges/badgr.co/shielded.json') - .expectJSON({ name: 'last commit', value: 'july 2013' }); + .expectJSON({ name: 'last commit', value: 'july 2013' }) t.create('last commit (repo not found)') .get('/last-commit/badges/helmets.json') - .expectJSON({ name: 'last commit', value: 'repo not found' }); + .expectJSON({ name: 'last commit', value: 'repo not found' }) t.create('github issue state') .get('/issues/detail/s/badges/shields/979.json') - .expectJSONTypes(Joi.object().keys({ - name: 'issue 979', - value: Joi.equal('open', 'closed'), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'issue 979', + value: Joi.equal('open', 'closed'), + }) + ) t.create('github issue state (repo not found)') .get('/issues/detail/s/badges/helmets/979.json') .expectJSON({ name: 'issue/pull request 979', value: 'issue, pull request or repo not found', - }); + }) t.create('github issue title') .get('/issues/detail/title/badges/shields/979.json') - .expectJSONTypes(Joi.object().keys({ - name: 'issue 979', - value: 'Github rate limits cause transient service test failures in CI', - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'issue 979', + value: 'Github rate limits cause transient service test failures in CI', + }) + ) t.create('github issue author') .get('/issues/detail/u/badges/shields/979.json') - .expectJSONTypes(Joi.object().keys({ name: 'author', value: 'paulmelnikow' })); + .expectJSONTypes(Joi.object().keys({ name: 'author', value: 'paulmelnikow' })) t.create('github issue label') .get('/issues/detail/label/badges/shields/979.json') - .expectJSONTypes(Joi.object().keys({ - name: 'label', - value: Joi.equal('bug | developer-experience', 'developer-experience | bug'), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'label', + value: Joi.equal( + 'bug | developer-experience', + 'developer-experience | bug' + ), + }) + ) t.create('github issue comments') .get('/issues/detail/comments/badges/shields/979.json') - .expectJSONTypes(Joi.object().keys({ - name: 'comments', - value: Joi.number().greater(15), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'comments', + value: Joi.number().greater(15), + }) + ) t.create('github issue age') .get('/issues/detail/age/badges/shields/979.json') - .expectJSONTypes(Joi.object().keys({ name: 'created', value: isFormattedDate })); + .expectJSONTypes( + Joi.object().keys({ name: 'created', value: isFormattedDate }) + ) t.create('github issue update') .get('/issues/detail/last-update/badges/shields/979.json') - .expectJSONTypes(Joi.object().keys({ name: 'updated', value: isFormattedDate })); + .expectJSONTypes( + Joi.object().keys({ name: 'updated', value: isFormattedDate }) + ) t.create('github pull request check state') .get('/status/s/pulls/badges/shields/1110.json') - .expectJSONTypes(Joi.object().keys({ name: 'checks', value: 'failure' })); + .expectJSONTypes(Joi.object().keys({ name: 'checks', value: 'failure' })) t.create('github pull request check state (pull request not found)') .get('/status/s/pulls/badges/shields/5110.json') - .expectJSON({ name: 'checks', value: 'pull request or repo not found' }); + .expectJSON({ name: 'checks', value: 'pull request or repo not found' }) t.create('github pull request check contexts') .get('/status/contexts/pulls/badges/shields/1110.json') - .expectJSONTypes(Joi.object().keys({ name: 'checks', value: '1 failure' })); + .expectJSONTypes(Joi.object().keys({ name: 'checks', value: '1 failure' })) t.create('top language') -.get('/languages/top/badges/shields.json') -.expectJSONTypes(Joi.object().keys({ - name: 'JavaScript', - value: Joi.string().regex(/^([1-9]?[0-9]\.[0-9]|100\.0)%$/), -})); + .get('/languages/top/badges/shields.json') + .expectJSONTypes( + Joi.object().keys({ + name: 'JavaScript', + value: Joi.string().regex(/^([1-9]?[0-9]\.[0-9]|100\.0)%$/), + }) + ) t.create('top language with empty repository') -.get('/languages/top/pyvesb/emptyrepo.json') -.expectJSON({ name: 'language', value: 'none' }); + .get('/languages/top/pyvesb/emptyrepo.json') + .expectJSON({ name: 'language', value: 'none' }) t.create('language count') -.get('/languages/count/badges/shields.json') -.expectJSONTypes(Joi.object().keys({ - name: 'languages', - value: Joi.number().integer().positive(), -})); + .get('/languages/count/badges/shields.json') + .expectJSONTypes( + Joi.object().keys({ + name: 'languages', + value: Joi.number() + .integer() + .positive(), + }) + ) t.create('language count (repo not found)') .get('/languages/count/badges/helmets.json') .expectJSON({ name: 'languages', value: 'repo not found', - }); + }) t.create('code size in bytes for all languages') -.get('/languages/code-size/badges/shields.json') -.expectJSONTypes(Joi.object().keys({ - name: 'code size', - value: isFileSize, -})); + .get('/languages/code-size/badges/shields.json') + .expectJSONTypes( + Joi.object().keys({ + name: 'code size', + value: isFileSize, + }) + ) t.create('repository size') -.get('/repo-size/badges/shields.json') -.expectJSONTypes(Joi.object().keys({ - name: 'repo size', - value: isFileSize, -})); + .get('/repo-size/badges/shields.json') + .expectJSONTypes( + Joi.object().keys({ + name: 'repo size', + value: isFileSize, + }) + ) t.create('repository size (repo not found)') .get('/repo-size/badges/helmets.json') .expectJSON({ name: 'repo size', value: 'repo not found', - }); + }) // Commit status t.create('commit status - commit in branch') -.get('/commit-status/badges/shields/master/5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c.json?style=_shields_test') -.expectJSON({ - name: 'commit status', - value: 'in master', - colorB: colorsB.brightgreen -}); - -t.create('commit status - checked commit is identical with the newest commit in branch') - .get('/commit-status/badges/shields/master/5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c.json?style=_shields_test') - .intercept(nock => nock('https://api.github.com') - .get('/repos/badges/shields/compare/master...5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c') - .reply(200, { status: 'identical' })) + .get( + '/commit-status/badges/shields/master/5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c.json?style=_shields_test' + ) .expectJSON({ name: 'commit status', value: 'in master', - colorB: colorsB.brightgreen -}); + colorB: colorsB.brightgreen, + }) + +t.create( + 'commit status - checked commit is identical with the newest commit in branch' +) + .get( + '/commit-status/badges/shields/master/5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c.json?style=_shields_test' + ) + .intercept(nock => + nock('https://api.github.com') + .get( + '/repos/badges/shields/compare/master...5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c' + ) + .reply(200, { status: 'identical' }) + ) + .expectJSON({ + name: 'commit status', + value: 'in master', + colorB: colorsB.brightgreen, + }) t.create('commit status - commit not in branch') -.get('/commit-status/badges/shields/master/960c5bf72d7d1539fcd453343eed3f8617427a41.json?style=_shields_test') -.expectJSON({ - name: 'commit status', - value: 'commit or branch not found', - colorB: colorsB.lightgrey -}); + .get( + '/commit-status/badges/shields/master/960c5bf72d7d1539fcd453343eed3f8617427a41.json?style=_shields_test' + ) + .expectJSON({ + name: 'commit status', + value: 'commit or branch not found', + colorB: colorsB.lightgrey, + }) t.create('commit status - unknown commit id') -.get('/commit-status/atom/atom/v1.27.1/7dfb45eb61a48a4ce18a0dd2e31f944ed4467ae3.json?style=_shields_test') -.expectJSON({ - name: 'commit status', - value: 'not in v1.27.1', - colorB: colorsB.yellow -}); + .get( + '/commit-status/atom/atom/v1.27.1/7dfb45eb61a48a4ce18a0dd2e31f944ed4467ae3.json?style=_shields_test' + ) + .expectJSON({ + name: 'commit status', + value: 'not in v1.27.1', + colorB: colorsB.yellow, + }) t.create('commit status - unknown branch') -.get('/commit-status/badges/shields/this-branch-does-not-exist/b551a3a8daf1c48dba32a3eab1edf99b10c28863.json?style=_shields_test') -.expectJSON({ - name: 'commit status', - value: 'commit or branch not found', - colorB: colorsB.lightgrey -}); + .get( + '/commit-status/badges/shields/this-branch-does-not-exist/b551a3a8daf1c48dba32a3eab1edf99b10c28863.json?style=_shields_test' + ) + .expectJSON({ + name: 'commit status', + value: 'commit or branch not found', + colorB: colorsB.lightgrey, + }) t.create('commit status - no common ancestor between commit and branch') -.get('/commit-status/badges/shields/master/b551a3a8daf1c48dba32a3eab1edf99b10c28863.json?style=_shields_test') -.expectJSON({ - name: 'commit status', - value: 'no common ancestor', - colorB: colorsB.lightgrey -}); + .get( + '/commit-status/badges/shields/master/b551a3a8daf1c48dba32a3eab1edf99b10c28863.json?style=_shields_test' + ) + .expectJSON({ + name: 'commit status', + value: 'no common ancestor', + colorB: colorsB.lightgrey, + }) t.create('commit status - invalid JSON') - .get('/commit-status/badges/shields/master/5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c.json?style=_shields_test') - .intercept(nock => nock('https://api.github.com') - .get('/repos/badges/shields/compare/master...5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c') - .reply(invalidJSON)) + .get( + '/commit-status/badges/shields/master/5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c.json?style=_shields_test' + ) + .intercept(nock => + nock('https://api.github.com') + .get( + '/repos/badges/shields/compare/master...5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c' + ) + .reply(invalidJSON) + ) .expectJSON({ name: 'commit status', value: 'invalid', - colorB: colorsB.lightgrey -}); + colorB: colorsB.lightgrey, + }) t.create('commit status - network error') - .get('/commit-status/badges/shields/master/5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c.json?style=_shields_test') + .get( + '/commit-status/badges/shields/master/5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c.json?style=_shields_test' + ) .networkOff() .expectJSON({ name: 'commit status', value: 'inaccessible', - colorB: colorsB.red -}); + colorB: colorsB.red, + }) t.create('commit status - github server error') - .get('/commit-status/badges/shields/master/5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c.json?style=_shields_test') - .intercept(nock => nock('https://api.github.com') - .get('/repos/badges/shields/compare/master...5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c') - .reply(500)) + .get( + '/commit-status/badges/shields/master/5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c.json?style=_shields_test' + ) + .intercept(nock => + nock('https://api.github.com') + .get( + '/repos/badges/shields/compare/master...5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c' + ) + .reply(500) + ) .expectJSON({ name: 'commit status', value: 'invalid', - colorB: colorsB.lightgrey -}); + colorB: colorsB.lightgrey, + }) t.create('commit status - 404 with empty JSON form github') - .get('/commit-status/badges/shields/master/5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c.json?style=_shields_test') - .intercept(nock => nock('https://api.github.com') - .get('/repos/badges/shields/compare/master...5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c') - .reply(404, {})) + .get( + '/commit-status/badges/shields/master/5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c.json?style=_shields_test' + ) + .intercept(nock => + nock('https://api.github.com') + .get( + '/repos/badges/shields/compare/master...5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c' + ) + .reply(404, {}) + ) .expectJSON({ name: 'commit status', value: 'invalid', - colorB: colorsB.lightgrey -}); + colorB: colorsB.lightgrey, + }) t.create('commit status - 404 with invalid JSON form github') - .get('/commit-status/badges/shields/master/5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c.json?style=_shields_test') - .intercept(nock => nock('https://api.github.com') - .get('/repos/badges/shields/compare/master...5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c') - .reply(404, invalidJSON)) + .get( + '/commit-status/badges/shields/master/5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c.json?style=_shields_test' + ) + .intercept(nock => + nock('https://api.github.com') + .get( + '/repos/badges/shields/compare/master...5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c' + ) + .reply(404, invalidJSON) + ) .expectJSON({ name: 'commit status', value: 'invalid', - colorB: colorsB.lightgrey -}); + colorB: colorsB.lightgrey, + }) diff --git a/services/gratipay/gratipay.tester.js b/services/gratipay/gratipay.tester.js index 14f4e64aac75026a8be3d56a0c6c7cc4c7ba1cdc..feb28c7d76148960f6f680738b810febfa968e63 100644 --- a/services/gratipay/gratipay.tester.js +++ b/services/gratipay/gratipay.tester.js @@ -1,13 +1,13 @@ -'use strict'; +'use strict' -const ServiceTester = require('../service-tester'); +const ServiceTester = require('../service-tester') -const t = new ServiceTester({ id: 'gratipay', title: 'Gratipay' }); -module.exports = t; +const t = new ServiceTester({ id: 'gratipay', title: 'Gratipay' }) +module.exports = t t.create('Receiving') .get('/Gratipay.json') .expectJSON({ name: 'gratipay', value: 'no longer available', - }); + }) diff --git a/services/hackage/hackage.tester.js b/services/hackage/hackage.tester.js index a9c4d7e143ad8cf494030c4954dd5c16b3285d1f..9f18b2d17354b65d9a7b5f28912d62b16f4db269 100644 --- a/services/hackage/hackage.tester.js +++ b/services/hackage/hackage.tester.js @@ -1,50 +1,54 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') -const { isVPlusDottedVersionAtLeastOne } = require('../test-validators'); - -const t = new ServiceTester({ id: 'hackage', title: 'Hackage' }); -module.exports = t; +const { isVPlusDottedVersionAtLeastOne } = require('../test-validators') +const t = new ServiceTester({ id: 'hackage', title: 'Hackage' }) +module.exports = t t.create('hackage version (valid)') .get('/v/lens.json') - .expectJSONTypes(Joi.object().keys({ - name: 'hackage', - value: isVPlusDottedVersionAtLeastOne, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'hackage', + value: isVPlusDottedVersionAtLeastOne, + }) + ) t.create('hackage deps (valid)') .get('-deps/v/lens.json') - .expectJSONTypes(Joi.object().keys({ - name: 'dependencies', - value: Joi.string().regex(/^(up to date|outdated)$/), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'dependencies', + value: Joi.string().regex(/^(up to date|outdated)$/), + }) + ) t.create('hackage version (not found)') .get('/v/not-a-package.json') - .expectJSON({name: 'hackage', value: 'not found'}); + .expectJSON({ name: 'hackage', value: 'not found' }) t.create('hackage version (not found)') .get('-deps/v/not-a-package.json') - .expectJSON({name: 'dependencies', value: 'not found'}); + .expectJSON({ name: 'dependencies', value: 'not found' }) t.create('hackage version (connection error)') .get('/v/lens.json') .networkOff() - .expectJSON({name: 'hackage', value: 'inaccessible'}); + .expectJSON({ name: 'hackage', value: 'inaccessible' }) t.create('hackage deps (connection error)') .get('-deps/v/lens.json') .networkOff() - .expectJSON({name: 'dependencies', value: 'inaccessible'}); + .expectJSON({ name: 'dependencies', value: 'inaccessible' }) t.create('hackage version (unexpected response)') .get('/v/lens.json') - .intercept(nock => nock('https://hackage.haskell.org') - .get('/package/lens/lens.cabal') - .reply(200, "") + .intercept(nock => + nock('https://hackage.haskell.org') + .get('/package/lens/lens.cabal') + .reply(200, '') ) - .expectJSON({name: 'hackage', value: 'invalid'}); + .expectJSON({ name: 'hackage', value: 'invalid' }) diff --git a/services/hexpm/hexpm.tester.js b/services/hexpm/hexpm.tester.js index 3878195edaa18bff4c90a9de6f2bdbab34b4e93d..68546e3c9d29e0bbd76a2da1cca722053fa4a6f7 100644 --- a/services/hexpm/hexpm.tester.js +++ b/services/hexpm/hexpm.tester.js @@ -1,45 +1,48 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { - isMetric, - isMetricOverTimePeriod -} = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isMetric, isMetricOverTimePeriod } = require('../test-validators') -const isHexpmVersion = Joi.string().regex(/^v\d+.\d+.?\d?$/); +const isHexpmVersion = Joi.string().regex(/^v\d+.\d+.?\d?$/) -const t = new ServiceTester({ id: 'hexpm', title: 'Hex.pm' }); -module.exports = t; +const t = new ServiceTester({ id: 'hexpm', title: 'Hex.pm' }) +module.exports = t t.create('downloads per week') .get('/dw/cowboy.json') - .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetricOverTimePeriod })); + .expectJSONTypes( + Joi.object().keys({ name: 'downloads', value: isMetricOverTimePeriod }) + ) t.create('downloads per day') .get('/dd/cowboy.json') - .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetricOverTimePeriod })); + .expectJSONTypes( + Joi.object().keys({ name: 'downloads', value: isMetricOverTimePeriod }) + ) t.create('downloads in total') .get('/dt/cowboy.json') - .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })); + .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })) t.create('version') .get('/v/cowboy.json') - .expectJSONTypes(Joi.object().keys({ name: 'hex', value: isHexpmVersion })); + .expectJSONTypes(Joi.object().keys({ name: 'hex', value: isHexpmVersion })) t.create('license') .get('/l/cowboy.json') - .expectJSONTypes(Joi.object().keys({ - name: 'license', - value: Joi.string().required() - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'license', + value: Joi.string().required(), + }) + ) t.create('unknown repo') .get('/l/this-repo-does-not-exist.json') - .expectJSON({ name: 'hex', value: 'invalid' }); + .expectJSON({ name: 'hex', value: 'invalid' }) t.create('connection error') .get('/l/cowboy.json') .networkOff() - .expectJSON({ name: 'hex', value: 'inaccessible' }); + .expectJSON({ name: 'hex', value: 'inaccessible' }) diff --git a/services/hhvm/hhvm.tester.js b/services/hhvm/hhvm.tester.js index 9988e50f8096b9e91639fc30725bbe9d1d793359..c94d38e366dd699b2ed87c7b48eddb951694fa55 100644 --- a/services/hhvm/hhvm.tester.js +++ b/services/hhvm/hhvm.tester.js @@ -1,46 +1,53 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { invalidJSON } = require('../response-fixtures'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { invalidJSON } = require('../response-fixtures') -const t = new ServiceTester({ id: 'hhvm', title: 'hhvm status' }); +const t = new ServiceTester({ id: 'hhvm', title: 'hhvm status' }) -const isAllowedStatus = Joi.string().regex(/^(tested|partially tested|not tested|maybe untested)$/); +const isAllowedStatus = Joi.string().regex( + /^(tested|partially tested|not tested|maybe untested)$/ +) -module.exports = t; +module.exports = t t.create('get default branch') - .get('/symfony/symfony.json') - .expectJSONTypes(Joi.object().keys({ + .get('/symfony/symfony.json') + .expectJSONTypes( + Joi.object().keys({ name: 'hhvm', - value: isAllowedStatus - })); + value: isAllowedStatus, + }) + ) t.create('get specific branch') - .get('/yiisoft/yii/1.1.19.json') - .expectJSONTypes(Joi.object().keys({ + .get('/yiisoft/yii/1.1.19.json') + .expectJSONTypes( + Joi.object().keys({ name: 'hhvm', - value: isAllowedStatus - })); + value: isAllowedStatus, + }) + ) t.create('invalid repo') - .get('/frodo/is-not-a-package.json') - .expectJSON({ name: 'hhvm', value: 'repo not found' }); + .get('/frodo/is-not-a-package.json') + .expectJSON({ name: 'hhvm', value: 'repo not found' }) t.create('invalid branch') - .get('/yiisoft/yii/1.1.666.json') - .expectJSON({ name: 'hhvm', value: 'branch not found' }); + .get('/yiisoft/yii/1.1.666.json') + .expectJSON({ name: 'hhvm', value: 'branch not found' }) t.create('connection error') .get('/symfony/symfony.json') .networkOff() - .expectJSON({name: 'hhvm', value: 'inaccessible'}); + .expectJSON({ name: 'hhvm', value: 'inaccessible' }) t.create('unexpected response') - .get('/symfony/symfony.json') - .intercept(nock => nock('https://php-eye.com') - .get('/api/v1/package/symfony/symfony.json') - .reply(invalidJSON) - ) - .expectJSON({name: 'hhvm', value: 'invalid'}); + .get('/symfony/symfony.json') + .intercept(nock => + nock('https://php-eye.com') + .get('/api/v1/package/symfony/symfony.json') + .reply(invalidJSON) + ) + .expectJSON({ name: 'hhvm', value: 'invalid' }) diff --git a/services/homebrew/homebrew.tester.js b/services/homebrew/homebrew.tester.js index 3962ed131d48bd2736fc627b46efeb4363618d0f..fb3c3ab0844451a0efb43be296359ee51d7c9e26 100644 --- a/services/homebrew/homebrew.tester.js +++ b/services/homebrew/homebrew.tester.js @@ -1,42 +1,45 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isVPlusTripleDottedVersion } = require('../test-validators'); -const { invalidJSON } = require('../response-fixtures'); - -const t = new ServiceTester({ id: 'homebrew', title: 'homebrew' }); -module.exports = t; +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isVPlusTripleDottedVersion } = require('../test-validators') +const { invalidJSON } = require('../response-fixtures') +const t = new ServiceTester({ id: 'homebrew', title: 'homebrew' }) +module.exports = t t.create('homebrew (valid)') .get('/v/cake.json') - .expectJSONTypes(Joi.object().keys({ - name: 'homebrew', - value: isVPlusTripleDottedVersion, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'homebrew', + value: isVPlusTripleDottedVersion, + }) + ) t.create('homebrew (valid, mocked response)') .get('/v/cake.json') - .intercept(nock => nock('https://formulae.brew.sh') - .get('/api/formula/cake.json') - .reply(200, {versions: {stable: '0.23.0', devel: null, head: null}}) + .intercept(nock => + nock('https://formulae.brew.sh') + .get('/api/formula/cake.json') + .reply(200, { versions: { stable: '0.23.0', devel: null, head: null } }) ) - .expectJSON({name: 'homebrew', value: 'v0.23.0'}); + .expectJSON({ name: 'homebrew', value: 'v0.23.0' }) t.create('homebrew (invalid)') .get('/v/not-a-package.json') - .expectJSON({name: 'homebrew', value: 'not found'}); + .expectJSON({ name: 'homebrew', value: 'not found' }) t.create('homebrew (connection error)') .get('/v/cake.json') .networkOff() - .expectJSON({name: 'homebrew', value: 'inaccessible'}); + .expectJSON({ name: 'homebrew', value: 'inaccessible' }) t.create('homebrew (unexpected response)') .get('/v/cake.json') - .intercept(nock => nock('https://formulae.brew.sh') - .get('/api/formula/cake.json') - .reply(invalidJSON) + .intercept(nock => + nock('https://formulae.brew.sh') + .get('/api/formula/cake.json') + .reply(invalidJSON) ) - .expectJSON({name: 'homebrew', value: 'invalid'}); + .expectJSON({ name: 'homebrew', value: 'invalid' }) diff --git a/services/index.js b/services/index.js index 70f7b38dbf1e92c7ca6740a868c07c5293909757..7eaa68b237726ba3d52e1a88f6a9564ef006c31a 100644 --- a/services/index.js +++ b/services/index.js @@ -1,32 +1,32 @@ -'use strict'; +'use strict' -const glob = require('glob'); +const glob = require('glob') function loadServiceClasses() { // New-style services - const services = glob.sync(`${__dirname}/**/*.service.js`) - .map(path => require(path)); + const services = glob + .sync(`${__dirname}/**/*.service.js`) + .map(path => require(path)) - const serviceClasses = []; + const serviceClasses = [] services.forEach(service => { if (typeof service === 'function') { - serviceClasses.push(service); + serviceClasses.push(service) } else { for (const serviceClass in service) { - serviceClasses.push(service[serviceClass]); + serviceClasses.push(service[serviceClass]) } } - }); + }) - return serviceClasses; + return serviceClasses } function loadTesters() { - return glob.sync(`${__dirname}/**/*.tester.js`) - .map(path => require(path)); + return glob.sync(`${__dirname}/**/*.tester.js`).map(path => require(path)) } module.exports = { loadServiceClasses, loadTesters, -}; +} diff --git a/services/itunes/itunes.tester.js b/services/itunes/itunes.tester.js index 0062b24af624bb5035b1f4f004b3738fc77c8ace..e35a07f9a7eb65a1d41de513bf740b86af19a2a3 100644 --- a/services/itunes/itunes.tester.js +++ b/services/itunes/itunes.tester.js @@ -1,40 +1,41 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') -const { isVPlusDottedVersionAtLeastOne } = require('../test-validators'); -const { invalidJSON } = require('../response-fixtures'); - -const t = new ServiceTester({ id: 'itunes', title: 'iTunes' }); -module.exports = t; +const { isVPlusDottedVersionAtLeastOne } = require('../test-validators') +const { invalidJSON } = require('../response-fixtures') +const t = new ServiceTester({ id: 'itunes', title: 'iTunes' }) +module.exports = t t.create('iTunes version (valid)') .get('/v/324684580.json') - .expectJSONTypes(Joi.object().keys({ - name: 'itunes app store', - value: isVPlusDottedVersionAtLeastOne - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'itunes app store', + value: isVPlusDottedVersionAtLeastOne, + }) + ) t.create('iTunes version (not found)') .get('/v/9.json') - .expectJSON({name: 'itunes app store', value: 'not found'}); + .expectJSON({ name: 'itunes app store', value: 'not found' }) t.create('iTunes version (invalid)') .get('/v/x.json') - .expectJSON({name: 'itunes app store', value: 'invalid'}); + .expectJSON({ name: 'itunes app store', value: 'invalid' }) t.create('iTunes version (connection error)') .get('/v/324684580.json') .networkOff() - .expectJSON({name: 'itunes app store', value: 'inaccessible'}); + .expectJSON({ name: 'itunes app store', value: 'inaccessible' }) t.create('iTunes version (unexpected response)') .get('/v/324684580.json') - .intercept(nock => nock('https://itunes.apple.com') - .get('/lookup?id=324684580') - .reply(invalidJSON) + .intercept(nock => + nock('https://itunes.apple.com') + .get('/lookup?id=324684580') + .reply(invalidJSON) ) - .expectJSON({name: 'itunes app store', value: 'invalid'}); - + .expectJSON({ name: 'itunes app store', value: 'invalid' }) diff --git a/services/jenkins/jenkins.tester.js b/services/jenkins/jenkins.tester.js index 0b4f4a2ae7a8a083f698de1ad6d507af891e8a41..b9d88b99eb1cb5069ea80eb0c5d12358ea4ef7b9 100644 --- a/services/jenkins/jenkins.tester.js +++ b/services/jenkins/jenkins.tester.js @@ -1,79 +1,130 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') -const t = new ServiceTester({ id: 'jenkins', title: 'Jenkins' }); -module.exports = t; +const t = new ServiceTester({ id: 'jenkins', title: 'Jenkins' }) +module.exports = t t.create('cobertura: latest version') .get('/plugin/v/blueocean.json') - .intercept(nock => nock('https://updates.jenkins-ci.org') - .get('/current/update-center.actual.json') - .reply(200, { plugins: { blueocean: { version: '1.1.6' } } }) + .intercept(nock => + nock('https://updates.jenkins-ci.org') + .get('/current/update-center.actual.json') + .reply(200, { plugins: { blueocean: { version: '1.1.6' } } }) + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'plugin', + value: Joi.string().regex(/^v(.*)$/), + }) ) - .expectJSONTypes(Joi.object().keys({ - name: 'plugin', - value: Joi.string().regex(/^v(.*)$/) - })); t.create('cobertura: version 0') .get('/plugin/v/blueocean.json') - .intercept(nock => nock('https://updates.jenkins-ci.org') - .get('/current/update-center.actual.json') - .reply(200, { plugins: { blueocean: { version: '0' } } }) + .intercept(nock => + nock('https://updates.jenkins-ci.org') + .get('/current/update-center.actual.json') + .reply(200, { plugins: { blueocean: { version: '0' } } }) + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'plugin', + value: Joi.string().regex(/^v0$/), + }) ) - .expectJSONTypes(Joi.object().keys({ - name: 'plugin', - value: Joi.string().regex(/^v0$/) - })); t.create('cobertura: inexistent artifact') .get('/plugin/v/inexistent-artifact-id.json') - .intercept(nock => nock('https://updates.jenkins-ci.org') - .get('/current/update-center.actual.json') - .reply(200, { plugins: { blueocean: { version: '1.1.6' } } }) + .intercept(nock => + nock('https://updates.jenkins-ci.org') + .get('/current/update-center.actual.json') + .reply(200, { plugins: { blueocean: { version: '1.1.6' } } }) ) - .expectJSON({ name: 'plugin', value: 'not found' }); + .expectJSON({ name: 'plugin', value: 'not found' }) t.create('cobertura: connection error') .get('/plugin/v/blueocean.json') .networkOff() - .expectJSON({ name: 'plugin', value: 'inaccessible' }); + .expectJSON({ name: 'plugin', value: 'inaccessible' }) t.create('jacoco: 81% | valid coverage') .get('/j/https/updates.jenkins-ci.org/job/hello-project/job/master.json') - .intercept(nock => nock('https://updates.jenkins-ci.org') - .get('/job/hello-project/job/master/lastBuild/jacoco/api/json?tree=instructionCoverage[covered,missed,percentage,total]') - .reply(200, { instructionCoverage: { covered: 39498, missed: 9508, percentage: 81, percentageFloat: 80.5983, total: 49006 } }) + .intercept(nock => + nock('https://updates.jenkins-ci.org') + .get( + '/job/hello-project/job/master/lastBuild/jacoco/api/json?tree=instructionCoverage[covered,missed,percentage,total]' + ) + .reply(200, { + instructionCoverage: { + covered: 39498, + missed: 9508, + percentage: 81, + percentageFloat: 80.5983, + total: 49006, + }, + }) ) - .expectJSONTypes({ name: 'coverage', value: '81%' }); + .expectJSONTypes({ name: 'coverage', value: '81%' }) t.create('jacoco: inaccessible | request error') .get('/j/https/updates.jenkins-ci.org/job/hello-project/job/master.json') .networkOff() - .expectJSONTypes({ name: 'coverage', value: 'inaccessible' }); + .expectJSONTypes({ name: 'coverage', value: 'inaccessible' }) t.create('jacoco: inaccessible | invalid coverage object') .get('/j/https/updates.jenkins-ci.org/job/hello-project/job/master.json') - .intercept(nock => nock('https://updates.jenkins-ci.org') - .get('/job/hello-project/job/master/lastBuild/jacoco/api/json?tree=instructionCoverage[covered,missed,percentage,total]') - .reply(200, { invalidCoverageObject: { covered: 39498, missed: 9508, percentage: 81, percentageFloat: 80.5983, total: 49006 } }) + .intercept(nock => + nock('https://updates.jenkins-ci.org') + .get( + '/job/hello-project/job/master/lastBuild/jacoco/api/json?tree=instructionCoverage[covered,missed,percentage,total]' + ) + .reply(200, { + invalidCoverageObject: { + covered: 39498, + missed: 9508, + percentage: 81, + percentageFloat: 80.5983, + total: 49006, + }, + }) ) - .expectJSONTypes({ name: 'coverage', value: 'inaccessible' }); + .expectJSONTypes({ name: 'coverage', value: 'inaccessible' }) t.create('jacoco: unknown | invalid coverage (non-numeric)') .get('/j/https/updates.jenkins-ci.org/job/hello-project/job/master.json') - .intercept(nock => nock('https://updates.jenkins-ci.org') - .get('/job/hello-project/job/master/lastBuild/jacoco/api/json?tree=instructionCoverage[covered,missed,percentage,total]') - .reply(200, { instructionCoverage: { covered: 39498, missed: 9508, percentage: 'non-numeric', percentageFloat: 80.5983, total: 49006 } }) + .intercept(nock => + nock('https://updates.jenkins-ci.org') + .get( + '/job/hello-project/job/master/lastBuild/jacoco/api/json?tree=instructionCoverage[covered,missed,percentage,total]' + ) + .reply(200, { + instructionCoverage: { + covered: 39498, + missed: 9508, + percentage: 'non-numeric', + percentageFloat: 80.5983, + total: 49006, + }, + }) ) - .expectJSONTypes({ name: 'coverage', value: 'unknown' }); + .expectJSONTypes({ name: 'coverage', value: 'unknown' }) t.create('jacoco: unknown | exception handling') .get('/j/https/updates.jenkins-ci.org/job/hello-project/job/master.json') - .intercept(nock => nock('https://updates.jenkins-ci.org') - .get('/job/hello-project/job/master/lastBuild/jacoco/api/json?tree=instructionCoverage[covered,missed,percentage,total]') - .reply(200, { instructionCoverage: { covered: 39498, missed: 9508, percentage: '81.x', percentageFloat: 80.5983, total: 49006 } }) + .intercept(nock => + nock('https://updates.jenkins-ci.org') + .get( + '/job/hello-project/job/master/lastBuild/jacoco/api/json?tree=instructionCoverage[covered,missed,percentage,total]' + ) + .reply(200, { + instructionCoverage: { + covered: 39498, + missed: 9508, + percentage: '81.x', + percentageFloat: 80.5983, + total: 49006, + }, + }) ) - .expectJSONTypes({ name: 'coverage', value: 'unknown' }); + .expectJSONTypes({ name: 'coverage', value: 'unknown' }) diff --git a/services/jetbrains/jetbrains.tester.js b/services/jetbrains/jetbrains.tester.js index 6755220041c97bfbe1c739835f3976c3fe5d533b..8cb50f03a305d56aedb065c30cc44d555a5c9bae 100644 --- a/services/jetbrains/jetbrains.tester.js +++ b/services/jetbrains/jetbrains.tester.js @@ -1,77 +1,91 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isMetric, isSemver } = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isMetric, isSemver } = require('../test-validators') -const t = new ServiceTester({ id: 'jetbrains', title: 'JetBrains' }); -module.exports = t; +const t = new ServiceTester({ id: 'jetbrains', title: 'JetBrains' }) +module.exports = t t.create('downloads (number as a plugin id)') .get('/plugin/d/7495.json') - .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })); + .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })) t.create('downloads (plugin id from plugin.xml)') .get('/plugin/d/org.intellij.scala.json') - .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })); + .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })) t.create('downloads (user friendly plugin id)') .get('/plugin/d/1347-scala.json') - .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })); + .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })) t.create('unknown plugin') .get('/plugin/d/unknown-plugin.json') - .expectJSON({ name: 'downloads', value: 'not found' }); + .expectJSON({ name: 'downloads', value: 'not found' }) t.create('connection error') .get('/plugin/d/7495.json') .networkOff() - .expectJSON({ name: 'downloads', value: 'inaccessible' }); + .expectJSON({ name: 'downloads', value: 'inaccessible' }) t.create('server error') .get('/plugin/d/7495.json') - .intercept(nock => nock('https://plugins.jetbrains.com') - .get('/plugins/list?pluginId=7495') - .reply(500) + .intercept(nock => + nock('https://plugins.jetbrains.com') + .get('/plugins/list?pluginId=7495') + .reply(500) ) - .expectJSON({ name: 'downloads', value: 'inaccessible' }); + .expectJSON({ name: 'downloads', value: 'inaccessible' }) t.create('empty response') .get('/plugin/d/7495.json') - .intercept(nock => nock('https://plugins.jetbrains.com') - .get('/plugins/list?pluginId=7495') - .reply(200, '') + .intercept(nock => + nock('https://plugins.jetbrains.com') + .get('/plugins/list?pluginId=7495') + .reply(200, '') ) - .expectJSON({ name: 'downloads', value: 'invalid' }); + .expectJSON({ name: 'downloads', value: 'invalid' }) t.create('incorrect response format (JSON instead of XML)') .get('/plugin/d/7495.json') - .intercept(nock => nock('https://plugins.jetbrains.com') - .get('/plugins/list?pluginId=7495') - .reply(200, { downloads: 2 }) + .intercept(nock => + nock('https://plugins.jetbrains.com') + .get('/plugins/list?pluginId=7495') + .reply(200, { downloads: 2 }) ) - .expectJSON({ name: 'downloads', value: 'invalid' }); + .expectJSON({ name: 'downloads', value: 'invalid' }) t.create('missing required XML element') .get('/plugin/d/9435.json') - .intercept(nock => nock('https://plugins.jetbrains.com') - .get('/plugins/list?pluginId=9435') - .reply(200, `<?xml version="1.0" encoding="UTF-8"?> + .intercept( + nock => + nock('https://plugins.jetbrains.com') + .get('/plugins/list?pluginId=9435') + .reply( + 200, + `<?xml version="1.0" encoding="UTF-8"?> <plugin-repository> <ff>"Code editing"</ff> <category name="Code editing"> <!-- no required idea-plugin element here --> </category> - </plugin-repository>`), { - 'Content-Type': 'text/xml;charset=UTF-8' - }) - .expectJSON({ name: 'downloads', value: 'invalid' }); + </plugin-repository>` + ), + { + 'Content-Type': 'text/xml;charset=UTF-8', + } + ) + .expectJSON({ name: 'downloads', value: 'invalid' }) t.create('missing required XML attribute') .get('/plugin/d/9435.json') - .intercept(nock => nock('https://plugins.jetbrains.com') - .get('/plugins/list?pluginId=9435') - .reply(200, `<?xml version="1.0" encoding="UTF-8"?> + .intercept( + nock => + nock('https://plugins.jetbrains.com') + .get('/plugins/list?pluginId=9435') + .reply( + 200, + `<?xml version="1.0" encoding="UTF-8"?> <plugin-repository> <ff>"Code editing"</ff> <category name="Code editing"> @@ -88,101 +102,130 @@ t.create('missing required XML attribute') <rating>00</rating> </idea-plugin> </category> - </plugin-repository>`), { - 'Content-Type': 'text/xml;charset=UTF-8' - }) - .expectJSON({ name: 'downloads', value: 'invalid' }); + </plugin-repository>` + ), + { + 'Content-Type': 'text/xml;charset=UTF-8', + } + ) + .expectJSON({ name: 'downloads', value: 'invalid' }) t.create('empty XML') .get('/plugin/d/9435.json') - .intercept(nock => nock('https://plugins.jetbrains.com') - .get('/plugins/list?pluginId=9435') - .reply(200, '<?xml version="1.0" encoding="UTF-8"?>'), { - 'Content-Type': 'text/xml;charset=UTF-8' - }) - .expectJSON({ name: 'downloads', value: 'invalid' }); + .intercept( + nock => + nock('https://plugins.jetbrains.com') + .get('/plugins/list?pluginId=9435') + .reply(200, '<?xml version="1.0" encoding="UTF-8"?>'), + { + 'Content-Type': 'text/xml;charset=UTF-8', + } + ) + .expectJSON({ name: 'downloads', value: 'invalid' }) t.create('404 status code') .get('/plugin/d/7495.json') - .intercept(nock => nock('https://plugins.jetbrains.com') - .get('/plugins/list?pluginId=7495') - .reply(404) + .intercept(nock => + nock('https://plugins.jetbrains.com') + .get('/plugins/list?pluginId=7495') + .reply(404) ) - .expectJSON({ name: 'downloads', value: 'inaccessible' }); + .expectJSON({ name: 'downloads', value: 'inaccessible' }) t.create('empty XML(v)') .get('/plugin/v/9435.json') - .intercept(nock => nock('https://plugins.jetbrains.com') - .get('/plugins/list?pluginId=9435') - .reply(200, '<?xml version="1.0" encoding="UTF-8"?>'), { - 'Content-Type': 'text/xml;charset=UTF-8' - }) - .expectJSON({ name: 'jetbrains plugin', value: 'invalid' }); + .intercept( + nock => + nock('https://plugins.jetbrains.com') + .get('/plugins/list?pluginId=9435') + .reply(200, '<?xml version="1.0" encoding="UTF-8"?>'), + { + 'Content-Type': 'text/xml;charset=UTF-8', + } + ) + .expectJSON({ name: 'jetbrains plugin', value: 'invalid' }) t.create('404 status code(v)') .get('/plugin/v/7495.json') - .intercept(nock => nock('https://plugins.jetbrains.com') - .get('/plugins/list?pluginId=7495') - .reply(404) + .intercept(nock => + nock('https://plugins.jetbrains.com') + .get('/plugins/list?pluginId=7495') + .reply(404) ) - .expectJSON({ name: 'jetbrains plugin', value: 'inaccessible' }); + .expectJSON({ name: 'jetbrains plugin', value: 'inaccessible' }) t.create('missing required XML element(v)') .get('/plugin/v/9435.json') - .intercept(nock => nock('https://plugins.jetbrains.com') - .get('/plugins/list?pluginId=9435') - .reply(200, `<?xml version="1.0" encoding="UTF-8"?> + .intercept( + nock => + nock('https://plugins.jetbrains.com') + .get('/plugins/list?pluginId=9435') + .reply( + 200, + `<?xml version="1.0" encoding="UTF-8"?> <plugin-repository> <ff>"Code editing"</ff> <category name="Code editing"> <!-- no required idea-plugin element here --> </category> - </plugin-repository>`), { - 'Content-Type': 'text/xml;charset=UTF-8' - }) - .expectJSON({ name: 'jetbrains plugin', value: 'invalid' }); + </plugin-repository>` + ), + { + 'Content-Type': 'text/xml;charset=UTF-8', + } + ) + .expectJSON({ name: 'jetbrains plugin', value: 'invalid' }) t.create('incorrect response format (JSON instead of XML)(v)') .get('/plugin/v/7495.json') - .intercept(nock => nock('https://plugins.jetbrains.com') - .get('/plugins/list?pluginId=7495') - .reply(200, { version: 2.0 }) + .intercept(nock => + nock('https://plugins.jetbrains.com') + .get('/plugins/list?pluginId=7495') + .reply(200, { version: 2.0 }) ) - .expectJSON({ name: 'jetbrains plugin', value: 'invalid' }); + .expectJSON({ name: 'jetbrains plugin', value: 'invalid' }) t.create('empty response(v)') .get('/plugin/v/7495.json') - .intercept(nock => nock('https://plugins.jetbrains.com') - .get('/plugins/list?pluginId=7495') - .reply(200, '') + .intercept(nock => + nock('https://plugins.jetbrains.com') + .get('/plugins/list?pluginId=7495') + .reply(200, '') ) - .expectJSON({ name: 'jetbrains plugin', value: 'invalid' }); + .expectJSON({ name: 'jetbrains plugin', value: 'invalid' }) t.create('server error(v)') .get('/plugin/v/7495.json') - .intercept(nock => nock('https://plugins.jetbrains.com') - .get('/plugins/list?pluginId=7495') - .reply(500) + .intercept(nock => + nock('https://plugins.jetbrains.com') + .get('/plugins/list?pluginId=7495') + .reply(500) ) - .expectJSON({ name: 'jetbrains plugin', value: 'inaccessible' }); + .expectJSON({ name: 'jetbrains plugin', value: 'inaccessible' }) t.create('connection error(v)') .get('/plugin/v/7495.json') .networkOff() - .expectJSON({ name: 'jetbrains plugin', value: 'inaccessible' }); + .expectJSON({ name: 'jetbrains plugin', value: 'inaccessible' }) t.create('version for unknown plugin') .get('/plugin/v/unknown-plugin.json') - .expectJSON({ name: 'jetbrains plugin', value: 'not found' }); + .expectJSON({ name: 'jetbrains plugin', value: 'not found' }) t.create('version (user friendly plugin id)') .get('/plugin/v/1347-scala.json') - .expectJSONTypes(Joi.object().keys({ name: 'jetbrains plugin', value: isSemver })); + .expectJSONTypes( + Joi.object().keys({ name: 'jetbrains plugin', value: isSemver }) + ) t.create('version (plugin id from plugin.xml)') .get('/plugin/v/org.intellij.scala.json') - .expectJSONTypes(Joi.object().keys({ name: 'jetbrains plugin', value: isSemver })); + .expectJSONTypes( + Joi.object().keys({ name: 'jetbrains plugin', value: isSemver }) + ) t.create('version (number as a plugin id)') .get('/plugin/v/7495.json') - .expectJSONTypes(Joi.object().keys({ name: 'jetbrains plugin', value: isSemver })); + .expectJSONTypes( + Joi.object().keys({ name: 'jetbrains plugin', value: isSemver }) + ) diff --git a/services/jitpack/jitpack.tester.js b/services/jitpack/jitpack.tester.js index 14bd6d7f73d75dbeb79c8f2789161d1532d019ee..f242ed4145b587199d5663241efe40fe2478a2f1 100644 --- a/services/jitpack/jitpack.tester.js +++ b/services/jitpack/jitpack.tester.js @@ -1,28 +1,28 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') // Github allows versions with chars, etc. -const isAnyV = Joi.string().regex(/^v.+$/); +const isAnyV = Joi.string().regex(/^v.+$/) -const t = new ServiceTester({ id: 'jitpack', title: 'JitPack' }); -module.exports = t; +const t = new ServiceTester({ id: 'jitpack', title: 'JitPack' }) +module.exports = t t.create('version') .get('/v/jitpack/maven-simple.json') - .expectJSONTypes(Joi.object().keys({ name: 'JitPack', value: isAnyV })); + .expectJSONTypes(Joi.object().keys({ name: 'JitPack', value: isAnyV })) t.create('unknown package') .get('/v/some-bogus-user/project.json') - .expectJSON({ name: 'JitPack', value: 'invalid' }); + .expectJSON({ name: 'JitPack', value: 'invalid' }) t.create('unknown info') .get('/z/devtools.json') .expectStatus(404) - .expectJSON({ name: '404', value: 'badge not found' }); + .expectJSON({ name: '404', value: 'badge not found' }) t.create('connection error') .get('/v/jitpack/maven-simple.json') .networkOff() - .expectJSON({ name: 'JitPack', value: 'inaccessible' }); + .expectJSON({ name: 'JitPack', value: 'inaccessible' }) diff --git a/services/json/json.tester.js b/services/json/json.tester.js index 0f9ca2b6f78640fd284d50fee423b4cf581db9b9..ad9378bb636b070a21837911c92ca4ba7fc50630 100644 --- a/services/json/json.tester.js +++ b/services/json/json.tester.js @@ -1,90 +1,163 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const { expect } = require('chai'); -const ServiceTester = require('../service-tester'); -const colorscheme = require('../../lib/colorscheme.json'); -const mapValues = require('lodash.mapvalues'); +const Joi = require('joi') +const { expect } = require('chai') +const ServiceTester = require('../service-tester') +const colorscheme = require('../../lib/colorscheme.json') +const mapValues = require('lodash.mapvalues') -const colorsB = mapValues(colorscheme, 'colorB'); +const colorsB = mapValues(colorscheme, 'colorB') -const t = new ServiceTester({ id: 'dynamic-json', title: 'User Defined JSON Source Data', pathPrefix: '/badge/dynamic/json' }); -module.exports = t; +const t = new ServiceTester({ + id: 'dynamic-json', + title: 'User Defined JSON Source Data', + pathPrefix: '/badge/dynamic/json', +}) +module.exports = t t.create('Connection error') - .get('.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.name&label=Package Name&style=_shields_test') + .get( + '.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.name&label=Package Name&style=_shields_test' + ) .networkOff() - .expectJSON({ name: 'Package Name', value: 'inaccessible', colorB: colorsB.red }); + .expectJSON({ + name: 'Package Name', + value: 'inaccessible', + colorB: colorsB.red, + }) t.create('No URL specified') .get('.json?query=$.name&label=Package Name&style=_shields_test') - .expectJSON({ name: 'Package Name', value: 'no url specified', colorB: colorsB.red }); + .expectJSON({ + name: 'Package Name', + value: 'no url specified', + colorB: colorsB.red, + }) t.create('No query specified') - .get('.json?url=https://github.com/badges/shields/raw/master/package.json&label=Package Name&style=_shields_test') - .expectJSON({ name: 'Package Name', value: 'no query specified', colorB: colorsB.red }); + .get( + '.json?url=https://github.com/badges/shields/raw/master/package.json&label=Package Name&style=_shields_test' + ) + .expectJSON({ + name: 'Package Name', + value: 'no query specified', + colorB: colorsB.red, + }) t.create('Malformed url') - .get('.json?url=https://github.com/badges/shields/raw/master/%0package.json&query=$.name&label=Package Name&style=_shields_test') - .expectJSON({ name: 'Package Name', value: 'malformed url', colorB: colorsB.red }); + .get( + '.json?url=https://github.com/badges/shields/raw/master/%0package.json&query=$.name&label=Package Name&style=_shields_test' + ) + .expectJSON({ + name: 'Package Name', + value: 'malformed url', + colorB: colorsB.red, + }) t.create('JSON from url') - .get('.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.name&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'gh-badges', colorB: colorsB.brightgreen }); + .get( + '.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.name&style=_shields_test' + ) + .expectJSON({ + name: 'custom badge', + value: 'gh-badges', + colorB: colorsB.brightgreen, + }) t.create('JSON from uri (support uri query paramater)') - .get('.json?uri=https://github.com/badges/shields/raw/master/package.json&query=$.name&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'gh-badges', colorB: colorsB.brightgreen }); + .get( + '.json?uri=https://github.com/badges/shields/raw/master/package.json&query=$.name&style=_shields_test' + ) + .expectJSON({ + name: 'custom badge', + value: 'gh-badges', + colorB: colorsB.brightgreen, + }) t.create('JSON from url | multiple results') - .get('.json?url=https://github.com/badges/shields/raw/master/package.json&query=$..keywords[0:2:1]') - .expectJSON({ name: 'custom badge', value: 'GitHub, badge' }); + .get( + '.json?url=https://github.com/badges/shields/raw/master/package.json&query=$..keywords[0:2:1]' + ) + .expectJSON({ name: 'custom badge', value: 'GitHub, badge' }) t.create('JSON from url | caching with new query params') - .get('.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.version') - .expectJSONTypes(Joi.object().keys({ - name: 'custom badge', - value: Joi.string().regex(/^\d+(\.\d+)?(\.\d+)?$/) - })); + .get( + '.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.version' + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'custom badge', + value: Joi.string().regex(/^\d+(\.\d+)?(\.\d+)?$/), + }) + ) t.create('JSON from url | with prefix & suffix & label') - .get('.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.version&prefix=v&suffix= dev&label=Shields') - .expectJSONTypes(Joi.object().keys({ - name: 'Shields', - value: Joi.string().regex(/^v\d+(\.\d+)?(\.\d+)?\sdev$/) - })); + .get( + '.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.version&prefix=v&suffix= dev&label=Shields' + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'Shields', + value: Joi.string().regex(/^v\d+(\.\d+)?(\.\d+)?\sdev$/), + }) + ) t.create('JSON from url | object doesnt exist') - .get('.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.does_not_exist&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'no result', colorB: colorsB.lightgrey }); + .get( + '.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.does_not_exist&style=_shields_test' + ) + .expectJSON({ + name: 'custom badge', + value: 'no result', + colorB: colorsB.lightgrey, + }) t.create('JSON from url | invalid url') - .get('.json?url=https://github.com/badges/shields/raw/master/notafile.json&query=$.version&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'resource not found', colorB: colorsB.lightgrey }); + .get( + '.json?url=https://github.com/badges/shields/raw/master/notafile.json&query=$.version&style=_shields_test' + ) + .expectJSON({ + name: 'custom badge', + value: 'resource not found', + colorB: colorsB.lightgrey, + }) t.create('JSON from url | user color overrides default') - .get('.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.name&colorB=10ADED&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'gh-badges', colorB: '#10ADED' }); + .get( + '.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.name&colorB=10ADED&style=_shields_test' + ) + .expectJSON({ name: 'custom badge', value: 'gh-badges', colorB: '#10ADED' }) t.create('JSON from url | error color overrides default') - .get('.json?url=https://github.com/badges/shields/raw/master/notafile.json&query=$.version&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'resource not found', colorB: colorsB.lightgrey }); + .get( + '.json?url=https://github.com/badges/shields/raw/master/notafile.json&query=$.version&style=_shields_test' + ) + .expectJSON({ + name: 'custom badge', + value: 'resource not found', + colorB: colorsB.lightgrey, + }) t.create('JSON from url | error color overrides user specified') .get('.json?query=$.version&colorB=10ADED&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'no url specified', colorB: colorsB.red }); + .expectJSON({ + name: 'custom badge', + value: 'no url specified', + colorB: colorsB.red, + }) -let headers; +let headers t.create('JSON from url | request should set Accept header') .get('.json?url=https://json-test/api.json&query=$.name') - .intercept(nock => nock('https://json-test') - .get('/api.json') - .reply(200, function (uri, requestBody) { - headers = this.req.headers; - return '{"name":"test"}'; - }) + .intercept(nock => + nock('https://json-test') + .get('/api.json') + .reply(200, function(uri, requestBody) { + headers = this.req.headers + return '{"name":"test"}' + }) ) .expectJSON({ name: 'custom badge', value: 'test' }) - .after(function () { - expect(headers).to.have.property('accept', 'application/json'); - }); + .after(function() { + expect(headers).to.have.property('accept', 'application/json') + }) diff --git a/services/lgtm/lgtm.tester.js b/services/lgtm/lgtm.tester.js index 5fff9cbf386358fbb40c37af368277a5da3e2cb6..7e5bbb134d4d29a049d0b59b74e722a060e5d0e3 100644 --- a/services/lgtm/lgtm.tester.js +++ b/services/lgtm/lgtm.tester.js @@ -1,66 +1,78 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { invalidJSON } = require('../response-fixtures'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { invalidJSON } = require('../response-fixtures') const t = new ServiceTester({ id: 'lgtm', title: 'LGTM' }) -module.exports = t; +module.exports = t // Alerts Badge t.create('alerts: total alerts for a project') .get('/alerts/g/apache/cloudstack.json') - .expectJSONTypes(Joi.object().keys({ - name: 'lgtm', - value: Joi.string().regex(/^[0-9kM.]+ alerts?$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'lgtm', + value: Joi.string().regex(/^[0-9kM.]+ alerts?$/), + }) + ) t.create('alerts: missing project') .get('/alerts/g/some-org/this-project-doesnt-exist.json') .expectJSON({ name: 'lgtm', - value: 'project not found' - }); + value: 'project not found', + }) t.create('alerts: no alerts') .get('/alerts/g/apache/cloudstack.json') - .intercept(nock => nock('https://lgtm.com') - .get('/api/v0.1/project/g/apache/cloudstack/details') - .reply(200, {alerts: 0})) - .expectJSON({ name: 'lgtm', value: '0 alerts' }); + .intercept(nock => + nock('https://lgtm.com') + .get('/api/v0.1/project/g/apache/cloudstack/details') + .reply(200, { alerts: 0 }) + ) + .expectJSON({ name: 'lgtm', value: '0 alerts' }) t.create('alerts: single alert') .get('/alerts/g/apache/cloudstack.json') - .intercept(nock => nock('https://lgtm.com') - .get('/api/v0.1/project/g/apache/cloudstack/details') - .reply(200, {alerts: 1})) - .expectJSON({ name: 'lgtm', value: '1 alert' }); + .intercept(nock => + nock('https://lgtm.com') + .get('/api/v0.1/project/g/apache/cloudstack/details') + .reply(200, { alerts: 1 }) + ) + .expectJSON({ name: 'lgtm', value: '1 alert' }) t.create('alerts: multiple alerts') .get('/alerts/g/apache/cloudstack.json') - .intercept(nock => nock('https://lgtm.com') - .get('/api/v0.1/project/g/apache/cloudstack/details') - .reply(200, {alerts: 123})) - .expectJSON({ name: 'lgtm', value: '123 alerts' }); + .intercept(nock => + nock('https://lgtm.com') + .get('/api/v0.1/project/g/apache/cloudstack/details') + .reply(200, { alerts: 123 }) + ) + .expectJSON({ name: 'lgtm', value: '123 alerts' }) t.create('alerts: json missing alerts') .get('/alerts/g/apache/cloudstack.json') - .intercept(nock => nock('https://lgtm.com') - .get('/api/v0.1/project/g/apache/cloudstack/details') - .reply(200, {})) - .expectJSON({ name: 'lgtm', value: 'invalid' }); + .intercept(nock => + nock('https://lgtm.com') + .get('/api/v0.1/project/g/apache/cloudstack/details') + .reply(200, {}) + ) + .expectJSON({ name: 'lgtm', value: 'invalid' }) t.create('alerts: invalid json') .get('/alerts/g/apache/cloudstack.json') - .intercept(nock => nock('https://lgtm.com') - .get('/api/v0.1/project/g/apache/cloudstack/details') - .reply(invalidJSON)) - .expectJSON({ name: 'lgtm', value: 'invalid' }); + .intercept(nock => + nock('https://lgtm.com') + .get('/api/v0.1/project/g/apache/cloudstack/details') + .reply(invalidJSON) + ) + .expectJSON({ name: 'lgtm', value: 'invalid' }) t.create('alerts: lgtm inaccessible') .get('/alerts/g/apache/cloudstack.json') .networkOff() - .expectJSON({ name: 'lgtm', value: 'inaccessible' }); + .expectJSON({ name: 'lgtm', value: 'inaccessible' }) // Grade Badge @@ -68,99 +80,121 @@ t.create('grade: missing project') .get('/grade/java/g/some-org/this-project-doesnt-exist.json') .expectJSON({ name: 'code quality: java', - value: 'project not found' - }); + value: 'project not found', + }) t.create('grade: lgtm inaccessible') .get('/grade/java/g/apache/cloudstack.json') .networkOff() - .expectJSON({ name: 'code quality: java', value: 'inaccessible' }); + .expectJSON({ name: 'code quality: java', value: 'inaccessible' }) t.create('grade: invalid json') .get('/grade/java/g/apache/cloudstack.json') - .intercept(nock => nock('https://lgtm.com') - .get('/api/v0.1/project/g/apache/cloudstack/details') - .reply(invalidJSON)) - .expectJSON({ name: 'code quality: java', value: 'invalid' }); + .intercept(nock => + nock('https://lgtm.com') + .get('/api/v0.1/project/g/apache/cloudstack/details') + .reply(invalidJSON) + ) + .expectJSON({ name: 'code quality: java', value: 'invalid' }) t.create('grade: json missing languages') .get('/grade/java/g/apache/cloudstack.json') - .intercept(nock => nock('https://lgtm.com') - .get('/api/v0.1/project/g/apache/cloudstack/details') - .reply(200, {})) - .expectJSON({ name: 'code quality: java', value: 'invalid' }); + .intercept(nock => + nock('https://lgtm.com') + .get('/api/v0.1/project/g/apache/cloudstack/details') + .reply(200, {}) + ) + .expectJSON({ name: 'code quality: java', value: 'invalid' }) t.create('grade: grade for a project (java)') .get('/grade/java/g/apache/cloudstack.json') - .expectJSONTypes(Joi.object().keys({ - name: 'code quality: java', - value: Joi.string().regex(/^(?:A\+)|A|B|C|D|E$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'code quality: java', + value: Joi.string().regex(/^(?:A\+)|A|B|C|D|E$/), + }) + ) t.create('grade: grade for missing language') .get('/grade/foo/g/apache/cloudstack.json') .expectJSON({ name: 'code quality: foo', - value: 'no data for language' - }); + value: 'no data for language', + }) // Test display of languages -const data = {languages: [ - {lang: 'cpp', grade: 'A+'}, - {lang: 'javascript', grade: 'A'}, - {lang: 'java', grade: 'B'}, - {lang: 'python', grade: 'C'}, - {lang: 'csharp', grade: 'D'}, - {lang: 'other', grade: 'E'}, - {lang: 'foo'} -]} +const data = { + languages: [ + { lang: 'cpp', grade: 'A+' }, + { lang: 'javascript', grade: 'A' }, + { lang: 'java', grade: 'B' }, + { lang: 'python', grade: 'C' }, + { lang: 'csharp', grade: 'D' }, + { lang: 'other', grade: 'E' }, + { lang: 'foo' }, + ], +} t.create('grade: cpp') .get('/grade/cpp/g/apache/cloudstack.json') - .intercept(nock => nock('https://lgtm.com') - .get('/api/v0.1/project/g/apache/cloudstack/details') - .reply(200, data)) - .expectJSON({ name: 'code quality: c/c++', value: 'A+' }); + .intercept(nock => + nock('https://lgtm.com') + .get('/api/v0.1/project/g/apache/cloudstack/details') + .reply(200, data) + ) + .expectJSON({ name: 'code quality: c/c++', value: 'A+' }) t.create('grade: javascript') .get('/grade/javascript/g/apache/cloudstack.json') - .intercept(nock => nock('https://lgtm.com') - .get('/api/v0.1/project/g/apache/cloudstack/details') - .reply(200, data)) - .expectJSON({ name: 'code quality: js/ts', value: 'A' }); + .intercept(nock => + nock('https://lgtm.com') + .get('/api/v0.1/project/g/apache/cloudstack/details') + .reply(200, data) + ) + .expectJSON({ name: 'code quality: js/ts', value: 'A' }) t.create('grade: java') .get('/grade/java/g/apache/cloudstack.json') - .intercept(nock => nock('https://lgtm.com') - .get('/api/v0.1/project/g/apache/cloudstack/details') - .reply(200, data)) - .expectJSON({ name: 'code quality: java', value: 'B' }); + .intercept(nock => + nock('https://lgtm.com') + .get('/api/v0.1/project/g/apache/cloudstack/details') + .reply(200, data) + ) + .expectJSON({ name: 'code quality: java', value: 'B' }) t.create('grade: python') .get('/grade/python/g/apache/cloudstack.json') - .intercept(nock => nock('https://lgtm.com') - .get('/api/v0.1/project/g/apache/cloudstack/details') - .reply(200, data)) - .expectJSON({ name: 'code quality: python', value: 'C' }); + .intercept(nock => + nock('https://lgtm.com') + .get('/api/v0.1/project/g/apache/cloudstack/details') + .reply(200, data) + ) + .expectJSON({ name: 'code quality: python', value: 'C' }) t.create('grade: csharp') .get('/grade/csharp/g/apache/cloudstack.json') - .intercept(nock => nock('https://lgtm.com') - .get('/api/v0.1/project/g/apache/cloudstack/details') - .reply(200, data)) - .expectJSON({ name: 'code quality: c#', value: 'D' }); + .intercept(nock => + nock('https://lgtm.com') + .get('/api/v0.1/project/g/apache/cloudstack/details') + .reply(200, data) + ) + .expectJSON({ name: 'code quality: c#', value: 'D' }) t.create('grade: other') .get('/grade/other/g/apache/cloudstack.json') - .intercept(nock => nock('https://lgtm.com') - .get('/api/v0.1/project/g/apache/cloudstack/details') - .reply(200, data)) - .expectJSON({ name: 'code quality: other', value: 'E' }); + .intercept(nock => + nock('https://lgtm.com') + .get('/api/v0.1/project/g/apache/cloudstack/details') + .reply(200, data) + ) + .expectJSON({ name: 'code quality: other', value: 'E' }) t.create('grade: foo (no grade for valid language)') .get('/grade/foo/g/apache/cloudstack.json') - .intercept(nock => nock('https://lgtm.com') - .get('/api/v0.1/project/g/apache/cloudstack/details') - .reply(200, data)) - .expectJSON({ name: 'code quality: foo', value: 'no data for language' }); + .intercept(nock => + nock('https://lgtm.com') + .get('/api/v0.1/project/g/apache/cloudstack/details') + .reply(200, data) + ) + .expectJSON({ name: 'code quality: foo', value: 'no data for language' }) diff --git a/services/liberapay/liberapay.tester.js b/services/liberapay/liberapay.tester.js index 1af86de537c4b4a47bd6580b987dd06f81017be5..8bbf7beddc55c33fb2a32c445841cdadacd45551 100644 --- a/services/liberapay/liberapay.tester.js +++ b/services/liberapay/liberapay.tester.js @@ -1,53 +1,64 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const isLiberapayTestValues = - Joi.string().regex(/^([0-9]*[1-9][0-9]*(\.[0-9]+)?|[0]+\.[0-9]*[1-9][0-9]*)[ A-Za-z]{4}\/week/); //values must be greater than zero -const {isMetric} = require('../test-validators'); -const t = new ServiceTester({ id: 'liberapay', title: 'Liberapay' }); -module.exports = t; +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const isLiberapayTestValues = Joi.string().regex( + /^([0-9]*[1-9][0-9]*(\.[0-9]+)?|[0]+\.[0-9]*[1-9][0-9]*)[ A-Za-z]{4}\/week/ +) //values must be greater than zero +const { isMetric } = require('../test-validators') +const t = new ServiceTester({ id: 'liberapay', title: 'Liberapay' }) +module.exports = t t.create('Receiving') .get('/receives/Liberapay.json') - .expectJSONTypes(Joi.object().keys({ - name: 'receives', - value: isLiberapayTestValues - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'receives', + value: isLiberapayTestValues, + }) + ) t.create('Giving') .get('/gives/Changaco.json') - .expectJSONTypes(Joi.object().keys({ - name: 'gives', - value: isLiberapayTestValues - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'gives', + value: isLiberapayTestValues, + }) + ) t.create('Patrons') .get('/patrons/Liberapay.json') - .expectJSONTypes(Joi.object().keys({ - name: 'patrons', - value: isMetric - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'patrons', + value: isMetric, + }) + ) t.create('Goal Progress') .get('/goal/Liberapay.json') - .expectJSONTypes(Joi.object().keys({ - name: 'goal progress', - value: Joi.string().regex(/^[0-9]+%/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'goal progress', + value: Joi.string().regex(/^[0-9]+%/), + }) + ) t.create('No Goal') .get('/goal/Liberapay.json') - .intercept(nock => nock('https://liberapay.com') - .get('/Liberapay/public.json') - .reply(200, { goal: null }) + .intercept(nock => + nock('https://liberapay.com') + .get('/Liberapay/public.json') + .reply(200, { goal: null }) ) - .expectJSON({ name: 'goal progress', value: 'anonymous'}); + .expectJSON({ name: 'goal progress', value: 'anonymous' }) t.create('Empty') .get('/receives/Liberapay.json') - .intercept(nock => nock('https://liberapay.com') - .get('/Liberapay/public.json') - .reply(200, { receiving: 0.00 }) + .intercept(nock => + nock('https://liberapay.com') + .get('/Liberapay/public.json') + .reply(200, { receiving: 0.0 }) ) - .expectJSON({ name: 'receives', value: 'anonymous'}); + .expectJSON({ name: 'receives', value: 'anonymous' }) diff --git a/services/librariesio/librariesio.tester.js b/services/librariesio/librariesio.tester.js index db4a78b3ae821af79f5d352eecc09b5059fffa15..395c81aefec4a3e5d20bee89f28350bf055d873b 100644 --- a/services/librariesio/librariesio.tester.js +++ b/services/librariesio/librariesio.tester.js @@ -1,41 +1,45 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { - isDependencyState -} = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isDependencyState } = require('../test-validators') -const t = new ServiceTester({ id: 'librariesio', title: 'Libraries.io' }); -module.exports = t; +const t = new ServiceTester({ id: 'librariesio', title: 'Libraries.io' }) +module.exports = t t.create('dependencies for releases') .get('/release/hex/phoenix/1.0.3.json') - .expectJSONTypes(Joi.object().keys({ - name: 'dependencies', - value: isDependencyState, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'dependencies', + value: isDependencyState, + }) + ) t.create('dependencies for github') .get('/github/pyvesb/notepad4e.json') - .expectJSONTypes(Joi.object().keys({ - name: 'dependencies', - value: isDependencyState, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'dependencies', + value: isDependencyState, + }) + ) t.create('release not found') .get('/release/hex/invalid/4.0.4.json') .expectJSON({ name: 'dependencies', value: 'not available', - }); + }) t.create('no response data') .get('/github/phoenixframework/phoenix.json') - .intercept(nock => nock('https://libraries.io') - .get('/api/github/phoenixframework/phoenix/dependencies') - .reply(200)) + .intercept(nock => + nock('https://libraries.io') + .get('/api/github/phoenixframework/phoenix/dependencies') + .reply(200) + ) .expectJSON({ - name: 'dependencies', - value: 'invalid', - }); + name: 'dependencies', + value: 'invalid', + }) diff --git a/services/libscore/libscore.tester.js b/services/libscore/libscore.tester.js index 67d4486d903fcc06dafbad17cb366dd9cfc81dc0..edd15ee8dd22a195b7d9e0c9686a4764ad7c3992 100644 --- a/services/libscore/libscore.tester.js +++ b/services/libscore/libscore.tester.js @@ -1,41 +1,45 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isMetric } = require('../test-validators'); -const { invalidJSON } = require('../response-fixtures'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isMetric } = require('../test-validators') +const { invalidJSON } = require('../response-fixtures') -const t = new ServiceTester({ id: 'libscore', title: 'libscore' }); -module.exports = t; +const t = new ServiceTester({ id: 'libscore', title: 'libscore' }) +module.exports = t t.create('libscore (valid)') .get('/s/jQuery.json') - .expectJSONTypes(Joi.object().keys({ - name: 'libscore', - value: isMetric, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'libscore', + value: isMetric, + }) + ) t.create('libscore (not found)') .get('/s/not-a-library.json') - .expectJSON({name: 'libscore', value: 'not found'}); + .expectJSON({ name: 'libscore', value: 'not found' }) t.create('libscore (connection error)') .get('/s/jQuery.json') .networkOff() - .expectJSON({name: 'libscore', value: 'inaccessible'}); + .expectJSON({ name: 'libscore', value: 'inaccessible' }) t.create('libscore (unexpected response)') .get('/s/jQuery.json') - .intercept(nock => nock('http://api.libscore.com') - .get('/v1/libraries/jQuery') - .reply(invalidJSON) + .intercept(nock => + nock('http://api.libscore.com') + .get('/v1/libraries/jQuery') + .reply(invalidJSON) ) - .expectJSON({name: 'libscore', value: 'invalid'}); + .expectJSON({ name: 'libscore', value: 'invalid' }) t.create('libscore (error response)') .get('/s/jQuery.json') - .intercept(nock => nock('http://api.libscore.com') - .get('/v1/libraries/jQuery') - .reply(500, '{"error":"oh noes!!"}') + .intercept(nock => + nock('http://api.libscore.com') + .get('/v1/libraries/jQuery') + .reply(500, '{"error":"oh noes!!"}') ) - .expectJSON({name: 'libscore', value: 'invalid'}); + .expectJSON({ name: 'libscore', value: 'invalid' }) diff --git a/services/luarocks/luarocks.tester.js b/services/luarocks/luarocks.tester.js index 1279ff4d4ac618626d4f94d2c366b9328ae1d06e..19907808c5a7fd475af4a4265eac1674acbe03c1 100644 --- a/services/luarocks/luarocks.tester.js +++ b/services/luarocks/luarocks.tester.js @@ -1,25 +1,27 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') -const t = new ServiceTester({ id: 'luarocks', title: 'LuaRocks' }); -module.exports = t; +const t = new ServiceTester({ id: 'luarocks', title: 'LuaRocks' }) +module.exports = t -const isLuaVersion = Joi.string().regex(/^v\d+\.\d+\.\d+-\d+$/); +const isLuaVersion = Joi.string().regex(/^v\d+\.\d+\.\d+-\d+$/) t.create('version') .get('/v/mpeterv/luacheck.json') - .expectJSONTypes(Joi.object().keys({ - name: 'luarocks', - value: isLuaVersion - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'luarocks', + value: isLuaVersion, + }) + ) t.create('unknown package') .get('/v/nil/does-not-exist.json') - .expectJSON({ name: 'luarocks', value: 'invalid' }); + .expectJSON({ name: 'luarocks', value: 'invalid' }) t.create('connection error') .get('/v/mpeterv/luacheck.json') .networkOff() - .expectJSON({ name: 'luarocks', value: 'inaccessible' }); + .expectJSON({ name: 'luarocks', value: 'inaccessible' }) diff --git a/services/magnumci/magnumci.tester.js b/services/magnumci/magnumci.tester.js index 5df50eeb059bcef0c0208d97026913eed43df1a1..3b75f3e203c1d94794da40a60558722b1419a77a 100644 --- a/services/magnumci/magnumci.tester.js +++ b/services/magnumci/magnumci.tester.js @@ -1,13 +1,13 @@ -'use strict'; +'use strict' -const ServiceTester = require('../service-tester'); +const ServiceTester = require('../service-tester') -const t = new ServiceTester({ id: 'magnumci', title: 'Magnum CI' }); -module.exports = t; +const t = new ServiceTester({ id: 'magnumci', title: 'Magnum CI' }) +module.exports = t t.create('no longer available') .get('/ci/96ffb83fa700f069024921b0702e76ff.json') .expectJSON({ name: 'magnum ci', - value: 'no longer available' - }); + value: 'no longer available', + }) diff --git a/services/maintenance/maintenance.tester.js b/services/maintenance/maintenance.tester.js index 7c7348848690fade4464b0e6573ce13ce0f2ee7c..9f3a9edac3007fb4a238b43e3ab88ae6ae24b24e 100644 --- a/services/maintenance/maintenance.tester.js +++ b/services/maintenance/maintenance.tester.js @@ -1,24 +1,24 @@ -'use strict'; +'use strict' -const ServiceTester = require('../service-tester'); +const ServiceTester = require('../service-tester') -const t = new ServiceTester({ id: 'maintenance', title: 'Maintenance' }); -module.exports = t; +const t = new ServiceTester({ id: 'maintenance', title: 'Maintenance' }) +module.exports = t -const currentYear = (new Date()).getUTCFullYear(); +const currentYear = new Date().getUTCFullYear() t.create('yes last maintained 2016 (no)') .get('/yes/2016.json') - .expectJSON({ name: 'maintained', value: 'no! (as of 2016)' }); + .expectJSON({ name: 'maintained', value: 'no! (as of 2016)' }) t.create('no longer maintained 2017 (no)') .get('/no/2017.json') - .expectJSON({ name: 'maintained', value: 'no! (as of 2017)' }); + .expectJSON({ name: 'maintained', value: 'no! (as of 2017)' }) t.create('yes this year (yes)') .get('/yes/' + currentYear + '.json') - .expectJSON({ name: 'maintained', value: 'yes' }); + .expectJSON({ name: 'maintained', value: 'yes' }) t.create('until end of ' + currentYear + ' (yes)') .get('/until end of ' + currentYear + '/' + currentYear + '.json') - .expectJSON({ name: 'maintained', value: 'until end of ' + currentYear }); + .expectJSON({ name: 'maintained', value: 'until end of ' + currentYear }) diff --git a/services/maven-central/maven-central.tester.js b/services/maven-central/maven-central.tester.js index f1ceae2d55050a8ae00db866cf85879a2a1a19c9..2125abfe5f5de637ddfbabb6debb128038ac43ae 100644 --- a/services/maven-central/maven-central.tester.js +++ b/services/maven-central/maven-central.tester.js @@ -1,37 +1,43 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') -const t = new ServiceTester({ id: 'maven-central', title: 'Maven Central' }); -module.exports = t; +const t = new ServiceTester({ id: 'maven-central', title: 'Maven Central' }) +module.exports = t t.create('latest version') .get('/v/com.github.fabriziocucci/yacl4j.json') // http://repo1.maven.org/maven2/com/github/fabriziocucci/yacl4j/ - .expectJSONTypes(Joi.object().keys({ - name: 'maven-central', - value: Joi.string().regex(/^v(.*)$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'maven-central', + value: Joi.string().regex(/^v(.*)$/), + }) + ) t.create('latest 0.8 version') .get('/v/com.github.fabriziocucci/yacl4j/0.8.json') // http://repo1.maven.org/maven2/com/github/fabriziocucci/yacl4j/ - .expectJSONTypes(Joi.object().keys({ - name: 'maven-central', - value: Joi.string().regex(/^v0\.8(.*)$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'maven-central', + value: Joi.string().regex(/^v0\.8(.*)$/), + }) + ) t.create('inexistent artifact') .get('/v/inexistent-group-id/inexistent-artifact-id.json') - .expectJSON({ name: 'maven-central', value: 'invalid' }); + .expectJSON({ name: 'maven-central', value: 'invalid' }) t.create('connection error') .get('/v/com.github.fabriziocucci/yacl4j.json') .networkOff() - .expectJSON({ name: 'maven-central', value: 'inaccessible' }); + .expectJSON({ name: 'maven-central', value: 'inaccessible' }) t.create('xml parsing error') .get('/v/com.github.fabriziocucci/yacl4j.json') - .intercept(nock => nock('http://repo1.maven.org/maven2') - .get('/com/github/fabriziocucci/yacl4j/maven-metadata.xml') - .reply(200, 'this should be a valid xml')) - .expectJSON({ name: 'maven-central', value: 'invalid' }); + .intercept(nock => + nock('http://repo1.maven.org/maven2') + .get('/com/github/fabriziocucci/yacl4j/maven-metadata.xml') + .reply(200, 'this should be a valid xml') + ) + .expectJSON({ name: 'maven-central', value: 'invalid' }) diff --git a/services/maven-metadata/maven-metadata.tester.js b/services/maven-metadata/maven-metadata.tester.js index 07b7afff2373203d4cc7b394d47ddefbaab0ab9f..0ca9405191698f883c3f4047b72aee9bd227ecdb 100644 --- a/services/maven-metadata/maven-metadata.tester.js +++ b/services/maven-metadata/maven-metadata.tester.js @@ -1,15 +1,22 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isVPlusDottedVersionAtLeastOne } = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isVPlusDottedVersionAtLeastOne } = require('../test-validators') -const t = new ServiceTester({ id: 'maven-metadata', title: 'maven-metadata badge' }); -module.exports = t; +const t = new ServiceTester({ + id: 'maven-metadata', + title: 'maven-metadata badge', +}) +module.exports = t t.create('valid maven-metadata.xml uri') - .get('/v/http/central.maven.org/maven2/com/google/code/gson/gson/maven-metadata.xml.json') - .expectJSONTypes(Joi.object().keys({ - name: 'maven', - value: isVPlusDottedVersionAtLeastOne - })); + .get( + '/v/http/central.maven.org/maven2/com/google/code/gson/gson/maven-metadata.xml.json' + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'maven', + value: isVPlusDottedVersionAtLeastOne, + }) + ) diff --git a/services/microbadger/microbadger.tester.js b/services/microbadger/microbadger.tester.js index 208c5220f51258891f19a44d8ddc29fc604987a4..376ba4fac178d6df846d5de3dbb0d24f3f24f9df 100644 --- a/services/microbadger/microbadger.tester.js +++ b/services/microbadger/microbadger.tester.js @@ -1,86 +1,103 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isFileSize } = require('../test-validators'); -const { invalidJSON } = require('../response-fixtures'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isFileSize } = require('../test-validators') +const { invalidJSON } = require('../response-fixtures') -const t = new ServiceTester({ id: 'microbadger', title: 'MicroBadger' }); -module.exports = t; +const t = new ServiceTester({ id: 'microbadger', title: 'MicroBadger' }) +module.exports = t t.create('image size without a specified tag') .get('/image-size/wikiwi/docker-build.json') - .expectJSONTypes(Joi.object().keys({ - name: 'image size', - value: isFileSize - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'image size', + value: isFileSize, + }) + ) t.create('image size with a specified tag') .get('/image-size/kelseyhightower/helloworld/appengine.json') - .expectJSONTypes(Joi.object().keys({ - name: 'image size', - value: isFileSize - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'image size', + value: isFileSize, + }) + ) t.create('layers without a specified tag') .get('/layers/_/hello-world.json') - .expectJSONTypes(Joi.object().keys({ - name: 'layers', - value: Joi.number().integer().positive() - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'layers', + value: Joi.number() + .integer() + .positive(), + }) + ) t.create('layers with a specified tag') .get('/layers/_/httpd/alpine.json') - .expectJSONTypes(Joi.object().keys({ - name: 'layers', - value: Joi.number().integer().positive() - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'layers', + value: Joi.number() + .integer() + .positive(), + }) + ) t.create('specified tag when repository has only one') .get('/layers/_/hello-world/wrong-tag.json') - .expectJSON({ name: 'layers', value: 'not found' }); + .expectJSON({ name: 'layers', value: 'not found' }) t.create('nonexistent repository') .get('/layers/_/unknown.json') - .intercept(nock => nock('https://api.microbadger.com') - .get('/v1/images/library/unknown') - .reply(404) + .intercept(nock => + nock('https://api.microbadger.com') + .get('/v1/images/library/unknown') + .reply(404) ) - .expectJSON({ name: 'layers', value: 'not found' }); + .expectJSON({ name: 'layers', value: 'not found' }) t.create('nonexistent tag') .get('/layers/_/unknown/wrong-tag.json') - .intercept(nock => nock('https://api.microbadger.com') - .get('/v1/images/library/unknown') - .reply(200, { Versions: [] }) + .intercept(nock => + nock('https://api.microbadger.com') + .get('/v1/images/library/unknown') + .reply(200, { Versions: [] }) ) - .expectJSON({ name: 'layers', value: 'not found' }); + .expectJSON({ name: 'layers', value: 'not found' }) t.create('server error') .get('/image-size/_/hello-world.json') - .intercept(nock => nock('https://api.microbadger.com') - .get('/v1/images/library/hello-world') - .reply(500, 'Something went wrong') + .intercept(nock => + nock('https://api.microbadger.com') + .get('/v1/images/library/hello-world') + .reply(500, 'Something went wrong') ) - .expectJSON({ name: 'image size', value: 'inaccessible' }); + .expectJSON({ name: 'image size', value: 'inaccessible' }) t.create('connection error') .get('/image-size/_/hello-world.json') .networkOff() - .expectJSON({ name: 'image size', value: 'inaccessible' }); + .expectJSON({ name: 'image size', value: 'inaccessible' }) t.create('unexpected response') .get('/image-size/_/hello-world.json') - .intercept(nock => nock('https://api.microbadger.com') - .get('/v1/images/library/hello-world') - .reply(invalidJSON) + .intercept(nock => + nock('https://api.microbadger.com') + .get('/v1/images/library/hello-world') + .reply(invalidJSON) ) - .expectJSON({ name: 'image size', value: 'error' }); + .expectJSON({ name: 'image size', value: 'error' }) t.create('missing download size') .get('/image-size/puppet/puppetserver.json') - .intercept(nock => nock('https://api.microbadger.com') - .get('/v1/images/puppet/puppetserver') - .reply(200, {}) + .intercept(nock => + nock('https://api.microbadger.com') + .get('/v1/images/puppet/puppetserver') + .reply(200, {}) ) - .expectJSON({ name: 'image size', value: 'unknown' }); + .expectJSON({ name: 'image size', value: 'unknown' }) diff --git a/services/myget/myget.tester.js b/services/myget/myget.tester.js index 3b1987854a3fe2115108b7dc0430429ca5290d81..6f58c89748853859979de5390bb5b10fb3bde6ae 100644 --- a/services/myget/myget.tester.js +++ b/services/myget/myget.tester.js @@ -1,233 +1,257 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') const { isMetric, isVPlusDottedVersionNClauses, isVPlusDottedVersionNClausesWithOptionalSuffix, -} = require('../test-validators'); -const colorscheme = require('../../lib/colorscheme.json'); +} = require('../test-validators') +const colorscheme = require('../../lib/colorscheme.json') const { queryIndex, nuGetV3VersionJsonWithDash, nuGetV3VersionJsonFirstCharZero, nuGetV3VersionJsonFirstCharNotZero, -} = require('../nuget-fixtures'); -const { invalidJSON } = require('../response-fixtures'); - -const t = new ServiceTester({ id: 'myget', title: 'MyGet' }); -module.exports = t; +} = require('../nuget-fixtures') +const { invalidJSON } = require('../response-fixtures') +const t = new ServiceTester({ id: 'myget', title: 'MyGet' }) +module.exports = t // downloads t.create('total downloads (valid)') .get('/mongodb/dt/MongoDB.Driver.Core.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetric, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetric, + }) + ) t.create('total downloads (not found)') .get('/mongodb/dt/not-a-real-package.json') - .expectJSON({name: 'downloads', value: 'not found'}); + .expectJSON({ name: 'downloads', value: 'not found' }) t.create('total downloads (connection error)') .get('/mongodb/dt/MongoDB.Driver.Core.json') .networkOff() - .expectJSON({name: 'downloads', value: 'inaccessible'}); + .expectJSON({ name: 'downloads', value: 'inaccessible' }) t.create('total downloads (unexpected first response)') .get('/mongodb/dt/MongoDB.Driver.Core.json') - .intercept(nock => nock('https://www.myget.org') - .get("/F/mongodb/api/v3/index.json") - .reply(invalidJSON) + .intercept(nock => + nock('https://www.myget.org') + .get('/F/mongodb/api/v3/index.json') + .reply(invalidJSON) ) - .expectJSON({name: 'downloads', value: 'invalid'}); + .expectJSON({ name: 'downloads', value: 'invalid' }) t.create('total downloads (unexpected second response)') .get('/mongodb/dt/MongoDB.Driver.Core.json') - .intercept(nock => nock('https://www.myget.org') - .get("/F/mongodb/api/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://www.myget.org') + .get('/F/mongodb/api/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:mongodb.driver.core&prerelease=true") - .reply(invalidJSON) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:mongodb.driver.core&prerelease=true') + .reply(invalidJSON) ) - .expectJSON({name: 'downloads', value: 'invalid'}); - + .expectJSON({ name: 'downloads', value: 'invalid' }) // version t.create('version (valid)') .get('/mongodb/v/MongoDB.Driver.Core.json') - .expectJSONTypes(Joi.object().keys({ - name: 'mongodb', - value: isVPlusDottedVersionNClauses, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'mongodb', + value: isVPlusDottedVersionNClauses, + }) + ) t.create('version (mocked, yellow badge)') .get('/mongodb/v/MongoDB.Driver.Core.json?style=_shields_test') - .intercept(nock => nock('https://www.myget.org') - .get("/F/mongodb/api/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://www.myget.org') + .get('/F/mongodb/api/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:mongodb.driver.core&prerelease=true") - .reply(200, nuGetV3VersionJsonWithDash) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:mongodb.driver.core&prerelease=true') + .reply(200, nuGetV3VersionJsonWithDash) ) .expectJSON({ name: 'mongodb', value: 'v1.2-beta', - colorB: colorscheme.yellow.colorB - }); + colorB: colorscheme.yellow.colorB, + }) t.create('version (mocked, orange badge)') .get('/mongodb/v/MongoDB.Driver.Core.json?style=_shields_test') - .intercept(nock => nock('https://www.myget.org') - .get("/F/mongodb/api/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://www.myget.org') + .get('/F/mongodb/api/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:mongodb.driver.core&prerelease=true") - .reply(200, nuGetV3VersionJsonFirstCharZero) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:mongodb.driver.core&prerelease=true') + .reply(200, nuGetV3VersionJsonFirstCharZero) ) .expectJSON({ name: 'mongodb', value: 'v0.35', - colorB: colorscheme.orange.colorB - }); + colorB: colorscheme.orange.colorB, + }) t.create('version (mocked, blue badge)') .get('/mongodb/v/MongoDB.Driver.Core.json?style=_shields_test') - .intercept(nock => nock('https://www.myget.org') - .get("/F/mongodb/api/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://www.myget.org') + .get('/F/mongodb/api/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:mongodb.driver.core&prerelease=true") - .reply(200, nuGetV3VersionJsonFirstCharNotZero) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:mongodb.driver.core&prerelease=true') + .reply(200, nuGetV3VersionJsonFirstCharNotZero) ) .expectJSON({ name: 'mongodb', value: 'v1.2.7', - colorB: colorscheme.blue.colorB - }); + colorB: colorscheme.blue.colorB, + }) t.create('version (not found)') .get('/foo/v/not-a-real-package.json') - .expectJSON({name: 'foo', value: 'not found'}); + .expectJSON({ name: 'foo', value: 'not found' }) t.create('version (connection error)') .get('/mongodb/v/MongoDB.Driver.Core.json') .networkOff() - .expectJSON({name: 'mongodb', value: 'inaccessible'}); + .expectJSON({ name: 'mongodb', value: 'inaccessible' }) t.create('version (unexpected first response)') .get('/mongodb/v/MongoDB.Driver.Core.json') - .intercept(nock => nock('https://www.myget.org') - .get("/F/mongodb/api/v3/index.json") - .reply(invalidJSON) + .intercept(nock => + nock('https://www.myget.org') + .get('/F/mongodb/api/v3/index.json') + .reply(invalidJSON) ) - .expectJSON({name: 'mongodb', value: 'invalid'}); + .expectJSON({ name: 'mongodb', value: 'invalid' }) t.create('version (unexpected second response)') .get('/mongodb/v/MongoDB.Driver.Core.json') - .intercept(nock => nock('https://www.myget.org') - .get("/F/mongodb/api/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://www.myget.org') + .get('/F/mongodb/api/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:mongodb.driver.core&prerelease=true") - .reply(invalidJSON) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:mongodb.driver.core&prerelease=true') + .reply(invalidJSON) ) - .expectJSON({name: 'mongodb', value: 'invalid'}); - + .expectJSON({ name: 'mongodb', value: 'invalid' }) // version (pre) t.create('version (pre) (valid)') .get('/mongodb/vpre/MongoDB.Driver.Core.json') - .expectJSONTypes(Joi.object().keys({ - name: 'mongodb', - value: isVPlusDottedVersionNClausesWithOptionalSuffix, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'mongodb', + value: isVPlusDottedVersionNClausesWithOptionalSuffix, + }) + ) t.create('version (pre) (mocked, yellow badge)') .get('/mongodb/vpre/MongoDB.Driver.Core.json?style=_shields_test') - .intercept(nock => nock('https://www.myget.org') - .get("/F/mongodb/api/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://www.myget.org') + .get('/F/mongodb/api/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:mongodb.driver.core&prerelease=true") - .reply(200, nuGetV3VersionJsonWithDash) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:mongodb.driver.core&prerelease=true') + .reply(200, nuGetV3VersionJsonWithDash) ) .expectJSON({ name: 'mongodb', value: 'v1.2-beta', - colorB: colorscheme.yellow.colorB - }); + colorB: colorscheme.yellow.colorB, + }) t.create('version (pre) (mocked, orange badge)') .get('/mongodb/vpre/MongoDB.Driver.Core.json?style=_shields_test') - .intercept(nock => nock('https://www.myget.org') - .get("/F/mongodb/api/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://www.myget.org') + .get('/F/mongodb/api/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:mongodb.driver.core&prerelease=true") - .reply(200, nuGetV3VersionJsonFirstCharZero) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:mongodb.driver.core&prerelease=true') + .reply(200, nuGetV3VersionJsonFirstCharZero) ) .expectJSON({ name: 'mongodb', value: 'v0.35', - colorB: colorscheme.orange.colorB - }); + colorB: colorscheme.orange.colorB, + }) t.create('version (pre) (mocked, blue badge)') .get('/mongodb/vpre/MongoDB.Driver.Core.json?style=_shields_test') - .intercept(nock => nock('https://www.myget.org') - .get("/F/mongodb/api/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://www.myget.org') + .get('/F/mongodb/api/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:mongodb.driver.core&prerelease=true") - .reply(200, nuGetV3VersionJsonFirstCharNotZero) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:mongodb.driver.core&prerelease=true') + .reply(200, nuGetV3VersionJsonFirstCharNotZero) ) .expectJSON({ name: 'mongodb', value: 'v1.2.7', - colorB: colorscheme.blue.colorB - }); + colorB: colorscheme.blue.colorB, + }) t.create('version (pre) (not found)') .get('/foo/vpre/not-a-real-package.json') - .expectJSON({name: 'foo', value: 'not found'}); + .expectJSON({ name: 'foo', value: 'not found' }) t.create('version (pre) (connection error)') .get('/mongodb/vpre/MongoDB.Driver.Core.json') .networkOff() - .expectJSON({name: 'mongodb', value: 'inaccessible'}); + .expectJSON({ name: 'mongodb', value: 'inaccessible' }) t.create('version (pre) (unexpected first response)') .get('/mongodb/vpre/MongoDB.Driver.Core.json') - .intercept(nock => nock('https://www.myget.org') - .get("/F/mongodb/api/v3/index.json") - .reply(invalidJSON) + .intercept(nock => + nock('https://www.myget.org') + .get('/F/mongodb/api/v3/index.json') + .reply(invalidJSON) ) - .expectJSON({name: 'mongodb', value: 'invalid'}); + .expectJSON({ name: 'mongodb', value: 'invalid' }) t.create('version (pre) (unexpected second response)') .get('/mongodb/vpre/MongoDB.Driver.Core.json') - .intercept(nock => nock('https://www.myget.org') - .get("/F/mongodb/api/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://www.myget.org') + .get('/F/mongodb/api/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:mongodb.driver.core&prerelease=true") - .reply(invalidJSON) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:mongodb.driver.core&prerelease=true') + .reply(invalidJSON) ) - .expectJSON({name: 'mongodb', value: 'invalid'}); + .expectJSON({ name: 'mongodb', value: 'invalid' }) diff --git a/services/nexus/nexus.tester.js b/services/nexus/nexus.tester.js index e19eaf77fb970770bd8931ce4d90372cfc9ac0a9..4d7c39a5f75802735593da55cd553b8a67e08934 100644 --- a/services/nexus/nexus.tester.js +++ b/services/nexus/nexus.tester.js @@ -1,74 +1,89 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { invalidJSON } = require('../response-fixtures'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { invalidJSON } = require('../response-fixtures') -const t = new ServiceTester({ id: 'nexus', title: 'Nexus' }); -module.exports = t; +const t = new ServiceTester({ id: 'nexus', title: 'Nexus' }) +module.exports = t t.create('search release version') .get('/r/https/repository.jboss.org/nexus/jboss/jboss-client.json') - .expectJSONTypes(Joi.object().keys({ - name: 'nexus', - value: Joi.string().regex(/^v4(\.\d+)+$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'nexus', + value: Joi.string().regex(/^v4(\.\d+)+$/), + }) + ) t.create('search release version of an inexistent artifact') .get('/r/https/repository.jboss.org/nexus/jboss/inexistent-artifact-id.json') - .expectJSON({ name: 'nexus', value: 'no-artifact' }); + .expectJSON({ name: 'nexus', value: 'no-artifact' }) t.create('search snapshot version') .get('/s/https/repository.jboss.org/nexus/com.progress.fuse/fusehq.json') - .expectJSONTypes(Joi.object().keys({ - name: 'nexus', - value: Joi.string().regex(/-SNAPSHOT$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'nexus', + value: Joi.string().regex(/-SNAPSHOT$/), + }) + ) t.create('search snapshot version not in latestSnapshot') .get('/s/https/repository.jboss.org/nexus/com.progress.fuse/fusehq.json') - .intercept(nock => nock('https://repository.jboss.org') - .get('/nexus/service/local/lucene/search') - .query({g: 'com.progress.fuse', a: 'fusehq'}) - .reply(200, '{ "data": [ { "version": "7.0.1-SNAPSHOT" } ] }')) - .expectJSON({ name: 'nexus', value: 'v7.0.1-SNAPSHOT' }); + .intercept(nock => + nock('https://repository.jboss.org') + .get('/nexus/service/local/lucene/search') + .query({ g: 'com.progress.fuse', a: 'fusehq' }) + .reply(200, '{ "data": [ { "version": "7.0.1-SNAPSHOT" } ] }') + ) + .expectJSON({ name: 'nexus', value: 'v7.0.1-SNAPSHOT' }) t.create('search snapshot version of a release artifact') .get('/s/https/repository.jboss.org/nexus/jboss/jboss-client.json') - .expectJSON({ name: 'nexus', value: 'undefined' }); + .expectJSON({ name: 'nexus', value: 'undefined' }) t.create('search snapshot version of an inexistent artifact') .get('/s/https/repository.jboss.org/nexus/jboss/inexistent-artifact-id.json') - .expectJSON({ name: 'nexus', value: 'no-artifact' }); + .expectJSON({ name: 'nexus', value: 'no-artifact' }) t.create('resolve version') .get('/developer/https/repository.jboss.org/nexus/ai.h2o/h2o-automl.json') - .expectJSONTypes(Joi.object().keys({ - name: 'nexus', - value: Joi.string().regex(/^v3(\.\d+)+$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'nexus', + value: Joi.string().regex(/^v3(\.\d+)+$/), + }) + ) t.create('resolve version with query') - .get('/fs-public-snapshots/https/repository.jboss.org/nexus/com.progress.fuse/fusehq:c=agent-apple-osx:p=tar.gz.json') - .expectJSONTypes(Joi.object().keys({ - name: 'nexus', - value: Joi.string().regex(/^v7(\.\d+)+-SNAPSHOT$/) - })); + .get( + '/fs-public-snapshots/https/repository.jboss.org/nexus/com.progress.fuse/fusehq:c=agent-apple-osx:p=tar.gz.json' + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'nexus', + value: Joi.string().regex(/^v7(\.\d+)+-SNAPSHOT$/), + }) + ) t.create('resolve version of an inexistent artifact') - .get('/developer/https/repository.jboss.org/nexus/jboss/inexistent-artifact-id.json') - .expectJSON({ name: 'nexus', value: 'no-artifact' }); + .get( + '/developer/https/repository.jboss.org/nexus/jboss/inexistent-artifact-id.json' + ) + .expectJSON({ name: 'nexus', value: 'no-artifact' }) t.create('connection error') .get('/r/https/repository.jboss.org/nexus/jboss/jboss-client.json') .networkOff() - .expectJSON({ name: 'nexus', value: 'inaccessible' }); + .expectJSON({ name: 'nexus', value: 'inaccessible' }) t.create('json parsing error') .get('/r/https/repository.jboss.org/nexus/jboss/jboss-client.json') - .intercept(nock => nock('https://repository.jboss.org') - .get('/nexus/service/local/lucene/search') - .query({g: 'jboss', a: 'jboss-client'}) - .reply(invalidJSON) + .intercept(nock => + nock('https://repository.jboss.org') + .get('/nexus/service/local/lucene/search') + .query({ g: 'jboss', a: 'jboss-client' }) + .reply(invalidJSON) ) - .expectJSON({ name: 'nexus', value: 'invalid' }); + .expectJSON({ name: 'nexus', value: 'invalid' }) diff --git a/services/node/node-version-color.js b/services/node/node-version-color.js index 9e51fea1ca85d080f8cd8c3a06b857a3fafb27bb..5c1a5abf735a4be3b1cd4141344b5c9548da5d8f 100644 --- a/services/node/node-version-color.js +++ b/services/node/node-version-color.js @@ -1,37 +1,37 @@ -'use strict'; +'use strict' -const { promisify } = require('util'); -const semver = require('semver'); -const { regularUpdate } = require('../../lib/regular-update'); +const { promisify } = require('util') +const semver = require('semver') +const { regularUpdate } = require('../../lib/regular-update') -function getLatestVersion () { +function getLatestVersion() { return promisify(regularUpdate)({ url: 'https://nodejs.org/dist/latest/SHASUMS256.txt', intervalMillis: 24 * 3600 * 1000, json: false, scraper: shasums => { // tarball index start, tarball index end - const taris = shasums.indexOf('node-v'); - const tarie = shasums.indexOf('\n', taris); - const tarball = shasums.slice(taris, tarie); - const version = tarball.split('-')[1]; - return version; + const taris = shasums.indexOf('node-v') + const tarie = shasums.indexOf('\n', taris) + const tarball = shasums.slice(taris, tarie) + const version = tarball.split('-')[1] + return version }, - }); + }) } async function versionColorForRange(range) { - const latestVersion = await getLatestVersion(); + const latestVersion = await getLatestVersion() try { if (semver.satisfies(latestVersion, range)) { - return 'brightgreen'; + return 'brightgreen' } else if (semver.gtr(latestVersion, range)) { - return 'yellow'; + return 'yellow' } else { - return 'orange'; + return 'orange' } - } catch(e) { - return 'lightgray'; + } catch (e) { + return 'lightgray' } } diff --git a/services/node/node.service.js b/services/node/node.service.js index e51c317bcfe138b7da2d027ddcb73e6b465be8ff..460d6ee13e7dc7c13a5055ce93c3a3fc5208ef36 100644 --- a/services/node/node.service.js +++ b/services/node/node.service.js @@ -1,19 +1,19 @@ -'use strict'; +'use strict' -const NPMBase = require('../npm/npm-base'); -const { versionColorForRange } = require('./node-version-color'); +const NPMBase = require('../npm/npm-base') +const { versionColorForRange } = require('./node-version-color') module.exports = class NodeVersion extends NPMBase { static get category() { - return 'version'; + return 'version' } static get defaultBadgeData() { - return { label: 'node' }; + return { label: 'node' } } static get url() { - return this.buildUrl('node/v', { withTag: true }); + return this.buildUrl('node/v', { withTag: true }) } static get examples() { @@ -43,24 +43,24 @@ module.exports = class NodeVersion extends NPMBase { query: { registry_uri: 'https://registry.npmjs.com' }, keywords: ['npm'], }, - ]; + ] } static async render({ tag, nodeVersionRange }) { - const label = tag ? `node@${tag}` : undefined; + const label = tag ? `node@${tag}` : undefined if (nodeVersionRange === undefined) { return { label, message: 'not specified', color: 'lightgray', - }; + } } else { return { label, message: nodeVersionRange, color: await versionColorForRange(nodeVersionRange), - }; + } } } @@ -70,16 +70,16 @@ module.exports = class NodeVersion extends NPMBase { packageName, tag, registryUrl, - } = this.constructor.unpackParams(namedParams, queryParams); + } = this.constructor.unpackParams(namedParams, queryParams) const { engines } = await this.fetchPackageData({ scope, packageName, registryUrl, tag, - }); + }) - const { node: nodeVersionRange } = engines || {}; + const { node: nodeVersionRange } = engines || {} - return this.constructor.render({ tag, nodeVersionRange }); + return this.constructor.render({ tag, nodeVersionRange }) } -}; +} diff --git a/services/node/node.tester.js b/services/node/node.tester.js index 973fda0517cd4b828603e0c172dc9c76b7fdceab..df986725476f7ac14ad108fc5a20fffc8ceaecdc 100644 --- a/services/node/node.tester.js +++ b/services/node/node.tester.js @@ -1,42 +1,52 @@ -'use strict'; +'use strict' -const { expect } = require('chai'); -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { Range } = require('semver'); +const { expect } = require('chai') +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { Range } = require('semver') -const t = new ServiceTester({ id: 'node', title: 'Node' }); -module.exports = t; +const t = new ServiceTester({ id: 'node', title: 'Node' }) +module.exports = t function expectSemverRange(value) { - expect(() => new Range(value)).not.to.throw(); + expect(() => new Range(value)).not.to.throw() } t.create('gets the node version of passport') .get('/v/passport.json') .expectJSONTypes(Joi.object({ name: 'node' }).unknown()) - .afterJSON(json => { expectSemverRange(json.value); }); + .afterJSON(json => { + expectSemverRange(json.value) + }) t.create('gets the node version of @stdlib/stdlib') .get('/v/@stdlib/stdlib.json') .expectJSONTypes(Joi.object({ name: 'node' }).unknown()) - .afterJSON(json => { expectSemverRange(json.value); }); + .afterJSON(json => { + expectSemverRange(json.value) + }) t.create("gets the tagged release's node version version of ionic") .get('/v/ionic/next.json') .expectJSONTypes(Joi.object({ name: 'node@next' }).unknown()) - .afterJSON(json => { expectSemverRange(json.value); }); + .afterJSON(json => { + expectSemverRange(json.value) + }) t.create('gets the node version of passport from a custom registry') .get('/v/passport.json?registry_uri=https://registry.npmjs.com') .expectJSONTypes(Joi.object({ name: 'node' }).unknown()) - .afterJSON(json => { expectSemverRange(json.value); }); + .afterJSON(json => { + expectSemverRange(json.value) + }) t.create("gets the tagged release's node version of @cycle/core") .get('/v/@cycle/core/canary.json') .expectJSONTypes(Joi.object({ name: 'node@canary' }).unknown()) - .afterJSON(json => { expectSemverRange(json.value); }); + .afterJSON(json => { + expectSemverRange(json.value) + }) t.create('invalid package name') .get('/v/frodo-is-not-a-package.json') - .expectJSON({ name: 'node', value: 'package not found' }); + .expectJSON({ name: 'node', value: 'package not found' }) diff --git a/services/npm/npm-base.js b/services/npm/npm-base.js index f6064d6ac13ad2c726eb7be000602f7510387b1a..956978c05d622782d6801cca752d203784d0dc06 100644 --- a/services/npm/npm-base.js +++ b/services/npm/npm-base.js @@ -1,12 +1,12 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const { BaseJsonService } = require('../base'); -const { InvalidResponse, NotFound } = require('../errors'); +const Joi = require('joi') +const { BaseJsonService } = require('../base') +const { InvalidResponse, NotFound } = require('../errors') const deprecatedLicenseObjectSchema = Joi.object({ type: Joi.string().required(), -}); +}) const schema = Joi.object({ devDependencies: Joi.object().pattern(/./, Joi.string()), engines: Joi.object().pattern(/./, Joi.string()), @@ -17,7 +17,7 @@ const schema = Joi.object({ Joi.alternatives(Joi.string(), deprecatedLicenseObjectSchema) ) ), -}).required(); +}).required() // Abstract class for NPM badges which display data about the latest version // of a package. @@ -29,14 +29,14 @@ module.exports = class NpmBase extends BaseJsonService { format: '(?:@([^/]+))?/?([^/]*)/?([^/]*)', capture: ['scope', 'packageName', 'tag'], queryParams: ['registry_uri'], - }; + } } else { return { base, format: '(?:@([^/]+)/)?([^/]+)', capture: ['scope', 'packageName'], queryParams: ['registry_uri'], - }; + } } } @@ -49,31 +49,31 @@ module.exports = class NpmBase extends BaseJsonService { packageName, tag, registryUrl, - }; + } } static encodeScopedPackage({ scope, packageName }) { // e.g. https://registry.npmjs.org/@cedx%2Fgulp-david - const encoded = encodeURIComponent(`${scope}/${packageName}`); - return `@${encoded}`; + const encoded = encodeURIComponent(`${scope}/${packageName}`) + return `@${encoded}` } async fetchPackageData({ registryUrl, scope, packageName, tag }) { - registryUrl = registryUrl || this.constructor.defaultRegistryUrl; - let url; + registryUrl = registryUrl || this.constructor.defaultRegistryUrl + let url if (scope === undefined) { // e.g. https://registry.npmjs.org/express/latest // Use this endpoint as an optimization. It covers the vast majority of // these badges, and the response is smaller. - url = `${registryUrl}/${packageName}/latest`; + url = `${registryUrl}/${packageName}/latest` } else { // e.g. https://registry.npmjs.org/@cedx%2Fgulp-david // because https://registry.npmjs.org/@cedx%2Fgulp-david/latest does not work const scoped = this.constructor.encodeScopedPackage({ scope, packageName, - }); - url = `${registryUrl}/${scoped}`; + }) + url = `${registryUrl}/${scoped}` } const json = await this._requestJson({ // We don't validate here because we need to pluck the desired subkey first. @@ -83,26 +83,26 @@ module.exports = class NpmBase extends BaseJsonService { // <https://github.com/npm/npmjs.org/issues/163> options: { Accept: '*/*' }, notFoundMessage: 'package not found', - }); + }) - let packageData; + let packageData if (scope === undefined) { - packageData = json; + packageData = json } else { - const registryTag = tag || 'latest'; - let latestVersion; + const registryTag = tag || 'latest' + let latestVersion try { - latestVersion = json['dist-tags'][registryTag]; + latestVersion = json['dist-tags'][registryTag] } catch (e) { - throw new NotFound({ prettyMessage: 'tag not found' }); + throw new NotFound({ prettyMessage: 'tag not found' }) } try { - packageData = json.versions[latestVersion]; + packageData = json.versions[latestVersion] } catch (e) { - throw new InvalidResponse('invalid json response'); + throw new InvalidResponse('invalid json response') } } - return this.constructor._validate(packageData, schema); + return this.constructor._validate(packageData, schema) } -}; +} diff --git a/services/npm/npm-downloads.service.js b/services/npm/npm-downloads.service.js index 3994226fef908b47aa4ec9468756cc3ac01268bd..a8e0be3576dd575e22861c6027c1dea1b994c114 100644 --- a/services/npm/npm-downloads.service.js +++ b/services/npm/npm-downloads.service.js @@ -1,8 +1,8 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const { BaseJsonService } = require('../base'); -const { metric } = require('../../lib/text-formatters'); +const Joi = require('joi') +const { BaseJsonService } = require('../base') +const { metric } = require('../../lib/text-formatters') // https://github.com/npm/registry/blob/master/docs/download-counts.md#output const pointResponseSchema = Joi.object({ @@ -10,14 +10,14 @@ const pointResponseSchema = Joi.object({ .integer() .min(0) .required(), -}).required(); +}).required() // https://github.com/npm/registry/blob/master/docs/download-counts.md#output-1 const rangeResponseSchema = Joi.object({ downloads: Joi.array() .items(pointResponseSchema) .required(), -}).required(); +}).required() function DownloadsForInterval(interval) { const { base, messageSuffix = '', query, isRange = false } = { @@ -41,15 +41,15 @@ function DownloadsForInterval(interval) { query: 'range/1000-01-01:3000-01-01', isRange: true, }, - }[interval]; + }[interval] - const schema = isRange ? rangeResponseSchema : pointResponseSchema; + const schema = isRange ? rangeResponseSchema : pointResponseSchema // This hits an entirely different API from the rest of the NPM services, so // it does not use NpmBase. return class NpmDownloads extends BaseJsonService { static get category() { - return 'downloads'; + return 'downloads' } static get url() { @@ -57,7 +57,7 @@ function DownloadsForInterval(interval) { base, format: '(.*)', capture: ['packageName'], - }; + } } static get examples() { @@ -67,14 +67,14 @@ function DownloadsForInterval(interval) { previewUrl: 'localeval', keywords: ['node'], }, - ]; + ] } static render({ downloads }) { return { message: `${metric(downloads)}${messageSuffix}`, color: downloads > 0 ? 'brightgreen' : 'red', - }; + } } async handle({ packageName }) { @@ -82,15 +82,15 @@ function DownloadsForInterval(interval) { schema, url: `https://api.npmjs.org/downloads/${query}/${packageName}`, notFoundMessage: 'project not found', - }); + }) if (isRange) { downloads = downloads .map(item => item.downloads) - .reduce((accum, current) => accum + current); + .reduce((accum, current) => accum + current) } - return this.constructor.render({ downloads }); + return this.constructor.render({ downloads }) } - }; + } } -module.exports = ['week', 'month', 'year', 'total'].map(DownloadsForInterval); +module.exports = ['week', 'month', 'year', 'total'].map(DownloadsForInterval) diff --git a/services/npm/npm-license.service.js b/services/npm/npm-license.service.js index cc4afb61d065275475a27fd2f03844071cc214dc..a77bfa9db56d242234b3210ffc143e60d023fa6f 100644 --- a/services/npm/npm-license.service.js +++ b/services/npm/npm-license.service.js @@ -1,20 +1,20 @@ -'use strict'; +'use strict' -const { licenseToColor } = require('../../lib/licenses'); -const { toArray } = require('../../lib/badge-data'); -const NpmBase = require('./npm-base'); +const { licenseToColor } = require('../../lib/licenses') +const { toArray } = require('../../lib/badge-data') +const NpmBase = require('./npm-base') module.exports = class NpmLicense extends NpmBase { static get category() { - return 'license'; + return 'license' } static get defaultBadgeData() { - return { label: 'license' }; + return { label: 'license' } } static get url() { - return this.buildUrl('npm/l', { withTag: false }); + return this.buildUrl('npm/l', { withTag: false }) } static get examples() { @@ -28,12 +28,12 @@ module.exports = class NpmLicense extends NpmBase { query: { registry_uri: 'https://registry.npmjs.com' }, keywords: ['node'], }, - ]; + ] } static render({ licenses }) { if (licenses.length === 0) { - return { message: 'missing', color: 'red' }; + return { message: 'missing', color: 'red' } } return { @@ -41,22 +41,22 @@ module.exports = class NpmLicense extends NpmBase { // TODO This does not provide a color when more than one license is // present. Probably that should be fixed. color: licenseToColor(licenses), - }; + } } async handle(namedParams, queryParams) { const { scope, packageName, registryUrl } = this.constructor.unpackParams( namedParams, queryParams - ); + ) const { license } = await this.fetchPackageData({ scope, packageName, registryUrl, - }); + }) const licenses = toArray(license).map( license => (typeof license === 'string' ? license : license.type) - ); - return this.constructor.render({ licenses }); + ) + return this.constructor.render({ licenses }) } -}; +} diff --git a/services/npm/npm-type-definitions.service.js b/services/npm/npm-type-definitions.service.js index cc21baab178763cbb7f9c7daf6799b5806ddc5f4..31028dfea024f24002dde703eed9ad38ce40cd16 100644 --- a/services/npm/npm-type-definitions.service.js +++ b/services/npm/npm-type-definitions.service.js @@ -1,19 +1,19 @@ -'use strict'; +'use strict' -const { rangeStart, minor } = require('../../lib/version'); -const NpmBase = require('./npm-base'); +const { rangeStart, minor } = require('../../lib/version') +const NpmBase = require('./npm-base') module.exports = class NpmTypeDefinitions extends NpmBase { static get category() { - return 'version'; + return 'version' } static get defaultBadgeData() { - return { label: 'type definitions' }; + return { label: 'type definitions' } } static get url() { - return this.buildUrl('npm/types', { withTag: false }); + return this.buildUrl('npm/types', { withTag: false }) } static get examples() { @@ -23,7 +23,7 @@ module.exports = class NpmTypeDefinitions extends NpmBase { previewUrl: 'chalk', keywords: ['node', 'typescript', 'flow'], }, - ]; + ] } static transform({ devDependencies }) { @@ -32,12 +32,12 @@ module.exports = class NpmTypeDefinitions extends NpmBase { { language: 'TypeScript', range: devDependencies.typescript }, { language: 'Flow', range: devDependencies['flow-bin'] }, ].filter(({ range }) => range !== undefined), - }; + } } static render({ supportedLanguages }) { if (supportedLanguages.length === 0) { - return { message: 'none', color: 'lightgray' }; + return { message: 'none', color: 'lightgray' } } else { return { message: supportedLanguages @@ -46,7 +46,7 @@ module.exports = class NpmTypeDefinitions extends NpmBase { ) .join(' | '), color: 'blue', - }; + } } } @@ -54,13 +54,13 @@ module.exports = class NpmTypeDefinitions extends NpmBase { const { scope, packageName, registryUrl } = this.constructor.unpackParams( namedParams, queryParams - ); + ) const json = await this.fetchPackageData({ scope, packageName, registryUrl, - }); - const props = this.constructor.transform(json); - return this.constructor.render(props); + }) + const props = this.constructor.transform(json) + return this.constructor.render(props) } -}; +} diff --git a/services/npm/npm-type-definitions.spec.js b/services/npm/npm-type-definitions.spec.js index d4d21aa5ace2bd238076b1ade2b215c175d6d083..c14068cda6e696b6d426f881bef07fe013d6592b 100644 --- a/services/npm/npm-type-definitions.spec.js +++ b/services/npm/npm-type-definitions.spec.js @@ -1,16 +1,16 @@ -'use strict'; +'use strict' -const { test, given } = require('sazerac'); -const NpmTypeDefinitions = require('./npm-type-definitions.service'); +const { test, given } = require('sazerac') +const NpmTypeDefinitions = require('./npm-type-definitions.service') const transformAndRender = json => - NpmTypeDefinitions.render(NpmTypeDefinitions.transform(json)); + NpmTypeDefinitions.render(NpmTypeDefinitions.transform(json)) describe('NPM type definitions badge', function() { test(transformAndRender, () => { given({ devDependencies: { typescript: '^2.4.7' } }).expect({ message: 'TypeScript v2.4', color: 'blue', - }); - }); -}); + }) + }) +}) diff --git a/services/npm/npm-version.service.js b/services/npm/npm-version.service.js index bf16e83abc8789165df697532afa345e2d7399d7..97634705b6e9ddd17747a42237e6467e7069c67a 100644 --- a/services/npm/npm-version.service.js +++ b/services/npm/npm-version.service.js @@ -1,27 +1,27 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const { addv } = require('../../lib/text-formatters'); -const { version: versionColor } = require('../../lib/color-formatters'); -const { NotFound } = require('../errors'); -const NpmBase = require('./npm-base'); +const Joi = require('joi') +const { addv } = require('../../lib/text-formatters') +const { version: versionColor } = require('../../lib/color-formatters') +const { NotFound } = require('../errors') +const NpmBase = require('./npm-base') // Joi.string should be a semver. const schema = Joi.object() .pattern(/./, Joi.string()) - .required(); + .required() module.exports = class NpmVersion extends NpmBase { static get category() { - return 'version'; + return 'version' } static get url() { - return this.buildUrl('npm/v', { withTag: true }); + return this.buildUrl('npm/v', { withTag: true }) } static get defaultBadgeData() { - return { label: 'npm' }; + return { label: 'npm' } } static get examples() { @@ -51,7 +51,7 @@ module.exports = class NpmVersion extends NpmBase { previewUrl: '@cycle/core/canary', keywords: ['node'], }, - ]; + ] } static render({ tag, version }) { @@ -59,7 +59,7 @@ module.exports = class NpmVersion extends NpmBase { label: tag ? `npm@${tag}` : undefined, message: addv(version), color: versionColor(version), - }; + } } async handle(namedParams, queryParams) { @@ -68,26 +68,26 @@ module.exports = class NpmVersion extends NpmBase { packageName, tag, registryUrl, - } = this.constructor.unpackParams(namedParams, queryParams); + } = this.constructor.unpackParams(namedParams, queryParams) const slug = scope === undefined ? packageName - : this.constructor.encodeScopedPackage({ scope, packageName }); + : this.constructor.encodeScopedPackage({ scope, packageName }) const packageData = await this._requestJson({ schema, url: `${registryUrl}/-/package/${slug}/dist-tags`, notFoundMessage: 'package not found', - }); + }) if (tag && !(tag in packageData)) { - throw new NotFound({ prettyMessage: 'tag not found' }); + throw new NotFound({ prettyMessage: 'tag not found' }) } return this.constructor.render({ tag, version: packageData[tag || 'latest'], - }); + }) } -}; +} diff --git a/services/npm/npm.tester.js b/services/npm/npm.tester.js index 486ed22e0fbcf5e796a5d3fc44bd74a9abc33d0d..6f8347b201e31599b9f5c2e15ad846aef24a0845 100644 --- a/services/npm/npm.tester.js +++ b/services/npm/npm.tester.js @@ -1,173 +1,221 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isMetric, isSemver } = require('../test-validators'); -const colorscheme = require('../../lib/colorscheme.json'); -const mapValues = require('lodash.mapvalues'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isMetric, isSemver } = require('../test-validators') +const colorscheme = require('../../lib/colorscheme.json') +const mapValues = require('lodash.mapvalues') -const t = new ServiceTester({ id: 'npm', title: 'NPM' }); -module.exports = t; -const colorsB = mapValues(colorscheme, 'colorB'); +const t = new ServiceTester({ id: 'npm', title: 'NPM' }) +module.exports = t +const colorsB = mapValues(colorscheme, 'colorB') -const isTypeDefinition = Joi.string().regex(/^(Flow|TypeScript) v?[0-9]+.[0-9]+( \| (Flow|TypeScript) v?[0-9]+.[0-9]+)?$/); +const isTypeDefinition = Joi.string().regex( + /^(Flow|TypeScript) v?[0-9]+.[0-9]+( \| (Flow|TypeScript) v?[0-9]+.[0-9]+)?$/ +) t.create('total downloads of left-pad') .get('/dt/left-pad.json?style=_shields_test') - .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric, colorB: colorsB.brightgreen })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetric, + colorB: colorsB.brightgreen, + }) + ) t.create('total downloads of @cycle/core') .get('/dt/@cycle/core.json') - .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })); + .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })) t.create('total downloads of package with zero downloads') .get('/dt/package-no-downloads.json?style=_shields_test') - .intercept(nock => nock('https://api.npmjs.org') - .get('/downloads/range/1000-01-01:3000-01-01/package-no-downloads') - .reply(200, { - downloads: [ - { downloads: 0, day: '2018-01-01' } - ], - })) - .expectJSON({ name: 'downloads', value: '0', colorB: colorsB.red }); + .intercept(nock => + nock('https://api.npmjs.org') + .get('/downloads/range/1000-01-01:3000-01-01/package-no-downloads') + .reply(200, { + downloads: [{ downloads: 0, day: '2018-01-01' }], + }) + ) + .expectJSON({ name: 'downloads', value: '0', colorB: colorsB.red }) t.create('exact total downloads value') .get('/dt/exact-value.json') - .intercept(nock => nock('https://api.npmjs.org') - .get('/downloads/range/1000-01-01:3000-01-01/exact-value') - .reply(200, { - downloads: [ - { downloads: 2, day: '2018-01-01' }, - { downloads: 3, day: '2018-01-02' }, - ], - })) - .expectJSON({ name: 'downloads', value: '5' }); + .intercept(nock => + nock('https://api.npmjs.org') + .get('/downloads/range/1000-01-01:3000-01-01/exact-value') + .reply(200, { + downloads: [ + { downloads: 2, day: '2018-01-01' }, + { downloads: 3, day: '2018-01-02' }, + ], + }) + ) + .expectJSON({ name: 'downloads', value: '5' }) t.create('total downloads when network is off') .get('/dt/@cycle/core.json?style=_shields_test') .networkOff() - .expectJSON({ name: 'downloads', value: 'inaccessible' , colorB: colorsB.lightgray }); + .expectJSON({ + name: 'downloads', + value: 'inaccessible', + colorB: colorsB.lightgray, + }) t.create('total downloads of unknown package') .get('/dt/npm-api-does-not-have-this-package.json?style=_shields_test') - .expectJSON({ name: 'downloads', value: 'project not found' , colorB: colorsB.red }); + .expectJSON({ + name: 'downloads', + value: 'project not found', + colorB: colorsB.red, + }) t.create('gets the package version of left-pad') .get('/v/left-pad.json') - .expectJSONTypes(Joi.object().keys({ name: 'npm', value: isSemver })); + .expectJSONTypes(Joi.object().keys({ name: 'npm', value: isSemver })) t.create('gets the package version of @cycle/core') .get('/v/@cycle/core.json') - .expectJSONTypes(Joi.object().keys({ name: 'npm', value: isSemver })); + .expectJSONTypes(Joi.object().keys({ name: 'npm', value: isSemver })) t.create('gets a tagged package version of npm') .get('/v/npm/next.json') - .expectJSONTypes(Joi.object().keys({ name: 'npm@next', value: isSemver })); + .expectJSONTypes(Joi.object().keys({ name: 'npm@next', value: isSemver })) t.create('gets the correct tagged package version of npm') - .intercept(nock => nock('https://registry.npmjs.org') - .get('/-/package/npm/dist-tags') - .reply(200, { latest: "1.2.3", next: "4.5.6" })) + .intercept(nock => + nock('https://registry.npmjs.org') + .get('/-/package/npm/dist-tags') + .reply(200, { latest: '1.2.3', next: '4.5.6' }) + ) .get('/v/npm/next.json') - .expectJSON({ name: 'npm@next', value: 'v4.5.6' }); + .expectJSON({ name: 'npm@next', value: 'v4.5.6' }) t.create('returns an error for version with an invalid tag') .get('/v/npm/frodo.json') - .expectJSON({ name: 'npm', value: 'tag not found' }); + .expectJSON({ name: 'npm', value: 'tag not found' }) t.create('gets the package version of left-pad from a custom registry') .get('/v/left-pad.json?registry_uri=https://registry.npmjs.com') - .expectJSONTypes(Joi.object().keys({ name: 'npm', value: isSemver })); + .expectJSONTypes(Joi.object().keys({ name: 'npm', value: isSemver })) t.create('gets the tagged package version of @cycle/core') .get('/v/@cycle/core/canary.json') - .expectJSONTypes(Joi.object().keys({ name: 'npm@canary', value: isSemver })); + .expectJSONTypes(Joi.object().keys({ name: 'npm@canary', value: isSemver })) -t.create('gets the tagged package version of @cycle/core from a custom registry') +t.create( + 'gets the tagged package version of @cycle/core from a custom registry' +) .get('/v/@cycle/core/canary.json?registry_uri=https://registry.npmjs.com') - .expectJSONTypes(Joi.object().keys({ name: 'npm@canary', value: isSemver })); + .expectJSONTypes(Joi.object().keys({ name: 'npm@canary', value: isSemver })) t.create('gets the license of express') .get('/l/express.json') - .expectJSONTypes(Joi.object().keys({ name: 'license', value: 'MIT' })); + .expectJSONTypes(Joi.object().keys({ name: 'license', value: 'MIT' })) t.create('gets the license of express from a custom registry') .get('/l/express.json?registry_uri=https://registry.npmjs.com') - .expectJSONTypes(Joi.object().keys({ name: 'license', value: 'MIT' })); + .expectJSONTypes(Joi.object().keys({ name: 'license', value: 'MIT' })) t.create('invalid package name') .get('/v/frodo-is-not-a-package.json') - .expectJSON({ name: 'npm', value: 'package not found' }); + .expectJSON({ name: 'npm', value: 'package not found' }) t.create('gets the package version of left-pad from a custom registry') .get('/v/left-pad.json?registry_uri=https://registry.npmjs.com') - .expectJSONTypes(Joi.object().keys({ name: 'npm', value: isSemver })); + .expectJSONTypes(Joi.object().keys({ name: 'npm', value: isSemver })) t.create('public domain license') .get('/l/redux-auth.json?style=_shields_test') - .expectJSON({ name: 'license', value: 'WTFPL', colorB: '#7cd958' }); + .expectJSON({ name: 'license', value: 'WTFPL', colorB: '#7cd958' }) t.create('copyleft license') .get('/l/trianglify.json?style=_shields_test') - .expectJSON({ name: 'license', value: 'GPL-3.0', colorB: colorsB.orange }); + .expectJSON({ name: 'license', value: 'GPL-3.0', colorB: colorsB.orange }) t.create('permissive license') .get('/l/express.json?style=_shields_test') - .expectJSON({ name: 'license', value: 'MIT', colorB: colorsB.green }); + .expectJSON({ name: 'license', value: 'MIT', colorB: colorsB.green }) t.create('permissive license for scoped package') .get('/l/@cycle%2Fcore.json?style=_shields_test') - .expectJSON({ name: 'license', value: 'MIT', colorB: colorsB.green }); + .expectJSON({ name: 'license', value: 'MIT', colorB: colorsB.green }) -t.create('permissive and copyleft licenses (SPDX license expression syntax version 2.0)') +t.create( + 'permissive and copyleft licenses (SPDX license expression syntax version 2.0)' +) .get('/l/rho-cc-promise.json?style=_shields_test') - .expectJSON({ name: 'license', value: '(MPL-2.0 OR MIT)', colorB: colorsB.lightgrey }); + .expectJSON({ + name: 'license', + value: '(MPL-2.0 OR MIT)', + colorB: colorsB.lightgrey, + }) t.create('license for package without a license property') .get('/l/package-without-license.json?style=_shields_test') - .intercept(nock => nock('https://registry.npmjs.org') - .get('/package-without-license/latest') - .reply(200, { - name: 'package-without-license' - })) - .expectJSON({ name: 'license', value: 'missing', colorB: colorsB.red }); + .intercept(nock => + nock('https://registry.npmjs.org') + .get('/package-without-license/latest') + .reply(200, { + name: 'package-without-license', + }) + ) + .expectJSON({ name: 'license', value: 'missing', colorB: colorsB.red }) t.create('license for package with a license object') .get('/l/package-license-object.json?style=_shields_test') - .intercept(nock => nock('https://registry.npmjs.org') - .get('/package-license-object/latest') - .reply(200, { - name: 'package-license-object', - license: { - type: 'MIT', - url: 'https://www.opensource.org/licenses/mit-license.php' - } - })) - .expectJSON({ name: 'license', value: 'MIT', colorB: colorsB.green }); + .intercept(nock => + nock('https://registry.npmjs.org') + .get('/package-license-object/latest') + .reply(200, { + name: 'package-license-object', + license: { + type: 'MIT', + url: 'https://www.opensource.org/licenses/mit-license.php', + }, + }) + ) + .expectJSON({ name: 'license', value: 'MIT', colorB: colorsB.green }) t.create('license for package with a license array') .get('/l/package-license-array.json?style=_shields_test') - .intercept(nock => nock('https://registry.npmjs.org') - .get('/package-license-array/latest') - .reply(200, { - name: 'package-license-object', - license: ['MPL-2.0', 'MIT'] - })) - .expectJSON({ name: 'license', value: 'MPL-2.0, MIT', colorB: colorsB.lightgrey }); + .intercept(nock => + nock('https://registry.npmjs.org') + .get('/package-license-array/latest') + .reply(200, { + name: 'package-license-object', + license: ['MPL-2.0', 'MIT'], + }) + ) + .expectJSON({ + name: 'license', + value: 'MPL-2.0, MIT', + colorB: colorsB.lightgrey, + }) t.create('license for unknown package') .get('/l/npm-registry-does-not-have-this-package.json?style=_shields_test') - .expectJSON({ name: 'license', value: 'package not found', colorB: colorsB.red }); + .expectJSON({ + name: 'license', + value: 'package not found', + colorB: colorsB.red, + }) t.create('license when network is off') .get('/l/pakage-network-off.json?style=_shields_test') .networkOff() - .expectJSON({ name: 'license', value: 'inaccessible', colorB: colorsB.lightgrey }); + .expectJSON({ + name: 'license', + value: 'inaccessible', + colorB: colorsB.lightgrey, + }) t.create('types') .get('/types/chalk.json') - .expectJSONTypes(Joi.object().keys({ name: 'type definitions', value: isTypeDefinition })); + .expectJSONTypes( + Joi.object().keys({ name: 'type definitions', value: isTypeDefinition }) + ) t.create('no types') .get('/types/left-pad.json') - .expectJSON({ name: 'type definitions', value: 'none' }); + .expectJSON({ name: 'type definitions', value: 'none' }) diff --git a/services/nsp/nsp.tester.js b/services/nsp/nsp.tester.js index e409cb60b264dcc304b218045c108504c583f2df..82469364b74d6190eee3593eb715c0474681923f 100644 --- a/services/nsp/nsp.tester.js +++ b/services/nsp/nsp.tester.js @@ -3,32 +3,32 @@ const Joi = require('joi') const ServiceTester = require('../service-tester') -const t = new ServiceTester({id: 'nsp', title: 'Node Security Platform'}) +const t = new ServiceTester({ id: 'nsp', title: 'Node Security Platform' }) const formats = { A: '/nsp/npm/:package.:format', B: '/nsp/npm/:package/:version.:format', C: '/nsp/npm/@:scope/:package.:format', - D: '/nsp/npm/@:scope/:package/:version.:format' + D: '/nsp/npm/@:scope/:package/:version.:format', } const noExistPackages = { A: '/npm/some-no-exist.json', B: '/npm/some-no-exist/1.0.0.json', C: '/npm/@some-no-exist/some-no-exist.json', - D: '/npm/@some-no-exist/some-no-exist/1.0.0.json' + D: '/npm/@some-no-exist/some-no-exist/1.0.0.json', } const withoutVulnerabilities = { A: '/npm/bronze.json', B: '/npm/bronze/1.4.0.json', C: '/npm/@cycle/core.json', - D: '/npm/@cycle/core/1.0.0.json' + D: '/npm/@cycle/core/1.0.0.json', } const withVulnerabilities = { A: '/npm/nodeaaaaa.json', - B: '/npm/express/1.0.0.json' + B: '/npm/express/1.0.0.json', } Object.keys(formats).forEach(format => { @@ -39,19 +39,24 @@ Object.keys(formats).forEach(format => { if (typeof noExist === 'string') { t.create(`Format '${formats[format]}' where it doesn't exist`) .get(noExist) - .expectJSON({name: 'nsp', value: 'no known vulnerabilities'}) + .expectJSON({ name: 'nsp', value: 'no known vulnerabilities' }) } if (typeof withVulnerability === 'string') { t.create(`Format '${formats[format]}' with vulnerabilities`) .get(withVulnerability) - .expectJSONTypes(Joi.object().keys({name: 'nsp', value: Joi.string().regex(/^[0-9]+ vulnerabilities$/)})) + .expectJSONTypes( + Joi.object().keys({ + name: 'nsp', + value: Joi.string().regex(/^[0-9]+ vulnerabilities$/), + }) + ) } if (typeof withoutVulnerability === 'string') { t.create(`Format '${formats[format]}' without vulnerabilities`) .get(withoutVulnerability) - .expectJSON({name: 'nsp', value: 'no known vulnerabilities'}) + .expectJSON({ name: 'nsp', value: 'no known vulnerabilities' }) } }) diff --git a/services/nuget-fixtures.js b/services/nuget-fixtures.js index ea7486b21d1b38582d5770cb8a374a6a84cf2c36..a5c5e1acb5cc0dfd603b12c15914d7d2e8768945 100644 --- a/services/nuget-fixtures.js +++ b/services/nuget-fixtures.js @@ -1,60 +1,51 @@ -'use strict'; +'use strict' const queryIndex = JSON.stringify({ - "resources": [ - { "@id": "https://api-v2v3search-0.nuget.org/query", "@type": "SearchQueryService" } - ] -}); + resources: [ + { + '@id': 'https://api-v2v3search-0.nuget.org/query', + '@type': 'SearchQueryService', + }, + ], +}) const nuGetV2VersionJsonWithDash = JSON.stringify({ d: { - results: [ - { NormalizedVersion: '1.2-beta' } - ] - } -}); + results: [{ NormalizedVersion: '1.2-beta' }], + }, +}) const nuGetV2VersionJsonFirstCharZero = JSON.stringify({ d: { - results: [ - { NormalizedVersion: '0.35' } - ] - } -}); + results: [{ NormalizedVersion: '0.35' }], + }, +}) const nuGetV2VersionJsonFirstCharNotZero = JSON.stringify({ d: { - results: [ - { NormalizedVersion: '1.2.7' } - ] - } -}); + results: [{ NormalizedVersion: '1.2.7' }], + }, +}) const nuGetV3VersionJsonWithDash = JSON.stringify({ data: [ { - versions: [ - { version: '1.2-beta' } - ] - } - ] -}); + versions: [{ version: '1.2-beta' }], + }, + ], +}) const nuGetV3VersionJsonFirstCharZero = JSON.stringify({ data: [ { - versions: [ - { version: '0.35' } - ] - } - ] -}); + versions: [{ version: '0.35' }], + }, + ], +}) const nuGetV3VersionJsonFirstCharNotZero = JSON.stringify({ data: [ { - versions: [ - { version: '1.2.7' } - ] - } - ] -}); + versions: [{ version: '1.2.7' }], + }, + ], +}) module.exports = { queryIndex, @@ -64,4 +55,4 @@ module.exports = { nuGetV3VersionJsonWithDash, nuGetV3VersionJsonFirstCharZero, nuGetV3VersionJsonFirstCharNotZero, -}; +} diff --git a/services/nuget/nuget.tester.js b/services/nuget/nuget.tester.js index 541d353a8a096b6d271d9e72250f3d7aea91cabc..19cf4f799ed74e3c5fb8bc0429ae5eedf32b5071 100644 --- a/services/nuget/nuget.tester.js +++ b/services/nuget/nuget.tester.js @@ -1,233 +1,257 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') const { isMetric, isVPlusDottedVersionNClauses, isVPlusDottedVersionNClausesWithOptionalSuffix, -} = require('../test-validators'); -const colorscheme = require('../../lib/colorscheme.json'); +} = require('../test-validators') +const colorscheme = require('../../lib/colorscheme.json') const { queryIndex, nuGetV3VersionJsonWithDash, nuGetV3VersionJsonFirstCharZero, nuGetV3VersionJsonFirstCharNotZero, -} = require('../nuget-fixtures'); -const { invalidJSON } = require('../response-fixtures'); - -const t = new ServiceTester({ id: 'nuget', title: 'NuGet' }); -module.exports = t; +} = require('../nuget-fixtures') +const { invalidJSON } = require('../response-fixtures') +const t = new ServiceTester({ id: 'nuget', title: 'NuGet' }) +module.exports = t // downloads t.create('total downloads (valid)') .get('/dt/Microsoft.AspNetCore.Mvc.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetric, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetric, + }) + ) t.create('total downloads (not found)') .get('/dt/not-a-real-package.json') - .expectJSON({name: 'downloads', value: 'not found'}); + .expectJSON({ name: 'downloads', value: 'not found' }) t.create('total downloads (connection error)') .get('/dt/Microsoft.AspNetCore.Mvc.json') .networkOff() - .expectJSON({name: 'downloads', value: 'inaccessible'}); + .expectJSON({ name: 'downloads', value: 'inaccessible' }) t.create('total downloads (unexpected first response)') .get('/dt/Microsoft.AspNetCore.Mvc.json') - .intercept(nock => nock('https://api.nuget.org') - .get("/v3/index.json") - .reply(invalidJSON) + .intercept(nock => + nock('https://api.nuget.org') + .get('/v3/index.json') + .reply(invalidJSON) ) - .expectJSON({name: 'downloads', value: 'invalid'}); + .expectJSON({ name: 'downloads', value: 'invalid' }) t.create('total downloads (unexpected second response)') .get('/dt/Microsoft.AspNetCore.Mvc.json') - .intercept(nock => nock('https://api.nuget.org') - .get("/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://api.nuget.org') + .get('/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true") - .reply(invalidJSON) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true') + .reply(invalidJSON) ) - .expectJSON({name: 'downloads', value: 'invalid'}); - + .expectJSON({ name: 'downloads', value: 'invalid' }) // version t.create('version (valid)') .get('/v/Microsoft.AspNetCore.Mvc.json') - .expectJSONTypes(Joi.object().keys({ - name: 'nuget', - value: isVPlusDottedVersionNClauses, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'nuget', + value: isVPlusDottedVersionNClauses, + }) + ) t.create('version (mocked, yellow badge)') .get('/v/Microsoft.AspNetCore.Mvc.json?style=_shields_test') - .intercept(nock => nock('https://api.nuget.org') - .get("/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://api.nuget.org') + .get('/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true") - .reply(200, nuGetV3VersionJsonWithDash) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true') + .reply(200, nuGetV3VersionJsonWithDash) ) .expectJSON({ name: 'nuget', value: 'v1.2-beta', - colorB: colorscheme.yellow.colorB - }); + colorB: colorscheme.yellow.colorB, + }) t.create('version (mocked, orange badge)') .get('/v/Microsoft.AspNetCore.Mvc.json?style=_shields_test') - .intercept(nock => nock('https://api.nuget.org') - .get("/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://api.nuget.org') + .get('/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true") - .reply(200, nuGetV3VersionJsonFirstCharZero) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true') + .reply(200, nuGetV3VersionJsonFirstCharZero) ) .expectJSON({ name: 'nuget', value: 'v0.35', - colorB: colorscheme.orange.colorB - }); + colorB: colorscheme.orange.colorB, + }) t.create('version (mocked, blue badge)') .get('/v/Microsoft.AspNetCore.Mvc.json?style=_shields_test') - .intercept(nock => nock('https://api.nuget.org') - .get("/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://api.nuget.org') + .get('/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true") - .reply(200, nuGetV3VersionJsonFirstCharNotZero) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true') + .reply(200, nuGetV3VersionJsonFirstCharNotZero) ) .expectJSON({ name: 'nuget', value: 'v1.2.7', - colorB: colorscheme.blue.colorB - }); + colorB: colorscheme.blue.colorB, + }) t.create('version (not found)') .get('/v/not-a-real-package.json') - .expectJSON({name: 'nuget', value: 'not found'}); + .expectJSON({ name: 'nuget', value: 'not found' }) t.create('version (connection error)') .get('/v/Microsoft.AspNetCore.Mvc.json') .networkOff() - .expectJSON({name: 'nuget', value: 'inaccessible'}); + .expectJSON({ name: 'nuget', value: 'inaccessible' }) t.create('version (unexpected first response)') .get('/v/Microsoft.AspNetCore.Mvc.json') - .intercept(nock => nock('https://api.nuget.org') - .get("/v3/index.json") - .reply(invalidJSON) + .intercept(nock => + nock('https://api.nuget.org') + .get('/v3/index.json') + .reply(invalidJSON) ) - .expectJSON({name: 'nuget', value: 'invalid'}); + .expectJSON({ name: 'nuget', value: 'invalid' }) t.create('version (unexpected second response)') .get('/v/Microsoft.AspNetCore.Mvc.json') - .intercept(nock => nock('https://api.nuget.org') - .get("/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://api.nuget.org') + .get('/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true") - .reply(invalidJSON) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true') + .reply(invalidJSON) ) - .expectJSON({name: 'nuget', value: 'invalid'}); - + .expectJSON({ name: 'nuget', value: 'invalid' }) // version (pre) t.create('version (pre) (valid)') .get('/vpre/Microsoft.AspNetCore.Mvc.json') - .expectJSONTypes(Joi.object().keys({ - name: 'nuget', - value: isVPlusDottedVersionNClausesWithOptionalSuffix, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'nuget', + value: isVPlusDottedVersionNClausesWithOptionalSuffix, + }) + ) - t.create('version (pre) (mocked, yellow badge)') +t.create('version (pre) (mocked, yellow badge)') .get('/vpre/Microsoft.AspNetCore.Mvc.json?style=_shields_test') - .intercept(nock => nock('https://api.nuget.org') - .get("/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://api.nuget.org') + .get('/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true") - .reply(200, nuGetV3VersionJsonWithDash) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true') + .reply(200, nuGetV3VersionJsonWithDash) ) .expectJSON({ name: 'nuget', value: 'v1.2-beta', - colorB: colorscheme.yellow.colorB - }); + colorB: colorscheme.yellow.colorB, + }) t.create('version (pre) (mocked, orange badge)') .get('/vpre/Microsoft.AspNetCore.Mvc.json?style=_shields_test') - .intercept(nock => nock('https://api.nuget.org') - .get("/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://api.nuget.org') + .get('/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true") - .reply(200, nuGetV3VersionJsonFirstCharZero) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true') + .reply(200, nuGetV3VersionJsonFirstCharZero) ) .expectJSON({ name: 'nuget', value: 'v0.35', - colorB: colorscheme.orange.colorB - }); + colorB: colorscheme.orange.colorB, + }) t.create('version (pre) (mocked, blue badge)') .get('/vpre/Microsoft.AspNetCore.Mvc.json?style=_shields_test') - .intercept(nock => nock('https://api.nuget.org') - .get("/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://api.nuget.org') + .get('/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true") - .reply(200, nuGetV3VersionJsonFirstCharNotZero) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true') + .reply(200, nuGetV3VersionJsonFirstCharNotZero) ) .expectJSON({ name: 'nuget', value: 'v1.2.7', - colorB: colorscheme.blue.colorB - }); + colorB: colorscheme.blue.colorB, + }) t.create('version (pre) (not found)') .get('/vpre/not-a-real-package.json') - .expectJSON({name: 'nuget', value: 'not found'}); + .expectJSON({ name: 'nuget', value: 'not found' }) t.create('version (pre) (connection error)') .get('/vpre/Microsoft.AspNetCore.Mvc.json') .networkOff() - .expectJSON({name: 'nuget', value: 'inaccessible'}); + .expectJSON({ name: 'nuget', value: 'inaccessible' }) t.create('version (pre) (unexpected first response)') .get('/vpre/Microsoft.AspNetCore.Mvc.json') - .intercept(nock => nock('https://api.nuget.org') - .get("/v3/index.json") - .reply(invalidJSON) + .intercept(nock => + nock('https://api.nuget.org') + .get('/v3/index.json') + .reply(invalidJSON) ) - .expectJSON({name: 'nuget', value: 'invalid'}); + .expectJSON({ name: 'nuget', value: 'invalid' }) t.create('version (pre) (unexpected second response)') .get('/vpre/Microsoft.AspNetCore.Mvc.json') - .intercept(nock => nock('https://api.nuget.org') - .get("/v3/index.json") - .reply(200, queryIndex) + .intercept(nock => + nock('https://api.nuget.org') + .get('/v3/index.json') + .reply(200, queryIndex) ) - .intercept(nock => nock('https://api-v2v3search-0.nuget.org') - .get("/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true") - .reply(invalidJSON) + .intercept(nock => + nock('https://api-v2v3search-0.nuget.org') + .get('/query?q=packageid:microsoft.aspnetcore.mvc&prerelease=true') + .reply(invalidJSON) ) - .expectJSON({name: 'nuget', value: 'invalid'}); + .expectJSON({ name: 'nuget', value: 'invalid' }) diff --git a/services/packagist/packagist.tester.js b/services/packagist/packagist.tester.js index f0a01eaca81c2feabc7d787f7da80cba3b9f311c..01f0f04e9d262808c62ec5b3a462eee7a3fe772c 100644 --- a/services/packagist/packagist.tester.js +++ b/services/packagist/packagist.tester.js @@ -1,12 +1,12 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') const { isComposerVersion, isMetric, - isMetricOverTimePeriod -} = require('../test-validators'); + isMetricOverTimePeriod, +} = require('../test-validators') /* validator for a packagist version number @@ -15,104 +15,109 @@ const { "version names should match 'X.Y.Z', or 'vX.Y.Z', with an optional suffix for RC, beta, alpha or patch versions" */ -const isPackagistVersion = Joi.string().regex(/^v?[0-9]+.[0-9]+.[0-9]+[\S]*$/); - -const t = new ServiceTester({ id: 'packagist', title: 'PHP version from Packagist' }); -module.exports = t; +const isPackagistVersion = Joi.string().regex(/^v?[0-9]+.[0-9]+.[0-9]+[\S]*$/) +const t = new ServiceTester({ + id: 'packagist', + title: 'PHP version from Packagist', +}) +module.exports = t // tests for php version support endpoint t.create('gets the package version of symfony') .get('/php-v/symfony/symfony.json') - .expectJSONTypes(Joi.object().keys({ name: 'PHP', value: isComposerVersion })); + .expectJSONTypes(Joi.object().keys({ name: 'PHP', value: isComposerVersion })) t.create('gets the package version of symfony 2.8') .get('/php-v/symfony/symfony/v2.8.0.json') - .expectJSONTypes(Joi.object().keys({ name: 'PHP', value: isComposerVersion })); + .expectJSONTypes(Joi.object().keys({ name: 'PHP', value: isComposerVersion })) t.create('invalid package name') .get('/php-v/frodo/is-not-a-package.json') - .expectJSON({ name: 'PHP', value: 'invalid' }); - + .expectJSON({ name: 'PHP', value: 'invalid' }) // tests for download stats endpoints t.create('daily downloads (valid, no package version specified)') .get('/dd/doctrine/orm.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetricOverTimePeriod - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetricOverTimePeriod, + }) + ) t.create('monthly downloads (valid, no package version specified)') .get('/dm/doctrine/orm.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetricOverTimePeriod - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetricOverTimePeriod, + }) + ) t.create('total downloads (valid, no package version specified)') .get('/dt/doctrine/orm.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetric - })); - + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetric, + }) + ) // note: packagist can't give us download stats for a specific version t.create('daily downloads (invalid, package version specified)') .get('/dd/symfony/symfony/v2.8.0.json') - .expectJSON({ name: 'downloads', value: 'invalid' }); + .expectJSON({ name: 'downloads', value: 'invalid' }) t.create('monthly downloads (invalid, package version in request)') .get('/dm/symfony/symfony/v2.8.0.json') - .expectJSON({ name: 'downloads', value: 'invalid' }); + .expectJSON({ name: 'downloads', value: 'invalid' }) t.create('total downloads (invalid, package version in request)') .get('/dt/symfony/symfony/v2.8.0.json') - .expectJSON({ name: 'downloads', value: 'invalid' }); - + .expectJSON({ name: 'downloads', value: 'invalid' }) t.create('daily downloads (invalid package name)') .get('/dd/frodo/is-not-a-package.json') - .expectJSON({ name: 'downloads', value: 'invalid' }); + .expectJSON({ name: 'downloads', value: 'invalid' }) t.create('monthly downloads (invalid package name)') .get('/dm/frodo/is-not-a-package.json') - .expectJSON({ name: 'downloads', value: 'invalid' }); + .expectJSON({ name: 'downloads', value: 'invalid' }) t.create('total downloads (invalid package name)') .get('/dt/frodo/is-not-a-package.json') - .expectJSON({ name: 'downloads', value: 'invalid' }); - + .expectJSON({ name: 'downloads', value: 'invalid' }) // tests for version endpoint t.create('version (valid)') .get('/v/symfony/symfony.json') - .expectJSONTypes(Joi.object().keys({ - name: 'packagist', - value: isPackagistVersion - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'packagist', + value: isPackagistVersion, + }) + ) t.create('version (invalid package name)') .get('/v/frodo/is-not-a-package.json') - .expectJSON({ name: 'packagist', value: 'invalid' }); - + .expectJSON({ name: 'packagist', value: 'invalid' }) // tests for license endpoint t.create('license (valid)') .get('/l/symfony/symfony.json') - .expectJSON({ name: 'license', value: 'MIT' }); + .expectJSON({ name: 'license', value: 'MIT' }) // note: packagist does serve up license at the version level // but our endpoint only supports fetching license for the lastest version t.create('license (invalid, package version in request)') .get('/l/symfony/symfony/v2.8.0.json') - .expectJSON({ name: 'license', value: 'invalid' }); + .expectJSON({ name: 'license', value: 'invalid' }) t.create('license (invalid)') .get('/l/frodo/is-not-a-package.json') - .expectJSON({ name: 'license', value: 'invalid' }); + .expectJSON({ name: 'license', value: 'invalid' }) diff --git a/services/php-eye/php-eye.tester.js b/services/php-eye/php-eye.tester.js index eafecce2b6d8123003e4f5be82610b938dc2a556..5e17684242b0963286d5d49da9bc86dc656cf8f0 100644 --- a/services/php-eye/php-eye.tester.js +++ b/services/php-eye/php-eye.tester.js @@ -1,26 +1,29 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { - isPhpVersionReduction -} = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isPhpVersionReduction } = require('../test-validators') -const t = new ServiceTester({ id: 'php-eye', title: 'PHP version from PHP-Eye' }); -module.exports = t; +const t = new ServiceTester({ + id: 'php-eye', + title: 'PHP version from PHP-Eye', +}) +module.exports = t t.create('gets the package version of symfony') - .get('/symfony/symfony.json') - .expectJSONTypes(Joi.object().keys({ name: 'PHP tested', value: isPhpVersionReduction })); + .get('/symfony/symfony.json') + .expectJSONTypes( + Joi.object().keys({ name: 'PHP tested', value: isPhpVersionReduction }) + ) t.create('gets the package version of symfony 2.8') - .get('/symfony/symfony/v2.8.0.json') - .expectJSON({ name: 'PHP tested', value: '5.3 - 7.0, HHVM' }); + .get('/symfony/symfony/v2.8.0.json') + .expectJSON({ name: 'PHP tested', value: '5.3 - 7.0, HHVM' }) t.create('gets the package version of yii') - .get('/yiisoft/yii.json') - .expectJSON({ name: 'PHP tested', value: '5.3 - 7.1' }); + .get('/yiisoft/yii.json') + .expectJSON({ name: 'PHP tested', value: '5.3 - 7.1' }) t.create('invalid package name') - .get('/frodo/is-not-a-package.json') - .expectJSON({ name: 'PHP tested', value: 'invalid' }); + .get('/frodo/is-not-a-package.json') + .expectJSON({ name: 'PHP tested', value: 'invalid' }) diff --git a/services/powershellgallery/powershellgallery.tester.js b/services/powershellgallery/powershellgallery.tester.js index 199c50782109365c56c3e0c568e44c5a022ee43d..8599a8bb3eff5f182982b910b7dc5359c650a69c 100644 --- a/services/powershellgallery/powershellgallery.tester.js +++ b/services/powershellgallery/powershellgallery.tester.js @@ -1,172 +1,205 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') const { isMetric, isVPlusDottedVersionNClauses, isVPlusDottedVersionNClausesWithOptionalSuffix, -} = require('../test-validators'); -const colorscheme = require('../../lib/colorscheme.json'); +} = require('../test-validators') +const colorscheme = require('../../lib/colorscheme.json') const { nuGetV2VersionJsonWithDash, nuGetV2VersionJsonFirstCharZero, - nuGetV2VersionJsonFirstCharNotZero -} = require('../nuget-fixtures'); -const { invalidJSON } = require('../response-fixtures'); - -const t = new ServiceTester({ id: 'powershellgallery', title: 'PowerShell Gallery' }); -module.exports = t; + nuGetV2VersionJsonFirstCharNotZero, +} = require('../nuget-fixtures') +const { invalidJSON } = require('../response-fixtures') +const t = new ServiceTester({ + id: 'powershellgallery', + title: 'PowerShell Gallery', +}) +module.exports = t // downloads t.create('total downloads (valid)') .get('/dt/ACMESharp.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetric, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetric, + }) + ) t.create('total downloads (not found)') .get('/dt/not-a-real-package.json') - .expectJSON({name: 'downloads', value: 'not found'}); + .expectJSON({ name: 'downloads', value: 'not found' }) t.create('total downloads (connection error)') .get('/dt/ACMESharp.json') .networkOff() - .expectJSON({name: 'downloads', value: 'inaccessible'}); + .expectJSON({ name: 'downloads', value: 'inaccessible' }) t.create('total downloads (unexpected response)') .get('/dt/ACMESharp.json') - .intercept(nock => nock('https://www.powershellgallery.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsLatestVersion%20eq%20true") - .reply(invalidJSON) + .intercept(nock => + nock('https://www.powershellgallery.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsLatestVersion%20eq%20true' + ) + .reply(invalidJSON) ) - .expectJSON({name: 'downloads', value: 'invalid'}); - + .expectJSON({ name: 'downloads', value: 'invalid' }) // version t.create('version (valid)') .get('/v/ACMESharp.json') - .expectJSONTypes(Joi.object().keys({ - name: 'powershellgallery', - value: isVPlusDottedVersionNClauses, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'powershellgallery', + value: isVPlusDottedVersionNClauses, + }) + ) t.create('version (mocked, yellow badge)') .get('/v/ACMESharp.json?style=_shields_test') - .intercept(nock => nock('https://www.powershellgallery.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonWithDash) + .intercept(nock => + nock('https://www.powershellgallery.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonWithDash) ) .expectJSON({ name: 'powershellgallery', value: 'v1.2-beta', - colorB: colorscheme.yellow.colorB - }); + colorB: colorscheme.yellow.colorB, + }) t.create('version (mocked, orange badge)') .get('/v/ACMESharp.json?style=_shields_test') - .intercept(nock => nock('https://www.powershellgallery.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonFirstCharZero) + .intercept(nock => + nock('https://www.powershellgallery.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonFirstCharZero) ) .expectJSON({ name: 'powershellgallery', value: 'v0.35', - colorB: colorscheme.orange.colorB - }); + colorB: colorscheme.orange.colorB, + }) t.create('version (mocked, blue badge)') .get('/v/ACMESharp.json?style=_shields_test') - .intercept(nock => nock('https://www.powershellgallery.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonFirstCharNotZero) + .intercept(nock => + nock('https://www.powershellgallery.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonFirstCharNotZero) ) .expectJSON({ name: 'powershellgallery', value: 'v1.2.7', - colorB: colorscheme.blue.colorB - }); + colorB: colorscheme.blue.colorB, + }) t.create('version (not found)') .get('/v/not-a-real-package.json') - .expectJSON({name: 'powershellgallery', value: 'not found'}); + .expectJSON({ name: 'powershellgallery', value: 'not found' }) t.create('version (connection error)') .get('/v/ACMESharp.json') .networkOff() - .expectJSON({name: 'powershellgallery', value: 'inaccessible'}); + .expectJSON({ name: 'powershellgallery', value: 'inaccessible' }) t.create('version (unexpected response)') .get('/v/ACMESharp.json') - .intercept(nock => nock('https://www.powershellgallery.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsLatestVersion%20eq%20true") - .reply(invalidJSON) + .intercept(nock => + nock('https://www.powershellgallery.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsLatestVersion%20eq%20true' + ) + .reply(invalidJSON) ) - .expectJSON({name: 'powershellgallery', value: 'invalid'}); - + .expectJSON({ name: 'powershellgallery', value: 'invalid' }) // version (pre) t.create('version (pre) (valid)') .get('/vpre/ACMESharp.json') - .expectJSONTypes(Joi.object().keys({ - name: 'powershellgallery', - value: isVPlusDottedVersionNClausesWithOptionalSuffix, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'powershellgallery', + value: isVPlusDottedVersionNClausesWithOptionalSuffix, + }) + ) t.create('version (pre) (mocked, yellow badge)') .get('/vpre/ACMESharp.json?style=_shields_test') - .intercept(nock => nock('https://www.powershellgallery.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsAbsoluteLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonWithDash) + .intercept(nock => + nock('https://www.powershellgallery.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsAbsoluteLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonWithDash) ) .expectJSON({ name: 'powershellgallery', value: 'v1.2-beta', - colorB: colorscheme.yellow.colorB - }); + colorB: colorscheme.yellow.colorB, + }) t.create('version (pre) (mocked, orange badge)') .get('/vpre/ACMESharp.json?style=_shields_test') - .intercept(nock => nock('https://www.powershellgallery.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsAbsoluteLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonFirstCharZero) + .intercept(nock => + nock('https://www.powershellgallery.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsAbsoluteLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonFirstCharZero) ) .expectJSON({ name: 'powershellgallery', value: 'v0.35', - colorB: colorscheme.orange.colorB - }); + colorB: colorscheme.orange.colorB, + }) t.create('version (pre) (mocked, blue badge)') .get('/vpre/ACMESharp.json?style=_shields_test') - .intercept(nock => nock('https://www.powershellgallery.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsAbsoluteLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonFirstCharNotZero) + .intercept(nock => + nock('https://www.powershellgallery.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsAbsoluteLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonFirstCharNotZero) ) .expectJSON({ name: 'powershellgallery', value: 'v1.2.7', - colorB: colorscheme.blue.colorB - }); + colorB: colorscheme.blue.colorB, + }) t.create('version (pre) (not found)') .get('/vpre/not-a-real-package.json') - .expectJSON({name: 'powershellgallery', value: 'not found'}); + .expectJSON({ name: 'powershellgallery', value: 'not found' }) t.create('version (pre) (connection error)') .get('/vpre/ACMESharp.json') .networkOff() - .expectJSON({name: 'powershellgallery', value: 'inaccessible'}); + .expectJSON({ name: 'powershellgallery', value: 'inaccessible' }) t.create('version (pre) (unexpected response)') .get('/vpre/ACMESharp.json') - .intercept(nock => nock('https://www.powershellgallery.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsAbsoluteLatestVersion%20eq%20true") - .reply(invalidJSON) + .intercept(nock => + nock('https://www.powershellgallery.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ACMESharp%27%20and%20IsAbsoluteLatestVersion%20eq%20true' + ) + .reply(invalidJSON) ) - .expectJSON({name: 'powershellgallery', value: 'invalid'}); + .expectJSON({ name: 'powershellgallery', value: 'invalid' }) diff --git a/services/pypi/pypi.tester.js b/services/pypi/pypi.tester.js index 453deb5114e8f840c0404afbff5f5e5b4d38f1a0..fd0a8a2bea3eb84703a7ea67cd30f743202c66a0 100644 --- a/services/pypi/pypi.tester.js +++ b/services/pypi/pypi.tester.js @@ -1,18 +1,21 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isSemver } = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isSemver } = require('../test-validators') -const isPsycopg2Version = Joi.string().regex(/^v([0-9][.]?)+$/); +const isPsycopg2Version = Joi.string().regex(/^v([0-9][.]?)+$/) // These regexes are the same, but defined separately for clarity. -const isCommaSeperatedPythonVersions = Joi.string().regex(/^([0-9]+.[0-9]+[,]?[ ]?)+$/); -const isCommaSeperatedDjangoVersions = Joi.string().regex(/^([0-9]+.[0-9]+[,]?[ ]?)+$/); - -const t = new ServiceTester({ id: 'pypi', title: 'PyPi badges' }); -module.exports = t; +const isCommaSeperatedPythonVersions = Joi.string().regex( + /^([0-9]+.[0-9]+[,]?[ ]?)+$/ +) +const isCommaSeperatedDjangoVersions = Joi.string().regex( + /^([0-9]+.[0-9]+[,]?[ ]?)+$/ +) +const t = new ServiceTester({ id: 'pypi', title: 'PyPi badges' }) +module.exports = t /* tests for downloads endpoints @@ -24,28 +27,27 @@ module.exports = t; */ t.create('daily downloads (expected failure)') .get('/dd/djangorestframework.json') - .expectJSON({ name: 'downloads', value: 'no longer available' }); + .expectJSON({ name: 'downloads', value: 'no longer available' }) t.create('weekly downloads (expected failure)') .get('/dw/djangorestframework.json') - .expectJSON({ name: 'downloads', value: 'no longer available' }); + .expectJSON({ name: 'downloads', value: 'no longer available' }) t.create('monthly downloads (expected failure)') .get('/dm/djangorestframework.json') - .expectJSON({ name: 'downloads', value: 'no longer available' }); + .expectJSON({ name: 'downloads', value: 'no longer available' }) t.create('daily downloads (invalid)') .get('/dd/not-a-package.json') - .expectJSON({ name: 'pypi', value: 'invalid' }); + .expectJSON({ name: 'pypi', value: 'invalid' }) t.create('weekly downloads (invalid)') .get('/dw/not-a-package.json') - .expectJSON({ name: 'pypi', value: 'invalid' }); + .expectJSON({ name: 'pypi', value: 'invalid' }) t.create('monthly downloads (invalid)') .get('/dm/not-a-package.json') - .expectJSON({ name: 'pypi', value: 'invalid' }); - + .expectJSON({ name: 'pypi', value: 'invalid' }) /* tests for version endpoint @@ -63,164 +65,169 @@ t.create('monthly downloads (invalid)') */ t.create('version (semver)') .get('/v/requests.json') - .expectJSONTypes(Joi.object().keys({ - name: 'pypi', - value: isSemver - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'pypi', + value: isSemver, + }) + ) // ..whereas this project does not folow SemVer t.create('version (not semver)') .get('/v/psycopg2.json') - .expectJSONTypes(Joi.object().keys({ - name: 'pypi', - value: isPsycopg2Version - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'pypi', + value: isPsycopg2Version, + }) + ) t.create('version (invalid)') .get('/v/not-a-package.json') - .expectJSON({ name: 'pypi', value: 'invalid' }); - + .expectJSON({ name: 'pypi', value: 'invalid' }) // tests for license endpoint t.create('license (valid, package version in request)') .get('/l/requests/2.18.4.json') - .expectJSON({ name: 'license', value: 'Apache 2.0' }); + .expectJSON({ name: 'license', value: 'Apache 2.0' }) t.create('license (valid, no package version specified)') .get('/l/requests.json') - .expectJSON({ name: 'license', value: 'Apache 2.0' }); + .expectJSON({ name: 'license', value: 'Apache 2.0' }) t.create('license (invalid)') .get('/l/not-a-package.json') - .expectJSON({ name: 'pypi', value: 'invalid' }); - + .expectJSON({ name: 'pypi', value: 'invalid' }) // tests for wheel endpoint t.create('wheel (has wheel, package version in request)') .get('/wheel/requests/2.18.4.json') - .expectJSON({ name: 'wheel', value: 'yes' }); + .expectJSON({ name: 'wheel', value: 'yes' }) t.create('wheel (has wheel, no package version specified)') .get('/wheel/requests.json') - .expectJSON({ name: 'wheel', value: 'yes' }); + .expectJSON({ name: 'wheel', value: 'yes' }) t.create('wheel (no wheel)') .get('/wheel/chai/1.1.2.json') - .expectJSON({ name: 'wheel', value: 'no' }); + .expectJSON({ name: 'wheel', value: 'no' }) t.create('wheel (invalid)') .get('/wheel/not-a-package.json') - .expectJSON({ name: 'pypi', value: 'invalid' }); - + .expectJSON({ name: 'pypi', value: 'invalid' }) // tests for format endpoint t.create('format (wheel, package version in request)') .get('/format/requests/2.18.4.json') - .expectJSON({ name: 'format', value: 'wheel' }); + .expectJSON({ name: 'format', value: 'wheel' }) t.create('format (wheel, no package version specified)') .get('/format/requests.json') - .expectJSON({ name: 'format', value: 'wheel' }); + .expectJSON({ name: 'format', value: 'wheel' }) t.create('format (source)') .get('/format/chai/1.1.2.json') - .expectJSON({ name: 'format', value: 'source' }); + .expectJSON({ name: 'format', value: 'source' }) t.create('format (egg)') .get('/format/virtualenv/0.8.2.json') - .expectJSON({ name: 'format', value: 'egg' }); + .expectJSON({ name: 'format', value: 'egg' }) t.create('format (invalid)') .get('/format/not-a-package.json') - .expectJSON({ name: 'pypi', value: 'invalid' }); - + .expectJSON({ name: 'pypi', value: 'invalid' }) // tests for pyversions endpoint t.create('python versions (valid, package version in request)') .get('/pyversions/requests/2.18.4.json') - .expectJSONTypes(Joi.object().keys({ - name: 'python', - value: isCommaSeperatedPythonVersions - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'python', + value: isCommaSeperatedPythonVersions, + }) + ) t.create('python versions (valid, no package version specified)') .get('/pyversions/requests.json') - .expectJSONTypes(Joi.object().keys({ - name: 'python', - value: isCommaSeperatedPythonVersions - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'python', + value: isCommaSeperatedPythonVersions, + }) + ) t.create('python versions (no versions specified)') .get('/pyversions/pyshp/1.2.12.json') - .expectJSON({ name: 'python', value: 'not found' }); + .expectJSON({ name: 'python', value: 'not found' }) t.create('python versions (invalid)') .get('/pyversions/not-a-package.json') - .expectJSON({ name: 'pypi', value: 'invalid' }); - + .expectJSON({ name: 'pypi', value: 'invalid' }) // tests for django versions endpoint t.create('supported django versions (valid, package version in request)') .get('/djversions/djangorestframework/3.7.3.json') - .expectJSONTypes(Joi.object().keys({ - name: 'django versions', - value: isCommaSeperatedDjangoVersions - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'django versions', + value: isCommaSeperatedDjangoVersions, + }) + ) t.create('supported django versions (valid, no package version specified)') .get('/djversions/djangorestframework.json') - .expectJSONTypes(Joi.object().keys({ - name: 'django versions', - value: isCommaSeperatedDjangoVersions - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'django versions', + value: isCommaSeperatedDjangoVersions, + }) + ) t.create('supported django versions (no versions specified)') .get('/djversions/django/1.11.json') - .expectJSON({ name: 'django versions', value: 'not found' }); + .expectJSON({ name: 'django versions', value: 'not found' }) t.create('supported django versions (invalid)') .get('/djversions/not-a-package.json') - .expectJSON({ name: 'pypi', value: 'invalid' }); - + .expectJSON({ name: 'pypi', value: 'invalid' }) // tests for implementation endpoint t.create('implementation (valid, package version in request)') .get('/implementation/beehive/1.0.json') - .expectJSON({ name: 'implementation', value: 'cpython, jython, pypy' }); + .expectJSON({ name: 'implementation', value: 'cpython, jython, pypy' }) t.create('implementation (valid, no package version specified)') .get('/implementation/numpy.json') - .expectJSON({ name: 'implementation', value: 'cpython' }); + .expectJSON({ name: 'implementation', value: 'cpython' }) t.create('implementation (not specified)') .get('/implementation/chai/1.1.2.json') - .expectJSON({ name: 'implementation', value: 'cpython' }); + .expectJSON({ name: 'implementation', value: 'cpython' }) t.create('implementation (invalid)') .get('/implementation/not-a-package.json') - .expectJSON({ name: 'pypi', value: 'invalid' }); - + .expectJSON({ name: 'pypi', value: 'invalid' }) // tests for status endpoint t.create('status (valid, stable, package version in request)') .get('/status/django/1.11.json') - .expectJSON({name: 'status', value: 'stable' }); + .expectJSON({ name: 'status', value: 'stable' }) t.create('status (valid, no package version specified)') .get('/status/typing.json') - .expectJSON({name: 'status', value: 'stable' }); + .expectJSON({ name: 'status', value: 'stable' }) t.create('status (valid, beta)') .get('/status/django/2.0rc1.json') - .expectJSON({ name: 'status', value: 'beta' }); + .expectJSON({ name: 'status', value: 'beta' }) t.create('status (invalid)') .get('/status/not-a-package.json') - .expectJSON({ name: 'pypi', value: 'invalid' }); + .expectJSON({ name: 'pypi', value: 'invalid' }) diff --git a/services/readthedocs/readthedocs.tester.js b/services/readthedocs/readthedocs.tester.js index e024a2679a1ec9d26d50f7206662d94845da9067..7687b9bc7423657d323770232d5c91b1625cd7ca 100644 --- a/services/readthedocs/readthedocs.tester.js +++ b/services/readthedocs/readthedocs.tester.js @@ -1,38 +1,44 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { isBuildStatus } = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isBuildStatus } = require('../test-validators') const t = new ServiceTester({ id: 'readthedocs', title: 'Read the Docs' }) -module.exports = t; +module.exports = t t.create('build status') .get('/pip.json') - .expectJSONTypes(Joi.object().keys({ - name: 'docs', - value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'docs', + value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), + }) + ) t.create('build status for named version') .get('/pip/stable.json') - .expectJSONTypes(Joi.object().keys({ - name: 'docs', - value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'docs', + value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), + }) + ) t.create('build status for named semantic version') .get('/scrapy/1.0.json') - .expectJSONTypes(Joi.object().keys({ - name: 'docs', - value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'docs', + value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), + }) + ) t.create('unknown project') .get('/this-repo/does-not-exist.json') - .expectJSON({ name: 'docs', value: 'unknown' }); + .expectJSON({ name: 'docs', value: 'unknown' }) t.create('connection error') .get('/pip.json') .networkOff() - .expectJSON({ name: 'docs', value: 'inaccessible' }); + .expectJSON({ name: 'docs', value: 'inaccessible' }) diff --git a/services/redmine/redmine.tester.js b/services/redmine/redmine.tester.js index 51ebca17b26a0b11c0c53abd081b8b71514bcfa7..d6a1ac95c7e8aa0c9a008eef579866783f3a1afe 100644 --- a/services/redmine/redmine.tester.js +++ b/services/redmine/redmine.tester.js @@ -1,59 +1,68 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { - isStarRating - } = require('../test-validators'); - -const t = new ServiceTester({ id: 'redmine', title: 'Redmine' }); -module.exports = t; +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isStarRating } = require('../test-validators') + +const t = new ServiceTester({ id: 'redmine', title: 'Redmine' }) +module.exports = t t.create('plugin rating') .get('/plugin/rating/redmine_xlsx_format_issue_exporter.json') - .intercept(nock => nock('https://www.redmine.org') - .get('/plugins/redmine_xlsx_format_issue_exporter.xml') - .reply(200, - '<redmine-plugin>' + - '<ratings-average type="float">1.23456</ratings-average>' + - '</redmine-plugin>' - ) + .intercept(nock => + nock('https://www.redmine.org') + .get('/plugins/redmine_xlsx_format_issue_exporter.xml') + .reply( + 200, + '<redmine-plugin>' + + '<ratings-average type="float">1.23456</ratings-average>' + + '</redmine-plugin>' + ) + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'rating', + value: Joi.string().regex(/^[0-9]+\.[0-9]+\/5\.0$/), + }) ) - .expectJSONTypes(Joi.object().keys({ - name: 'rating', - value: Joi.string().regex(/^[0-9]+\.[0-9]+\/5\.0$/) - })); t.create('plugin stars') -.get('/plugin/stars/redmine_xlsx_format_issue_exporter.json') -.intercept(nock => nock('https://www.redmine.org') - .get('/plugins/redmine_xlsx_format_issue_exporter.xml') - .reply(200, - '<redmine-plugin>' + + .get('/plugin/stars/redmine_xlsx_format_issue_exporter.json') + .intercept(nock => + nock('https://www.redmine.org') + .get('/plugins/redmine_xlsx_format_issue_exporter.xml') + .reply( + 200, + '<redmine-plugin>' + '<ratings-average type="float">1.23456</ratings-average>' + - '</redmine-plugin>' + '</redmine-plugin>' + ) + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'stars', + value: isStarRating, + }) ) -) -.expectJSONTypes(Joi.object().keys({ - name: 'stars', - value: isStarRating -})); t.create('plugin not found') .get('/plugin/rating/plugin_not_found.json') - .intercept(nock => nock('https://www.redmine.org') - .get('/plugins/plugin_not_found.xml') - .reply(404, '') + .intercept(nock => + nock('https://www.redmine.org') + .get('/plugins/plugin_not_found.xml') + .reply(404, '') + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'rating', + value: 'invalid', + }) ) - .expectJSONTypes(Joi.object().keys({ - name: 'rating', - value: 'invalid' - })); t.create('connection error') .get('/plugin/rating/redmine_xlsx_format_issue_exporter.json') .networkOff() .expectJSON({ - name: 'rating', - value: 'inaccessible' - }); + name: 'rating', + value: 'inaccessible', + }) diff --git a/services/requires/requires.tester.js b/services/requires/requires.tester.js index b1718a1a0d4ad0b6c7d9860887554edfa2ffc38f..6640833acaf92c78de7859c5604288b05060ade1 100644 --- a/services/requires/requires.tester.js +++ b/services/requires/requires.tester.js @@ -1,42 +1,48 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { invalidJSON } = require('../response-fixtures'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { invalidJSON } = require('../response-fixtures') -const isRequireStatus = Joi.string().regex(/^(up to date|outdated|insecure|unknown)$/); - -const t = new ServiceTester({ id: 'requires', title: 'Requires.io' }); -module.exports = t; +const isRequireStatus = Joi.string().regex( + /^(up to date|outdated|insecure|unknown)$/ +) +const t = new ServiceTester({ id: 'requires', title: 'Requires.io' }) +module.exports = t t.create('requirements (valid, without branch)') .get('/github/celery/celery.json') - .expectJSONTypes(Joi.object().keys({ - name: 'requirements', - value: isRequireStatus - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'requirements', + value: isRequireStatus, + }) + ) t.create('requirements (valid, with branch)') .get('/github/celery/celery/master.json') - .expectJSONTypes(Joi.object().keys({ - name: 'requirements', - value: isRequireStatus - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'requirements', + value: isRequireStatus, + }) + ) t.create('requirements (not found)') .get('/github/PyvesB/EmptyRepo.json') - .expectJSON({name: 'requirements', value: 'not found'}); + .expectJSON({ name: 'requirements', value: 'not found' }) t.create('requirements (connection error)') .get('/github/celery/celery.json') .networkOff() - .expectJSON({name: 'requirements', value: 'inaccessible'}); + .expectJSON({ name: 'requirements', value: 'inaccessible' }) t.create('requirements (unexpected response)') .get('/github/celery/celery.json') - .intercept(nock => nock('https://requires.io/') - .get('/api/v1/status/github/celery/celery') - .reply(invalidJSON) + .intercept(nock => + nock('https://requires.io/') + .get('/api/v1/status/github/celery/celery') + .reply(invalidJSON) ) - .expectJSON({name: 'requirements', value: 'invalid'}); + .expectJSON({ name: 'requirements', value: 'invalid' }) diff --git a/services/resharper/resharper.tester.js b/services/resharper/resharper.tester.js index c20526e5f6fca1819f93dd98b18622f5449113ee..707e630b9701c2921c4464d988b28e67d95da883 100644 --- a/services/resharper/resharper.tester.js +++ b/services/resharper/resharper.tester.js @@ -1,172 +1,202 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') const { isMetric, isVPlusDottedVersionNClauses, isVPlusDottedVersionNClausesWithOptionalSuffix, -} = require('../test-validators'); -const colorscheme = require('../../lib/colorscheme.json'); +} = require('../test-validators') +const colorscheme = require('../../lib/colorscheme.json') const { nuGetV2VersionJsonWithDash, nuGetV2VersionJsonFirstCharZero, - nuGetV2VersionJsonFirstCharNotZero -} = require('../nuget-fixtures'); -const { invalidJSON } = require('../response-fixtures'); - -const t = new ServiceTester({ id: 'resharper', title: 'ReSharper' }); -module.exports = t; + nuGetV2VersionJsonFirstCharNotZero, +} = require('../nuget-fixtures') +const { invalidJSON } = require('../response-fixtures') +const t = new ServiceTester({ id: 'resharper', title: 'ReSharper' }) +module.exports = t // downloads t.create('total downloads (valid)') .get('/dt/ReSharper.Nuke.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetric, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetric, + }) + ) t.create('total downloads (not found)') .get('/dt/not-a-real-package.json') - .expectJSON({name: 'downloads', value: 'not found'}); + .expectJSON({ name: 'downloads', value: 'not found' }) t.create('total downloads (connection error)') .get('/dt/ReSharper.Nuke.json') .networkOff() - .expectJSON({name: 'downloads', value: 'inaccessible'}); + .expectJSON({ name: 'downloads', value: 'inaccessible' }) t.create('total downloads (unexpected response)') .get('/dt/ReSharper.Nuke.json') - .intercept(nock => nock('https://resharper-plugins.jetbrains.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true") - .reply(invalidJSON) + .intercept(nock => + nock('https://resharper-plugins.jetbrains.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true' + ) + .reply(invalidJSON) ) - .expectJSON({name: 'downloads', value: 'invalid'}); - + .expectJSON({ name: 'downloads', value: 'invalid' }) // version t.create('version (valid)') .get('/v/ReSharper.Nuke.json') - .expectJSONTypes(Joi.object().keys({ - name: 'resharper', - value: isVPlusDottedVersionNClauses, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'resharper', + value: isVPlusDottedVersionNClauses, + }) + ) t.create('version (mocked, yellow badge)') .get('/v/ReSharper.Nuke.json?style=_shields_test') - .intercept(nock => nock('https://resharper-plugins.jetbrains.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonWithDash) + .intercept(nock => + nock('https://resharper-plugins.jetbrains.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonWithDash) ) .expectJSON({ name: 'resharper', value: 'v1.2-beta', - colorB: colorscheme.yellow.colorB - }); + colorB: colorscheme.yellow.colorB, + }) t.create('version (mocked, orange badge)') .get('/v/ReSharper.Nuke.json?style=_shields_test') - .intercept(nock => nock('https://resharper-plugins.jetbrains.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonFirstCharZero) + .intercept(nock => + nock('https://resharper-plugins.jetbrains.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonFirstCharZero) ) .expectJSON({ name: 'resharper', value: 'v0.35', - colorB: colorscheme.orange.colorB - }); + colorB: colorscheme.orange.colorB, + }) t.create('version (mocked, blue badge)') .get('/v/ReSharper.Nuke.json?style=_shields_test') - .intercept(nock => nock('https://resharper-plugins.jetbrains.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonFirstCharNotZero) + .intercept(nock => + nock('https://resharper-plugins.jetbrains.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonFirstCharNotZero) ) .expectJSON({ name: 'resharper', value: 'v1.2.7', - colorB: colorscheme.blue.colorB - }); + colorB: colorscheme.blue.colorB, + }) t.create('version (not found)') .get('/v/not-a-real-package.json') - .expectJSON({name: 'resharper', value: 'not found'}); + .expectJSON({ name: 'resharper', value: 'not found' }) t.create('version (connection error)') .get('/v/ReSharper.Nuke.json') .networkOff() - .expectJSON({name: 'resharper', value: 'inaccessible'}); + .expectJSON({ name: 'resharper', value: 'inaccessible' }) t.create('version (unexpected response)') .get('/v/ReSharper.Nuke.json') - .intercept(nock => nock('https://resharper-plugins.jetbrains.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true") - .reply(invalidJSON) + .intercept(nock => + nock('https://resharper-plugins.jetbrains.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true' + ) + .reply(invalidJSON) ) - .expectJSON({name: 'resharper', value: 'invalid'}); - + .expectJSON({ name: 'resharper', value: 'invalid' }) // version (pre) t.create('version (pre) (valid)') .get('/vpre/ReSharper.Nuke.json') - .expectJSONTypes(Joi.object().keys({ - name: 'resharper', - value: isVPlusDottedVersionNClausesWithOptionalSuffix, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'resharper', + value: isVPlusDottedVersionNClausesWithOptionalSuffix, + }) + ) t.create('version (pre) (mocked, yellow badge)') .get('/vpre/ReSharper.Nuke.json?style=_shields_test') - .intercept(nock => nock('https://resharper-plugins.jetbrains.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonWithDash) + .intercept(nock => + nock('https://resharper-plugins.jetbrains.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonWithDash) ) .expectJSON({ name: 'resharper', value: 'v1.2-beta', - colorB: colorscheme.yellow.colorB - }); + colorB: colorscheme.yellow.colorB, + }) t.create('version (pre) (mocked, orange badge)') .get('/vpre/ReSharper.Nuke.json?style=_shields_test') - .intercept(nock => nock('https://resharper-plugins.jetbrains.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonFirstCharZero) + .intercept(nock => + nock('https://resharper-plugins.jetbrains.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonFirstCharZero) ) .expectJSON({ name: 'resharper', value: 'v0.35', - colorB: colorscheme.orange.colorB - }); + colorB: colorscheme.orange.colorB, + }) t.create('version (pre) (mocked, blue badge)') .get('/vpre/ReSharper.Nuke.json?style=_shields_test') - .intercept(nock => nock('https://resharper-plugins.jetbrains.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true") - .reply(200, nuGetV2VersionJsonFirstCharNotZero) + .intercept(nock => + nock('https://resharper-plugins.jetbrains.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true' + ) + .reply(200, nuGetV2VersionJsonFirstCharNotZero) ) .expectJSON({ name: 'resharper', value: 'v1.2.7', - colorB: colorscheme.blue.colorB - }); + colorB: colorscheme.blue.colorB, + }) t.create('version (pre) (not found)') .get('/vpre/not-a-real-package.json') - .expectJSON({name: 'resharper', value: 'not found'}); + .expectJSON({ name: 'resharper', value: 'not found' }) t.create('version (pre) (connection error)') .get('/vpre/ReSharper.Nuke.json') .networkOff() - .expectJSON({name: 'resharper', value: 'inaccessible'}); + .expectJSON({ name: 'resharper', value: 'inaccessible' }) t.create('version (pre) (unexpected response)') .get('/vpre/ReSharper.Nuke.json') - .intercept(nock => nock('https://resharper-plugins.jetbrains.com') - .get("/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true") - .reply(invalidJSON) + .intercept(nock => + nock('https://resharper-plugins.jetbrains.com') + .get( + '/api/v2/Packages()?$filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true' + ) + .reply(invalidJSON) ) - .expectJSON({name: 'resharper', value: 'invalid'}); + .expectJSON({ name: 'resharper', value: 'invalid' }) diff --git a/services/response-fixtures.js b/services/response-fixtures.js index ac5efaa5b73f726a7dae650b6fa6708f19de3926..49ba6d8a7f0ba8758ff7808a3edadb3e082e9661 100644 --- a/services/response-fixtures.js +++ b/services/response-fixtures.js @@ -1,13 +1,9 @@ -'use strict'; +'use strict' const invalidJSON = function() { - return [ - 200, - '{{{{{invalid json}}', - { 'Content-Type': 'application/json' } - ]; -}; + return [200, '{{{{{invalid json}}', { 'Content-Type': 'application/json' }] +} module.exports = { - invalidJSON -}; + invalidJSON, +} diff --git a/services/scrutinizer/scrutinizer.tester.js b/services/scrutinizer/scrutinizer.tester.js index c8199574eb2a44d1e85ff8d1bd15439fccfb953f..baa00e0185ce0840084469db77ac9eabee2ef47c 100644 --- a/services/scrutinizer/scrutinizer.tester.js +++ b/services/scrutinizer/scrutinizer.tester.js @@ -1,77 +1,88 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { - isBuildStatus, - isIntegerPercentage -} = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isBuildStatus, isIntegerPercentage } = require('../test-validators') -const t = new ServiceTester({ id: 'scrutinizer', title: 'Scrutinizer' }); -module.exports = t; +const t = new ServiceTester({ id: 'scrutinizer', title: 'Scrutinizer' }) +module.exports = t t.create('code quality') .get('/g/filp/whoops.json') - .expectJSONTypes(Joi.object().keys({ - name: 'code quality', - value: Joi.number().positive(), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'code quality', + value: Joi.number().positive(), + }) + ) t.create('code quality (branch)') .get('/g/phpmyadmin/phpmyadmin/master.json') - .expectJSONTypes(Joi.object().keys({ - name: 'code quality', - value: Joi.number().positive(), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'code quality', + value: Joi.number().positive(), + }) + ) t.create('code coverage') .get('/coverage/g/filp/whoops.json') - .expectJSONTypes(Joi.object().keys({ - name: 'coverage', - value: isIntegerPercentage, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'coverage', + value: isIntegerPercentage, + }) + ) t.create('code coverage (branch)') .get('/coverage/g/doctrine/doctrine2/master.json') - .expectJSONTypes(Joi.object().keys({ - name: 'coverage', - value: isIntegerPercentage, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'coverage', + value: isIntegerPercentage, + }) + ) t.create('build') .get('/build/g/filp/whoops.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), + }) + ) t.create('build (branch)') .get('/build/g/phpmyadmin/phpmyadmin/master.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), + }) + ) t.create('project not found') .get('/build/g/does-not-exist/does-not-exist.json') .expectJSON({ - name: 'build', - value: 'project or branch not found', - }); + name: 'build', + value: 'project or branch not found', + }) t.create('code coverage unknown') .get('/coverage/g/phpmyadmin/phpmyadmin/master.json') .expectJSON({ - name: 'coverage', - value: 'unknown', - }); + name: 'coverage', + value: 'unknown', + }) t.create('unexpected response data') .get('/coverage/g/filp/whoops.json') - .intercept(nock => nock('https://scrutinizer-ci.com') - .get('/api/repositories/g/filp/whoops') - .reply(200, '{"unexpected":"data"}')) + .intercept(nock => + nock('https://scrutinizer-ci.com') + .get('/api/repositories/g/filp/whoops') + .reply(200, '{"unexpected":"data"}') + ) .expectJSON({ - name: 'coverage', - value: 'invalid', - }); + name: 'coverage', + value: 'invalid', + }) diff --git a/services/service-tester.js b/services/service-tester.js index 78f182583c93434990d80baec710e2d88b07d5d0..cb4933997d18da97c887af43e84d048d85dcdef5 100644 --- a/services/service-tester.js +++ b/services/service-tester.js @@ -1,7 +1,7 @@ -'use strict'; +'use strict' -const frisby = require('icedfrisby-nock')(require('icedfrisby')); -const config = require('../lib/test-config'); +const frisby = require('icedfrisby-nock')(require('icedfrisby')) +const config = require('../lib/test-config') /** * Encapsulate a suite of tests. Create new tests using create() and register @@ -14,23 +14,22 @@ class ServiceTester { * Mocha output. The `path` is the path prefix which is automatically * prepended to each tested URI. The default is `/${attrs.id}`. */ - constructor (attrs) { + constructor(attrs) { Object.assign(this, { id: attrs.id, title: attrs.title, - pathPrefix: attrs.pathPrefix === undefined - ? `/${attrs.id}` - : attrs.pathPrefix, + pathPrefix: + attrs.pathPrefix === undefined ? `/${attrs.id}` : attrs.pathPrefix, specs: [], - _only: false - }); + _only: false, + }) } /** * Invoked before each test. This is a stub which can be overridden on * instances. */ - beforeEach () {} + beforeEach() {} /** * Create a new test. The hard work is delegated to IcedFrisby. @@ -40,34 +39,39 @@ class ServiceTester { * invoked automatically by the tester. * @param msg The name of the test */ - create (msg) { - const spec = frisby.create(msg) + create(msg) { + const spec = frisby + .create(msg) .baseUri(`http://localhost:${config.port}${this.pathPrefix}`) - .before(() => { this.beforeEach(); }); + .before(() => { + this.beforeEach() + }) - this.specs.push(spec); + this.specs.push(spec) - return spec; + return spec } /** * Run only this tester. This can be invoked using the --only argument to * the CLI, or directly on the tester. */ - only () { - this._only = true; + only() { + this._only = true } /** * Register the tests with Mocha. */ - toss () { - const specs = this.specs; + toss() { + const specs = this.specs - const fn = this._only ? describe.only : describe; - fn(this.title, function () { - specs.forEach(spec => { spec.toss(); }); - }); + const fn = this._only ? describe.only : describe + fn(this.title, function() { + specs.forEach(spec => { + spec.toss() + }) + }) } } -module.exports = ServiceTester; +module.exports = ServiceTester diff --git a/services/shippable/shippable.tester.js b/services/shippable/shippable.tester.js index 0ccf7ee1577ba24b2d7d2627e9ca6587dc3aec4d..55bacd0f1e3a25e892b12ff4965af2dd182984f1 100644 --- a/services/shippable/shippable.tester.js +++ b/services/shippable/shippable.tester.js @@ -1,53 +1,58 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { invalidJSON } = require('../response-fixtures'); -const { isBuildStatus } = require('../test-validators'); - -const t = new ServiceTester({ id: 'shippable', title: 'Shippable CI' }); -module.exports = t; +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { invalidJSON } = require('../response-fixtures') +const { isBuildStatus } = require('../test-validators') +const t = new ServiceTester({ id: 'shippable', title: 'Shippable CI' }) +module.exports = t t.create('build status (valid, without branch)') .get('/5444c5ecb904a4b21567b0ff.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: isBuildStatus - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: isBuildStatus, + }) + ) t.create('build status (valid, with branch)') .get('/5444c5ecb904a4b21567b0ff/master.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: isBuildStatus - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: isBuildStatus, + }) + ) t.create('build status (branch not found)') .get('/5444c5ecb904a4b21567b0ff/not-a-branch.json') - .expectJSON({name: 'build', value: 'branch not found'}); + .expectJSON({ name: 'build', value: 'branch not found' }) t.create('build status (not found)') .get('/not-a-build.json') - .expectJSON({name: 'build', value: 'not found'}); + .expectJSON({ name: 'build', value: 'not found' }) t.create('build status (connection error)') .get('/5444c5ecb904a4b21567b0ff.json') .networkOff() - .expectJSON({name: 'build', value: 'inaccessible'}); + .expectJSON({ name: 'build', value: 'inaccessible' }) t.create('build status (unexpected response)') .get('/5444c5ecb904a4b21567b0ff.json') - .intercept(nock => nock('https://api.shippable.com/') - .get('/projects/5444c5ecb904a4b21567b0ff/branchRunStatus') - .reply(invalidJSON) + .intercept(nock => + nock('https://api.shippable.com/') + .get('/projects/5444c5ecb904a4b21567b0ff/branchRunStatus') + .reply(invalidJSON) ) - .expectJSON({name: 'build', value: 'invalid'}); + .expectJSON({ name: 'build', value: 'invalid' }) t.create('build status (unexpected status code)') .get('/5444c5ecb904a4b21567b0ff.json') - .intercept(nock => nock('https://api.shippable.com/') - .get('/projects/5444c5ecb904a4b21567b0ff/branchRunStatus') - .reply(200, '[{ "branchName": "master", "statusCode": 63 }]') + .intercept(nock => + nock('https://api.shippable.com/') + .get('/projects/5444c5ecb904a4b21567b0ff/branchRunStatus') + .reply(200, '[{ "branchName": "master", "statusCode": 63 }]') ) - .expectJSON({name: 'build', value: 'invalid'}); + .expectJSON({ name: 'build', value: 'invalid' }) diff --git a/services/snap-ci/snap-ci.tester.js b/services/snap-ci/snap-ci.tester.js index 0de847160addcc354a67e610ba889909a885877b..baeaa01e1ded55c8e77d0f720e01bfd06079dfb4 100644 --- a/services/snap-ci/snap-ci.tester.js +++ b/services/snap-ci/snap-ci.tester.js @@ -1,13 +1,13 @@ -'use strict'; +'use strict' -const ServiceTester = require('../service-tester'); +const ServiceTester = require('../service-tester') -const t = new ServiceTester({ id: 'snap-ci', title: 'Snap CI' }); -module.exports = t; +const t = new ServiceTester({ id: 'snap-ci', title: 'Snap CI' }) +module.exports = t t.create('no longer available (previously build state)') .get('/snap-ci/ThoughtWorksStudios/eb_deployer/master.json') .expectJSON({ name: 'snap CI', value: 'no longer available', - }); + }) diff --git a/services/sonarqube/sonarqube.tester.js b/services/sonarqube/sonarqube.tester.js index b1c8c451393867918bd4405a08edbb4ffa04657d..02f49977b619ce4eb6a6737d4ba2aa88044e2977 100644 --- a/services/sonarqube/sonarqube.tester.js +++ b/services/sonarqube/sonarqube.tester.js @@ -1,46 +1,64 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { - isIntegerPercentage, -} = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isIntegerPercentage } = require('../test-validators') -const t = new ServiceTester({ id: 'sonar', title: 'SonarQube' }); -module.exports = t; +const t = new ServiceTester({ id: 'sonar', title: 'SonarQube' }) +module.exports = t t.create('Tech Debt') - .get('/http/sonar.petalslink.com/org.ow2.petals%3Apetals-se-ase/tech_debt.json') - .expectJSONTypes(Joi.object().keys({ - name: 'tech debt', - value: isIntegerPercentage - })); + .get( + '/http/sonar.petalslink.com/org.ow2.petals%3Apetals-se-ase/tech_debt.json' + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'tech debt', + value: isIntegerPercentage, + }) + ) t.create('Coverage') - .get('/http/sonar.petalslink.com/org.ow2.petals%3Apetals-se-ase/coverage.json') - .expectJSONTypes(Joi.object().keys({ - name: 'coverage', - value: isIntegerPercentage - })); + .get( + '/http/sonar.petalslink.com/org.ow2.petals%3Apetals-se-ase/coverage.json' + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'coverage', + value: isIntegerPercentage, + }) + ) t.create('Tech Debt (legacy API supported)') - .get('/4.2/http/sonar.petalslink.com/org.ow2.petals%3Apetals-se-ase/tech_debt.json') - .expectJSONTypes(Joi.object().keys({ - name: 'tech debt', - value: isIntegerPercentage - })); + .get( + '/4.2/http/sonar.petalslink.com/org.ow2.petals%3Apetals-se-ase/tech_debt.json' + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'tech debt', + value: isIntegerPercentage, + }) + ) t.create('Coverage (legacy API supported)') - .get('/4.2/http/sonar.petalslink.com/org.ow2.petals%3Apetals-se-ase/coverage.json') - .expectJSONTypes(Joi.object().keys({ - name: 'coverage', - value: isIntegerPercentage - })); + .get( + '/4.2/http/sonar.petalslink.com/org.ow2.petals%3Apetals-se-ase/coverage.json' + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'coverage', + value: isIntegerPercentage, + }) + ) t.create('Tech Debt (legacy API unsupported)') - .get('/4.2/https/sonarqube.com/com.github.dannil:scb-java-client/tech_debt.json') - .expectJSON({ name: 'tech debt', value: 'invalid' }); + .get( + '/4.2/https/sonarqube.com/com.github.dannil:scb-java-client/tech_debt.json' + ) + .expectJSON({ name: 'tech debt', value: 'invalid' }) t.create('Coverage (legacy API unsupported)') - .get('/4.2/https/sonarqube.com/com.github.dannil:scb-java-client/coverage.json') - .expectJSON({ name: 'coverage', value: 'invalid' }); + .get( + '/4.2/https/sonarqube.com/com.github.dannil:scb-java-client/coverage.json' + ) + .expectJSON({ name: 'coverage', value: 'invalid' }) diff --git a/services/sourceforge/sourceforge.tester.js b/services/sourceforge/sourceforge.tester.js index b045812248fa7c412eec77f44b089597872c4b62..b07eced536f98da10033e155a163c4bf72b4d4f6 100644 --- a/services/sourceforge/sourceforge.tester.js +++ b/services/sourceforge/sourceforge.tester.js @@ -1,54 +1,59 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { - isMetric, - isMetricOverTimePeriod -} = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isMetric, isMetricOverTimePeriod } = require('../test-validators') -const t = new ServiceTester({ id: 'sourceforge', title: 'SourceForge' }); -module.exports = t; +const t = new ServiceTester({ id: 'sourceforge', title: 'SourceForge' }) +module.exports = t t.create('total downloads') .get('/dt/sevenzip.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetric, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetric, + }) + ) t.create('monthly downloads') .get('/dm/sevenzip.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetricOverTimePeriod, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetricOverTimePeriod, + }) + ) t.create('weekly downloads') .get('/dw/sevenzip.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetricOverTimePeriod, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetricOverTimePeriod, + }) + ) t.create('daily downloads') .get('/dd/sevenzip.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetricOverTimePeriod, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetricOverTimePeriod, + }) + ) t.create('invalid project') .get('/dd/invalid.json') .expectJSON({ name: 'downloads', value: 'invalid', - }); + }) t.create('total downloads (connection error)') .get('/dt/sevenzip.json') .networkOff() .expectJSON({ name: 'downloads', - value: 'inaccessible' - }); + value: 'inaccessible', + }) diff --git a/services/suggest/suggest.tester.js b/services/suggest/suggest.tester.js index 2a7c9f2eea13e4124bc09044e9bf556900fee462..fbac8b593718a3a4a3ed2eaebae6895335236afa 100644 --- a/services/suggest/suggest.tester.js +++ b/services/suggest/suggest.tester.js @@ -1,13 +1,17 @@ -'use strict'; +'use strict' // These tests are for the badge-suggestion endpoint in lib/suggest.js. This // endpoint is called from frontend/components/suggestion-and-search.js. -const ServiceTester = require('../service-tester'); -const { invalidJSON } = require('../response-fixtures'); +const ServiceTester = require('../service-tester') +const { invalidJSON } = require('../response-fixtures') -const t = new ServiceTester({ id: 'suggest', title: 'suggest', pathPrefix: '/$suggest' }); -module.exports = t; +const t = new ServiceTester({ + id: 'suggest', + title: 'suggest', + pathPrefix: '/$suggest', +}) +module.exports = t t.create('issues, forks, stars and twitter') .get('/v1?url=' + encodeURIComponent('https://github.com/atom/atom')) @@ -15,64 +19,71 @@ t.create('issues, forks, stars and twitter') .expectJSON('badges.?', { name: 'GitHub issues', link: 'https://github.com/atom/atom/issues', - badge: 'https://img.shields.io/github/issues/atom/atom.svg' + badge: 'https://img.shields.io/github/issues/atom/atom.svg', }) .expectJSON('badges.?', { name: 'GitHub forks', link: 'https://github.com/atom/atom/network', - badge: 'https://img.shields.io/github/forks/atom/atom.svg' + badge: 'https://img.shields.io/github/forks/atom/atom.svg', }) .expectJSON('badges.?', { name: 'GitHub stars', link: 'https://github.com/atom/atom/stargazers', - badge: 'https://img.shields.io/github/stars/atom/atom.svg' + badge: 'https://img.shields.io/github/stars/atom/atom.svg', }) .expectJSON('badges.?', { name: 'Twitter', - link: 'https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fatom%2Fatom', - badge: 'https://img.shields.io/twitter/url/https/github.com/atom/atom.svg?style=social' - }); + link: + 'https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fatom%2Fatom', + badge: + 'https://img.shields.io/twitter/url/https/github.com/atom/atom.svg?style=social', + }) t.create('license') .get('/v1?url=' + encodeURIComponent('https://github.com/atom/atom')) .expectJSON('badges.?', { name: 'GitHub license', link: 'https://github.com/atom/atom/blob/master/LICENSE.md', - badge: 'https://img.shields.io/github/license/atom/atom.svg' - }); + badge: 'https://img.shields.io/github/license/atom/atom.svg', + }) t.create('license for non-existing project') .get('/v1?url=' + encodeURIComponent('https://github.com/atom/atom')) - .intercept(nock => nock('https://api.github.com') - .get(/\/repos\/atom\/atom\/license/) - .reply(404)) + .intercept(nock => + nock('https://api.github.com') + .get(/\/repos\/atom\/atom\/license/) + .reply(404) + ) .expectJSON('badges.?', { name: 'GitHub license', link: 'https://github.com/atom/atom', - badge: 'https://img.shields.io/github/license/atom/atom.svg' - }); + badge: 'https://img.shields.io/github/license/atom/atom.svg', + }) t.create('license when json response is invalid') .get('/v1?url=' + encodeURIComponent('https://github.com/atom/atom')) - .intercept(nock => nock('https://api.github.com') - .get(/\/repos\/atom\/atom\/license/) - .reply(invalidJSON) + .intercept(nock => + nock('https://api.github.com') + .get(/\/repos\/atom\/atom\/license/) + .reply(invalidJSON) ) .expectJSON('badges.?', { name: 'GitHub license', link: 'https://github.com/atom/atom', - badge: 'https://img.shields.io/github/license/atom/atom.svg' - }); + badge: 'https://img.shields.io/github/license/atom/atom.svg', + }) t.create('license when html_url not found in GitHub api response') .get('/v1?url=' + encodeURIComponent('https://github.com/atom/atom')) - .intercept(nock => nock('https://api.github.com') - .get(/\/repos\/atom\/atom\/license/) - .reply(200, { - license: 'MIT' - })) + .intercept(nock => + nock('https://api.github.com') + .get(/\/repos\/atom\/atom\/license/) + .reply(200, { + license: 'MIT', + }) + ) .expectJSON('badges.?', { name: 'GitHub license', link: 'https://github.com/atom/atom', - badge: 'https://img.shields.io/github/license/atom/atom.svg' - }); + badge: 'https://img.shields.io/github/license/atom/atom.svg', + }) diff --git a/services/test-validators.js b/services/test-validators.js index 9211f24e3791d0ba016d3791d3e4182bc5e49747..6f7d992a7a9e2a07b650b36c2230132da3693963 100644 --- a/services/test-validators.js +++ b/services/test-validators.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' /* Note: @@ -7,24 +7,26 @@ should be declared in that service's test file. */ -const Joi = require('joi'); -const semverRegex = require('semver-regex')(); +const Joi = require('joi') +const semverRegex = require('semver-regex')() -const withRegex = (re) => Joi.string().regex(re); +const withRegex = re => Joi.string().regex(re) -const isSemver = withRegex(semverRegex); +const isSemver = withRegex(semverRegex) -const isVPlusTripleDottedVersion = withRegex(/^v[0-9]+.[0-9]+.[0-9]+$/); +const isVPlusTripleDottedVersion = withRegex(/^v[0-9]+.[0-9]+.[0-9]+$/) -const isVPlusDottedVersionAtLeastOne = withRegex(/^v\d+(\.\d+)?(\.\d+)?$/); +const isVPlusDottedVersionAtLeastOne = withRegex(/^v\d+(\.\d+)?(\.\d+)?$/) // matches a version number with N 'clauses' e.g: v1.2 or v1.22.7.392 are valid -const isVPlusDottedVersionNClauses = withRegex(/^v\d+(\.\d+)*$/); +const isVPlusDottedVersionNClauses = withRegex(/^v\d+(\.\d+)*$/) // matches a version number with N 'clauses' // and an optional text suffix // e.g: -beta, -preview1, -release-candidate etc -const isVPlusDottedVersionNClausesWithOptionalSuffix = withRegex(/^v\d+(\.\d+)*(-.*)?$/); +const isVPlusDottedVersionNClausesWithOptionalSuffix = withRegex( + /^v\d+(\.\d+)*(-.*)?$/ +) // Simple regex for test Composer versions rule // https://getcomposer.org/doc/articles/versions.md @@ -38,40 +40,76 @@ const isVPlusDottedVersionNClausesWithOptionalSuffix = withRegex(/^v\d+(\.\d+)*( // This regex not support branches, minimum-stability, ref and any (*) // https://getcomposer.org/doc/04-schema.md#package-links // https://getcomposer.org/doc/04-schema.md#minimum-stability -const isComposerVersion = withRegex(/^\s*(>=|>|<|<=|!=|\^|~)?\d+(\.(\*|(\d+(\.(\d+|\*))?)))?((\s*\|\|)?\s*(>=|>|<|<=|!=|\^|~)?\d+(\.(\*|(\d+(\.(\d+|\*))?)))?)*\s*$/); +const isComposerVersion = withRegex( + /^\s*(>=|>|<|<=|!=|\^|~)?\d+(\.(\*|(\d+(\.(\d+|\*))?)))?((\s*\|\|)?\s*(>=|>|<|<=|!=|\^|~)?\d+(\.(\*|(\d+(\.(\d+|\*))?)))?)*\s*$/ +) // Regex for validate php-version.versionReduction() // >= 7 // >= 7.1 // 5.4, 5.6, 7.2 // 5.4 - 7.1, HHVM -const isPhpVersionReduction = withRegex(/^((>= \d+(\.\d+)?)|(\d+\.\d+(, \d+\.\d+)*)|(\d+\.\d+ \\- \d+\.\d+))(, HHVM)?$/); +const isPhpVersionReduction = withRegex( + /^((>= \d+(\.\d+)?)|(\d+\.\d+(, \d+\.\d+)*)|(\d+\.\d+ \\- \d+\.\d+))(, HHVM)?$/ +) -const isStarRating = withRegex(/^(?=.{5}$)(\u2605{0,5}[\u00BC\u00BD\u00BE]?\u2606{0,5})$/); +const isStarRating = withRegex( + /^(?=.{5}$)(\u2605{0,5}[\u00BC\u00BD\u00BE]?\u2606{0,5})$/ +) // Required to be > 0, because accepting zero masks many problems. -const isMetric = withRegex(/^[1-9][0-9]*[kMGTPEZY]?$/); +const isMetric = withRegex(/^[1-9][0-9]*[kMGTPEZY]?$/) -const isMetricOpenIssues = withRegex(/^[1-9][0-9]*[kMGTPEZY]? open$/); +const isMetricOpenIssues = withRegex(/^[1-9][0-9]*[kMGTPEZY]? open$/) -const isMetricOverTimePeriod = withRegex(/^[1-9][0-9]*[kMGTPEZY]?\/(year|month|4 weeks|week|day)$/); +const isMetricOverTimePeriod = withRegex( + /^[1-9][0-9]*[kMGTPEZY]?\/(year|month|4 weeks|week|day)$/ +) -const isIntegerPercentage = withRegex(/^[0-9]+%$/); -const isDecimalPercentage = withRegex(/^[0-9]+\.[0-9]*%$/); -const isPercentage = Joi.alternatives().try(isIntegerPercentage, isDecimalPercentage); +const isIntegerPercentage = withRegex(/^[0-9]+%$/) +const isDecimalPercentage = withRegex(/^[0-9]+\.[0-9]*%$/) +const isPercentage = Joi.alternatives().try( + isIntegerPercentage, + isDecimalPercentage +) -const isFileSize = withRegex(/^[0-9]*[.]?[0-9]+\s(B|kB|MB|GB|TB|PB|EB|ZB|YB)$/); +const isFileSize = withRegex(/^[0-9]*[.]?[0-9]+\s(B|kB|MB|GB|TB|PB|EB|ZB|YB)$/) const isFormattedDate = Joi.alternatives().try( Joi.equal('today', 'yesterday'), Joi.string().regex(/^last (sun|mon|tues|wednes|thurs|fri|satur)day$/), - Joi.string().regex(/^(january|february|march|april|may|june|july|august|september|october|november|december)( \d{4})?$/)); - -const isDependencyState = withRegex(/^(\d+ out of date|\d+ deprecated|up to date)$/); - -const isBuildStatus = Joi.equal('building', 'cancelled', 'error', 'expired', 'failed', 'failing', 'no tests', - 'not built', 'not run', 'passing', 'pending', 'processing', 'queued', 'running', - 'scheduled', 'skipped', 'stopped', 'success', 'timeout', 'unstable', 'waiting'); + Joi.string().regex( + /^(january|february|march|april|may|june|july|august|september|october|november|december)( \d{4})?$/ + ) +) + +const isDependencyState = withRegex( + /^(\d+ out of date|\d+ deprecated|up to date)$/ +) + +const isBuildStatus = Joi.equal( + 'building', + 'cancelled', + 'error', + 'expired', + 'failed', + 'failing', + 'no tests', + 'not built', + 'not run', + 'passing', + 'pending', + 'processing', + 'queued', + 'running', + 'scheduled', + 'skipped', + 'stopped', + 'success', + 'timeout', + 'unstable', + 'waiting' +) module.exports = { isSemver, @@ -92,4 +130,4 @@ module.exports = { isFormattedDate, isDependencyState, isBuildStatus, -}; +} diff --git a/services/time/time.service.js b/services/time/time.service.js index 2d07171660007bc396ca10b17055def6dc582e66..04f83eb50f7fd9f26a21400634954db8a1bd0736 100644 --- a/services/time/time.service.js +++ b/services/time/time.service.js @@ -1,11 +1,10 @@ -'use strict'; +'use strict' -const { BaseService } = require('../base'); +const { BaseService } = require('../base') module.exports = class Time extends BaseService { - async handle() { - return { message: new Date() }; + return { message: new Date() } } // Metadata @@ -13,19 +12,18 @@ module.exports = class Time extends BaseService { return { label: 'time', color: 'blue', - }; + } } static get category() { - return 'debug'; + return 'debug' } static get url() { return { base: 'servertime', format: '', - capture: [] - }; + capture: [], + } } - -}; +} diff --git a/services/travis/travis.tester.js b/services/travis/travis.tester.js index d7fb9b1fa2bc43e6895839e4c24b25e1abcbab4b..f8e9270a82388f0bb43e07e36bd1cc8669cb31c5 100644 --- a/services/travis/travis.tester.js +++ b/services/travis/travis.tester.js @@ -1,93 +1,111 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { - isBuildStatus, - isPhpVersionReduction -} = require('../test-validators'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isBuildStatus, isPhpVersionReduction } = require('../test-validators') -const t = new ServiceTester({ id: 'travis', title: 'Travis CI/PHP version from .travis.yml' }); -module.exports = t; +const t = new ServiceTester({ + id: 'travis', + title: 'Travis CI/PHP version from .travis.yml', +}) +module.exports = t // Travis CI t.create('build status on default branch') .get('/rust-lang/rust.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), + }) + ) t.create('build status on named branch') .get('/rust-lang/rust/stable.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), + }) + ) t.create('unknown repo') .get('/this-repo/does-not-exist.json') - .expectJSON({ name: 'build', value: 'unknown' }); + .expectJSON({ name: 'build', value: 'unknown' }) t.create('missing content-disposition header') .get('/foo/bar.json') - .intercept(nock => nock('https://api.travis-ci.org') - .head('/foo/bar.svg') - .reply(200)) - .expectJSON({ name: 'build', value: 'invalid' }); + .intercept(nock => + nock('https://api.travis-ci.org') + .head('/foo/bar.svg') + .reply(200) + ) + .expectJSON({ name: 'build', value: 'invalid' }) t.create('connection error') .get('/foo/bar.json') .networkOff() - .expectJSON({ name: 'build', value: 'inaccessible' }); + .expectJSON({ name: 'build', value: 'inaccessible' }) // Travis (.com) CI t.create('build status on default branch') .get('/com/ivandelabeldad/rackian-gateway.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), + }) + ) t.create('build status on named branch') .get('/com/ivandelabeldad/rackian-gateway.json') - .expectJSONTypes(Joi.object().keys({ - name: 'build', - value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'build', + value: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')), + }) + ) t.create('unknown repo') .get('/com/this-repo/does-not-exist.json') - .expectJSON({ name: 'build', value: 'unknown' }); + .expectJSON({ name: 'build', value: 'unknown' }) t.create('missing content-disposition header') .get('/com/foo/bar.json') - .intercept(nock => nock('https://api.travis-ci.com') - .head('/foo/bar.svg') - .reply(200)) - .expectJSON({ name: 'build', value: 'invalid' }); + .intercept(nock => + nock('https://api.travis-ci.com') + .head('/foo/bar.svg') + .reply(200) + ) + .expectJSON({ name: 'build', value: 'invalid' }) t.create('connection error') .get('/com/foo/bar.json') .networkOff() - .expectJSON({ name: 'build', value: 'inaccessible' }); + .expectJSON({ name: 'build', value: 'inaccessible' }) // PHP version from .travis.yml t.create('gets the package version of symfony') - .get('/php-v/symfony/symfony.json') - .expectJSONTypes(Joi.object().keys({ name: 'PHP', value: isPhpVersionReduction })); + .get('/php-v/symfony/symfony.json') + .expectJSONTypes( + Joi.object().keys({ name: 'PHP', value: isPhpVersionReduction }) + ) t.create('gets the package version of symfony 2.8') - .get('/php-v/symfony/symfony/2.8.json') - .expectJSONTypes(Joi.object().keys({ name: 'PHP', value: isPhpVersionReduction })); + .get('/php-v/symfony/symfony/2.8.json') + .expectJSONTypes( + Joi.object().keys({ name: 'PHP', value: isPhpVersionReduction }) + ) t.create('gets the package version of yii') - .get('/php-v/yiisoft/yii.json') - .expectJSONTypes(Joi.object().keys({ name: 'PHP', value: isPhpVersionReduction })); + .get('/php-v/yiisoft/yii.json') + .expectJSONTypes( + Joi.object().keys({ name: 'PHP', value: isPhpVersionReduction }) + ) t.create('invalid package name') - .get('/php-v/frodo/is-not-a-package.json') - .expectJSON({ name: 'PHP', value: 'invalid' }); + .get('/php-v/frodo/is-not-a-package.json') + .expectJSON({ name: 'PHP', value: 'invalid' }) diff --git a/services/twitter/twitter.tester.js b/services/twitter/twitter.tester.js index 7f66416b226490d76ea3d00a5b412cf4701b00f4..0d92607dd1723296b975c185d2d4ff25832ab8d7 100644 --- a/services/twitter/twitter.tester.js +++ b/services/twitter/twitter.tester.js @@ -1,41 +1,45 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') -const { - isMetric -} = require('../test-validators'); +const { isMetric } = require('../test-validators') -const t = new ServiceTester({ id: 'twitter', title: 'Twitter' }); -module.exports = t; +const t = new ServiceTester({ id: 'twitter', title: 'Twitter' }) +module.exports = t t.create('Followers') .get('/follow/shields_io.json') - .expectJSONTypes(Joi.object().keys({ - name: 'Follow @shields_io', - value: isMetric - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'Follow @shields_io', + value: isMetric, + }) + ) t.create('Followers - Custom Label') .get('/follow/shields_io.json?label=Follow') - .expectJSONTypes(Joi.object().keys({ - name: 'Follow', - value: isMetric - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'Follow', + value: isMetric, + }) + ) t.create('Invalid Username Specified') .get('/follow/invalidusernamethatshouldnotexist.json?label=Follow') - .expectJSONTypes(Joi.object().keys({ - name: 'Follow', - value: 'invalid user' - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'Follow', + value: 'invalid user', + }) + ) t.create('No connection') .get('/follow/shields_io.json?label=Follow') .networkOff() - .expectJSON({ name: 'Follow', value: 'inaccessible' }); + .expectJSON({ name: 'Follow', value: 'inaccessible' }) t.create('URL') .get('/url/https/shields.io.json') - .expectJSON({ name: 'tweet', value: '' }); \ No newline at end of file + .expectJSON({ name: 'tweet', value: '' }) diff --git a/services/uptimerobot/uptimerobot.tester.js b/services/uptimerobot/uptimerobot.tester.js index a86b53601283bb8d7c30971afade2ef475424d57..37d4563ec5e80958b0bd69e6ffe166f3f56a76df 100644 --- a/services/uptimerobot/uptimerobot.tester.js +++ b/services/uptimerobot/uptimerobot.tester.js @@ -1,123 +1,138 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') -const isUptimeStatus = Joi.string().regex(/^(paused|not checked yet|up|seems down|down)$/); -const { isPercentage } = require('../test-validators'); -const { invalidJSON } = require('../response-fixtures'); - -const t = new ServiceTester({ id: 'uptimerobot', title: 'Uptime Robot' }); -module.exports = t; +const isUptimeStatus = Joi.string().regex( + /^(paused|not checked yet|up|seems down|down)$/ +) +const { isPercentage } = require('../test-validators') +const { invalidJSON } = require('../response-fixtures') +const t = new ServiceTester({ id: 'uptimerobot', title: 'Uptime Robot' }) +module.exports = t t.create('Uptime Robot: Status (valid)') .get('/status/m778918918-3e92c097147760ee39d02d36.json') - .expectJSONTypes(Joi.object().keys({ - name: 'status', - value: isUptimeStatus, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'status', + value: isUptimeStatus, + }) + ) t.create('Uptime Robot: Status (invalid, correct format)') .get('/status/m777777777-333333333333333333333333.json') - .expectJSON({name: 'status', value: 'api_key not found.'}); + .expectJSON({ name: 'status', value: 'api_key not found.' }) t.create('Uptime Robot: Status (invalid, incorrect format)') .get('/status/not-a-service.json') - .expectJSON({name: 'status', value: 'must use a monitor key'}); + .expectJSON({ name: 'status', value: 'must use a monitor key' }) t.create('Uptime Robot: Status (unspecified error)') .get('/status/m778918918-3e92c097147760ee39d02d36.json') - .intercept(nock => nock('https://api.uptimerobot.com') - .post('/v2/getMonitors') - .reply(200, '{"stat": "fail"}') + .intercept(nock => + nock('https://api.uptimerobot.com') + .post('/v2/getMonitors') + .reply(200, '{"stat": "fail"}') ) - .expectJSON({name: 'status', value: 'vendor error'}); + .expectJSON({ name: 'status', value: 'vendor error' }) t.create('Uptime Robot: Status (connection error)') .get('/status/m778918918-3e92c097147760ee39d02d36.json') .networkOff() - .expectJSON({name: 'status', value: 'inaccessible'}); + .expectJSON({ name: 'status', value: 'inaccessible' }) t.create('Uptime Robot: Status (service unavailable)') .get('/status/m778918918-3e92c097147760ee39d02d36.json') - .intercept(nock => nock('https://api.uptimerobot.com') - .post('/v2/getMonitors') - .reply(503, '{"error": "oh noes!!"}') + .intercept(nock => + nock('https://api.uptimerobot.com') + .post('/v2/getMonitors') + .reply(503, '{"error": "oh noes!!"}') ) - .expectJSON({name: 'status', value: 'inaccessible'}); + .expectJSON({ name: 'status', value: 'inaccessible' }) t.create('Uptime Robot: Status (unexpected response, valid json)') .get('/status/m778918918-3e92c097147760ee39d02d36.json') - .intercept(nock => nock('https://api.uptimerobot.com') - .post('/v2/getMonitors') - .reply(200, "[]") + .intercept(nock => + nock('https://api.uptimerobot.com') + .post('/v2/getMonitors') + .reply(200, '[]') ) - .expectJSON({name: 'status', value: 'invalid'}); + .expectJSON({ name: 'status', value: 'invalid' }) t.create('Uptime Robot: Status (unexpected response, invalid json)') .get('/status/m778918918-3e92c097147760ee39d02d36.json') - .intercept(nock => nock('https://api.uptimerobot.com') - .post('/v2/getMonitors') - .reply(invalidJSON) + .intercept(nock => + nock('https://api.uptimerobot.com') + .post('/v2/getMonitors') + .reply(invalidJSON) ) - .expectJSON({name: 'status', value: 'inaccessible'}); + .expectJSON({ name: 'status', value: 'inaccessible' }) t.create('Uptime Robot: Percentage (valid)') .get('/ratio/m778918918-3e92c097147760ee39d02d36.json') - .expectJSONTypes(Joi.object().keys({ - name: 'uptime', - value: isPercentage, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'uptime', + value: isPercentage, + }) + ) t.create('Uptime Robot: Percentage (valid, with numberOfDays param)') .get('/ratio/7/m778918918-3e92c097147760ee39d02d36.json') - .expectJSONTypes(Joi.object().keys({ - name: 'uptime', - value: isPercentage, - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'uptime', + value: isPercentage, + }) + ) t.create('Uptime Robot: Percentage (invalid, correct format)') .get('/ratio/m777777777-333333333333333333333333.json') - .expectJSON({name: 'uptime', value: 'api_key not found.'}); + .expectJSON({ name: 'uptime', value: 'api_key not found.' }) t.create('Uptime Robot: Percentage (invalid, incorrect format)') .get('/ratio/not-a-service.json') - .expectJSON({name: 'uptime', value: 'must use a monitor key'}); + .expectJSON({ name: 'uptime', value: 'must use a monitor key' }) t.create('Uptime Robot: Percentage (unspecified error)') .get('/ratio/m778918918-3e92c097147760ee39d02d36.json') - .intercept(nock => nock('https://api.uptimerobot.com') - .post('/v2/getMonitors') - .reply(200, '{"stat": "fail"}') + .intercept(nock => + nock('https://api.uptimerobot.com') + .post('/v2/getMonitors') + .reply(200, '{"stat": "fail"}') ) - .expectJSON({name: 'uptime', value: 'vendor error'}); + .expectJSON({ name: 'uptime', value: 'vendor error' }) t.create('Uptime Robot: Percentage (connection error)') .get('/ratio/m778918918-3e92c097147760ee39d02d36.json') .networkOff() - .expectJSON({name: 'uptime', value: 'inaccessible'}); + .expectJSON({ name: 'uptime', value: 'inaccessible' }) t.create('Uptime Robot: Percentage (service unavailable)') .get('/ratio/m778918918-3e92c097147760ee39d02d36.json') - .intercept(nock => nock('https://api.uptimerobot.com') - .post('/v2/getMonitors') - .reply(503, '{"error": "oh noes!!"}') + .intercept(nock => + nock('https://api.uptimerobot.com') + .post('/v2/getMonitors') + .reply(503, '{"error": "oh noes!!"}') ) - .expectJSON({name: 'uptime', value: 'inaccessible'}); + .expectJSON({ name: 'uptime', value: 'inaccessible' }) t.create('Uptime Robot: Percentage (unexpected response, valid json)') .get('/ratio/m778918918-3e92c097147760ee39d02d36.json') - .intercept(nock => nock('https://api.uptimerobot.com') - .post('/v2/getMonitors') - .reply(200, "[]") + .intercept(nock => + nock('https://api.uptimerobot.com') + .post('/v2/getMonitors') + .reply(200, '[]') ) - .expectJSON({name: 'uptime', value: 'invalid'}); + .expectJSON({ name: 'uptime', value: 'invalid' }) t.create('Uptime Robot: Percentage (unexpected response, invalid json)') .get('/ratio/m778918918-3e92c097147760ee39d02d36.json') - .intercept(nock => nock('https://api.uptimerobot.com') - .post('/v2/getMonitors') - .reply(invalidJSON) + .intercept(nock => + nock('https://api.uptimerobot.com') + .post('/v2/getMonitors') + .reply(invalidJSON) ) - .expectJSON({name: 'uptime', value: 'inaccessible'}); + .expectJSON({ name: 'uptime', value: 'inaccessible' }) diff --git a/services/vaadin-directory/vaadin-directory.tester.js b/services/vaadin-directory/vaadin-directory.tester.js index 8f2616686bfa160fd6c09e3846d922a90e316e50..cd970d04f0db59bc94cf26ea81de88bcb48ec952 100644 --- a/services/vaadin-directory/vaadin-directory.tester.js +++ b/services/vaadin-directory/vaadin-directory.tester.js @@ -1,102 +1,129 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') // Get validate function from validator.js lib const { isSemver, isStarRating, - isFormattedDate -} = require('../test-validators'); + isFormattedDate, +} = require('../test-validators') const t = new ServiceTester({ id: 'vaadin-directory', - title: 'Vaadin Directory' -}); -module.exports = t; + title: 'Vaadin Directory', +}) +module.exports = t t.create('stars of component displayed in star icons') .get('/star/vaadinvaadin-grid.json') - .expectJSONTypes(Joi.object().keys({ - name: 'stars', - value: isStarRating - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'stars', + value: isStarRating, + }) + ) t.create('stars of component displayed in star icons') .get('/stars/vaadinvaadin-grid.json') - .expectJSONTypes(Joi.object().keys({ - name: 'stars', - value: isStarRating - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'stars', + value: isStarRating, + }) + ) t.create('publish status of the component') .get('/status/vaadinvaadin-grid.json') - .expectJSONTypes(Joi.object().keys({ - name: 'Vaadin Directory', - value: Joi.equal('published', 'unpublished', 'incomplete', 'reported', 'suspended', 'deleted') - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'Vaadin Directory', + value: Joi.equal( + 'published', + 'unpublished', + 'incomplete', + 'reported', + 'suspended', + 'deleted' + ), + }) + ) t.create('rating of the compoennt (eg: 4.2/5)') .get('/rating/vaadinvaadin-grid.json') - .expectJSONTypes(Joi.object().keys({ - name: 'rating', - value: Joi.string().regex(/^\d\.\d\/5$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'rating', + value: Joi.string().regex(/^\d\.\d\/5$/), + }) + ) t.create('rating count of component') .get('/rc/vaadinvaadin-grid.json') - .expectJSONTypes(Joi.object().keys({ - name: 'rating count', - value: Joi.string().regex(/^\d+?\stotal$/) - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'rating count', + value: Joi.string().regex(/^\d+?\stotal$/), + }) + ) - t.create('rating count of component') - .get('/rating-count/vaadinvaadin-grid.json') - .expectJSONTypes(Joi.object().keys({ +t.create('rating count of component') + .get('/rating-count/vaadinvaadin-grid.json') + .expectJSONTypes( + Joi.object().keys({ name: 'rating count', - value: Joi.string().regex(/^\d+?\stotal$/) - })); + value: Joi.string().regex(/^\d+?\stotal$/), + }) + ) t.create('latest version of the component (can have v prefixed or without)') .get('/v/vaadinvaadin-grid.json') - .expectJSONTypes(Joi.object().keys({ - name: 'latest ver', - value: isSemver - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'latest ver', + value: isSemver, + }) + ) - t.create('latest version of the component (can have v prefixed or without)') - .get('/version/vaadinvaadin-grid.json') - .expectJSONTypes(Joi.object().keys({ +t.create('latest version of the component (can have v prefixed or without)') + .get('/version/vaadinvaadin-grid.json') + .expectJSONTypes( + Joi.object().keys({ name: 'latest ver', - value: isSemver - })); + value: isSemver, + }) + ) t.create('latest release date of the component (format: yyyy-mm-dd)') .get('/rd/vaadinvaadin-grid.json') - .expectJSONTypes(Joi.object().keys({ - name: 'latest release date', - value: isFormattedDate - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'latest release date', + value: isFormattedDate, + }) + ) - t.create('latest release date of the component (format: yyyy-mm-dd)') - .get('/release-date/vaadinvaadin-grid.json') - .expectJSONTypes(Joi.object().keys({ +t.create('latest release date of the component (format: yyyy-mm-dd)') + .get('/release-date/vaadinvaadin-grid.json') + .expectJSONTypes( + Joi.object().keys({ name: 'latest release date', - value: isFormattedDate - })); + value: isFormattedDate, + }) + ) t.create('Invalid addon') .get('/stars/404.json') .expectJSON({ name: 'Vaadin Directory', - value: 'not found' - }); + value: 'not found', + }) t.create('No connection') .get('/stars/vaadinvaadin-grid.json') .networkOff() .expectJSON({ name: 'Vaadin Directory', - value: 'inaccessible' - }); + value: 'inaccessible', + }) diff --git a/services/vscode-marketplace/vscode-marketplace.tester.js b/services/vscode-marketplace/vscode-marketplace.tester.js index 36a246ad6ff76f8f78861a276e6d4f59456017f9..58054cfa60920e7b95f53002c7285caf34f8021f 100644 --- a/services/vscode-marketplace/vscode-marketplace.tester.js +++ b/services/vscode-marketplace/vscode-marketplace.tester.js @@ -1,70 +1,89 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') const { isVPlusTripleDottedVersion, isMetric, - isStarRating -} = require('../test-validators'); + isStarRating, +} = require('../test-validators') -const isVscodeRating = Joi.string().regex(/[0-5].[0-9]{2}\/5?\s*\([0-9]*\)$/); +const isVscodeRating = Joi.string().regex(/[0-5].[0-9]{2}\/5?\s*\([0-9]*\)$/) -const t = new ServiceTester({ id: 'vscode-marketplace', title: 'VS Code Marketplace' }); -module.exports = t; +const t = new ServiceTester({ + id: 'vscode-marketplace', + title: 'VS Code Marketplace', +}) +module.exports = t t.create('Downloads') .get('/d/ritwickdey.LiveServer.json') - .expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetric - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetric, + }) + ) t.create('Downloads | User specified label') .get('/d/ritwickdey.LiveServer.json?label=Total Installs') - .expectJSONTypes(Joi.object().keys({ - name: 'Total Installs', - value: isMetric - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'Total Installs', + value: isMetric, + }) + ) t.create('Rating') .get('/r/ritwickdey.LiveServer.json') - .expectJSONTypes(Joi.object().keys({ - name: 'rating', - value: isVscodeRating - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'rating', + value: isVscodeRating, + }) + ) t.create('Rating | User specified label') .get('/r/ritwickdey.LiveServer.json?label=My custom rating label') - .expectJSONTypes(Joi.object().keys({ - name: 'My custom rating label', - value: isVscodeRating - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'My custom rating label', + value: isVscodeRating, + }) + ) t.create('Star Rating') .get('/stars/ritwickdey.LiveServer.json') - .expectJSONTypes(Joi.object().keys({ - name: 'rating', - value: isStarRating - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'rating', + value: isStarRating, + }) + ) t.create('Star Rating | User specified label') .get('/stars/ritwickdey.LiveServer.json?label=My custom rating label') - .expectJSONTypes(Joi.object().keys({ - name: 'My custom rating label', - value: isStarRating - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'My custom rating label', + value: isStarRating, + }) + ) t.create('Version') .get('/v/ritwickdey.LiveServer.json') - .expectJSONTypes(Joi.object().keys({ - name: 'visual studio marketplace', - value: isVPlusTripleDottedVersion - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'visual studio marketplace', + value: isVPlusTripleDottedVersion, + }) + ) t.create('Version | User specified label') .get('/v/ritwickdey.LiveServer.json?label=VSM') - .expectJSONTypes(Joi.object().keys({ - name: 'VSM', - value: isVPlusTripleDottedVersion - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'VSM', + value: isVPlusTripleDottedVersion, + }) + ) diff --git a/services/waffle/waffle.tester.js b/services/waffle/waffle.tester.js index 0fc5d735b61364c0bfee4430b182ef43c22c0800..f1431f0a89d0761c1814c20e1a97bf480440632c 100644 --- a/services/waffle/waffle.tester.js +++ b/services/waffle/waffle.tester.js @@ -1,90 +1,101 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); -const { invalidJSON } = require('../response-fixtures'); - -const t = new ServiceTester({ id: 'waffle', title: 'Waffle.io' }); -module.exports = t; +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { invalidJSON } = require('../response-fixtures') +const t = new ServiceTester({ id: 'waffle', title: 'Waffle.io' }) +module.exports = t const fakeData = [ { - "label": null, - "count": 20 + label: null, + count: 20, }, { - "count": 10 + count: 10, }, { - "label": { - "color": "c5def5", - "name": "feature" + label: { + color: 'c5def5', + name: 'feature', }, - "count": 3 + count: 3, }, { - "label": { - "name": "bug", - "color": "fbca04" + label: { + name: 'bug', + color: 'fbca04', }, - "count": 5 - } -]; - + count: 5, + }, +] -t.create('label should be `bug` & value should be exactly 5 as supplied in `fakeData`. e.g: bug|5') +t.create( + 'label should be `bug` & value should be exactly 5 as supplied in `fakeData`. e.g: bug|5' +) .get('/label/userName/repoName/bug.json?style=_shields_test') - .intercept(nock => nock('https://api.waffle.io/') - .get('/userName/repoName/columns?with=count') - .reply(200, fakeData)) + .intercept(nock => + nock('https://api.waffle.io/') + .get('/userName/repoName/columns?with=count') + .reply(200, fakeData) + ) .expectJSON({ name: 'bug', value: '5', colorB: '#fbca04', - }); + }) t.create('label should be `Mybug` & value should be formated. e.g: Mybug|25') .get('/label/ritwickdey/vscode-live-server/bug.json?label=Mybug') - .expectJSONTypes(Joi.object().keys({ - name: 'Mybug', - value: Joi.number().integer().positive() - })); + .expectJSONTypes( + Joi.object().keys({ + name: 'Mybug', + value: Joi.number() + .integer() + .positive(), + }) + ) t.create('label (repo not found)') .get('/label/not-a-user/not-a-repo/bug.json') .expectJSON({ name: 'waffle', value: 'not found', - }); + }) t.create('label (label not found)') - .get('/label/ritwickdey/vscode-live-server/not-a-real-label.json?style=_shields_test') + .get( + '/label/ritwickdey/vscode-live-server/not-a-real-label.json?style=_shields_test' + ) .expectJSON({ name: 'not-a-real-label', value: '0', colorB: '#78bdf2', - }); + }) t.create('label (empty response)') .get('/label/userName/repoName/bug.json') - .intercept(nock => nock('https://api.waffle.io/') - .get('/userName/repoName/columns?with=count') - .reply(200, [])) + .intercept(nock => + nock('https://api.waffle.io/') + .get('/userName/repoName/columns?with=count') + .reply(200, []) + ) .expectJSON({ name: 'waffle', value: 'absent', - }); + }) t.create('label (connection error)') .get('/label/ritwickdey/vscode-live-server/bug.json') .networkOff() - .expectJSON({name: 'waffle', value: 'inaccessible'}); + .expectJSON({ name: 'waffle', value: 'inaccessible' }) t.create('label (unexpected response)') .get('/label/userName/repoName/bug.json') - .intercept(nock => nock('https://api.waffle.io/') - .get('/userName/repoName/columns?with=count') - .reply(invalidJSON) + .intercept(nock => + nock('https://api.waffle.io/') + .get('/userName/repoName/columns?with=count') + .reply(invalidJSON) ) - .expectJSON({name: 'waffle', value: 'invalid'}); + .expectJSON({ name: 'waffle', value: 'invalid' }) diff --git a/services/website/website.tester.js b/services/website/website.tester.js index ee1db11f5c6bc0b82efa0968fb16ce68cdae367d..8809436848161899ed0a9db04e3bee0976fc965f 100644 --- a/services/website/website.tester.js +++ b/services/website/website.tester.js @@ -1,40 +1,46 @@ -'use strict'; +'use strict' -const ServiceTester = require('../service-tester'); -const colorscheme = require('../../lib/colorscheme.json'); -const mapValues = require('lodash.mapvalues'); +const ServiceTester = require('../service-tester') +const colorscheme = require('../../lib/colorscheme.json') +const mapValues = require('lodash.mapvalues') -const t = new ServiceTester({ id: 'website', title: 'website' }); -module.exports = t; -const colorsB = mapValues(colorscheme, 'colorB'); +const t = new ServiceTester({ id: 'website', title: 'website' }) +module.exports = t +const colorsB = mapValues(colorscheme, 'colorB') t.create('status of http://shields.io') .get('/http/shields.io.json?style=_shields_test') - .expectJSON({ name: 'website', value: 'online', colorB: colorsB.brightgreen }); + .expectJSON({ name: 'website', value: 'online', colorB: colorsB.brightgreen }) t.create('status of https://shields.io') .get('/https/shields.io.json?style=_shields_test') - .expectJSON({ name: 'website', value: 'online', colorB: colorsB.brightgreen }); + .expectJSON({ name: 'website', value: 'online', colorB: colorsB.brightgreen }) t.create('status of nonexistent domain') .get('/https/shields-io.io.json?style=_shields_test') - .expectJSON({ name: 'website', value: 'offline', colorB: colorsB.red }); + .expectJSON({ name: 'website', value: 'offline', colorB: colorsB.red }) t.create('status when network is off') .get('/http/shields.io.json?style=_shields_test') .networkOff() - .expectJSON({ name: 'website', value: 'offline', colorB: colorsB.red }); + .expectJSON({ name: 'website', value: 'offline', colorB: colorsB.red }) t.create('custom online label, online message and online color') - .get('-up-down-green-grey/http/online.com.json?style=_shields_test&label=homepage') - .intercept(nock => nock('http://online.com') - .head('/') - .reply(200)) - .expectJSON({ name: 'homepage', value: 'up', colorB: colorsB.green }); + .get( + '-up-down-green-grey/http/online.com.json?style=_shields_test&label=homepage' + ) + .intercept(nock => + nock('http://online.com') + .head('/') + .reply(200) + ) + .expectJSON({ name: 'homepage', value: 'up', colorB: colorsB.green }) t.create('custom offline message and offline color') .get('-up-down-green-grey/http/offline.com.json?style=_shields_test') - .intercept(nock => nock('http://offline.com') - .head('/') - .reply(500)) - .expectJSON({ name: 'website', value: 'down', colorB: colorsB.grey }); + .intercept(nock => + nock('http://offline.com') + .head('/') + .reply(500) + ) + .expectJSON({ name: 'website', value: 'down', colorB: colorsB.grey }) diff --git a/services/wordpress/wordpress.tester.js b/services/wordpress/wordpress.tester.js index ef6abfc567ae1af11f8323c9c87d0be7771f591e..175bb1fe8c624b21cc405a8c69397f1da1057682 100644 --- a/services/wordpress/wordpress.tester.js +++ b/services/wordpress/wordpress.tester.js @@ -1,54 +1,66 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const ServiceTester = require('../service-tester'); +const Joi = require('joi') +const ServiceTester = require('../service-tester') const { isMetric, isStarRating, - isVPlusDottedVersionAtLeastOne -} = require('../test-validators'); + isVPlusDottedVersionAtLeastOne, +} = require('../test-validators') -const t = new ServiceTester({ id: 'wordpress', title: 'Wordpress' }); -module.exports = t; +const t = new ServiceTester({ id: 'wordpress', title: 'Wordpress' }) +module.exports = t t.create('supported version') -.get('/v/akismet.json') -.expectJSONTypes(Joi.object().keys({ - name: 'wordpress', - value: Joi.string().regex(/^\d+(\.\d+)?(\.\d+)? tested$/) -})); + .get('/v/akismet.json') + .expectJSONTypes( + Joi.object().keys({ + name: 'wordpress', + value: Joi.string().regex(/^\d+(\.\d+)?(\.\d+)? tested$/), + }) + ) t.create('plugin version') -.get('/plugin/v/akismet.json') -.expectJSONTypes(Joi.object().keys({ - name: 'plugin', - value: isVPlusDottedVersionAtLeastOne -})); + .get('/plugin/v/akismet.json') + .expectJSONTypes( + Joi.object().keys({ + name: 'plugin', + value: isVPlusDottedVersionAtLeastOne, + }) + ) t.create('plugin rating') -.get('/plugin/r/akismet.json') -.expectJSONTypes(Joi.object().keys({ - name: 'rating', - value: isStarRating -})); + .get('/plugin/r/akismet.json') + .expectJSONTypes( + Joi.object().keys({ + name: 'rating', + value: isStarRating, + }) + ) t.create('plugin downloads') -.get('/plugin/dt/akismet.json') -.expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetric -})); + .get('/plugin/dt/akismet.json') + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetric, + }) + ) t.create('theme rating') -.get('/theme/r/hestia.json') -.expectJSONTypes(Joi.object().keys({ - name: 'rating', - value: isStarRating -})); + .get('/theme/r/hestia.json') + .expectJSONTypes( + Joi.object().keys({ + name: 'rating', + value: isStarRating, + }) + ) t.create('theme downloads') -.get('/theme/dt/hestia.json') -.expectJSONTypes(Joi.object().keys({ - name: 'downloads', - value: isMetric -})); + .get('/theme/dt/hestia.json') + .expectJSONTypes( + Joi.object().keys({ + name: 'downloads', + value: isMetric, + }) + ) diff --git a/services/xml/xml.tester.js b/services/xml/xml.tester.js index 429fd25ebfb7f72393e67b54a3713edc7848f774..9d00ea8ada56c37741713fa239149b8cb8290cdd 100644 --- a/services/xml/xml.tester.js +++ b/services/xml/xml.tester.js @@ -1,104 +1,187 @@ -'use strict'; +'use strict' -const Joi = require('joi'); -const { expect } = require('chai'); -const ServiceTester = require('../service-tester'); -const { - isSemver, -} = require('../test-validators'); +const Joi = require('joi') +const { expect } = require('chai') +const ServiceTester = require('../service-tester') +const { isSemver } = require('../test-validators') -const colorscheme = require('../../lib/colorscheme.json'); -const mapValues = require('lodash.mapvalues'); +const colorscheme = require('../../lib/colorscheme.json') +const mapValues = require('lodash.mapvalues') -const colorsB = mapValues(colorscheme, 'colorB'); +const colorsB = mapValues(colorscheme, 'colorB') -const t = new ServiceTester({ id: 'dynamic-xml', title: 'User Defined XML Source Data', pathPrefix: '/badge/dynamic/xml' }); -module.exports = t; +const t = new ServiceTester({ + id: 'dynamic-xml', + title: 'User Defined XML Source Data', + pathPrefix: '/badge/dynamic/xml', +}) +module.exports = t t.create('Connection error') - .get('.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/name&label=Package Name&style=_shields_test') + .get( + '.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/name&label=Package Name&style=_shields_test' + ) .networkOff() - .expectJSON({ name: 'Package Name', value: 'inaccessible', colorB: colorsB.red }); + .expectJSON({ + name: 'Package Name', + value: 'inaccessible', + colorB: colorsB.red, + }) t.create('No URL specified') .get('.json?query=//name&label=Package Name&style=_shields_test') - .expectJSON({ name: 'Package Name', value: 'no url specified', colorB: colorsB.red }); + .expectJSON({ + name: 'Package Name', + value: 'no url specified', + colorB: colorsB.red, + }) t.create('No query specified') - .get('.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&label=Package Name&style=_shields_test') - .expectJSON({ name: 'Package Name', value: 'no query specified', colorB: colorsB.red }); + .get( + '.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&label=Package Name&style=_shields_test' + ) + .expectJSON({ + name: 'Package Name', + value: 'no query specified', + colorB: colorsB.red, + }) t.create('XML from url') - .get('.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/name&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'IndieGala Helper', colorB: colorsB.brightgreen }); + .get( + '.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/name&style=_shields_test' + ) + .expectJSON({ + name: 'custom badge', + value: 'IndieGala Helper', + colorB: colorsB.brightgreen, + }) t.create('XML from uri (support uri query paramater)') - .get('.json?uri=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/name&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'IndieGala Helper', colorB: colorsB.brightgreen }); + .get( + '.json?uri=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/name&style=_shields_test' + ) + .expectJSON({ + name: 'custom badge', + value: 'IndieGala Helper', + colorB: colorsB.brightgreen, + }) t.create('XML from url (attribute)') - .get('.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/reviews/@num') - .expectJSONTypes(Joi.object().keys({ - name: 'custom badge', - value: Joi.string().regex(/^\d+$/) - })); + .get( + '.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/reviews/@num' + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'custom badge', + value: Joi.string().regex(/^\d+$/), + }) + ) t.create('XML from url | multiple results') - .get('.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/compatible_applications/application/name') - .expectJSONTypes(Joi.object().keys({ - name: 'custom badge', - value: Joi.string().regex(/^Firefox( for Android)?,\sFirefox( for Android)?$/) - })); + .get( + '.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/compatible_applications/application/name' + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'custom badge', + value: Joi.string().regex( + /^Firefox( for Android)?,\sFirefox( for Android)?$/ + ), + }) + ) t.create('XML from url | caching with new query params') - .get('.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/version') - .expectJSONTypes(Joi.object().keys({ - name: 'custom badge', - value: isSemver - })); + .get( + '.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/version' + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'custom badge', + value: isSemver, + }) + ) t.create('XML from url | with prefix & suffix & label') - .get('.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=//version&prefix=v&suffix= dev&label=IndieGala Helper') - .expectJSONTypes(Joi.object().keys({ - name: 'IndieGala Helper', - value: Joi.string().regex(/^v\d+(\.\d+)?(\.\d+)?\sdev$/) - })); + .get( + '.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=//version&prefix=v&suffix= dev&label=IndieGala Helper' + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'IndieGala Helper', + value: Joi.string().regex(/^v\d+(\.\d+)?(\.\d+)?\sdev$/), + }) + ) t.create('XML from url | query doesnt exist') - .get('.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/does/not/exist&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'no result', colorB: colorsB.lightgrey }); + .get( + '.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/does/not/exist&style=_shields_test' + ) + .expectJSON({ + name: 'custom badge', + value: 'no result', + colorB: colorsB.lightgrey, + }) t.create('XML from url | query doesnt exist (attribute)') - .get('.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/does/not/@exist&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'no result', colorB: colorsB.lightgrey }); + .get( + '.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/does/not/@exist&style=_shields_test' + ) + .expectJSON({ + name: 'custom badge', + value: 'no result', + colorB: colorsB.lightgrey, + }) t.create('XML from url | invalid url') - .get('.json?url=https://github.com/badges/shields/raw/master/notafile.xml&query=//version&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'resource not found', colorB: colorsB.lightgrey }); + .get( + '.json?url=https://github.com/badges/shields/raw/master/notafile.xml&query=//version&style=_shields_test' + ) + .expectJSON({ + name: 'custom badge', + value: 'resource not found', + colorB: colorsB.lightgrey, + }) t.create('XML from url | user color overrides default') - .get('.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/name&colorB=10ADED&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'IndieGala Helper', colorB: '#10ADED' }); + .get( + '.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/name&colorB=10ADED&style=_shields_test' + ) + .expectJSON({ + name: 'custom badge', + value: 'IndieGala Helper', + colorB: '#10ADED', + }) t.create('XML from url | error color overrides default') - .get('.json?url=https://github.com/badges/shields/raw/master/notafile.xml&query=//version&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'resource not found', colorB: colorsB.lightgrey }); + .get( + '.json?url=https://github.com/badges/shields/raw/master/notafile.xml&query=//version&style=_shields_test' + ) + .expectJSON({ + name: 'custom badge', + value: 'resource not found', + colorB: colorsB.lightgrey, + }) t.create('XML from url | error color overrides user specified') .get('.json?query=//version&colorB=10ADED&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'no url specified', colorB: colorsB.red }); + .expectJSON({ + name: 'custom badge', + value: 'no url specified', + colorB: colorsB.red, + }) -let headers; +let headers t.create('XML from url | request should set Accept header') .get('.json?url=https://xml-test/api.xml&query=/name') - .intercept(nock => nock('https://xml-test') - .get('/api.xml') - .reply(200, function (uri, requestBody) { - headers = this.req.headers; - return '<?xml version="1.0" encoding="utf-8" ?><name>dynamic xml</name>'; - }) + .intercept(nock => + nock('https://xml-test') + .get('/api.xml') + .reply(200, function(uri, requestBody) { + headers = this.req.headers + return '<?xml version="1.0" encoding="utf-8" ?><name>dynamic xml</name>' + }) ) .expectJSON({ name: 'custom badge', value: 'dynamic xml' }) - .after(function () { - expect(headers).to.have.property('accept', 'application/xml, text/xml'); - }); + .after(function() { + expect(headers).to.have.property('accept', 'application/xml, text/xml') + }) diff --git a/services/yaml/yaml.tester.js b/services/yaml/yaml.tester.js index 8606c4f34a3cb4c8ec751e4de9e02ef6fa7714f3..fff75272a22ab506f77d430732cff57a89381e63 100644 --- a/services/yaml/yaml.tester.js +++ b/services/yaml/yaml.tester.js @@ -1,63 +1,125 @@ -'use strict'; +'use strict' -const ServiceTester = require('../service-tester'); -const colorscheme = require('../../lib/colorscheme.json'); -const mapValues = require('lodash.mapvalues'); +const ServiceTester = require('../service-tester') +const colorscheme = require('../../lib/colorscheme.json') +const mapValues = require('lodash.mapvalues') -const colorsB = mapValues(colorscheme, 'colorB'); +const colorsB = mapValues(colorscheme, 'colorB') -const t = new ServiceTester({ id: 'dynamic-yaml', title: 'User Defined YAML Source Data', pathPrefix: '/badge/dynamic/yaml' }); -module.exports = t; +const t = new ServiceTester({ + id: 'dynamic-yaml', + title: 'User Defined YAML Source Data', + pathPrefix: '/badge/dynamic/yaml', +}) +module.exports = t t.create('Connection error') - .get('.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.name&label=Package Name&style=_shields_test') + .get( + '.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.name&label=Package Name&style=_shields_test' + ) .networkOff() - .expectJSON({ name: 'Package Name', value: 'inaccessible', colorB: colorsB.red }); + .expectJSON({ + name: 'Package Name', + value: 'inaccessible', + colorB: colorsB.red, + }) t.create('No URL specified') .get('.json?query=$.name&label=Package Name&style=_shields_test') - .expectJSON({ name: 'Package Name', value: 'no url specified', colorB: colorsB.red }); + .expectJSON({ + name: 'Package Name', + value: 'no url specified', + colorB: colorsB.red, + }) t.create('No query specified') - .get('.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&label=Package Name&style=_shields_test') - .expectJSON({ name: 'Package Name', value: 'no query specified', colorB: colorsB.red }); + .get( + '.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&label=Package Name&style=_shields_test' + ) + .expectJSON({ + name: 'Package Name', + value: 'no query specified', + colorB: colorsB.red, + }) t.create('YAML from url') - .get('.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.name&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'coredns', colorB: colorsB.brightgreen }); + .get( + '.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.name&style=_shields_test' + ) + .expectJSON({ + name: 'custom badge', + value: 'coredns', + colorB: colorsB.brightgreen, + }) t.create('YAML from uri (support uri query paramater)') - .get('.json?uri=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.name&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'coredns', colorB: colorsB.brightgreen }); + .get( + '.json?uri=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.name&style=_shields_test' + ) + .expectJSON({ + name: 'custom badge', + value: 'coredns', + colorB: colorsB.brightgreen, + }) t.create('YAML from url | multiple results') - .get('.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$..keywords[0:2:1]') - .expectJSON({ name: 'custom badge', value: 'coredns, dns' }); + .get( + '.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$..keywords[0:2:1]' + ) + .expectJSON({ name: 'custom badge', value: 'coredns, dns' }) t.create('YAML from url | caching with new query params') - .get('.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.version') - .expectJSON({ name: 'custom badge', value: '0.8.0' }); + .get( + '.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.version' + ) + .expectJSON({ name: 'custom badge', value: '0.8.0' }) t.create('YAML from url | with prefix & suffix & label') - .get('.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.version&prefix=v&suffix= dev&label=Shields') - .expectJSON({ name: 'Shields', value: 'v0.8.0 dev' }); + .get( + '.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.version&prefix=v&suffix= dev&label=Shields' + ) + .expectJSON({ name: 'Shields', value: 'v0.8.0 dev' }) t.create('YAML from url | object doesnt exist') - .get('.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.does_not_exist&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'no result', colorB: colorsB.lightgrey }); + .get( + '.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.does_not_exist&style=_shields_test' + ) + .expectJSON({ + name: 'custom badge', + value: 'no result', + colorB: colorsB.lightgrey, + }) t.create('YAML from url | invalid url') - .get('.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/notafile.yaml&query=$.version&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'resource not found', colorB: colorsB.lightgrey }); + .get( + '.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/notafile.yaml&query=$.version&style=_shields_test' + ) + .expectJSON({ + name: 'custom badge', + value: 'resource not found', + colorB: colorsB.lightgrey, + }) t.create('YAML from url | user color overrides default') - .get('.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.name&colorB=10ADED&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'coredns', colorB: '#10ADED' }); + .get( + '.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.name&colorB=10ADED&style=_shields_test' + ) + .expectJSON({ name: 'custom badge', value: 'coredns', colorB: '#10ADED' }) t.create('YAML from url | error color overrides default') - .get('.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/notafile.yaml&query=$.version&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'resource not found', colorB: colorsB.lightgrey }); + .get( + '.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/notafile.yaml&query=$.version&style=_shields_test' + ) + .expectJSON({ + name: 'custom badge', + value: 'resource not found', + colorB: colorsB.lightgrey, + }) t.create('YAML from url | error color overrides user specified') .get('.json?query=$.version&colorB=10ADED&style=_shields_test') - .expectJSON({ name: 'custom badge', value: 'no url specified', colorB: colorsB.red }); + .expectJSON({ + name: 'custom badge', + value: 'no url specified', + colorB: colorsB.red, + })