diff --git a/package.json b/package.json index 25b406f..98aa914 100644 --- a/package.json +++ b/package.json @@ -84,5 +84,5 @@ } } }, - "version": "0.0.0-development" + "version": "1.0.0-beta1" } diff --git a/src/connector.js b/src/connector.js index 3bc78f0..965d5d4 100644 --- a/src/connector.js +++ b/src/connector.js @@ -7,4 +7,4 @@ export default class NumbersConnector extends GraphQLConnector { this.headers['Content-Type'] = 'application/json'; this.apiBaseUri = `http://numbersapi.com`; } -} \ No newline at end of file +} diff --git a/src/index.js b/src/index.js index e06b7ea..d82aadf 100644 --- a/src/index.js +++ b/src/index.js @@ -10,7 +10,7 @@ import mocks from './mocks'; */ export default { namespace: 'Numbers', - context: new Model({ connector: new Connector() }), + context: { model: new Model({ connector: new Connector() }) }, typeDefs, resolvers, mocks, diff --git a/src/mocks.js b/src/mocks.js index cdf03dd..99842f3 100644 --- a/src/mocks.js +++ b/src/mocks.js @@ -1,4 +1,3 @@ -import { MockList } from 'graphql-tools'; import casual from 'casual'; export default { @@ -8,6 +7,6 @@ export default { found: casual.boolean, number: casual.number, type: casual.random_element(['trivia', 'date', 'math', 'year']), - date: casual.date('mmm D') + date: casual.date('mmm D'), }), }; diff --git a/src/model.js b/src/model.js index 02dbfda..48a483b 100644 --- a/src/model.js +++ b/src/model.js @@ -2,15 +2,13 @@ import { GrampsError } from '@gramps/errors'; import { GraphQLModel } from '@gramps/rest-helpers'; export default class NumbersModel extends GraphQLModel { - async get(number, type) { - console.log(await this.connector.get(`/${number}/${type}`)) return this.connector.get(`/${number}/${type}`).catch(res => this.throwError(res.error, { description: 'Could not get the info', }), ); - } + } /** * Throws a custom GrAMPS error. @@ -32,9 +30,9 @@ export default class NumbersModel extends GraphQLModel { errorCode: `${this.constructor.name}_Error`, description: message, graphqlModel: this.constructor.name, - docsLink: 'https://ibm.biz/gramps-data-source-tutorial', + docsLink: 'https://gramps.js.org/data-source/data-source-overview/', }; - throw GrampsError(Object.assign({defaults}, {customErrorData})); + throw GrampsError(Object.assign({ defaults }, { customErrorData })); } -} \ No newline at end of file +} diff --git a/src/resolvers.js b/src/resolvers.js index 988f8c0..5b0a241 100644 --- a/src/resolvers.js +++ b/src/resolvers.js @@ -1,10 +1,12 @@ export default { Query: { - // TODO: Update query resolver name and args to match the schema - // TODO: Update the context method to load the correct data - trivia: (_, { number }, context) => context.get(number, 'trivia'), - date: (_, { date }, context) => context.get(date, 'date'), - math: (_, { number }, context) => context.get(number, 'math'), - year: (_, { number }, context) => context.get(number, 'year'), - } + trivia: (_, { number }, context) => context.model.get(number, 'trivia'), + date: (_, { date }, context) => context.model.get(date, 'date'), + math: (_, { number }, context) => context.model.get(number, 'math'), + year: (_, { number }, context) => context.model.get(number, 'year'), + }, + Numbers_Trivia: { + date: data => data.date || null, + year: data => data.year || null, + }, }; diff --git a/src/schema.graphql b/src/schema.graphql index c003a91..c253bde 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -1,14 +1,34 @@ type Query { - trivia(number: Int): Numbers_Trivia - date(date: String): Numbers_Trivia - math(number: Int): Numbers_Trivia - year(number: Int): Numbers_Trivia + trivia(number: Int!): Numbers_Trivia! + + """ + Load trivia for a data using the `MM/DD` format. + + For example, to load trivia for January 14th: + + `date(date: "1/14") { text }` + """ + date(date: String!): Numbers_Trivia! + math(number: Int!): Numbers_Trivia! + year(number: Int!): Numbers_Trivia! } type Numbers_Trivia { - text: String - found: Boolean - number: Int - type: String + "A string of the fact text itself." + text: String! + + "Boolean of whether there was a fact for the requested number." + found: Boolean! + + "The number that was queried." + number: Int! + + "String of the category of the returned fact." + type: String! + + "If relevant, a day of year associated with some year facts, as a string." date: String + + "If relevant, a year associated with some date facts, as a string." + year: String } diff --git a/test/connector.test.js b/test/connector.test.js new file mode 100644 index 0000000..9a60603 --- /dev/null +++ b/test/connector.test.js @@ -0,0 +1,14 @@ +import { GraphQLConnector } from '@gramps/rest-helpers'; +import Connector from '../src/connector'; + +const connector = new Connector(); + +describe(`NumbersConnector`, () => { + it('inherits the GraphQLConnector class', () => { + expect(connector).toBeInstanceOf(GraphQLConnector); + }); + + it('uses the appropriate URL', () => { + expect(connector.apiBaseUri).toBe(`http://numbersapi.com`); + }); +}); diff --git a/test/context.test.js b/test/context.test.js deleted file mode 100644 index 672ed60..0000000 --- a/test/context.test.js +++ /dev/null @@ -1,13 +0,0 @@ -import context from '../src/context'; - -describe('Data Source Context', () => { - describe('getById()', () => { - it('loads data by its ID', () => { - expect(context.getById(123)).toEqual({ - id: 123, - name: 'GrAMPS GraphQL Data Source Base', - lucky_numbers: [1, 2, 3, 5, 8, 13, 21], - }); - }); - }); -}); diff --git a/test/index.test.js b/test/index.test.js index ee77a8e..0474075 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -1,9 +1,9 @@ import dataSource from '../src'; // TODO: Update the data source name. -describe(`Data Source: DataSourceBase`, () => { +describe(`Data Source: Numbers`, () => { it('contains a namespace property', () => { - expect(dataSource.namespace).toBe('DataSourceBase'); + expect(dataSource.namespace).toBe('Numbers'); }); it('contains a context property', () => { diff --git a/test/mocks.test.js b/test/mocks.test.js index 5a2bf9a..f84a63d 100644 --- a/test/mocks.test.js +++ b/test/mocks.test.js @@ -1,15 +1,17 @@ import expectMockFields from './helpers/expectMockFields'; -import expectMockList from './helpers/expectMockList'; import mocks from '../src/mocks'; describe('mock resolvers', () => { - describe('PFX_DataSourceBase', () => { - const mockResolvers = mocks.PFX_DataSourceBase(); + describe('Numbers_Trivia', () => { + const mockResolvers = mocks.Numbers_Trivia(); // This helper creates a test to ensure each field has a mock resolver. - expectMockFields(mockResolvers, ['id', 'name', 'lucky_numbers']); - - // This helper creates a test to check that these fields return `MockList`s. - expectMockList(mockResolvers, ['lucky_numbers']); + expectMockFields(mockResolvers, [ + 'text', + 'found', + 'number', + 'type', + 'date', + ]); }); }); diff --git a/test/model.test.js b/test/model.test.js new file mode 100644 index 0000000..8358aba --- /dev/null +++ b/test/model.test.js @@ -0,0 +1,95 @@ +import { GraphQLModel } from '@gramps/rest-helpers'; +import Model from '../src/model'; +import Connector from '../src/connector'; + +// Mock the connector because we’re only testing the model here. +jest.mock('../src/connector', () => + jest.fn(() => ({ + get: jest.fn(() => Promise.resolve()), + apiBaseUri: 'https://example.org', + })), +); + +const DATA_SOURCE_NAME = 'Numbers'; + +const connector = new Connector(); +const model = new Model({ connector }); + +describe(`${DATA_SOURCE_NAME}Model`, () => { + it('inherits the GraphQLModel class', () => { + expect(model).toBeInstanceOf(GraphQLModel); + }); + + describe('get()', () => { + it('calls the correct endpoint to load the number trivia', () => { + const spy = jest.spyOn(connector, 'get'); + + model.get(1234, 'trivia'); + expect(spy).toHaveBeenCalledWith('/1234/trivia'); + }); + + it('throws a GrampsError if something goes wrong', async () => { + expect.assertions(1); + + model.connector.get.mockImplementationOnce(() => + Promise.reject(Error('boom')), + ); + + try { + await model.get(1, 'trivia'); + } catch (error) { + expect(error.isBoom).toEqual(true); + } + }); + }); + + describe.skip('throwError()', () => { + const mockError = { + statusCode: 401, + }; + + it('converts an error from the endpoint into a GrampsError', async () => { + expect.assertions(4); + + /* + * To simulate a failed call, we tell Jest to return a rejected Promise + * with our mock error. + */ + model.connector.get.mockImplementationOnce(() => + Promise.reject(mockError), + ); + + try { + await model.get(1234, 'trivia'); + } catch (error) { + // Check that GrampsError properly received the error detail. + expect(error).toHaveProperty('isBoom', true); + expect(error.output).toHaveProperty('statusCode', 401); + expect(error.output.payload).toHaveProperty( + 'targetEndpoint', + 'https://example.org/1234/info.0.json', + ); + expect(error.output.payload).toHaveProperty( + 'graphqlModel', + `${DATA_SOURCE_NAME}Model`, + ); + } + }); + + it('creates a default GrampsError if no custom error data is supplied', async () => { + try { + await model.throwError({}); + } catch (error) { + expect(error.output.statusCode).toBe(500); + expect(error.output.payload.errorCode).toBe( + `${DATA_SOURCE_NAME}Model_Error`, + ); + expect(error.output.payload.description).toBe('Something went wrong.'); + expect(error.output.payload.graphqlModel).toBe( + `${DATA_SOURCE_NAME}Model`, + ); + expect(error.output.payload.targetEndpoint).toBeNull(); + } + }); + }); +}); diff --git a/test/resolvers.test.js b/test/resolvers.test.js index 2e862e2..46f58cb 100644 --- a/test/resolvers.test.js +++ b/test/resolvers.test.js @@ -2,25 +2,57 @@ import resolvers from '../src/resolvers'; import expectNullable from './helpers/expectNullable'; describe('Data Source Resolvers', () => { - describe('query resolvers', () => { - describe('getById()', () => { - it('loads a thing by its ID', () => { + describe('Query', () => { + const mockContext = { + model: { + get: (val, type) => Promise.resolve({ [type]: val }), + }, + }; + + describe('trivia', () => { + test('loads trivia for a number', () => { + expect.assertions(1); + + return expect( + resolvers.Query.trivia({}, { number: 123 }, mockContext), + ).resolves.toEqual({ trivia: 123 }); + }); + }); + + describe('date', () => { + test('loads trivia for a date', () => { + expect.assertions(1); + + return expect( + resolvers.Query.date({}, { date: '2017-08' }, mockContext), + ).resolves.toEqual({ date: '2017-08' }); + }); + }); + + describe('math', () => { + test('loads math-related trivia for a number', () => { expect.assertions(1); - const mockContext = { - getById: id => Promise.resolve(id), - }; + return expect( + resolvers.Query.math({}, { number: 123 }, mockContext), + ).resolves.toEqual({ math: 123 }); + }); + }); + + describe('year', () => { + test('loads year-related trivia for a number', () => { + expect.assertions(1); return expect( - resolvers.Query.getById({}, { id: 123 }, mockContext), - ).resolves.toEqual(123); + resolvers.Query.year({}, { number: 123 }, mockContext), + ).resolves.toEqual({ year: 123 }); }); }); }); - describe('PFX_DataSourceBase', () => { - const resolver = resolvers.PFX_DataSourceBase; + describe('Numbers_Trivia', () => { + const resolver = resolvers.Numbers_Trivia; - expectNullable(resolver, ['name']); + expectNullable(resolver, ['date', 'year']); }); });