diff --git a/01 Programming Refresher NodeJS.md b/01 Programming Refresher NodeJS.md index ec8b1e4..1c0d5ce 100644 --- a/01 Programming Refresher NodeJS.md +++ b/01 Programming Refresher NodeJS.md @@ -1,6 +1,52 @@ # Programming Refresher NodeJS +If you are completing this worksheet it is assumed that you are already familiar with both NodeJS and the `Express` package that is used to build dynamic websites. This worksheet will serve as a refresher whilst learning how to use a technique called **Test-Driven Development** which you will need to apply on a regular basis during the remainder of this module. + +If you are not familiar with NodeJS and Express ask your lab tutor for additional resources. + +Navigate to the `exercises/01_tdd_nodejs` directory and open the `package.json` file. Notice that there are certain modules listed as dependencies and others labelled as devDependencies. Use the `npm install` command to install all of these. + +If you examine the folder structure you will see that there are several directories: + +1. The `modules/` directory contains the three modules that will contain the business logic plus a `__mocks__` directory that we will be exploring at a later stage. +2. The `__tests__/` directory contains test scripts that matche each of the modules +3. The html directory contains the page templates used by the express renderer +4. The public directory contains files that can be directly accessed by the web server + +``` +├── __tests__ +│ ├── books.test.js +│ ├── google.test.js +│ └── utility.test.js +├── html +│ └── index.html +├── index.js +├── modules +│ ├── __mocks__ +│ └── google.js +│ ├── books.js +│ ├── google.js +│ └── utility.js +├── node_modules +├── package-lock.json +├── package.json +└── public + └── style.css +``` + +Start the web server using `node index.js` and access the URL (this will be `localhost:8080` if you are running it on your workstation). + +``` +│ │ └── __mockData__ +│ │ ├── 9781785885581.json +│ │ ├── 9781785885587.json +│ │ ├── extractedData.json +│ │ ├── java.json +│ │ ├── req.json +│ │ └── validTable.txt +``` + TDD ## 7 Preparation @@ -14,4 +60,4 @@ You will need to ensure your skills are up to date before we begin preparation i ### 7.3 Front End -1. Get familiar with the mapping APIs! (Google Maps / Apple Maps) \ No newline at end of file +1. Get familiar with the mapping APIs! (Google Maps / Apple Maps) diff --git a/exercises/01_tdd_nodejs/books/__tests__/books.test.js b/exercises/01_tdd_nodejs/books/__tests__/books.test.js new file mode 100644 index 0000000..b7ac565 --- /dev/null +++ b/exercises/01_tdd_nodejs/books/__tests__/books.test.js @@ -0,0 +1,65 @@ + +'use strict' + +/* eslint-disable no-magic-numbers */ + +const fs = require('fs') +const books = require('../modules/books') + +jest.mock('../modules/google') + +const path = './modules/__mocks__/__mockData__/' + +describe('search', () => { + + let req + + beforeEach( () => { + req = JSON.parse(fs.readFileSync(`${path}req.json`, 'utf8')) + }) + + test('make a search with valid request', async() => { + const result = await books.search(req) + const expected = fs.readFileSync(`${path}validTable.txt`, 'utf8') + // compare text using regex to remove whitespace + expect(result.replace(/\s/g, '')).toBe(expected.replace(/\s/g, '')) + }) + + test('make a search with no query parameters', async() => { + delete req.query + const result = await books.search(req) + expect(result.replace(/\s/g, '')).toBe('') + }) + + test('make a search with missing q query parameter', async() => { + try { + delete req.query.q + await books.search(req) + } catch(err) { + expect(err.message).toMatch('invalid isbn') + } + }) + +}) + +describe('details', () => { + + let req + + beforeEach( () => { + req = JSON.parse(fs.readFileSync(`${path}req.json`, 'utf8')) + }) + + test('return details of a book using a valid isbn number', async() => { + console.log(req) + const result = await books.details(req) + const expected = fs.readFileSync(`${path}9781785885587.json`, 'utf8') + expect(result.replace(/\s/g, '')).toBe(expected.replace(/\s/g, '')) + }) + + // test('request a book with no params', async() => { + // delete req.params + // await books.details(req).rejects.toEqual(Error('invalid isbn')) + // }) + +}) diff --git a/exercises/01_tdd_nodejs/books/__tests__/google.test.js b/exercises/01_tdd_nodejs/books/__tests__/google.test.js new file mode 100644 index 0000000..b7e156f --- /dev/null +++ b/exercises/01_tdd_nodejs/books/__tests__/google.test.js @@ -0,0 +1,48 @@ + +'use strict' + +/* eslint-disable no-magic-numbers */ + +const google = require('../modules/google') + + +describe('search', () => { + + test('check response is valid', async() => { + const base = 'https://www.googleapis.com/books/v1/volumes' + const fields = 'items(id,volumeInfo(title,industryIdentifiers))' + const url = `${base}?maxResults=20&fields=${fields}&q=java` + const data = await google.search(url) + expect(typeof data).toBe('string') + const json = JSON.parse(data) + expect(Array.isArray(json.items)).toBeTruthy() + expect(json.items.length).toBe(20) + }) + +}) + +describe('getBook', async() => { + + test('get book with valid isbn', async() => { + const fields = 'items(volumeInfo(title,authors,description,publisher))' + const url = `https://www.googleapis.com/books/v1/volumes?fields=${fields}&q=isbn:9781785885587` + const data = await google.getBook(url) + expect(typeof data).toBe('string') + const json = JSON.parse(data) + expect(json.title).toBeTruthy() + expect(json.authors).toBeTruthy() + expect(Array.isArray(json.authors)) + }) + + // toThrowError does not support promises! + test('try to use invalid isbn number', async() => { + try { + const fields = 'items(volumeInfo(title,authors,description,publisher))' + const url = `https://www.googleapis.com/books/v1/volumes?fields=${fields}&q=isbn:9781785885581` + await google.getBook(url) + } catch(err) { + expect(err.message).toMatch('invalid isbn') + } + }) + +}) diff --git a/exercises/01_tdd_nodejs/books/__tests__/utility.test.js b/exercises/01_tdd_nodejs/books/__tests__/utility.test.js new file mode 100644 index 0000000..6852233 --- /dev/null +++ b/exercises/01_tdd_nodejs/books/__tests__/utility.test.js @@ -0,0 +1,142 @@ + +'use strict' + +/* eslint-disable no-magic-numbers */ + +const fs = require('fs') + +const utility = require('../modules/utility') + +const path = './modules/__mocks__/__mockData__/' + +describe('buildString', () => { + + test('build url with default count', () => { + const str = utility.buildString('java') + expect(str) + .toBe('https://www.googleapis.com/books/v1/volumes?maxResults=20&fields=items(id,volumeInfo(title,industryIdentifiers))&q=java') + }) + + test('build url specifying count', () => { + const str = utility.buildString('java', 2) + expect(str) + .toBe('https://www.googleapis.com/books/v1/volumes?maxResults=2&fields=items(id,volumeInfo(title,industryIdentifiers))&q=java') + }) + + test('throw error if count too large', () => { + expect(() => utility.buildString('java', 21)) + .toThrowError('invalid count parameter') + }) + + test('throw error if count 0', () => { + expect(() => utility.buildString('java', 0)) + .toThrowError('invalid count parameter') + }) + + test('throw error if count negative', () => { + expect(() => utility.buildString('test', -1)) + .toThrowError('invalid count parameter') + }) + +}) + +describe('buildBookURL', () => { + + test('build url with valid isbn13 number', () => { + const str = utility.buildBookURL(9780226285108) + expect(str) + .toBe('https://www.googleapis.com/books/v1/volumes?fields=items(volumeInfo(title,authors,description,publisher))&q=isbn:9780226285108') + }) + + test('passing isbn as a string', () => { + expect(() => utility.buildBookURL('9780226285108')) + .toThrowError('parameter has invalid data type') + }) + + test('passing isbn number that is too long', () => { + expect(() => utility.buildBookURL(97802262851081)) + .toThrowError('invalid isbn') + }) + + test('passing isbn number that is too short', () => { + expect(() => utility.buildBookURL(978022628510)) + .toThrowError('invalid isbn') + }) + +}) + +describe('extractFields', () => { + + let goodData + + beforeAll( () => { + goodData = fs.readFileSync(`${path}java.json`, 'utf8') + expect(typeof goodData).toBe('string') + }) + + test('extracted data is in an array', () => { + const bookData = utility.extractFields(goodData) + expect(Array.isArray(bookData)).toBeTruthy() + expect(bookData.length).toBe(20) + }) + + test('passing string without books array', () => { + expect(() => utility.extractFields('{"name": "Mark"}')) + .toThrowError('no book data found') + }) + + test('passing object instead of string', () => { + expect(() => utility.extractFields({name: 'Mark'})) + .toThrowError('parameter has invalid data type') + }) + + test('extract title fields', () => { + const bookData = utility.extractFields(goodData) + expect(bookData[0].title).toBe('Thinking in Java') + expect(bookData[1].title).toBe('Practical Java') + }) + + test('extract ISBN13 data', () => { + const bookData = utility.extractFields(goodData) + expect(bookData[0].isbn).toBe(9780131002876) + expect(bookData[1].isbn).toBe(9780201616460) + }) + +}) + +describe('build table string', () => { + + let goodData + let goodHTML + + beforeAll( () => { + goodData = JSON.parse(fs.readFileSync(`${path}extractedData.json`, 'utf8')) + goodHTML = fs.readFileSync(`${path}validTable.txt`, 'utf8') + }) + + test('check parameter is an object', () => { + expect(typeof goodData).toBe('object') + }) + + test('thow error if parameter is not an object', () => { + expect(() => utility.buildTable('bad parameter')) + .toThrowError('invalid parameter data type') + }) + + test('check that parameter is an array (not object)', () => { + expect(() => utility.buildTable({name: 'Mark'})) + .toThrowError('invalid parameter data type') + }) + + test('check the function returns a string', () => { + const table = utility.buildTable(goodData) + expect(typeof table).toBe('string') + }) + + test('build 2 column table', async() => { + const table = utility.buildTable(goodData) + // compare text using regex to remove whitespace + expect(table.replace(/\s/g, '')).toBe(goodHTML.replace(/\s/g, '')) + }) + +}) diff --git a/exercises/01_tdd_nodejs/books/html/index.html b/exercises/01_tdd_nodejs/books/html/index.html new file mode 100644 index 0000000..9209f66 --- /dev/null +++ b/exercises/01_tdd_nodejs/books/html/index.html @@ -0,0 +1,18 @@ + + + + + + + Google Book Search + + + + +
+ + +
+ ${books} + + diff --git a/exercises/01_tdd_nodejs/books/index.js b/exercises/01_tdd_nodejs/books/index.js new file mode 100644 index 0000000..ae53933 --- /dev/null +++ b/exercises/01_tdd_nodejs/books/index.js @@ -0,0 +1,39 @@ +#!/usr/bin/env node + +'use strict' + +const express = require('express') +const es6Renderer = require('express-es6-template-engine') +const bodyParser = require('body-parser') +const app = express() +app.use(express.static('public')) +app.use(bodyParser.urlencoded({ extended: true })) + +app.engine('html', es6Renderer) +app.set('views', 'html') +app.set('view engine', 'html') + +const books = require('./modules/books') + +const port = 8080 + +// const status = { +// ok: 200, +// created: 201, +// notFound: 404, +// notAcceptable: 406, +// conflict: 409 +// } + +app.get('/', async(req, res) => { + try { + console.log(`query ${req.query.q}`) + const bookList = await books.search(req) + console.log(bookList) + res.render('index', {locals: {books: bookList}}) + } catch(err) { + console.log(err.message) + } +}) + +app.listen(port, () => console.log(`app listening on port ${port}`)) diff --git a/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/9781785885581.json b/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/9781785885581.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/9781785885581.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/9781785885587.json b/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/9781785885587.json new file mode 100644 index 0000000..e4b3294 --- /dev/null +++ b/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/9781785885587.json @@ -0,0 +1,9 @@ +{ + "title": "Node.Js Design Patterns - Second Edition", + "authors": [ + "Mario Casciaro", + "Luciano Mammino" + ], + "publisher": "Packt Publishing", + "description": "Get the best out of Node.js by mastering its most powerful components and patterns to create modular and scalable applications with easeAbout This Book- Create reusable patterns and modules by leveraging the new features of Node.js .- Understand the asynchronous single thread design of node and grasp all its features and patterns to take advantage of various functions.- This unique guide will help you get the most out of Node.js and its ecosystem.Who This Book Is ForThe book is meant for developers and software architects with a basic working knowledge of JavaScript who are interested in acquiring a deeper understanding of how to design and develop enterprise-level Node.js applications.Basic knowledge of Node.js is also helpful to get the most out of this book.What You Will Learn- Design and implement a series of server-side JavaScript patterns so you understand why and when to apply them in different use case scenarios- Become comfortable with writing asynchronous code by leveraging constructs such as callbacks, promises, generators and the async-await syntax- Identify the most important concerns and apply unique tricks to achieve higher scalability and modularity in your Node.js application- Untangle your modules by organizing and connecting them coherently- Reuse well-known techniques to solve common design and coding issues- Explore the latest trends in Universal JavaScript, learn how to write code that runs on both Node.js and the browser and leverage React and its ecosystem to implement universal applicationsIn DetailNode.js is a massively popular software platform that lets you use JavaScript to easily create scalable server-side applications. It allows you to create efficient code, enabling a more sustainable way of writing software made of only one language across the full stack, along with extreme levels of reusability, pragmatism, simplicity, and collaboration. Node.js is revolutionizing the web and the way people and companies create their software.In this book, we will take you on a journey across various ideas and components, and the challenges you would commonly encounter while designing and developing software using the Node.js platform. You will also discover the \"Node.js way\" of dealing with design and coding decisions.The book kicks off by exploring the basics of Node.js describing it's asynchronous single-threaded architecture and the main design patterns. It then shows you how to master the asynchronous control flow patterns,and the stream component and it culminates into a detailed list of Node.js implementations of the most common design patterns as well as some specific design patterns that are exclusive to the Node.js world.Lastly, it dives into more advanced concepts such as Universal Javascript, and scalability' and it's meant to conclude the journey by giving the reader all the necessary concepts to be able to build an enterprise grade application using Node.js.Style and approachThis book takes its intended readers through a comprehensive explanation to create a scalable and efficient real-time server-side apps." +} diff --git a/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/extractedData.json b/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/extractedData.json new file mode 100644 index 0000000..238c353 --- /dev/null +++ b/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/extractedData.json @@ -0,0 +1,80 @@ +[ + { + "title": "Thinking in Java", + "isbn": 9780131002876 + }, + { + "title": "Practical Java", + "isbn": 9780201616460 + }, + { + "title": "Java in a Time of Revolution", + "isbn": 9789793780146 + }, + { + "title": "Java", + "isbn": 9780764535437 + }, + { + "title": "The History of Java" + }, + { + "title": "The Religion of Java", + "isbn": 9780226285108 + }, + { + "title": "Natural Language Processing with Java", + "isbn": 9781784398941 + }, + { + "title": "Java and Modern Europe", + "isbn": 9780700704330 + }, + { + "title": "Java Web Development Illuminated", + "isbn": 9780763734237 + }, + { + "title": "Introduction to Neural Networks with Java", + "isbn": 9781604390087 + }, + { + "title": "TCP/IP Sockets in Java", + "isbn": 9780080568782 + }, + { + "title": "Java in a Nutshell", + "isbn": 9780596007737 + }, + { + "title": "Java and XSLT", + "isbn": 9780596001438 + }, + { + "title": "Wicked Cool Java", + "isbn": 9781593270612 + }, + { + "title": "Java NIO Ron Hitchens", + "isbn": 9780596002886 + }, + { + "title": "Effective Java", + "isbn": 9780132778046 + }, + { + "title": "Java I/O", + "isbn": 9781449390884 + }, + { + "title": "The History of Java" + }, + { + "title": "Programming with Java", + "isbn": 9780070141698 + }, + { + "title": "Concurrent Programming in Java", + "isbn": 9780201310092 + } +] diff --git a/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/java.json b/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/java.json new file mode 100644 index 0000000..0e371c1 --- /dev/null +++ b/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/java.json @@ -0,0 +1,316 @@ +{ + "items": [ + { + "id": "Ql6QgWf6i7cC", + "volumeInfo": { + "title": "Thinking in Java", + "industryIdentifiers": [ + { + "type": "ISBN_10", + "identifier": "0131002872" + }, + { + "type": "ISBN_13", + "identifier": "9780131002876" + } + ] + } + }, + { + "id": "iWPeqljHNcoC", + "volumeInfo": { + "title": "Practical Java", + "industryIdentifiers": [ + { + "type": "ISBN_10", + "identifier": "0201616467" + }, + { + "type": "ISBN_13", + "identifier": "9780201616460" + } + ] + } + }, + { + "id": "87totx4p3ZcC", + "volumeInfo": { + "title": "Java in a Time of Revolution", + "industryIdentifiers": [ + { + "type": "ISBN_13", + "identifier": "9789793780146" + }, + { + "type": "ISBN_10", + "identifier": "9793780142" + } + ] + } + }, + { + "id": "Ql0UWMUu3ooC", + "volumeInfo": { + "title": "Java", + "industryIdentifiers": [ + { + "type": "ISBN_10", + "identifier": "0764535439" + }, + { + "type": "ISBN_13", + "identifier": "9780764535437" + } + ] + } + }, + { + "id": "gJEC2q7DzpQC", + "volumeInfo": { + "title": "The History of Java", + "industryIdentifiers": [ + { + "type": "OTHER", + "identifier": "HARVARD:32044021066998" + } + ] + } + }, + { + "id": "-SYM4PW-YAgC", + "volumeInfo": { + "title": "The Religion of Java", + "industryIdentifiers": [ + { + "type": "ISBN_10", + "identifier": "0226285103" + }, + { + "type": "ISBN_13", + "identifier": "9780226285108" + } + ] + } + }, + { + "id": "q7y4BwAAQBAJ", + "volumeInfo": { + "title": "Natural Language Processing with Java", + "industryIdentifiers": [ + { + "type": "ISBN_13", + "identifier": "9781784398941" + }, + { + "type": "ISBN_10", + "identifier": "1784398942" + } + ] + } + }, + { + "id": "qXayo7k3oakC", + "volumeInfo": { + "title": "Java and Modern Europe", + "industryIdentifiers": [ + { + "type": "ISBN_10", + "identifier": "0700704337" + }, + { + "type": "ISBN_13", + "identifier": "9780700704330" + } + ] + } + }, + { + "id": "oY9fShrQyUgC", + "volumeInfo": { + "title": "Java Web Development Illuminated", + "industryIdentifiers": [ + { + "type": "ISBN_10", + "identifier": "0763734233" + }, + { + "type": "ISBN_13", + "identifier": "9780763734237" + } + ] + } + }, + { + "id": "Swlcw7M4uD8C", + "volumeInfo": { + "title": "Introduction to Neural Networks with Java", + "industryIdentifiers": [ + { + "type": "ISBN_13", + "identifier": "9781604390087" + }, + { + "type": "ISBN_10", + "identifier": "1604390085" + } + ] + } + }, + { + "id": "lfHo7uMk7r4C", + "volumeInfo": { + "title": "TCP/IP Sockets in Java", + "industryIdentifiers": [ + { + "type": "ISBN_10", + "identifier": "0080568785" + }, + { + "type": "ISBN_13", + "identifier": "9780080568782" + } + ] + } + }, + { + "id": "mvzgNSmHEUAC", + "volumeInfo": { + "title": "Java in a Nutshell", + "industryIdentifiers": [ + { + "type": "ISBN_10", + "identifier": "0596007736" + }, + { + "type": "ISBN_13", + "identifier": "9780596007737" + } + ] + } + }, + { + "id": "eSRnOKwU4hUC", + "volumeInfo": { + "title": "Java and XSLT", + "industryIdentifiers": [ + { + "type": "ISBN_10", + "identifier": "0596001436" + }, + { + "type": "ISBN_13", + "identifier": "9780596001438" + } + ] + } + }, + { + "id": "diqHjRjMhW0C", + "volumeInfo": { + "title": "Wicked Cool Java", + "industryIdentifiers": [ + { + "type": "ISBN_13", + "identifier": "9781593270612" + }, + { + "type": "ISBN_10", + "identifier": "1593270615" + } + ] + } + }, + { + "id": "z7TQ8NSooS4C", + "volumeInfo": { + "title": "Java NIO Ron Hitchens", + "industryIdentifiers": [ + { + "type": "ISBN_10", + "identifier": "0596002882" + }, + { + "type": "ISBN_13", + "identifier": "9780596002886" + } + ] + } + }, + { + "id": "ka2VUBqHiWkC", + "volumeInfo": { + "title": "Effective Java", + "industryIdentifiers": [ + { + "type": "ISBN_10", + "identifier": "0132778041" + }, + { + "type": "ISBN_13", + "identifier": "9780132778046" + } + ] + } + }, + { + "id": "42etT_9-_9MC", + "volumeInfo": { + "title": "Java I/O", + "industryIdentifiers": [ + { + "type": "ISBN_10", + "identifier": "1449390889" + }, + { + "type": "ISBN_13", + "identifier": "9781449390884" + } + ] + } + }, + { + "id": "_-dCAAAAcAAJ", + "volumeInfo": { + "title": "The History of Java", + "industryIdentifiers": [ + { + "type": "OTHER", + "identifier": "BSB:BSB10359645" + } + ] + } + }, + { + "id": "ZdBYoyWIsMQC", + "volumeInfo": { + "title": "Programming with Java", + "industryIdentifiers": [ + { + "type": "ISBN_10", + "identifier": "007014169X" + }, + { + "type": "ISBN_13", + "identifier": "9780070141698" + } + ] + } + }, + { + "id": "-x1S4neCSOYC", + "volumeInfo": { + "title": "Concurrent Programming in Java", + "industryIdentifiers": [ + { + "type": "ISBN_10", + "identifier": "0201310090" + }, + { + "type": "ISBN_13", + "identifier": "9780201310092" + } + ] + } + } + ] + } \ No newline at end of file diff --git a/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/req.json b/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/req.json new file mode 100644 index 0000000..9e8c606 --- /dev/null +++ b/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/req.json @@ -0,0 +1,14 @@ +{ + "query": { + "q": "java" + }, + "params": { + "isbn": 9781785885587 + }, + "method": "GET", + "url": "/?q=java", + "headers": { + "host": "xxx", + "user-agent": "xxx" + } +} \ No newline at end of file diff --git a/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/validTable.txt b/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/validTable.txt new file mode 100644 index 0000000..b8fedbf --- /dev/null +++ b/exercises/01_tdd_nodejs/books/modules/__mocks__/__mockData__/validTable.txt @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Thinking in Java9780131002876
Practical Java9780201616460
Java in a Time of Revolution9789793780146
Java9780764535437
The History of Javano ISBN
The Religion of Java9780226285108
Natural Language Processing with Java9781784398941
Java and Modern Europe9780700704330
Java Web Development Illuminated9780763734237
Introduction to Neural Networks with Java9781604390087
TCP/IP Sockets in Java9780080568782
Java in a Nutshell9780596007737
Java and XSLT9780596001438
Wicked Cool Java9781593270612
Java NIO Ron Hitchens9780596002886
Effective Java9780132778046
Java I/O9781449390884
The History of Javano ISBN
Programming with Java9780070141698
Concurrent Programming in Java9780201310092
\ No newline at end of file diff --git a/exercises/01_tdd_nodejs/books/modules/__mocks__/google.js b/exercises/01_tdd_nodejs/books/modules/__mocks__/google.js new file mode 100644 index 0000000..a2b093a --- /dev/null +++ b/exercises/01_tdd_nodejs/books/modules/__mocks__/google.js @@ -0,0 +1,32 @@ + +'use strict' + +/* eslint-disable no-magic-numbers */ + +const fs = require('fs') + +/** + * Makes a Google Books API query + * @param {String} searchString the URL to use for the query + * @returns {String} a JSON-formatter string + */ +module.exports.search = async url => { + const [,query] = url.split('q=') // destructuring assignment + const file = `./modules/__mocks__/__mockData__/${query}.json` + const data = fs.readFileSync(file) + const json = JSON.parse(data) + return JSON.stringify(json, null, 2) +} + +/** + * Retrieved data on a book using a RESTful API URL + * @param {String} searchString the URL to use for the query + * @returns {String} a JSON-formatter string + */ +module.exports.getBook = async url => { + const [,isbn] = url.split('q=isbn:') // destructuring assignment + const file = `./modules/__mocks__/__mockData__/${isbn}.json` + const data = fs.readFileSync(file) + const json = JSON.parse(data) + return JSON.stringify(json, null, 2) +} diff --git a/exercises/01_tdd_nodejs/books/modules/books.js b/exercises/01_tdd_nodejs/books/modules/books.js new file mode 100644 index 0000000..8d3b579 --- /dev/null +++ b/exercises/01_tdd_nodejs/books/modules/books.js @@ -0,0 +1,37 @@ + +'use strict' + +/* eslint-disable no-magic-numbers */ + +const utility = require('./utility') +const google = require('./google') +//jest.mock('./google') + +/** + * Makes a Google Books API query + * @param {String} request the http request object + * @returns {Promise} an html table of data as a string + */ +module.exports.search = async request => { + if(request.query === undefined) { + return '' + } + if(request.query.q === undefined) { + return '' + } + const url = utility.buildString(request.query.q) + const data = await google.search(url) + const books = utility.extractFields(data) + const table = utility.buildTable(books) + return table +} + +module.exports.details = async request => { + //delete request.params + if(request.params === undefined) { + return Promise.reject('invalid isbn') + } + const url = utility.buildBookURL(request.params.isbn) + const data = await google.getBook(url) + return data +} diff --git a/exercises/01_tdd_nodejs/books/modules/google.js b/exercises/01_tdd_nodejs/books/modules/google.js new file mode 100644 index 0000000..67e84dd --- /dev/null +++ b/exercises/01_tdd_nodejs/books/modules/google.js @@ -0,0 +1,32 @@ + +'use strict' + +/* eslint-disable no-magic-numbers */ + +const rest = require('rest') + +/** + * Makes a Google Books API query + * @param {String} url the URL to use for the query + * @returns {Promise} An array of books as a JSON string + */ +module.exports.search = async url => { + const data = await rest(url) + //console.log(data.entity) + return data.entity +} + +/** + * Makes a Google Books API query to return details of one book + * based on its ISBN. + * @param {String} url the URL to use for the query + * @returns {Promise} Book details as a JSON string + * @throws {Error} invalid isbn + */ +module.exports.getBook = async url => { + const data = await rest(url) + const len = parseInt(data.headers['Content-Length']) + if(len <= 4) throw new Error('invalid isbn') + const json = JSON.parse(data.entity) + return JSON.stringify(json.items[0].volumeInfo, null, 2) +} diff --git a/exercises/01_tdd_nodejs/books/modules/utility.js b/exercises/01_tdd_nodejs/books/modules/utility.js new file mode 100644 index 0000000..3ef2fab --- /dev/null +++ b/exercises/01_tdd_nodejs/books/modules/utility.js @@ -0,0 +1,91 @@ + +'use strict' + +const maxRecords = 20 + +/** Builds the url needed by the Google Books API. + * @function buildString + * @param {String} query the string to search for + * @param {Number} [count=40] the number of records to return (max 40) + * @returns {String} the URL + */ +module.exports.buildString = (query, count = maxRecords) => { + if(count > maxRecords || count < 1) { + throw new Error('invalid count parameter') + } + const base = 'https://www.googleapis.com/books/v1/volumes' + const fields = 'items(id,volumeInfo(title,industryIdentifiers))' + const url = `${base}?maxResults=${count}&fields=${fields}&q=${query}` + //console.log(url) + return url +} + +module.exports.buildBookURL = isbn => { + const isbnLen = 13 + if(typeof isbn !== 'number') { + throw new Error('parameter has invalid data type') + } + if(isbn.toString().length !== isbnLen) { + throw new Error('invalid isbn') + } + const fields = 'items(volumeInfo(title,authors,description,publisher))' + const url = `https://www.googleapis.com/books/v1/volumes?fields=${fields}&q=isbn:${isbn}` + return url +} + +/* -------------------------------------------------------------------------- */ + +/** + * Extracts data from the json + * @param {String} jsonStr the json string to parse + * @returns {Array} an array of book details + */ +module.exports.extractFields = jsonStr => { + const bookArray = [] + if(typeof jsonStr !== 'string') { + throw new Error('parameter has invalid data type') + } + const json = JSON.parse(jsonStr) + if(!Array.isArray(json.items)) throw new Error('no book data found') + for(const n of json.items) { + const item = {} + item.title = n.volumeInfo.title + for(const m of n.volumeInfo.industryIdentifiers) { + if(m.type === 'ISBN_13') { + item.isbn = parseInt(m.identifier) + } + } + bookArray.push(item) + } + return bookArray +} + +/** + * Extracts data from the json + * @param {Array} bookArray an array containing the books found + * @returns {String} a string containing the html table + */ +module.exports.buildTable = bookArray => { + if(typeof bookArray !== 'object') { + throw new Error('invalid parameter data type') + } + if(!Array.isArray(bookArray)) { + throw new Error('invalid parameter data type') + } + let result = '\n' + for(const n of bookArray) { + if(n.isbn !== undefined) { + result += ` + + + ` + } else { + result += ` + + + ` + } + } + result += '
${n.title}${n.isbn}
${n.title}no ISBN
' + return result +} diff --git a/exercises/01_tdd_nodejs/books/package.json b/exercises/01_tdd_nodejs/books/package.json new file mode 100644 index 0000000..0f6255a --- /dev/null +++ b/exercises/01_tdd_nodejs/books/package.json @@ -0,0 +1,27 @@ +{ + "name": "books", + "version": "1.0.0", + "description": "Simple dynamic website that uses the Google Books API", + "main": "index.js", + "scripts": { + "tests": "jest", + "watch": "node_modules/.bin/jest --coverage --watchAll", + "coverage": "node_modules/.bin/jest --coverage", + "jsdoc": "node_modules/.bin/jsdoc -d docs/jsdoc/ modules/" + }, + "author": "", + "license": "ISC", + "jest": { + "testEnvironment": "node", + "verbose": true + }, + "dependencies": { + "express": "^4.16.3", + "express-es6-template-engine": "^2.0.3", + "rest": "^2.0.0" + }, + "devDependencies": { + "jest": "^23.1.0", + "jsdoc": "^3.5.5" + } +} diff --git a/exercises/01_tdd_nodejs/books/public/style.css b/exercises/01_tdd_nodejs/books/public/style.css new file mode 100644 index 0000000..e69de29