Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,5 @@
}
}
},
"version": "0.0.0-development"
"version": "1.0.0-beta1"
}
2 changes: 1 addition & 1 deletion src/connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ export default class NumbersConnector extends GraphQLConnector {
this.headers['Content-Type'] = 'application/json';
this.apiBaseUri = `http://numbersapi.com`;
}
}
}
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 1 addition & 2 deletions src/mocks.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { MockList } from 'graphql-tools';
import casual from 'casual';

export default {
Expand All @@ -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'),
}),
};
10 changes: 4 additions & 6 deletions src/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 }));
}
}
}
16 changes: 9 additions & 7 deletions src/resolvers.js
Original file line number Diff line number Diff line change
@@ -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,
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you add resolvers for the optional response fields?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The date and year fields aren’t always returned, so they might come back undefined. This way we can make sure nothing explodes and we’re explicitly returning null if these aren’t set.

};
36 changes: 28 additions & 8 deletions src/schema.graphql
Original file line number Diff line number Diff line change
@@ -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
}
14 changes: 14 additions & 0 deletions test/connector.test.js
Original file line number Diff line number Diff line change
@@ -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`);
});
});
13 changes: 0 additions & 13 deletions test/context.test.js

This file was deleted.

4 changes: 2 additions & 2 deletions test/index.test.js
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down
16 changes: 9 additions & 7 deletions test/mocks.test.js
Original file line number Diff line number Diff line change
@@ -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',
]);
});
});
95 changes: 95 additions & 0 deletions test/model.test.js
Original file line number Diff line number Diff line change
@@ -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();
}
});
});
});
54 changes: 43 additions & 11 deletions test/resolvers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
});
});