2 |
5 |
40 |
--------------------------------------------------------------------------------
/bookstore-webapp/src/app/components/edit-book/edit-book.component.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { ActivatedRoute, Router } from '@angular/router';
4 | import { Component, NgZone, OnInit } from '@angular/core';
5 | import { FormBuilder, FormGroup } from '@angular/forms';
6 |
7 | import { BooksService } from '../../services/books.service';
8 | import { IBookDto } from '../../interfaces/book.Dto';
9 |
10 | @Component({
11 | selector: 'app-edit-book',
12 | templateUrl: './edit-book.component.html',
13 | styleUrls: ['./edit-book.component.css']
14 | })
15 | export class EditBookComponent implements OnInit {
16 |
17 | editBookDto: IBookDto;
18 | editBookForm: FormGroup;
19 |
20 | constructor(private route: ActivatedRoute, private bookstoreService: BooksService,
21 | private ngZone: NgZone, private router: Router, private formBuilder: FormBuilder) {
22 |
23 | this.editBookForm = this.formBuilder.group({
24 | dateOfPublish: '',
25 | language: '',
26 | author: '',
27 | title: '',
28 | id: ''
29 | });
30 | }
31 |
32 | ngOnInit(): void {
33 |
34 | this.route.paramMap.subscribe(params => {
35 |
36 | this.bookstoreService.GetBookById(params.get('bookId'))
37 | .subscribe((editBookDto: IBookDto) => {
38 |
39 | this.editBookDto = editBookDto;
40 | console.log(`${this.editBookDto.title}`);
41 | });
42 | });
43 | }
44 |
45 | /* For Modify */
46 | onBookEdit(id: string, bookstoreData: IBookDto): void {
47 |
48 | console.warn(`Book Edit Request for Id: ${id}`);
49 |
50 | this.bookstoreService.EditBookById(id, bookstoreData).subscribe(res => {
51 |
52 | console.log('Book Modified!')
53 | this.ngZone.run(() => this.router.navigateByUrl('/books'))
54 | });
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/bookstore-webapp/src/app/components/dashboard/dashboard.component.html:
--------------------------------------------------------------------------------
1 |
Application Dashboard
2 |
3 |
4 |
5 |
6 |
7 |
8 |
Books
9 |
Library Books Module. Add, View, Edit and Delete Books. The backend is Node JS + Express. It using Mongo Db for data store
10 |
Books
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
Professors
19 |
College Professors Module. View, Edit and Delete Professors. The backend is .Net Core 3.1 with EF Core and SQL Server. It also uses Redis Cache.
20 |
Coming Soon
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
Stock Ticker
32 |
Retrieving the Stock Price. Add, View, Edit and Delete Books. The backend is Node JS + Express. It using Mongo Db for data store
33 |
Coming Soon
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
Students
42 |
Retrieving the Students. Add, View, Edit and Delete Books. The backend is Node JS + Express. It using Mongo Db for data store
43 |
Coming Soon
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/bookstore-webapp/src/app/components/edit-book/edit-book.component.html:
--------------------------------------------------------------------------------
1 |
50 |
--------------------------------------------------------------------------------
/bookstore-webapp/src/app/components/delete-book/delete-book.component.html:
--------------------------------------------------------------------------------
1 |
51 |
--------------------------------------------------------------------------------
/bookstore-webapi/tests/unit/app.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const request = require('supertest');
4 | const app = require('../../src/app');
5 | const mockMongoDb = require('./__mocks__/MongoDbMock');
6 |
7 | describe('Testing /src/app.js', () => {
8 |
9 | const apiServer = request(app);
10 |
11 | beforeAll(async () => await mockMongoDb.connect());
12 |
13 | afterEach(async () => await mockMongoDb.clearDatabase());
14 |
15 | afterAll(async () => await mockMongoDb.closeDatabase());
16 |
17 | describe('Testing API Routes', () => {
18 |
19 | // "/" Routes
20 | describe('App :: "/" Routes', () => {
21 |
22 | const defaultMessage = 'Welcome to Books Web API.';
23 |
24 | test('API Should return default response', async function (done) {
25 | const response = await apiServer.get('/api');
26 |
27 | expect(response.status).toBe(200);
28 | expect(JSON.parse(response.text)).toBe(defaultMessage);
29 |
30 | done();
31 | });
32 |
33 | });
34 |
35 | // "/api/books" Routes
36 | describe('Book Routers :: "/api/books" Routes', () => {
37 |
38 | const Book = require('../../src/models/book.Model');
39 |
40 | const _book = {
41 | '_id': '5f0745314c16a3084cfa41fc',
42 | 'author': 'Dummy Author',
43 | 'title': 'Node JS',
44 | 'dateOfPublish': '01-Jan-2021',
45 | 'language': "JavaScript",
46 | 'read': false
47 | };
48 |
49 | test('Book Router Should return 500 when invalid id is sent', async function (done) {
50 | const response = await apiServer.get('/api/books/InvalidId');
51 |
52 | expect(response.status).toBe(500);
53 |
54 | done();
55 | });
56 |
57 | test('Book Router Should return 404 when no data available', async function (done) {
58 | const response = await apiServer.get('/api/books/5f0745314c16a3084cfa41fc');
59 |
60 | expect(response.status).toBe(404);
61 |
62 | done();
63 | });
64 |
65 | test('Book Router Should return 200 when data available', async function (done) {
66 | Book.create(_book);
67 |
68 | const response = await apiServer.get('/api/books/5f0745314c16a3084cfa41fc');
69 |
70 | expect(response.status).toBe(200);
71 |
72 | done();
73 | });
74 |
75 | });
76 |
77 | });
78 |
79 | });
80 |
--------------------------------------------------------------------------------
/bookstore-webapp/src/app/services/books.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient, HttpHeaders } from '@angular/common/http';
3 | import { Observable, throwError, from } from 'rxjs';
4 | import { retry, catchError } from 'rxjs/operators';
5 |
6 | import { IBookDto } from '../interfaces/book.Dto';
7 | import { IAddBookDto } from '../interfaces/addBook.Dto';
8 |
9 | const baseUrl = "http://localhost:4000/api";
10 | const httpOptions = {
11 | headers: new HttpHeaders({
12 | 'Content-Type': 'application/json',
13 | }),
14 | };
15 |
16 | @Injectable({
17 | providedIn: 'root'
18 | })
19 | export class BooksService {
20 |
21 | constructor(private httpClient: HttpClient) {
22 | }
23 |
24 | // GET All Books
25 | GetAllBooks(): Observable
{
26 |
27 | console.log(`Get All Books request received.`);
28 |
29 | return this.httpClient
30 | .get(`${baseUrl}/books`)
31 | .pipe(retry(1), catchError(this.errorHandler));
32 | }
33 |
34 | // Retrieve Book By Id
35 | GetBookById(id: string): Observable {
36 |
37 | console.log(`Get Book request received for ${id}`);
38 |
39 | return this.httpClient
40 | .get(`${baseUrl}/books/${id}`)
41 | .pipe(retry(1), catchError(this.errorHandler));
42 | }
43 |
44 | //Add Book
45 | AddBooks(bookstore: IAddBookDto): Observable {
46 |
47 | console.log(`Adding New Book: ${JSON.stringify(bookstore)}`);
48 |
49 | return this.httpClient
50 | .post(`${baseUrl}/books`, JSON.stringify(bookstore), httpOptions)
51 | .pipe(
52 | retry(1),
53 | catchError(this.errorHandler)
54 | )
55 | }
56 |
57 | // Edit Book By Id
58 | EditBookById(id: string, bookstore: IBookDto) {
59 |
60 | console.log(`Edit Book request received for ${id} ${JSON.stringify(bookstore)}`);
61 |
62 | return this.httpClient
63 | .put(`${baseUrl}/books/${id}`, JSON.stringify(bookstore), httpOptions)
64 | .pipe(retry(1), catchError(this.errorHandler));
65 | }
66 |
67 | RemoveBookById(id: string) {
68 | console.log(`Removed Book request received for ${id}`);
69 | return this.httpClient.delete(`${baseUrl}/books/${id}`, httpOptions)
70 | .pipe(
71 | retry(1),
72 | catchError(this.errorHandler)
73 | )
74 | }
75 |
76 | // Error handling
77 | errorHandler(error) {
78 |
79 | let errorMessage = '';
80 | if (error.error instanceof ErrorEvent) {
81 | // Get client-side error
82 | errorMessage = error.error.message;
83 | } else {
84 | // Get server-side error
85 | errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
86 | }
87 | console.log(errorMessage);
88 | return throwError(errorMessage);
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/bookstore-webapi/tests/unit/book-Router.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const sinon = require('sinon');
4 | const proxyquire = require('proxyquire')
5 | const httpMock = require('node-mocks-http');
6 |
7 | describe('Testing /src/routes/bookRouter.js', () => {
8 |
9 | let expressStub, controllerStub, RouterStub, rootRouteStub, idRouteStub
10 | let request, response, next;
11 |
12 | describe('router', () => {
13 |
14 | beforeEach(() => {
15 |
16 | rootRouteStub = {
17 | "get": sinon.stub().callsFake(() => rootRouteStub),
18 | "post": sinon.stub().callsFake(() => rootRouteStub)
19 | };
20 |
21 | idRouteStub = {
22 | "get": sinon.stub().callsFake(() => idRouteStub),
23 | "use": sinon.stub().callsFake(() => idRouteStub)
24 | };
25 |
26 | RouterStub = {
27 | route: sinon.stub().callsFake((route) => {
28 | if (route === '/books/:bookId') {
29 | return idRouteStub
30 | }
31 | return rootRouteStub
32 | })
33 | };
34 |
35 | expressStub = {
36 | Router: sinon.stub().returns(RouterStub)
37 | };
38 |
39 | controllerStub = {
40 | post: sinon.mock(),
41 | get: sinon.mock(),
42 | getBookById: sinon.mock()
43 | };
44 |
45 | proxyquire('../../src/routes/book-Router.js',
46 | {
47 | 'express': expressStub,
48 | '../controllers/books.Controller.js': controllerStub
49 | }
50 | );
51 |
52 | request = httpMock.createRequest();
53 | response = httpMock.createResponse();
54 | next = sinon.mock();
55 |
56 | });
57 |
58 | test('should map root get() router with controller::get()', () => {
59 |
60 | rootRouteStub.get('/books', controllerStub.get);
61 |
62 | expect(RouterStub.route.calledWith('/books'));
63 | expect(rootRouteStub.get.calledWith(controllerStub.get));
64 | });
65 |
66 | test('should map root getBookById() router with controller::getBookById()', () => {
67 |
68 | idRouteStub.use('/books/:bookId', (request, response, next) => { });
69 |
70 | idRouteStub.get('/books/:bookId', controllerStub.getBookById);
71 |
72 | expect(RouterStub.route.calledWith('/books/:bookId'));
73 | expect(idRouteStub.get.calledWith(controllerStub.getBookById));
74 | });
75 |
76 | test('should map root post() router with controller::post()', () => {
77 |
78 | rootRouteStub.post('/books', controllerStub.post);
79 |
80 | expect(RouterStub.route.calledWith('/books'));
81 | expect(rootRouteStub.post.calledWith(controllerStub.post));
82 |
83 | });
84 |
85 | });
86 |
87 | });
88 |
89 |
--------------------------------------------------------------------------------
/bookstore-webapp/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
22 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
23 |
24 | /**
25 | * Web Animations `@angular/platform-browser/animations`
26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
28 | */
29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
30 |
31 | /**
32 | * By default, zone.js will patch all possible macroTask and DomEvents
33 | * user can disable parts of macroTask/DomEvents patch by setting following flags
34 | * because those flags need to be set before `zone.js` being loaded, and webpack
35 | * will put import in the top of bundle, so user need to create a separate file
36 | * in this directory (for example: zone-flags.ts), and put the following flags
37 | * into that file, and then add the following code before importing zone.js.
38 | * import './zone-flags';
39 | *
40 | * The flags allowed in zone-flags.ts are listed here.
41 | *
42 | * The following flags will work for all browsers.
43 | *
44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
47 | *
48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
50 | *
51 | * (window as any).__Zone_enable_cross_context_check = true;
52 | *
53 | */
54 |
55 | /***************************************************************************************************
56 | * Zone JS is required by default for Angular itself.
57 | */
58 | import 'zone.js/dist/zone'; // Included with Angular CLI.
59 |
60 |
61 | /***************************************************************************************************
62 | * APPLICATION IMPORTS
63 | */
64 |
--------------------------------------------------------------------------------
/bookstore-webapi/src/controllers/books.Controller.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const bookSchemaValidator = require('../models/book.SchemaValidator');
4 |
5 | function booksController(Book) {
6 |
7 | async function post(request, response) {
8 |
9 | console.log(`Input Received: ${JSON.stringify(request.body)}`);
10 |
11 | // We need to verify both Author Name and Title
12 | const isBookValid = bookSchemaValidator.validate(request.body);
13 |
14 | if (isBookValid.error) {
15 | console.log("validation result", isBookValid);
16 | return response.status(400).json(isBookValid.error);
17 | }
18 |
19 | // Verify if book's title with same author already exists.
20 | const similarBookExist = await Book.findOne({ author: request.body.author, title: request.body.title, language: request.body.language });
21 |
22 | if (similarBookExist) {
23 | console.log(`Does Similar Book Exists: ${similarBookExist}`);
24 | return response.status(400).json(`Book with "${request.body.title}" title exists from "${request.body.author}" author.`);
25 | }
26 |
27 | try {
28 | const book = await (Book.create(request.body))
29 |
30 | console.log(`Sending Output: ${JSON.stringify(book)}`);
31 | return response.status(201).json(book);
32 |
33 | } catch (error) {
34 | return response.status(500).json(error);
35 | }
36 |
37 | }
38 |
39 | async function get(request, response) {
40 | try {
41 |
42 | const allBooks = await Book.find({});
43 |
44 | if (allBooks && allBooks.length > 0) {
45 | return response.status(200).json(allBooks);
46 | } else {
47 | return response.status(404).json();
48 | }
49 |
50 | } catch (error) {
51 | return response.status(500).json(error);
52 | }
53 | }
54 |
55 | async function getBookById(request, response) {
56 | return response.status(200).json(request.book);
57 | }
58 |
59 | async function updateBookById(request, response) {
60 |
61 | console.log(`Book Id: ${JSON.parse(JSON.stringify(request.book))._id} | Complete Book: ${JSON.stringify(request.book)}`);
62 |
63 | Book.findByIdAndUpdate(request.params.bookId, request.body, {
64 | new: true,
65 | useFindAndModify: false,
66 | runValidators: true
67 | },
68 | function (error, book) {
69 | if (error) {
70 | return response.status(500).json(error);
71 | } else {
72 | return response.status(200).json({ 'success': true, 'Message': 'Book updated Successfully', data: book });
73 | }
74 | });
75 |
76 | }
77 |
78 | async function deleteBookById(request, response) {
79 |
80 | console.log(`Book Id: ${JSON.parse(JSON.stringify(request.book))._id} | Completed Book: ${JSON.stringify(request.book)}`);
81 |
82 | Book.findByIdAndDelete(request.book._id, function (error, book) {
83 | if (error) {
84 | return response.status(500).json(error);
85 | }
86 | else {
87 | return response.status(204).json({ 'success': true, 'Message': 'Book Deleted Successfully' });
88 | }
89 | });
90 |
91 | }
92 |
93 | return { post, get, getBookById, updateBookById, deleteBookById };
94 | }
95 |
96 | module.exports = booksController;
97 |
--------------------------------------------------------------------------------
/bookstore-webapp/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tslint:recommended",
3 | "rules": {
4 | "align": {
5 | "options": [
6 | "parameters",
7 | "statements"
8 | ]
9 | },
10 | "array-type": false,
11 | "arrow-return-shorthand": true,
12 | "curly": true,
13 | "deprecation": {
14 | "severity": "warning"
15 | },
16 | "component-class-suffix": true,
17 | "contextual-lifecycle": true,
18 | "directive-class-suffix": true,
19 | "directive-selector": [
20 | true,
21 | "attribute",
22 | "app",
23 | "camelCase"
24 | ],
25 | "component-selector": [
26 | true,
27 | "element",
28 | "app",
29 | "kebab-case"
30 | ],
31 | "eofline": true,
32 | "import-blacklist": [
33 | true,
34 | "rxjs/Rx"
35 | ],
36 | "import-spacing": true,
37 | "indent": {
38 | "options": [
39 | "spaces"
40 | ]
41 | },
42 | "max-classes-per-file": false,
43 | "max-line-length": [
44 | true,
45 | 140
46 | ],
47 | "member-ordering": [
48 | true,
49 | {
50 | "order": [
51 | "static-field",
52 | "instance-field",
53 | "static-method",
54 | "instance-method"
55 | ]
56 | }
57 | ],
58 | "no-console": [
59 | true,
60 | "debug",
61 | "info",
62 | "time",
63 | "timeEnd",
64 | "trace"
65 | ],
66 | "no-empty": false,
67 | "no-inferrable-types": [
68 | true,
69 | "ignore-params"
70 | ],
71 | "no-non-null-assertion": true,
72 | "no-redundant-jsdoc": true,
73 | "no-switch-case-fall-through": true,
74 | "no-var-requires": false,
75 | "object-literal-key-quotes": [
76 | true,
77 | "as-needed"
78 | ],
79 | "quotemark": [
80 | true,
81 | "single"
82 | ],
83 | "semicolon": {
84 | "options": [
85 | "always"
86 | ]
87 | },
88 | "space-before-function-paren": {
89 | "options": {
90 | "anonymous": "never",
91 | "asyncArrow": "always",
92 | "constructor": "never",
93 | "method": "never",
94 | "named": "never"
95 | }
96 | },
97 | "typedef": [
98 | true,
99 | "call-signature"
100 | ],
101 | "typedef-whitespace": {
102 | "options": [
103 | {
104 | "call-signature": "nospace",
105 | "index-signature": "nospace",
106 | "parameter": "nospace",
107 | "property-declaration": "nospace",
108 | "variable-declaration": "nospace"
109 | },
110 | {
111 | "call-signature": "onespace",
112 | "index-signature": "onespace",
113 | "parameter": "onespace",
114 | "property-declaration": "onespace",
115 | "variable-declaration": "onespace"
116 | }
117 | ]
118 | },
119 | "variable-name": {
120 | "options": [
121 | "ban-keywords",
122 | "check-format",
123 | "allow-pascal-case"
124 | ]
125 | },
126 | "whitespace": {
127 | "options": [
128 | "check-branch",
129 | "check-decl",
130 | "check-operator",
131 | "check-separator",
132 | "check-type",
133 | "check-typecast"
134 | ]
135 | },
136 | "no-conflicting-lifecycle": true,
137 | "no-host-metadata-property": true,
138 | "no-input-rename": true,
139 | "no-inputs-metadata-property": true,
140 | "no-output-native": true,
141 | "no-output-on-prefix": true,
142 | "no-output-rename": true,
143 | "no-outputs-metadata-property": true,
144 | "template-banana-in-box": true,
145 | "template-no-negated-async": true,
146 | "use-lifecycle-interface": true,
147 | "use-pipe-transform-interface": true
148 | },
149 | "rulesDirectory": [
150 | "codelyzer"
151 | ]
152 | }
--------------------------------------------------------------------------------
/bookstore-webapp/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "bookstore-webapp": {
7 | "projectType": "application",
8 | "schematics": {},
9 | "root": "",
10 | "sourceRoot": "src",
11 | "prefix": "app",
12 | "architect": {
13 | "build": {
14 | "builder": "@angular-devkit/build-angular:browser",
15 | "options": {
16 | "outputPath": "dist/bookstore-webapp",
17 | "index": "src/index.html",
18 | "main": "src/main.ts",
19 | "polyfills": "src/polyfills.ts",
20 | "tsConfig": "tsconfig.app.json",
21 | "aot": true,
22 | "assets": [
23 | "src/favicon.ico",
24 | "src/assets"
25 | ],
26 | "styles": [
27 | "src/styles.css",
28 | "./node_modules/font-awesome/css/font-awesome.css",
29 | "./node_modules/bootstrap/dist/css/bootstrap.css"
30 | ],
31 | "scripts": [
32 | "./node_modules/jquery/dist/jquery.js",
33 | "./node_modules/popper.js/dist/umd/popper.js",
34 | "./node_modules/bootstrap/dist/js/bootstrap.js"
35 | ]
36 | },
37 | "configurations": {
38 | "production": {
39 | "fileReplacements": [
40 | {
41 | "replace": "src/environments/environment.ts",
42 | "with": "src/environments/environment.prod.ts"
43 | }
44 | ],
45 | "optimization": true,
46 | "outputHashing": "all",
47 | "sourceMap": false,
48 | "extractCss": true,
49 | "namedChunks": false,
50 | "extractLicenses": true,
51 | "vendorChunk": false,
52 | "buildOptimizer": true,
53 | "budgets": [
54 | {
55 | "type": "initial",
56 | "maximumWarning": "2mb",
57 | "maximumError": "5mb"
58 | },
59 | {
60 | "type": "anyComponentStyle",
61 | "maximumWarning": "6kb",
62 | "maximumError": "10kb"
63 | }
64 | ]
65 | }
66 | }
67 | },
68 | "serve": {
69 | "builder": "@angular-devkit/build-angular:dev-server",
70 | "options": {
71 | "browserTarget": "bookstore-webapp:build",
72 | "port": 5003
73 | },
74 | "configurations": {
75 | "production": {
76 | "browserTarget": "bookstore-webapp:build:production"
77 | }
78 | }
79 | },
80 | "extract-i18n": {
81 | "builder": "@angular-devkit/build-angular:extract-i18n",
82 | "options": {
83 | "browserTarget": "bookstore-webapp:build"
84 | }
85 | },
86 | "test": {
87 | "builder": "@angular-devkit/build-angular:karma",
88 | "options": {
89 | "main": "src/test.ts",
90 | "polyfills": "src/polyfills.ts",
91 | "tsConfig": "tsconfig.spec.json",
92 | "karmaConfig": "karma.conf.js",
93 | "assets": [
94 | "src/favicon.ico",
95 | "src/assets"
96 | ],
97 | "styles": [
98 | "src/styles.css"
99 | ],
100 | "scripts": []
101 | }
102 | },
103 | "lint": {
104 | "builder": "@angular-devkit/build-angular:tslint",
105 | "options": {
106 | "tsConfig": [
107 | "tsconfig.app.json",
108 | "tsconfig.spec.json",
109 | "e2e/tsconfig.json"
110 | ],
111 | "exclude": [
112 | "**/node_modules/**"
113 | ]
114 | }
115 | },
116 | "e2e": {
117 | "builder": "@angular-devkit/build-angular:protractor",
118 | "options": {
119 | "protractorConfig": "e2e/protractor.conf.js",
120 | "devServerTarget": "bookstore-webapp:serve"
121 | },
122 | "configurations": {
123 | "production": {
124 | "devServerTarget": "bookstore-webapp:serve:production"
125 | }
126 | }
127 | }
128 | }
129 | }
130 | },
131 | "defaultProject": "bookstore-webapp",
132 | "cli": {
133 | "analytics": "3ca1c347-709d-4f19-87e5-822d599aa016"
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/bookstore-webapi/tests/unit/books.Controller.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Book = require('../../src/models/book.Model');
4 | const booksController = require('../../src/controllers/books.Controller')(Book);
5 | const httpMock = require('node-mocks-http');
6 |
7 | describe('Testing books.Controller /src/controllers/books.Controller.js', () => {
8 |
9 | // Variables.
10 | let request, response;
11 |
12 | const _book = {
13 | 'author': 'Dummy Author',
14 | 'title': 'Node JS',
15 | 'dateOfPublish': '01-Jan-2020',
16 | 'language': "JavaScript",
17 | 'read': false
18 | };
19 |
20 | const _bookInvalid = {
21 | title: 'Node JS',
22 | dateOfPublish: '01-Jan-2020',
23 | language: "JavaScript",
24 | read: false
25 | };
26 |
27 | const _bookExists = {
28 | dateOfPublish: '2020 - 07 - 07T03: 37: 43.000Z',
29 | language: 'Python',
30 | read: false,
31 | _id: '5f03ee290d4b4a1198c4e1e8',
32 | author: 'Viswanatha Swamy',
33 | title: '4th Book',
34 | __v: 0
35 | };
36 |
37 | beforeEach(() => {
38 | request = httpMock.createRequest();
39 | response = httpMock.createResponse();
40 |
41 | request.book = _book;
42 | Book.find = jest.fn();
43 | });
44 |
45 | afterEach(() => {
46 | Book.find.mockClear();
47 |
48 | request.book = {};
49 | });
50 |
51 | // getBookById() return 200
52 | describe('Books Controller :: getBookById()', () => {
53 |
54 | test('getBookById() function is defined', async (done) => {
55 |
56 | expect(typeof booksController.getBookById).toBe('function');
57 |
58 | done();
59 | });
60 |
61 | test('getBookById() function should return 200', async (done) => {
62 |
63 | await booksController.getBookById(request, response);
64 |
65 | expect(response.statusCode).toBe(200);
66 | expect(response._getJSONData()).toStrictEqual(_book);
67 |
68 | done();
69 | });
70 |
71 | });
72 |
73 | // get() Returns all the books. 200, 404 OR 500
74 | describe('Books Controller :: get()', () => {
75 |
76 | test('get() function is defined', async () => {
77 |
78 | expect(typeof booksController.get).toBe('function');
79 |
80 | });
81 |
82 | test('get() function should return 404', async (done) => {
83 |
84 | Book.find = jest.fn().mockReturnValue([]);
85 |
86 | await booksController.get(request, response);
87 |
88 | expect(response.statusCode).toBe(404);
89 |
90 | done();
91 | });
92 |
93 | test('get() function should return 200', async (done) => {
94 | Book.find = jest.fn().mockResolvedValue([_book]);
95 |
96 | await booksController.get(request, response);
97 |
98 | expect(response.statusCode).toBe(200);
99 |
100 | done();
101 | });
102 |
103 | test('get() function should return 500', async (done) => {
104 | Book.find = jest.fn().mockRejectedValue('Dummy Error');
105 |
106 | await booksController.get(request, response);
107 |
108 | expect(response.statusCode).toBe(500);
109 |
110 | done();
111 | });
112 |
113 | });
114 |
115 | // post() return 200, 400, and 500
116 | describe('Books Controller :: post()', () => {
117 |
118 | test('post() function is defined', async (done) => {
119 |
120 | expect(typeof booksController.post).toBe('function');
121 |
122 | done();
123 | });
124 |
125 | test('post() function should return 400 when Invalid request is sent', async (done) => {
126 |
127 | request.body = _bookInvalid;
128 |
129 | await booksController.post(request, response);
130 |
131 | expect(response.statusCode).toBe(400);
132 |
133 | done();
134 | });
135 |
136 | test('post() function should return 400 when record already exists', async (done) => {
137 |
138 | request.body = _book;
139 |
140 | Book.findOne = jest.fn().mockReturnValue(_bookExists);
141 |
142 | await booksController.post(request, response);
143 |
144 | expect(response.statusCode).toBe(400);
145 |
146 | console.log(`Response: ${JSON.stringify(response._getJSONData())}`);
147 |
148 | done();
149 | });
150 |
151 | test('post() function should return 201 when record does not exists', async (done) => {
152 |
153 | request.body = _book;
154 |
155 | Book.findOne = jest.fn().mockReturnValue(null);
156 | Book.create = jest.fn().mockReturnValue(_book);
157 |
158 | await booksController.post(request, response);
159 |
160 | expect(response.statusCode).toBe(201);
161 |
162 | done();
163 | });
164 |
165 | test('post() function should return 500 when it fail to create', async (done) => {
166 |
167 | request.body = _book;
168 |
169 | Book.findOne = jest.fn().mockReturnValue(null);
170 | Book.create = jest.fn().mockRejectedValue('Unable to save');
171 |
172 | await booksController.post(request, response);
173 |
174 | expect(response.statusCode).toBe(500);
175 |
176 | done();
177 | });
178 |
179 | });
180 |
181 | });
182 |
183 | // console.log(`Response: ${JSON.stringify(response)}`);
184 | // expect(response._getJSONData()).toStrictEqual(_book);
185 | // console.log(`Request.Book: ${JSON.stringify(request.book)}`);
186 | // expect(response._getJSONData()).toStrictEqual(_book);
187 | // console.log(`Response: ${JSON.stringify(response._getJSONData())}`);
188 | // console.log(`Response: ${JSON.stringify(response._getJSONData())}`);
189 | // console.log(`Response: ${JSON.stringify(response._getJSONData())}`);
190 | // console.log(`Output Received: ${JSON.stringify(response._getJSONData())}`);
--------------------------------------------------------------------------------