Skip to content

Commit 935d4bb

Browse files
committed
feat(*): add 'search books by title' query
1 parent 8d543dd commit 935d4bb

10 files changed

+78
-87
lines changed

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
{
2-
"name": "@gramps/data-source-base",
3-
"description": "Base modules for a GrAMPS GraphQL data source.",
2+
"name": "@gramps/data-source-openlibrary",
3+
"description": "GrAMPS GraphQL data source for the OpenLibrary API.",
44
"contributors": [
5-
"Jason Lengstorf <[email protected]>"
5+
"Jason Lengstorf <[email protected]>",
6+
"Ryan Mackey <[email protected]>"
67
],
78
"repository": {
89
"type": "git",
9-
"url": "https://github.com/gramps-graphql/data-source-base.git"
10+
"url": "https://github.com/gramps-graphql/data-source-openlibrary.git"
1011
},
1112
"main": "dist/index.js",
1213
"directories": {

src/connector.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { GraphQLConnector } from '@gramps/gramps-express';
22

3-
// TODO: change `YourDataSourceConnector` to a descriptive name
4-
export default class YourDataSourceConnector extends GraphQLConnector {
3+
export default class OpenLibraryConnector extends GraphQLConnector {
54
/**
6-
* TODO: describe this API endpoint
5+
* Open Library URL
76
* @member {string}
87
*/
9-
apiBaseUri = `https://example.org/v2`;
8+
apiBaseUri = `https://openlibrary.org`;
109
}

src/index.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import Model from './model';
88
* https://ibm.biz/graphql-data-source-main
99
*/
1010
export default {
11-
// TODO: Rename the context to describe the data source.
12-
context: 'YourDataSource',
11+
context: 'OpenLibrary',
1312
model: new Model({ connector: new Connector() }),
1413
schema,
1514
resolvers,

src/model.js

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,16 @@ import { GraphQLModel, GrampsError } from '@gramps/gramps-express';
55
* https://ibm.biz/graphql-data-source-model
66
*/
77

8-
// TODO: change `YourDataSourceModel` to a descriptive name
9-
export default class YourDataSourceModel extends GraphQLModel {
8+
export default class OpenLibraryModel extends GraphQLModel {
109
/**
11-
* Loads a thing by its ID
12-
* @param {String} id the ID of the thing to load
10+
* Search for books
11+
* @param {String} title the title of the thing to load
1312
* @return {Promise} resolves with the loaded user data
1413
*/
15-
getById(id) {
16-
return this.connector.get(`/data/${id}`).catch(res =>
14+
searchBooksByTitle(title) {
15+
return this.connector.get(`/search.json?q=${title}`).catch(res =>
1716
this.throwError(res, {
18-
description: 'This is an example call. Add your own!',
19-
docsLink:
20-
'https://gramps-graphql.github.io/gramps-express/data-source/tutorial/',
17+
description: 'Error querying OpenLibrary.',
2118
}),
2219
);
2320
}

src/resolvers.js

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MockList } from 'graphql-tools';
1+
// import { MockList } from 'graphql-tools';
22
import casual from 'casual';
33

44
/*
@@ -9,33 +9,40 @@ import casual from 'casual';
99
export default {
1010
// Queries (where does the data come from?)
1111
queryResolvers: {
12-
// TODO: Update query resolver name(s) to match schema queries
13-
YourDataSource: (rootValue, { id }, context) =>
12+
SearchBooksByTitle: (rootValue, { title }, context) =>
1413
new Promise((resolve, reject) => {
15-
// TODO: Update to use the model and call the proper method.
16-
context.YourDataSource
17-
.getById(id)
18-
.then(resolve)
14+
context.OpenLibrary
15+
.searchBooksByTitle(title)
16+
.then(data => resolve(data.docs[0]))
1917
.catch(reject);
2018
}),
2119
},
2220

2321
// Data fields (which data from the response goes to which field?)
2422
dataResolvers: {
25-
// TODO: Update to reference the schema type(s) and field(s).
26-
PFX_YourDataSource: {
27-
// If a field isn’t always set, but it shouldn’t break the response, make it nullable.
28-
name: data => data.name || null,
23+
OL_SearchResult: {
24+
title: data => data.title_suggest || null,
25+
author: data => {
26+
if (data.author_name && data.author_name.length) {
27+
return data.author_name[0];
28+
}
29+
return null;
30+
},
31+
isbn: data => {
32+
if (data.isbn && data.isbn.length) {
33+
return data.isbn[0];
34+
}
35+
return null;
36+
},
2937
},
3038
},
3139

3240
// Mock data (How can I get real-feeling fake data while working offline?)
3341
mockResolvers: {
34-
// TODO: Update to mock all schema fields and types.
35-
PFX_YourDataSource: () => ({
36-
id: casual.uuid,
37-
name: casual.name,
38-
lucky_numbers: () => new MockList([0, 3]),
42+
OL_SearchResult: () => ({
43+
title: casual.title,
44+
author: casual.name,
45+
isbn: casual.uuid,
3946
}),
4047
},
4148
};

src/schema.graphql

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
1-
# The query/queries to access data MUST extend the Query type.
21
extend type Query {
3-
# TODO: rename and add a description of this query
4-
YourDataSource(
5-
# TODO: Describe this argument
6-
id: ID!
7-
): PFX_YourDataSource
2+
SearchBooksByTitle(title: String!): OL_SearchResult
83
}
94

10-
# TODO: Choose a unique prefix and rename the type descriptively.
11-
type PFX_YourDataSource {
12-
# The unique ID of the thing.
13-
id: ID!
14-
# Describe each field to help people use the data more effectively.
15-
name: String
16-
lucky_numbers: [Int]
5+
type OL_SearchResult {
6+
title: String!
7+
author: String
8+
isbn: String
179
}

test/connector.test.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { GraphQLConnector } from '@gramps/gramps-express';
22
import Connector from '../src/connector';
33

4-
// TODO: Update the data source name.
5-
const DATA_SOURCE_NAME = 'YourDataSource';
4+
const DATA_SOURCE_NAME = 'openlibrary';
65
const connector = new Connector();
76

87
describe(`${DATA_SOURCE_NAME}Connector`, () => {
@@ -11,7 +10,6 @@ describe(`${DATA_SOURCE_NAME}Connector`, () => {
1110
});
1211

1312
it('uses the appropriate URL', () => {
14-
// TODO: Update the data source API endpoint.
15-
expect(connector.apiBaseUri).toBe(`https://example.org/v2`);
13+
expect(connector.apiBaseUri).toBe(`https://openlibrary.org`);
1614
});
1715
});

test/index.test.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import dataSource from '../src';
22
import Model from '../src/model';
33

4-
// TODO: Update the data source name.
5-
const DATA_SOURCE_NAME = 'YourDataSource';
4+
const DATA_SOURCE_NAME = 'OpenLibrary';
65

76
describe(`Data Source: ${DATA_SOURCE_NAME}`, () => {
87
it('returns a context type', () => {

test/model.test.js

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ jest.mock('../src/connector', () =>
1010
})),
1111
);
1212

13-
// TODO: Update the data source name.
14-
const DATA_SOURCE_NAME = 'YourDataSource';
13+
const DATA_SOURCE_NAME = 'OpenLibrary';
1514

1615
const connector = new Connector();
1716
const model = new Model({ connector });
@@ -22,12 +21,12 @@ describe(`${DATA_SOURCE_NAME}Model`, () => {
2221
});
2322

2423
// TODO: Update this test to use your model’s method(s).
25-
describe('getById()', () => {
26-
it('calls the correct endpoint with a given ID', () => {
24+
describe('searchBooksByTitle()', () => {
25+
it('calls the correct endpoint with a given title', () => {
2726
const spy = jest.spyOn(connector, 'get');
2827

29-
model.getById('1234');
30-
expect(spy).toHaveBeenCalledWith('/data/1234');
28+
model.searchBooksByTitle('Fight Club');
29+
expect(spy).toHaveBeenCalledWith('/search.json?q=Fight Club');
3130
});
3231

3332
it('throws a GrampsError if something goes wrong', async () => {
@@ -38,8 +37,7 @@ describe(`${DATA_SOURCE_NAME}Model`, () => {
3837
);
3938

4039
try {
41-
// TODO: Update to use one of your model’s methods.
42-
await model.getById('1234');
40+
await model.searchBooksByTitle('Fight Club');
4341
} catch (error) {
4442
expect(error.isBoom).toEqual(true);
4543
}
@@ -51,7 +49,7 @@ describe(`${DATA_SOURCE_NAME}Model`, () => {
5149
const mockError = {
5250
statusCode: 401,
5351
options: {
54-
uri: 'https://example.org/',
52+
uri: 'https://openlibrary.org',
5553
},
5654
};
5755

@@ -67,15 +65,14 @@ describe(`${DATA_SOURCE_NAME}Model`, () => {
6765
);
6866

6967
try {
70-
// TODO: Update to use one of your model’s methods.
71-
await model.getById(1234);
68+
await model.searchBooksByTitle('Fight Club');
7269
} catch (error) {
7370
// Check that GrampsError properly received the error detail.
7471
expect(error).toHaveProperty('isBoom', true);
7572
expect(error.output).toHaveProperty('statusCode', 401);
7673
expect(error.output.payload).toHaveProperty(
7774
'targetEndpoint',
78-
'https://example.org/',
75+
'https://openlibrary.org',
7976
);
8077
expect(error.output.payload).toHaveProperty(
8178
'graphqlModel',

test/resolvers.test.js

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import resolvers from '../src/resolvers';
22
import expectMockFields from './helpers/expectMockFields';
3-
import expectMockList from './helpers/expectMockList';
3+
// import expectMockList from './helpers/expectMockList';
44
import expectNullable from './helpers/expectNullable';
55

6-
// TODO: Update the data source name.
7-
const DATA_SOURCE_NAME = 'YourDataSource';
6+
const DATA_SOURCE_NAME = 'OpenLibrary';
87

98
describe(`${DATA_SOURCE_NAME} resolvers`, () => {
109
it('returns valid resolvers', () => {
@@ -15,37 +14,41 @@ describe(`${DATA_SOURCE_NAME} resolvers`, () => {
1514
]);
1615
});
1716

17+
const modelResult = {
18+
title_suggest: 'Fight Club',
19+
author_name: ['Chuck Palahniuk'],
20+
isbn: ['9782070422401'],
21+
};
22+
1823
describe('queryResolvers', () => {
1924
describe(DATA_SOURCE_NAME, () => {
20-
it('loads a thing by its ID', () => {
25+
it('searches for a book by title', () => {
2126
expect.assertions(1);
2227

2328
const req = {};
2429

25-
// TODO: Update with mock arguments for your model method.
26-
const args = { id: 'abc1234' };
30+
const args = { title: 'Fight Club' };
2731

28-
// TODO: Update with the data source model name and method(s).
2932
const mockContext = {
30-
YourDataSource: {
31-
// For testing, we mock the model to simply return the ID.
32-
getById: id => Promise.resolve(id),
33+
OpenLibrary: {
34+
searchBooksByTitle: () => Promise.resolve({ docs: [modelResult] }),
3335
},
3436
};
3537

3638
return expect(
37-
// TODO: Update to use your data source.
38-
resolvers.queryResolvers.YourDataSource(req, args, mockContext),
39-
).resolves.toEqual('abc1234');
39+
resolvers.queryResolvers.SearchBooksByTitle(req, args, mockContext),
40+
).resolves.toEqual(modelResult);
4041
});
4142
});
4243
});
4344

4445
describe('dataResolvers', () => {
45-
describe('PFX_YourDataSource', () => {
46-
const resolver = resolvers.dataResolvers.PFX_YourDataSource;
47-
48-
expectNullable(resolver, ['name']);
46+
describe('OL_SearchResult', () => {
47+
const resolver = resolvers.dataResolvers.OL_SearchResult;
48+
expect(resolver.title(modelResult)).toBe('Fight Club');
49+
expect(resolver.author(modelResult)).toBe('Chuck Palahniuk');
50+
expect(resolver.isbn(modelResult)).toBe('9782070422401');
51+
expectNullable(resolver, ['title', 'author', 'isbn']);
4952
});
5053
});
5154

@@ -55,11 +58,10 @@ describe(`${DATA_SOURCE_NAME} resolvers`, () => {
5558
* exploding. To that end, we’ll just check that the mock response returns
5659
* the proper fields.
5760
*/
58-
describe('PFX_YourDataSource', () => {
59-
const mockResolvers = resolvers.mockResolvers.PFX_YourDataSource();
61+
describe('OL_SearchResult', () => {
62+
const mockResolvers = resolvers.mockResolvers.OL_SearchResult();
6063

61-
expectMockFields(mockResolvers, ['id', 'name', 'lucky_numbers']);
62-
expectMockList(mockResolvers, ['lucky_numbers']);
64+
expectMockFields(mockResolvers, ['title', 'author', 'isbn']);
6365
});
6466
});
6567
});

0 commit comments

Comments
 (0)