├── 1.2 - Setting up ├── package-lock.json ├── package.json ├── spec │ ├── desktop.ini │ └── main.spec.js └── src │ ├── desktop.ini │ └── main.js ├── 1.4 - TDD ├── .vscode │ └── settings.json ├── package-lock.json ├── package.json ├── spec │ ├── desktop.ini │ └── main.spec.js └── src │ ├── desktop.ini │ └── main.js ├── 2.2 - Unit testing best practices ├── .vscode │ └── settings.json ├── package-lock.json ├── package.json ├── spec │ ├── desktop.ini │ └── main.spec.js └── src │ ├── desktop.ini │ └── main.js ├── 2.3 - Assertions ├── .vscode │ └── settings.json ├── package-lock.json ├── package.json ├── src │ ├── desktop.ini │ └── main.js └── test │ ├── desktop.ini │ └── main.spec.js ├── 2.4 - Comparing frameworks ├── jasmine │ ├── desktop.ini │ ├── jasmine.json │ ├── package-lock.json │ ├── package.json │ ├── spec │ │ ├── helpers │ │ │ └── reporter.js │ │ ├── main.spec.js │ │ └── node-spec.js1 │ └── src │ │ └── main.js ├── jest │ ├── desktop.ini │ ├── package-lock.json │ ├── package.json │ └── src │ │ ├── __tests__ │ │ └── main.spec.js │ │ └── main.js └── mocha │ ├── desktop.ini │ ├── package-lock.json │ ├── package.json │ ├── spec │ └── main.spec.js │ └── src │ └── main.js ├── 2.5 - Typescript testing ├── jasmine │ ├── jasmine.json │ ├── package-lock.json │ ├── package.json │ ├── spec │ │ ├── jasmine.json │ │ └── main.spec.ts │ ├── src │ │ ├── desktop.ini │ │ ├── main.spec.ts │ │ └── main.ts │ └── tsconfig.json ├── jest │ ├── jest.config.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── desktop.ini │ │ ├── main.spec.ts │ │ └── main.ts │ └── tsconfig.json └── mocha │ ├── desktop.ini │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── desktop.ini │ ├── main.spec.ts │ └── main.ts │ └── tsconfig.json ├── 3.1 - Test doubles importance ├── package-lock.json ├── package.json ├── spec │ └── main.spec.js └── src │ └── main.js ├── 3.3 - SinonJS mocking in practice ├── .vscode │ ├── desktop.ini │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── package-lock.json ├── package.json ├── src │ ├── desktop.ini │ └── main.js └── test │ ├── desktop.ini │ └── main.spec.js ├── 3.4 - Mocking for API tests ├── .vscode │ ├── desktop.ini │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── desktop.ini ├── jsrv │ ├── db.json │ └── routes.json ├── package-lock.json ├── package.json ├── runsrv.cmd ├── spec │ ├── desktop.ini │ └── main.spec.js └── src │ ├── desktop.ini │ └── main.js ├── 4.1 - Sample App & Testing ├── BackEnd │ ├── .vscode │ │ ├── desktop.ini │ │ ├── launch.json │ │ ├── settings.json │ │ └── tasks.json │ ├── Dockerfile │ ├── config.json │ ├── gulpfile.js │ ├── package-lock.json │ ├── package.json │ ├── run.bat │ ├── songs.json │ ├── src │ │ ├── app.js │ │ ├── app.js.map │ │ ├── app.ts │ │ ├── config.json │ │ ├── dal │ │ │ ├── dal-playlists.js │ │ │ ├── dal-playlists.js.map │ │ │ ├── dal-playlists.ts │ │ │ ├── dal-songs.js │ │ │ ├── dal-songs.js.map │ │ │ ├── dal-songs.ts │ │ │ ├── desktop.ini │ │ │ ├── etcd.ts1 │ │ │ ├── redis-cache.ts1 │ │ │ ├── service-config.js │ │ │ ├── service-config.js.map │ │ │ └── service-config.ts │ │ ├── desktop.ini │ │ ├── logger.js │ │ ├── logger.js.map │ │ ├── logger.ts │ │ ├── songs.json │ │ └── types │ │ │ ├── config.js │ │ │ ├── config.js.map │ │ │ ├── config.ts │ │ │ ├── desktop.ini │ │ │ ├── list-item.js │ │ │ ├── list-item.js.map │ │ │ ├── list-item.ts │ │ │ ├── playlist.js │ │ │ ├── playlist.js.map │ │ │ ├── playlist.ts │ │ │ ├── song.js │ │ │ ├── song.js.map │ │ │ └── song.ts │ ├── test │ │ ├── desktop.ini │ │ ├── playlist-api-spec.test.js │ │ ├── playlist-api-spec.test.js.map │ │ ├── playlist-api-spec.test.ts │ │ ├── playlist-db-spec.test.js │ │ ├── playlist-db-spec.test.js.map │ │ └── playlist-db-spec.test.ts │ ├── tsconfig.json │ └── tslint.json └── FrontEnd │ ├── .gitignore │ ├── Dockerfile │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── desktop.ini │ ├── favicon.ico │ ├── img │ │ ├── desktop.ini │ │ └── icons │ │ │ ├── android-chrome-192x192.png │ │ │ ├── android-chrome-512x512.png │ │ │ ├── apple-touch-icon-120x120.png │ │ │ ├── apple-touch-icon-152x152.png │ │ │ ├── apple-touch-icon-180x180.png │ │ │ ├── apple-touch-icon-60x60.png │ │ │ ├── apple-touch-icon-76x76.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── desktop.ini │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── msapplication-icon-144x144.png │ │ │ ├── mstile-150x150.png │ │ │ └── safari-pinned-tab.svg │ ├── index.html │ ├── manifest.json │ └── robots.txt │ ├── run.bat │ ├── src │ ├── App.vue │ ├── assets │ │ ├── backspace16.png │ │ ├── delb16.png │ │ ├── desktop.ini │ │ ├── googleButton.png │ │ ├── logo.png │ │ └── musical-note.png │ ├── components │ │ ├── HelloWorld.vue │ │ ├── Songs.vue │ │ └── desktop.ini │ ├── desktop.ini │ ├── main.ts │ ├── registerServiceWorker.ts │ ├── router.ts │ ├── shims-tsx.d.ts │ ├── shims-vue.d.ts │ └── views │ │ ├── Home.vue │ │ ├── Login.vue │ │ └── desktop.ini │ ├── tsconfig.json │ └── tslint.json ├── 4.3 - API tests for sample app ├── .vscode │ ├── launch.json │ └── settings.json ├── package-lock.json ├── package.json └── test │ ├── desktop.ini │ └── playlist-api-spec.test.js ├── 4.4 - UI tests for sample app ├── .vscode │ ├── launch.json │ └── settings.json ├── package-lock.json ├── package.json ├── src │ └── index.js └── test │ ├── playlist-ui-cypress-spec.js │ ├── playlist-ui-puppeteer-spec.js │ └── playlist-ui-selenium-spec.js ├── LICENSE └── README.md /1.2 - Setting up/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mocha-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha ./spec/*.js --exit -s 0" 8 | }, 9 | "author": "amit bezalel", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "chai": "^4.2.0", 13 | "mocha": "^6.1.4" 14 | }, 15 | "dependencies": {} 16 | } 17 | -------------------------------------------------------------------------------- /1.2 - Setting up/spec/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /1.2 - Setting up/spec/main.spec.js: -------------------------------------------------------------------------------- 1 | const expect = require ("chai").expect; 2 | 3 | describe("dummy test",()=>{ 4 | it("should return false", async ()=>{ 5 | expect(false,"the value should be false").to.be.false; 6 | }); 7 | it("should return true", async ()=>{ 8 | expect(true,"the value should be true").to.be.true; 9 | }); 10 | }); -------------------------------------------------------------------------------- /1.2 - Setting up/src/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /1.2 - Setting up/src/main.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /1.4 - TDD/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "mochaExplorer.files": "spec/**/*.js", 3 | "mochaExplorer.require": "", 4 | "mochaExplorer.timeout": 10000 5 | } 6 | -------------------------------------------------------------------------------- /1.4 - TDD/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mocha-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha ./spec/*spec*.js --exit -s 0" 8 | }, 9 | "author": "amit bezalel", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "chai": "^4.2.0", 13 | "mocha": "^6.1.4" 14 | }, 15 | "dependencies": {} 16 | } 17 | -------------------------------------------------------------------------------- /1.4 - TDD/spec/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /1.4 - TDD/spec/main.spec.js: -------------------------------------------------------------------------------- 1 | const expect = require("chai").expect; 2 | const User = require("../src/main.js").User; 3 | 4 | describe("dummy test", () => { 5 | it("email can be set", async () => { 6 | let user = new User(); 7 | user.setEmail("amit@gmail.com"); 8 | expect(user.email).to.equal("amit@gmail.com"); 9 | }); 10 | 11 | it("email will not be set if its format is bad", async () => { 12 | let user = new User(); 13 | user.setEmail("amitgmail.com"); 14 | expect(user.email).to.be.undefined; 15 | }); 16 | 17 | }); -------------------------------------------------------------------------------- /1.4 - TDD/src/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /1.4 - TDD/src/main.js: -------------------------------------------------------------------------------- 1 | class User { 2 | constructor() { 3 | this.firstName = undefined; 4 | this.lastName = undefined; 5 | this.email = undefined; 6 | } 7 | setEmail(anEmail) { 8 | if (anEmail.match(/[\w]+\@[\w]+\.com|org|net/i)) { 9 | this.email = anEmail; 10 | } 11 | } 12 | } 13 | 14 | module.exports.User = User; 15 | -------------------------------------------------------------------------------- /2.2 - Unit testing best practices/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "mochaExplorer.files": "spec/**/*.js", 3 | "mochaExplorer.require": "", 4 | "mochaExplorer.timeout": 10000 5 | } 6 | -------------------------------------------------------------------------------- /2.2 - Unit testing best practices/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mocha-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha spec/*spec*.js --exit -s 0" 8 | }, 9 | "author": "Amit Bezalel", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "chai": "^4.2.0", 13 | "mocha": "^6.1.4" 14 | }, 15 | "dependencies": {} 16 | } 17 | -------------------------------------------------------------------------------- /2.2 - Unit testing best practices/spec/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /2.2 - Unit testing best practices/spec/main.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require ("chai"); 2 | //ex4: using variables 3 | let counter; 4 | 5 | //ex2: using hooks (before and after for initializations) 6 | before(async function(){ 7 | counter = 0; 8 | // runs before all tests in this block 9 | console.log("-----before all-----"); 10 | }); 11 | 12 | after(async function(){ 13 | // runs after all tests in this block 14 | console.log("-----after all-----"); 15 | }); 16 | 17 | beforeEach(async function(){ 18 | // runs before each test in this block 19 | console.log("before each"); 20 | }); 21 | 22 | afterEach(async function(){ 23 | // runs after each test in this block 24 | console.log("after each"); 25 | }); 26 | 27 | //ex1: 28 | // a. multiDecribe-it-assertion testing, 29 | // b. importance of text and standard patterns of describe, it and expect messages 30 | // b. using async await for testing 31 | // c. using functions, not arrow functions =>, so you can access "this" for mocha context 32 | // d. tests shold be readable and maintainable, looking around for reusable value definitions or jumping around the code for contants which can be good for code, is bad for testing code 33 | // d. helper functions: try to keep most logic in the test, here duplication can be ok. Long logic can be separated out, but try to keep the test compact and local 34 | // e. test only public APIs not private functions, testing publics ensures your component performs as expected, why put constraints on how it gets there? 35 | // e. BDD tells us to use product doc definintions as our test cases, this makes sure we test for public functionality. 36 | describe("dummy test",async function(){ 37 | 38 | it("should return false",async function(){ 39 | chai.expect(false,"the value should be false").to.be.false; 40 | }); 41 | it("should return true", async function(){ 42 | chai.expect(true,"the value should be true").to.be.true; 43 | }); 44 | 45 | //ex4: using retries 46 | it("counter should eventually reach 3", async function(){ 47 | this.retries(4); 48 | console.log(counter); 49 | ++counter; 50 | chai.expect(counter,"couter should be 3").to.equal(3); 51 | }); 52 | //ex3: skipping 53 | it.skip("should be skipped", async function(){ 54 | chai.expect(true,"the value should be true").to.be.true; 55 | }); 56 | }); -------------------------------------------------------------------------------- /2.2 - Unit testing best practices/src/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /2.2 - Unit testing best practices/src/main.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /2.3 - Assertions/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "mochaExplorer.files": "test/**/*.js", 3 | "mochaExplorer.require": "", 4 | "mochaExplorer.timeout": 10000 5 | } 6 | -------------------------------------------------------------------------------- /2.3 - Assertions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mocha-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha test/*.js --exit -s 0" 8 | }, 9 | "author": "Amit Bezalel", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "chai": "^4.2.0", 13 | "mocha": "^6.1.4" 14 | }, 15 | "dependencies": { 16 | "chai-http": "^4.3.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /2.3 - Assertions/src/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /2.3 - Assertions/src/main.js: -------------------------------------------------------------------------------- 1 | function makeIceCream(flavor) { 2 | if (flavor == "Chocolate" || flavor == "Vanilla") { 3 | return { 4 | cone: true, 5 | flavor: flavor, 6 | topping: "hotChocolate", 7 | } 8 | } else throw ("invalid flavor"); 9 | 10 | } 11 | 12 | 13 | module.exports.makeIceCream = makeIceCream; -------------------------------------------------------------------------------- /2.3 - Assertions/test/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /2.3 - Assertions/test/main.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const assert = chai.assert; // Using Assert style 3 | const expect = chai.expect; // Using Expect style 4 | const should = chai.should; // Using Should style 5 | const makeIceCream = require("../src/main").makeIceCream; 6 | should(); // Modifies `Object.prototype` 7 | 8 | 9 | 10 | //chai plugins 11 | const chaiHttp = require("chai-http"); // Using Should style 12 | chai.use(chaiHttp); 13 | 14 | describe("assertion style tests", async function () { 15 | let response = { 16 | status: 200, 17 | body: { 18 | deletionConfirmation: { 19 | name: "Big Hat", 20 | _id: 11 21 | } 22 | } 23 | } 24 | 25 | let opComplete = true; 26 | var numbers = [1, 2, 3, 4, 5]; 27 | var numbers1 = [1, 2, 3, 4, 5]; 28 | let myNullVar = null; 29 | 30 | // assert is less expressive, more programmatic 31 | // but tests should be very readable 32 | it("assert style", async function () { 33 | 34 | assert.isTrue(opComplete, "operation should be complete"); 35 | assert.isArray(numbers, "numbers should be an array"); 36 | assert.include(numbers, 2, "numbers should contain 2"); 37 | assert.lengthOf(numbers, 5, "numbers should contains 5 numbers"); 38 | assert.includeDeepOrderedMembers(numbers, [1, 2, 3]); 39 | 40 | assert.notEqual(numbers, numbers1); //shallow 41 | assert.deepStrictEqual(numbers, numbers1); // deep 42 | }); 43 | 44 | // expect is very expressive, with most lines reading very naturally 45 | // it by far the most popular assertion format 46 | it("expect style", async function () { 47 | // an additional message can be provided to explain the error: 48 | expect(opComplete, "operation should be complete").to.be.true; 49 | 50 | // this is almost plain english: 51 | expect(numbers).to.be.an("array").of.length(5).that.includes(2); 52 | expect(numbers).to.have.lengthOf(5); 53 | 54 | //shallow vs. deep equality 55 | expect(numbers).not.to.equal(numbers1); //shallow - pointers not equal 56 | expect(numbers).to.deep.equal(numbers1); // deep 57 | expect(numbers).to.eql(numbers1); // deep 58 | expect(numbers).to.have.ordered.members([1, 2, 3, 4, 5]); //ordered members 59 | 60 | // testing an object: 61 | expect(response.status).to.equal(200); 62 | expect(response.body).to.be.an("object").with.property("deletionConfirmation"); 63 | expect(response.body.deletionConfirmation) 64 | .to.be.an("object") 65 | .with.property("name") 66 | .that.equals("Big Hat"); 67 | }); 68 | 69 | // should style is very similar to expect, it is arguably even more natural than expect 70 | // but this comes at a cost of adding functions on the Object prototype 71 | // this may seldomely cause issues, which is the main reason for not using should. 72 | it("should style", async function () { 73 | opComplete.should.be.true; 74 | 75 | numbers.should.be.an("array").that.includes(2); 76 | numbers.should.have.lengthOf(5); 77 | 78 | //myVar.should.equal(null); 79 | 80 | response.status.should.equal(200); 81 | 82 | response.body.should.be.an("object"); 83 | response.body.should.have.property("deletionConfirmation"); 84 | response.body.deletionConfirmation.should.be.an("object").with.property("name").that.equals("Big Hat"); 85 | // response.body.deletionConfirmation.should.have.property("name"); 86 | // response.body.deletionConfirmation.should.have.property("_id"); 87 | // response.body.deletionConfirmation.name.should.equal("Big Hat"); 88 | }); 89 | 90 | //assertion readability: bad example 91 | it("sad path is hard to understand", async () => { 92 | let myError; 93 | let result; 94 | try { 95 | result = makeIceCream("Janilla"); 96 | } 97 | catch (error) { 98 | assert.equal(error, "invalid flavor"); 99 | myError = error; 100 | } 101 | assert.isNotNull(myError); 102 | 103 | //one-liner alternatives: 104 | assert.throws(makeIceCream.bind(null, "Janilla"), "invalid flavor"); 105 | expect(makeIceCream.bind(null, "Janilla")).to.throw("invalid flavor"); 106 | makeIceCream.bind(null, "Janilla").should.throw("invalid flavor"); 107 | }); 108 | 109 | 110 | //chai plugins 111 | it('test chai http plugin', async function () { // <= No done callback 112 | let res = await chai.request("https://www.boredapi.com/api/activity") 113 | .get('/'); 114 | 115 | expect(res.body, "got error: " + res.body.error).to.not.have.property("error"); 116 | expect(res).to.have.status(200); 117 | expect(res.body).to.be.an("object"); 118 | expect(res.body).to.have.property("activity").that.is.a.string; 119 | expect(res.body.activity).to.be.a.string; 120 | expect(res.body.participants).to.eql(1); 121 | }); 122 | }); -------------------------------------------------------------------------------- /2.4 - Comparing frameworks/jasmine/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /2.4 - Comparing frameworks/jasmine/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js" 8 | ], 9 | "stopSpecOnExpectationFailure": false, 10 | "random": false 11 | } 12 | -------------------------------------------------------------------------------- /2.4 - Comparing frameworks/jasmine/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jasmine-test", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "balanced-match": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 10 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 11 | "dev": true 12 | }, 13 | "brace-expansion": { 14 | "version": "1.1.11", 15 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 16 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 17 | "dev": true, 18 | "requires": { 19 | "balanced-match": "^1.0.0", 20 | "concat-map": "0.0.1" 21 | } 22 | }, 23 | "colors": { 24 | "version": "1.1.2", 25 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", 26 | "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", 27 | "dev": true 28 | }, 29 | "concat-map": { 30 | "version": "0.0.1", 31 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 32 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 33 | "dev": true 34 | }, 35 | "fs.realpath": { 36 | "version": "1.0.0", 37 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 38 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 39 | "dev": true 40 | }, 41 | "glob": { 42 | "version": "7.1.4", 43 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", 44 | "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", 45 | "dev": true, 46 | "requires": { 47 | "fs.realpath": "^1.0.0", 48 | "inflight": "^1.0.4", 49 | "inherits": "2", 50 | "minimatch": "^3.0.4", 51 | "once": "^1.3.0", 52 | "path-is-absolute": "^1.0.0" 53 | } 54 | }, 55 | "inflight": { 56 | "version": "1.0.6", 57 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 58 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 59 | "dev": true, 60 | "requires": { 61 | "once": "^1.3.0", 62 | "wrappy": "1" 63 | } 64 | }, 65 | "inherits": { 66 | "version": "2.0.3", 67 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 68 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 69 | "dev": true 70 | }, 71 | "jasmine": { 72 | "version": "3.4.0", 73 | "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.4.0.tgz", 74 | "integrity": "sha512-sR9b4n+fnBFDEd7VS2el2DeHgKcPiMVn44rtKFumq9q7P/t8WrxsVIZPob4UDdgcDNCwyDqwxCt4k9TDRmjPoQ==", 75 | "dev": true, 76 | "requires": { 77 | "glob": "^7.1.3", 78 | "jasmine-core": "~3.4.0" 79 | } 80 | }, 81 | "jasmine-core": { 82 | "version": "3.4.0", 83 | "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.4.0.tgz", 84 | "integrity": "sha512-HU/YxV4i6GcmiH4duATwAbJQMlE0MsDIR5XmSVxURxKHn3aGAdbY1/ZJFmVRbKtnLwIxxMJD7gYaPsypcbYimg==", 85 | "dev": true 86 | }, 87 | "jasmine-spec-reporter": { 88 | "version": "4.2.1", 89 | "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz", 90 | "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==", 91 | "dev": true, 92 | "requires": { 93 | "colors": "1.1.2" 94 | } 95 | }, 96 | "minimatch": { 97 | "version": "3.0.4", 98 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 99 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 100 | "dev": true, 101 | "requires": { 102 | "brace-expansion": "^1.1.7" 103 | } 104 | }, 105 | "once": { 106 | "version": "1.4.0", 107 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 108 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 109 | "dev": true, 110 | "requires": { 111 | "wrappy": "1" 112 | } 113 | }, 114 | "path-is-absolute": { 115 | "version": "1.0.1", 116 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 117 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 118 | "dev": true 119 | }, 120 | "wrappy": { 121 | "version": "1.0.2", 122 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 123 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 124 | "dev": true 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /2.4 - Comparing frameworks/jasmine/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jasmine-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "./node_modules/.bin/jasmine --config=jasmine.json" 8 | }, 9 | "author": "amit bezalel", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jasmine": "^3.4.0", 13 | "jasmine-spec-reporter": "^4.2.1" 14 | }, 15 | "dependencies": {} 16 | } 17 | -------------------------------------------------------------------------------- /2.4 - Comparing frameworks/jasmine/spec/helpers/reporter.js: -------------------------------------------------------------------------------- 1 | const SpecReporter = require('jasmine-spec-reporter').SpecReporter; 2 | 3 | jasmine.getEnv().clearReporters(); // remove default reporter logs 4 | jasmine.getEnv().addReporter(new SpecReporter({ // add jasmine-spec-reporter 5 | spec: { 6 | displayPending: true, 7 | }, 8 | summary: { 9 | displayDuration: false, 10 | } 11 | })); 12 | -------------------------------------------------------------------------------- /2.4 - Comparing frameworks/jasmine/spec/main.spec.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | beforeAll(async ()=>{ 4 | console.log("before all"); 5 | }); 6 | 7 | beforeEach(async ()=>{ 8 | console.log("before each"); 9 | }); 10 | 11 | afterEach(async ()=>{ 12 | console.log("after each") 13 | }); 14 | 15 | afterAll(async ()=>{ 16 | console.log("after all"); 17 | }); 18 | 19 | describe("dummy test",()=>{ 20 | it("should return false",async ()=>{ 21 | expect(false).toBe(false); 22 | }); 23 | it("should return true", async ()=>{ 24 | expect(true).toBe(true); 25 | }); 26 | }); -------------------------------------------------------------------------------- /2.4 - Comparing frameworks/jasmine/spec/node-spec.js1: -------------------------------------------------------------------------------- 1 | describe('first suite', () => { 2 | it('should be ok', () => { 3 | expect(true).toBe(true); 4 | }); 5 | 6 | it('should be pending', () => { 7 | pending('will work soon'); 8 | expect(true).toBe(true); 9 | }); 10 | 11 | it('should failed', () => { 12 | expect(true).toBe(false); 13 | }); 14 | 15 | it('should be ok', () => { 16 | expect(true).toBe(true); 17 | }); 18 | }); 19 | 20 | describe('second suite', () => { 21 | xit('should be pending', () => { 22 | expect(true).toBe(false); 23 | }); 24 | 25 | it('should be ok', () => { 26 | expect(true).toBe(true); 27 | }); 28 | 29 | describe('first child suite', () => { 30 | describe('first grandchild suite', () => { 31 | it('should failed', () => { 32 | expect(true).toBe(false); 33 | expect(true).toBe(false); 34 | expect(true).toBe(true); 35 | }); 36 | 37 | it('should failed', () => { 38 | expect(true).toBe(false); 39 | }); 40 | 41 | it('should be ok', () => { 42 | expect(true).toBe(true); 43 | }); 44 | }); 45 | 46 | describe('second grandchild suite', () => { 47 | it('should failed', () => { 48 | expect(true).toBe(false); 49 | }); 50 | 51 | it('should be ok', () => { 52 | expect(true).toBe(true); 53 | }); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /2.4 - Comparing frameworks/jasmine/src/main.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /2.4 - Comparing frameworks/jest/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /2.4 - Comparing frameworks/jest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jest-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest" 8 | }, 9 | "author": "amit bezalel", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jest": "^24.8.0" 13 | }, 14 | "dependencies": {} 15 | } 16 | -------------------------------------------------------------------------------- /2.4 - Comparing frameworks/jest/src/__tests__/main.spec.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | beforeAll(async ()=>{ 4 | console.log("before all"); 5 | }); 6 | 7 | beforeEach(async ()=>{ 8 | console.log("before each"); 9 | }); 10 | 11 | afterEach(async ()=>{ 12 | console.log("after each") 13 | }); 14 | 15 | afterAll(async ()=>{ 16 | console.log("after all"); 17 | }); 18 | 19 | test('should return false given external link', async () => { 20 | expect(false).toBe(false) 21 | }) 22 | 23 | test('should return true given internal link', async () => { 24 | expect(true).toBe(true) 25 | }) -------------------------------------------------------------------------------- /2.4 - Comparing frameworks/jest/src/main.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /2.4 - Comparing frameworks/mocha/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /2.4 - Comparing frameworks/mocha/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mocha-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha spec/*spec*.js --exit -s 0" 8 | }, 9 | "author": "amit bezalel", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "chai": "^4.2.0", 13 | "mocha": "^6.1.4" 14 | }, 15 | "dependencies": {} 16 | } 17 | -------------------------------------------------------------------------------- /2.4 - Comparing frameworks/mocha/spec/main.spec.js: -------------------------------------------------------------------------------- 1 | const expect = require ("chai").expect; 2 | before(async ()=>{ 3 | // runs before all tests in this block 4 | console.log("before all"); 5 | }); 6 | 7 | after(async ()=>{ 8 | // runs after all tests in this block 9 | console.log("after all"); 10 | }); 11 | 12 | beforeEach(async ()=>{ 13 | // runs before each test in this block 14 | console.log("before each"); 15 | }); 16 | 17 | afterEach(async ()=>{ 18 | // runs after each test in this block 19 | console.log("after each"); 20 | }); 21 | 22 | 23 | describe("dummy test",()=>{ 24 | it("should return false",async ()=>{ 25 | expect(false,"the value should be false").to.be.false; 26 | }); 27 | it("should return true", async ()=>{ 28 | expect(true,"the value should be true").to.be.true; 29 | }); 30 | }); -------------------------------------------------------------------------------- /2.4 - Comparing frameworks/mocha/src/main.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /2.5 - Typescript testing/jasmine/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "src", 3 | "spec_files": [ 4 | "*.spec.ts" 5 | ], 6 | "helpers": [ 7 | 8 | ], 9 | "stopSpecOnExpectationFailure": false, 10 | "random": false, 11 | "reporters": [ 12 | { 13 | "name": "jasmine-spec-reporter#SpecReporter", 14 | "options": { 15 | "displayStacktrace": "all" 16 | } 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /2.5 - Typescript testing/jasmine/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jasmine-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "./node_modules/.bin/jasmine-ts ./server/tsconfig.json --config=jasmine.json" 8 | }, 9 | "author": "amit bezalel", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@types/jasmine": "^3.3.12", 13 | "jasmine": "^3.4.0", 14 | "jasmine-spec-reporter": "^4.2.1", 15 | "jasmine-ts": "^0.3.0", 16 | "ts-node": "^8.1.0", 17 | "typescript": "^3.4.5" 18 | }, 19 | "dependencies": {} 20 | } 21 | -------------------------------------------------------------------------------- /2.5 - Typescript testing/jasmine/spec/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js" 8 | ], 9 | "stopSpecOnExpectationFailure": false, 10 | "random": false, 11 | "reporters": [ 12 | { 13 | "name": "jasmine-spec-reporter#SpecReporter", 14 | "options": { 15 | "displayStacktrace": "all" 16 | } 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /2.5 - Typescript testing/jasmine/spec/main.spec.ts: -------------------------------------------------------------------------------- 1 | const jasmine = require('jasmine'); 2 | 3 | jasmine.describe("dummy test",()=>{ 4 | jasmine.it("should return false",async ()=>{ 5 | jasmine.expect(false).toBe(false); 6 | }); 7 | jasmine.it("should return true", async ()=>{ 8 | jasmine.expect(true).toBe(true); 9 | }); 10 | }); -------------------------------------------------------------------------------- /2.5 - Typescript testing/jasmine/src/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /2.5 - Typescript testing/jasmine/src/main.spec.ts: -------------------------------------------------------------------------------- 1 | describe("dummy test",()=>{ 2 | it("should return false",async ()=>{ 3 | expect(false).toBe(false); 4 | }); 5 | it("should return true", async ()=>{ 6 | expect(true).toBe(true); 7 | }); 8 | }); -------------------------------------------------------------------------------- /2.5 - Typescript testing/jasmine/src/main.ts: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /2.5 - Typescript testing/jasmine/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "lib": ["es2015"], 6 | "strict": true, 7 | "declaration": true, 8 | "outDir": "dist", 9 | "sourceMap": true 10 | }, 11 | "include": ["src/**/*"], 12 | "exclude": ["node_modules", "**/*.spec.ts"] 13 | } -------------------------------------------------------------------------------- /2.5 - Typescript testing/jest/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest', 5 | }, 6 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 8 | } -------------------------------------------------------------------------------- /2.5 - Typescript testing/jest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jest-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "jest.config.js", 6 | "scripts": { 7 | "test": "jest" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@types/jest": "^24.0.12", 13 | "jest": "^24.7.1", 14 | "ts-jest": "^24.0.2", 15 | "typescript": "^3.4.5" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /2.5 - Typescript testing/jest/src/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /2.5 - Typescript testing/jest/src/main.spec.ts: -------------------------------------------------------------------------------- 1 | 2 | test('should return false given external link', () => { 3 | expect(false).toBe(false) 4 | }) 5 | 6 | test('should return true given internal link', () => { 7 | expect(true).toBe(true) 8 | }) -------------------------------------------------------------------------------- /2.5 - Typescript testing/jest/src/main.ts: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /2.5 - Typescript testing/jest/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "lib": ["es2015"], 6 | "strict": true, 7 | "declaration": true, 8 | "outDir": "dist", 9 | "sourceMap": true 10 | }, 11 | "include": ["src/**/*"], 12 | "exclude": ["node_modules", "**/*.spec.ts"] 13 | } -------------------------------------------------------------------------------- /2.5 - Typescript testing/mocha/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /2.5 - Typescript testing/mocha/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mocha-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node_modules/.bin/ts-mocha ./src/*spec*.ts --exit -s 0" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "chai": "^4.2.0", 13 | "mocha": "^6.1.4", 14 | "ts-mocha": "^6.0.0", 15 | "ts-node": "^8.1.0", 16 | "typescript": "^3.4.5" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /2.5 - Typescript testing/mocha/src/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /2.5 - Typescript testing/mocha/src/main.spec.ts: -------------------------------------------------------------------------------- 1 | const chai = require ("chai"); 2 | 3 | describe("dummy test",()=>{ 4 | it("should return false",async ()=>{ 5 | chai.expect(false,"the value should be false").to.be.false; 6 | }); 7 | it("should return true", async ()=>{ 8 | chai.expect(true,"the value should be true").to.be.true; 9 | }); 10 | }); -------------------------------------------------------------------------------- /2.5 - Typescript testing/mocha/src/main.ts: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /2.5 - Typescript testing/mocha/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "lib": ["es2015"], 6 | "strict": true, 7 | "declaration": true, 8 | "outDir": "dist", 9 | "sourceMap": true 10 | }, 11 | "include": ["src/**/*"], 12 | "exclude": ["node_modules", "**/*.spec.ts"] 13 | } -------------------------------------------------------------------------------- /3.1 - Test doubles importance/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mocha-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha spec/*spec*.js --exit -s 0" 8 | }, 9 | "author": "Amit Bezalel", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "chai": "^4.2.0", 13 | "mocha": "^6.1.4" 14 | }, 15 | "dependencies": { 16 | "chai-http": "^4.3.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /3.1 - Test doubles importance/spec/main.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; // Using Expect style 3 | const makeIceCream = require("../src/main").makeIceCream; 4 | const Wallet = require("../src/main").Wallet; 5 | 6 | describe("mocking importance", async function () { 7 | it("should show why we need to mock some functions", async function () { 8 | let wallet = new Wallet(10); 9 | 10 | makeIceCream("Vanilla"); 11 | makeIceCream("Chocolate"); 12 | makeIceCream("Chocolate"); 13 | let ok = wallet.chargePrepaidAccount(3, 2.75); 14 | 15 | expect(ok, "charge should end with success").to.be.true; 16 | // we should assert wallet.chargeAccountForCone called 3 times 17 | // we should also prevent calls to the actual payment system 18 | }); 19 | 20 | it("should fail to charge wallet when insufficient funds", async function () { 21 | let wallet = new Wallet(10); 22 | 23 | makeIceCream("Vanilla"); 24 | makeIceCream("Vanilla"); 25 | makeIceCream("Chocolate"); 26 | let ok = wallet.chargePrepaidAccount(3, 3.75); 27 | 28 | expect(ok, "charge should end with success").to.be.false; 29 | //check transaction rollback behavior 30 | }); 31 | 32 | }); -------------------------------------------------------------------------------- /3.1 - Test doubles importance/src/main.js: -------------------------------------------------------------------------------- 1 | 2 | function makeIceCream(flavor) { 3 | if (flavor == "Chocolate" || flavor == "Vanilla") { 4 | return { 5 | cone: true, 6 | flavor: flavor, 7 | topping: "hotChocolate", 8 | } 9 | } else throw ("invalid flavor"); 10 | } 11 | 12 | 13 | class Wallet { 14 | 15 | constructor(initialBalance) { 16 | this.balance = initialBalance; 17 | } 18 | 19 | chargeAccountForCone(conePrice) { 20 | if (this.balance >= conePrice) { 21 | this.balance -= conePrice; 22 | return true; 23 | } else { 24 | //not enough cache 25 | return false; 26 | } 27 | } 28 | 29 | chargePrepaidAccount(coneCount, conePrice) { 30 | for (let i = 0; i < coneCount; ++i) { 31 | let ok = this.chargeAccountForCone(conePrice); 32 | if (!ok) { 33 | return false; 34 | } 35 | } 36 | return true; 37 | } 38 | } 39 | 40 | module.exports.makeIceCream = makeIceCream; 41 | module.exports.Wallet = Wallet; -------------------------------------------------------------------------------- /3.3 - SinonJS mocking in practice/.vscode/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /3.3 - SinonJS mocking in practice/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceFolder}\\index.js" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /3.3 - SinonJS mocking in practice/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "mochaExplorer.files": "test/**/*.js", 3 | "mochaExplorer.require": "", 4 | "mochaExplorer.timeout": 10000 5 | } 6 | -------------------------------------------------------------------------------- /3.3 - SinonJS mocking in practice/.vscode/tasks.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/3.3 - SinonJS mocking in practice/.vscode/tasks.json -------------------------------------------------------------------------------- /3.3 - SinonJS mocking in practice/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mocha-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha test/*.js --exit -s 0" 8 | }, 9 | "author": "Amit Bezalel", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "chai": "^4.2.0", 13 | "chai-sinon": "^2.8.1", 14 | "mocha": "^6.1.4", 15 | "sinon": "^7.3.2" 16 | }, 17 | "dependencies": { 18 | "axios": "^0.18.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /3.3 - SinonJS mocking in practice/src/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /3.3 - SinonJS mocking in practice/src/main.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | 3 | function doSomething(callback) { 4 | callback(5); 5 | } 6 | 7 | class Fruit { 8 | constructor() { 9 | this.fruitUrl = 10 | "https://raw.githubusercontent.com/amitbet/fruits/master/fruit.json"; 11 | this.myShape = ""; 12 | this.myRotation = 15; 13 | } 14 | 15 | async getFruitFromList(numFruit) { 16 | const response = await axios.get(this.fruitUrl); 17 | const json = response.data; 18 | return json.fruits[numFruit]; 19 | } 20 | 21 | setShape(shapeType) { 22 | this.myShape = shapeType; 23 | return "this went ok!"; 24 | } 25 | 26 | getShape() { 27 | return this.myShape; 28 | } 29 | 30 | setRotation(angle) { 31 | myRotation = angle; 32 | } 33 | } 34 | 35 | module.exports.doSomething = doSomething; 36 | module.exports.Fruit = Fruit; 37 | -------------------------------------------------------------------------------- /3.3 - SinonJS mocking in practice/test/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /3.3 - SinonJS mocking in practice/test/main.spec.js: -------------------------------------------------------------------------------- 1 | const expect = require("chai").expect; // Using Expect style 2 | const chai = require("chai"); 3 | const sinon = require("sinon"); 4 | const chaiSinon = require("chai-sinon"); 5 | const code = require("../src/main"); 6 | const axios = require("axios"); 7 | 8 | chai.use(chaiSinon); 9 | 10 | describe("sinon spy tests", async function() { 11 | it("test sinon anonymous spy", async function() { 12 | let myCallback = sinon.spy(); 13 | code.doSomething(myCallback); 14 | 15 | expect(myCallback).to.have.been.calledOnce; 16 | expect(myCallback).to.have.been.calledWith(5); 17 | code.doSomething(myCallback); 18 | expect(myCallback).to.have.been.calledTwice; 19 | 20 | code.doSomething(myCallback); 21 | expect(myCallback).to.have.been.calledThrice; 22 | 23 | //lets run it 7 more times to complete 10 calls 24 | for (let i = 3; i < 10; i++) { 25 | code.doSomething(myCallback); 26 | } 27 | expect(myCallback).to.have.callCount(10); 28 | }); 29 | 30 | it("test sinon function spies", function() { 31 | let fruit = new code.Fruit(); 32 | let anotherSpy = sinon.spy(); 33 | let setShapeSpy = sinon.spy(fruit, "setShape"); 34 | let setRotationSpy = sinon.spy(fruit, "setRotation"); 35 | try { 36 | retVal = fruit.setShape("round"); 37 | expect(setShapeSpy).to.have.been.calledOnce; 38 | expect(setShapeSpy).to.have.been.calledWith("round"); 39 | expect(setShapeSpy).to.have.been.calledOn(fruit); 40 | expect(setShapeSpy).to.have.been.calledBefore(setRotationSpy); 41 | expect(setShapeSpy).to.have.returned("this went ok!"); 42 | } finally { 43 | setRotationSpy.restore(); 44 | setShapeSpy.restore(); 45 | } 46 | }); 47 | }); 48 | 49 | describe("sinon stub tests", async function() { 50 | it("test sinon stub.resolves", async function() { 51 | let fruit = new code.Fruit(); 52 | 53 | // every request will now return our promise... there is a better way to do this with nock. 54 | let fruitUrl = 55 | "https://raw.githubusercontent.com/amitbet/fruits/master/fruit.json"; 56 | 57 | let myStub = sinon 58 | .stub(axios, "get") 59 | .withArgs(fruitUrl) 60 | .resolves({ 61 | data: { 62 | fruits: ["0", "1", "2", "3", "orange", "apple"] 63 | } 64 | }); 65 | 66 | let retVal = await fruit.getFruitFromList(5); 67 | expect(myStub).to.have.been.calledOnce; 68 | expect(retVal).to.equal("apple"); 69 | 70 | // important to restore, stub.restore doesn't exist when using resolves in this sinon version 71 | axios.get.restore(); 72 | }); 73 | 74 | it("test sinon stub.returns", async function() { 75 | let fruit = new code.Fruit(); 76 | let stub = sinon.stub(fruit, "getShape"); 77 | stub.returns("square"); 78 | 79 | let gotShape = fruit.getShape(); 80 | expect(gotShape).to.equal("square"); 81 | 82 | stub.restore(); 83 | }); 84 | }); 85 | 86 | describe("sinon mock tests", async function() { 87 | it("test sinon mock", async function() { 88 | let fruit = new code.Fruit(); 89 | let myMock = sinon.mock(fruit); 90 | // expect between 2 to 4 calls to setShape 91 | myMock 92 | .expects("setShape") 93 | .atLeast(2) 94 | .atMost(4); 95 | // make sure one of these calls gets "roundish" as a parameter 96 | myMock 97 | .expects("setShape") 98 | .once() 99 | .on(fruit) 100 | .withArgs("roundish"); 101 | // replace the first call to getShape to return "roundish" 102 | myMock 103 | .expects("getShape") 104 | .once() 105 | .returns("roundish"); 106 | myMock.expects("getFruitFromList").never(); 107 | 108 | fruit.setShape("smooth"); 109 | fruit.setShape("wrinkled"); 110 | fruit.setShape("roundish"); 111 | 112 | let lastShape = fruit.getShape(); 113 | 114 | // check that our mock performed as expected, and returned "roundish" 115 | expect(lastShape).to.equal("roundish"); 116 | 117 | //now verify all the expectations we defined: 118 | myMock.verify(); 119 | myMock.restore(); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /3.4 - Mocking for API tests/.vscode/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /3.4 - Mocking for API tests/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceFolder}\\index.js" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /3.4 - Mocking for API tests/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "mochaExplorer.files": "spec/**/*.js", 3 | "mochaExplorer.require": "" 4 | } 5 | -------------------------------------------------------------------------------- /3.4 - Mocking for API tests/.vscode/tasks.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/3.4 - Mocking for API tests/.vscode/tasks.json -------------------------------------------------------------------------------- /3.4 - Mocking for API tests/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /3.4 - Mocking for API tests/jsrv/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "myFruitApi": [ 3 | { 4 | "id": 1, 5 | "description": "bla bla", 6 | "fruits": [ 7 | "apple", 8 | "apricot", 9 | "avocado", 10 | "banana", 11 | "bell pepper", 12 | "guava", 13 | "bilberry" 14 | ] 15 | } 16 | ], 17 | "fruit": [ 18 | { "id": 1, "name": "apple", "shape": "roundish" }, 19 | { "id": 2, "name": "orange", "shape": "round" }, 20 | { "id": 3, "name": "cumquat", "shape": "squished" }, 21 | { "id": 4, "name": "avocado", "shape": "eggish" }, 22 | { "id": 5, "name": "jackfruit", "shape": "bumpy" } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /3.4 - Mocking for API tests/jsrv/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "/amitbet/fruits/master/fruit.json": "/myFruitApi/1" 3 | } 4 | -------------------------------------------------------------------------------- /3.4 - Mocking for API tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mocha-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha spec/*spec*.js --exit -s 0" 8 | }, 9 | "author": "Amit Bezalel", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "chai": "^4.2.0", 13 | "mocha": "^6.2.0" 14 | }, 15 | "dependencies": { 16 | "axios": "^0.18.0", 17 | "chai-sinon": "^2.8.1", 18 | "json-server": "^0.15.1", 19 | "nock": "^10.0.6", 20 | "sinon": "^7.5.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /3.4 - Mocking for API tests/runsrv.cmd: -------------------------------------------------------------------------------- 1 | json-server ./jsrv/db.json --routes ./jsrv/routes.json -------------------------------------------------------------------------------- /3.4 - Mocking for API tests/spec/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /3.4 - Mocking for API tests/spec/main.spec.js: -------------------------------------------------------------------------------- 1 | const expect = require("chai").expect; 2 | const chai = require("chai"); 3 | const axios = require("axios"); 4 | const nock = require("nock"); 5 | const sinon = require("sinon"); 6 | const chaiSinon = require("chai-sinon"); 7 | const code = require("../src/main"); 8 | chai.use(chaiSinon); 9 | 10 | var targetServerUrl = "https://raw.githubusercontent.com"; 11 | after(async function() { 12 | nock.cleanAll(); 13 | }); 14 | 15 | describe("API test mocking", async function() { 16 | it("test sinon stub.resolves", async function() { 17 | let fruitUrl = targetServerUrl + "/amitbet/fruits/master/fruit.json"; 18 | let fruit = new code.Fruit(fruitUrl); 19 | 20 | let myStub = sinon 21 | .stub(axios, "get") 22 | .resolves({ 23 | data: { 24 | fruits: ["0", "1", "2", "3", "orange", "guava"] 25 | } 26 | }) 27 | .withArgs(fruitUrl); 28 | 29 | let retVal = await fruit.getFruitFromList(5); 30 | expect(retVal).to.equal("guava"); 31 | 32 | axios.get.restore(); 33 | }); 34 | 35 | it("test nock API replacement ", async function() { 36 | let fruitUrl = targetServerUrl + "/amitbet/fruits/master/fruit.json"; 37 | 38 | let fruit = new code.Fruit(fruitUrl); 39 | 40 | nock("https://raw.githubusercontent.com/") 41 | .get("/amitbet/fruits/master/fruit.json") 42 | .reply(200, { fruits: ["0", "1", "2", "3", "orange", "guava"] }); 43 | 44 | let retVal = await fruit.getFruitFromList(5); 45 | expect(retVal).to.equal("guava"); 46 | 47 | nock.restore(); 48 | }); 49 | 50 | it("test with an external server (using json-server)", async () => { 51 | targetServerUrl = "http://localhost:3000"; 52 | let fruitUrl = targetServerUrl + "/amitbet/fruits/master/fruit.json"; 53 | let fruit = new code.Fruit(fruitUrl); 54 | let retVal = await fruit.getFruitFromList(5); 55 | expect(retVal).to.equal("guava"); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /3.4 - Mocking for API tests/src/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /3.4 - Mocking for API tests/src/main.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | 3 | class Fruit { 4 | constructor(fruitUrl) { 5 | this.fruitUrl = fruitUrl; 6 | this.myShape = ""; 7 | this.myRotation = 15; 8 | } 9 | 10 | async getFruitFromList(numFruit) { 11 | const response = await axios.get(this.fruitUrl); 12 | const json = response.data; 13 | return json.fruits[numFruit]; 14 | } 15 | 16 | setShape(shapeType) { 17 | this.myShape = shapeType; 18 | return "this went ok!"; 19 | } 20 | 21 | getShape() { 22 | return this.myShape; 23 | } 24 | 25 | setRotation(angle) { 26 | myRotation = angle; 27 | } 28 | } 29 | 30 | module.exports.Fruit = Fruit; 31 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/.vscode/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "type": "node", 10 | "request": "launch", 11 | "name": "Launch Program", 12 | "program": "${workspaceFolder}/dist/app.js", 13 | "cwd": "${workspaceFolder}/dist/", 14 | "outFiles": [ 15 | "${workspaceFolder}/**/*.js" 16 | ] 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/*.js.map": true, 4 | "**/*.d.ts": true, 5 | "**/.classpath": true, 6 | "**/.project": true, 7 | "**/.settings": true, 8 | "**/.factorypath": true, 9 | "**/*.js": { "when": "$(basename).ts" } 10 | }, 11 | "mochaExplorer.files": "test/**/*.ts", 12 | "mochaExplorer.require": "ts-node/register", 13 | "mochaExplorer.timeout": 10000 14 | } 15 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [{ 6 | "type": "typescript", 7 | "tsconfig": "tsconfig.json", 8 | "problemMatcher": [ 9 | "$tsc" 10 | ] 11 | }, 12 | { 13 | "type": "typescript", 14 | "tsconfig": "tsconfig.json", 15 | "option": "watch", 16 | "problemMatcher": [ 17 | "$tsc-watch" 18 | ], 19 | "group": { 20 | "kind": "build", 21 | "isDefault": true 22 | } 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.13-slim AS build-env 2 | 3 | # Create app directory 4 | RUN mkdir -p /opt/my-musik/ 5 | WORKDIR /opt/my-musik/ 6 | RUN npm install typescript -g 7 | 8 | COPY ./gulpfile.js . 9 | COPY ./package.json . 10 | COPY ./tsconfig.json . 11 | COPY ./tslint.json . 12 | COPY ./config.json . 13 | COPY ./Dockerfile . 14 | 15 | # Install app dependencies 16 | RUN npm install --no-optional 17 | 18 | # Bundle app source 19 | COPY ./src ./src 20 | 21 | RUN "node_modules/.bin/gulp" 22 | 23 | 24 | # final stage 25 | FROM node:8.13-slim 26 | RUN mkdir -p /opt/my-musik/ 27 | WORKDIR /opt/my-musik/ 28 | COPY --from=build-env /opt/my-musik/dist ./dist 29 | COPY --from=build-env /opt/my-musik/package.json ./ 30 | COPY --from=build-env /opt/my-musik/config.json ./ 31 | RUN npm install --no-optional --only=prod 32 | 33 | EXPOSE 3002 34 | 35 | CMD [ "node", "dist/app.js" ] -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "logLevel": "debug" 3 | } -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"); 2 | var ts = require("gulp-typescript"); 3 | var del = require("del"); 4 | var tsProject = ts.createProject("tsconfig.json"); 5 | var tslint = require("gulp-tslint"); 6 | 7 | var outputFolder = "./dist/"; 8 | 9 | gulp.task("clean", function() { 10 | return del([outputFolder]); 11 | }); 12 | 13 | gulp.task("lint", function() { 14 | return gulp 15 | .src(["src/**/*.ts", "!src/**/*.d.ts"]) 16 | .pipe( 17 | tslint({ 18 | formatter: "verbose", 19 | configuration: "./tslint.json" 20 | }) 21 | ) 22 | .pipe(tslint.report()); 23 | }); 24 | 25 | gulp.task("compile", function() { 26 | return tsProject 27 | .src() 28 | .pipe(tsProject()) 29 | .js.pipe(gulp.dest(outputFolder)); 30 | }); 31 | 32 | gulp.task("copyContent", function() { 33 | return gulp 34 | .src(["./src/config11.json", "./Dockerfile"]) 35 | .pipe(gulp.dest(outputFolder)); 36 | }); 37 | 38 | gulp.task("default", gulp.series("clean", "lint", "compile", "copyContent")); 39 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stage1", 3 | "version": "1.0.0", 4 | "description": "a sample service", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "node_modules/.bin/ts-mocha ./test/*.ts", 8 | "start": "node src/app.js" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "dependencies": { 13 | "@types/cors": "^2.8.4", 14 | "@types/express": "^4.16.0", 15 | "@types/mongoose": "^5.3.26", 16 | "@types/node": "^10.14.21", 17 | "cors": "^2.8.5", 18 | "express": "^4.16.4", 19 | "mongoose": "^5.5.2", 20 | "request-promise-native": "^1.0.7" 21 | }, 22 | "devDependencies": { 23 | "@types/chai": "^4.1.7", 24 | "@types/mocha": "^5.2.5", 25 | "@types/request": "^2.48.3", 26 | "chai": "^4.2.0", 27 | "del": "^3.0.0", 28 | "gulp": "^4.0.0", 29 | "gulp-tslint": "^8.1.3", 30 | "gulp-typescript": "^5.0.0-alpha.3", 31 | "mocha": "^5.2.0", 32 | "request": "^2.88.0", 33 | "ts-mocha": "^2.0.0", 34 | "ts-node": "^7.0.1", 35 | "tslint": "^5.11.0", 36 | "typescript": "^3.2.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/run.bat: -------------------------------------------------------------------------------- 1 | npm start -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/songs.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": 111, 3 | "name": "Ironic", 4 | "playtimeSecs": 189, 5 | "link": "/storage/songs/Ironic.mp3", 6 | "artist": "Alanis" 7 | }, 8 | { 9 | "id": 112, 10 | "name": "Alehandro", 11 | "playtimeSecs": 211, 12 | "link": "/storage/songs/Alehandro.mp3", 13 | "artist": "Lady Gaga" 14 | }, 15 | { 16 | "id": 113, 17 | "name": "Lithium", 18 | "playtimeSecs": 173, 19 | "link": "/storage/songs/Lithium.mp3", 20 | "artist": "Nirvana" 21 | }, 22 | { 23 | "artist": "Alicia Keys", 24 | "name": "If I Ain't Got You", 25 | "id": 1, 26 | "playtimeSecs": 146, 27 | "link": "/storage/songs/If I Ain't Got You.mp3" 28 | }, 29 | { 30 | "artist": "Britney Spears", 31 | "name": "Toxic", 32 | "id": 2, 33 | "playtimeSecs": 147, 34 | "link": "/storage/songs/Toxic.mp3" 35 | }, 36 | { 37 | "artist": "Kurt Hugo Schneider", 38 | "name": "Toxic- Britney Spears Cover", 39 | "id": 3, 40 | "playtimeSecs": 148, 41 | "link": "/storage/songs/Toxic (feat. Casey Breves) - Britney Spears Cover.mp3" 42 | }, 43 | { 44 | "artist": "Beyoncé", 45 | "name": "Crazy In Love", 46 | "id": 4, 47 | "playtimeSecs": 149, 48 | "link": "/storage/songs/Crazy In Love.mp3" 49 | }, 50 | { 51 | "artist": "Justin Timberlake", 52 | "name": "Rock Your Body", 53 | "id": 5, 54 | "playtimeSecs": 150, 55 | "link": "/storage/songs/Rock Your Body.mp3" 56 | }, 57 | { 58 | "artist": "Sean Kingston", 59 | "name": "Beautiful Girls", 60 | "id": 6, 61 | "playtimeSecs": 151, 62 | "link": "/storage/songs/Beautiful Girls.mp3" 63 | }, 64 | { 65 | "artist": "Usher", 66 | "name": "Yeah!", 67 | "id": 7, 68 | "playtimeSecs": 152, 69 | "link": "/storage/songs/Yeah!.mp3" 70 | }, 71 | { 72 | "artist": "T.I.", 73 | "name": "Live Your Life (ft. Rihanna)", 74 | "id": 8, 75 | "playtimeSecs": 153, 76 | "link": "/storage/songs/Live Your Life (feat. Rihanna).mp3" 77 | }, 78 | { 79 | "artist": "Shakira", 80 | "name": "Hips Don't Lie", 81 | "id": 9, 82 | "playtimeSecs": 154, 83 | "link": "/storage/songs/Hips Don't Lie.mp3" 84 | }, 85 | { 86 | "artist": "Train", 87 | "name": "Hey, Soul Sister", 88 | "id": 10, 89 | "playtimeSecs": 155, 90 | "link": "/storage/songs/Hey, Soul Sister.mp3" 91 | }, 92 | { 93 | "artist": "Foster The People", 94 | "name": "Pumped Up Kicks", 95 | "id": 11, 96 | "playtimeSecs": 156, 97 | "link": "/storage/songs/Pumped Up Kicks.mp3" 98 | }, 99 | { 100 | "artist": "Michael Jackson", 101 | "name": "Love Never Felt so Good", 102 | "id": 12, 103 | "playtimeSecs": 157, 104 | "link": "/storage/songs/Love Never Felt so Good.mp3" 105 | }, 106 | { 107 | "artist": "The Script", 108 | "name": "Hall of Fame", 109 | "id": 13, 110 | "playtimeSecs": 158, 111 | "link": "/storage/songs/Hall of Fame.mp3" 112 | }, 113 | { 114 | "artist": "Alicia Keys", 115 | "name": "No One", 116 | "id": 14, 117 | "playtimeSecs": 159, 118 | "link": "/storage/songs/No One.mp3" 119 | }, 120 | { 121 | "artist": "John Legend", 122 | "name": "All of Me", 123 | "id": 15, 124 | "playtimeSecs": 160, 125 | "link": "/storage/songs/All of Me.mp3" 126 | }, 127 | { 128 | "artist": "A Great Big World", 129 | "name": "Say Something", 130 | "id": 16, 131 | "playtimeSecs": 161, 132 | "link": "/storage/songs/Say Something.mp3" 133 | }, 134 | { 135 | "artist": "Train", 136 | "name": "Drops of Jupiter", 137 | "id": 17, 138 | "playtimeSecs": 162, 139 | "link": "/storage/songs/Drops of Jupiter.mp3" 140 | }, 141 | { 142 | "artist": "Miley Cyrus", 143 | "name": "We Can't Stop", 144 | "id": 18, 145 | "playtimeSecs": 163, 146 | "link": "/storage/songs/We Can't Stop.mp3" 147 | }, 148 | { 149 | "artist": "Kelly Clarkson", 150 | "name": "Since U Been Gone", 151 | "id": 19, 152 | "playtimeSecs": 164, 153 | "link": "/storage/songs/Since U Been Gone.mp3" 154 | }, 155 | { 156 | "artist": "OutKast", 157 | "name": "Hey Ya! - Radio Mix / Club Mix", 158 | "id": 20, 159 | "playtimeSecs": 165, 160 | "link": "/storage/songs/Hey Ya! - Radio Mix / Club Mix.mp3" 161 | }, 162 | { 163 | "artist": "Kesha", 164 | "name": "TiK ToK", 165 | "id": 21, 166 | "playtimeSecs": 166, 167 | "link": "/storage/songs/TiK ToK.mp3" 168 | }, 169 | { 170 | "artist": "Chris Brown", 171 | "name": "Look At Me Now", 172 | "id": 22, 173 | "playtimeSecs": 167, 174 | "link": "/storage/songs/Look At Me Now.mp3" 175 | }, 176 | { 177 | "artist": "Justin Timberlake", 178 | "name": "SexyBack - Main Version - Clean", 179 | "id": 23, 180 | "playtimeSecs": 168, 181 | "link": "/storage/songs/SexyBack - Main Version - Clean.mp3" 182 | }, 183 | { 184 | "artist": "Chris Brown", 185 | "name": "With You", 186 | "id": 24, 187 | "playtimeSecs": 169, 188 | "link": "/storage/songs/With You.mp3" 189 | }, 190 | { 191 | "artist": "The Script", 192 | "name": "Breakeven", 193 | "id": 25, 194 | "playtimeSecs": 170, 195 | "link": "/storage/songs/Breakeven.mp3" 196 | }, 197 | { 198 | "artist": "Pitbull", 199 | "name": "I Know You Want Me (Calle Ocho)", 200 | "id": 26, 201 | "playtimeSecs": 171, 202 | "link": "/storage/songs/I Know You Want Me (Calle Ocho).mp3" 203 | }, 204 | { 205 | "artist": "*NSYNC", 206 | "name": "Bye Bye Bye", 207 | "id": 27, 208 | "playtimeSecs": 172, 209 | "link": "/storage/songs/Bye Bye Bye.mp3" 210 | }, 211 | { 212 | "artist": "Avril Lavigne", 213 | "name": "Complicated", 214 | "id": 28, 215 | "playtimeSecs": 173, 216 | "link": "/storage/songs/Complicated.mp3" 217 | }, 218 | { 219 | "artist": "Mario", 220 | "name": "Let Me Love You", 221 | "id": 29, 222 | "playtimeSecs": 174, 223 | "link": "/storage/songs/Let Me Love You.mp3" 224 | }, 225 | { 226 | "artist": "Christina Aguilera", 227 | "name": "Beautiful", 228 | "id": 30, 229 | "playtimeSecs": 175, 230 | "link": "/storage/songs/Beautiful.mp3" 231 | }, 232 | { 233 | "artist": "P!nk", 234 | "name": "Raise Your Glass", 235 | "id": 31, 236 | "playtimeSecs": 176, 237 | "link": "/storage/songs/Raise Your Glass.mp3" 238 | }, 239 | { 240 | "artist": "Pitbull", 241 | "name": "Timber", 242 | "id": 32, 243 | "playtimeSecs": 177, 244 | "link": "/storage/songs/Timber.mp3" 245 | }, 246 | { 247 | "artist": "One Direction", 248 | "name": "What Makes You Beautiful", 249 | "id": 33, 250 | "playtimeSecs": 178, 251 | "link": "/storage/songs/What Makes You Beautiful.mp3" 252 | }, 253 | { 254 | "artist": "Avril Lavigne", 255 | "name": "Sk8er Boi", 256 | "id": 34, 257 | "playtimeSecs": 179, 258 | "link": "/storage/songs/Sk8er Boi.mp3" 259 | }, 260 | { 261 | "artist": "Britney Spears", 262 | "name": "Womanizer", 263 | "id": 35, 264 | "playtimeSecs": 180, 265 | "link": "/storage/songs/Womanizer.mp3" 266 | }, 267 | { 268 | "artist": "Pitbull", 269 | "name": "Give Me Everything", 270 | "id": 36, 271 | "playtimeSecs": 181, 272 | "link": "/storage/songs/Give Me Everything.mp3" 273 | }, 274 | { 275 | "artist": "Beyoncé", 276 | "name": "Single Ladies", 277 | "id": 37, 278 | "playtimeSecs": 182, 279 | "link": "/storage/songs/Single Ladies (Put a Ring on It).mp3" 280 | }, 281 | { 282 | "artist": "Leona Lewis", 283 | "name": "Bleeding Love", 284 | "id": 38, 285 | "playtimeSecs": 183, 286 | "link": "/storage/songs/Bleeding Love.mp3" 287 | }, 288 | { 289 | "artist": "Destiny's Child", 290 | "name": "Lose My Breath", 291 | "id": 39, 292 | "playtimeSecs": 184, 293 | "link": "/storage/songs/Lose My Breath.mp3" 294 | }, 295 | { 296 | "artist": "Sara Bareilles", 297 | "name": "Love Song", 298 | "id": 40, 299 | "playtimeSecs": 185, 300 | "link": "/storage/songs/Love Song.mp3" 301 | }, 302 | { 303 | "artist": "OutKast", 304 | "name": "Ms. Jackson", 305 | "id": 41, 306 | "playtimeSecs": 186, 307 | "link": "/storage/songs/Ms. Jackson.mp3" 308 | }, 309 | { 310 | "artist": "Kesha", 311 | "name": "Die Young", 312 | "id": 42, 313 | "playtimeSecs": 187, 314 | "link": "/storage/songs/Die Young.mp3" 315 | }, 316 | { 317 | "artist": "Destiny's Child", 318 | "name": "Survivor", 319 | "id": 43, 320 | "playtimeSecs": 188, 321 | "link": "/storage/songs/Survivor.mp3" 322 | }, 323 | { 324 | "artist": "Britney Spears", 325 | "name": "Oops!...I Did It Again", 326 | "id": 44, 327 | "playtimeSecs": 189, 328 | "link": "/storage/songs/Oops!...I Did It Again.mp3" 329 | }, 330 | { 331 | "artist": "Justin Timberlake", 332 | "name": "Cry Me a River", 333 | "id": 45, 334 | "playtimeSecs": 190, 335 | "link": "/storage/songs/Cry Me a River.mp3" 336 | }, 337 | { 338 | "artist": "Daft Punk", 339 | "name": "Get Lucky (feat. Pharrell Williams & Nile Rodgers) - Radio Edit", 340 | "id": 46, 341 | "playtimeSecs": 191, 342 | "link": "/storage/songs/Get Lucky (feat. Pharrell Williams & Nile Rodgers) - Radio Edit.mp3" 343 | }, 344 | { 345 | "artist": "T.I.", 346 | "name": "Whatever You Like", 347 | "id": 47, 348 | "playtimeSecs": 192, 349 | "link": "/storage/songs/Whatever You Like.mp3" 350 | }, 351 | { 352 | "artist": "Ciara", 353 | "name": "One, Two Step", 354 | "id": 48, 355 | "playtimeSecs": 193, 356 | "link": "/storage/songs/One, Two Step.mp3" 357 | }, 358 | { 359 | "artist": "Natasha Bedingfield", 360 | "name": "Unwritten", 361 | "id": 49, 362 | "playtimeSecs": 194, 363 | "link": "/storage/songs/Unwritten.mp3" 364 | }, 365 | { 366 | "artist": "Miley Cyrus", 367 | "name": "Wrecking Ball", 368 | "id": 50, 369 | "playtimeSecs": 195, 370 | "link": "/storage/songs/Wrecking Ball.mp3" 371 | }, 372 | { 373 | "artist": "P!nk", 374 | "name": "So What", 375 | "id": 51, 376 | "playtimeSecs": 196, 377 | "link": "/storage/songs/So What.mp3" 378 | }, 379 | { 380 | "artist": "Shakira", 381 | "name": "Whenever, Wherever", 382 | "id": 52, 383 | "playtimeSecs": 197, 384 | "link": "/storage/songs/Whenever, Wherever.mp3" 385 | }, 386 | { 387 | "artist": "Beyoncé", 388 | "name": "Baby Boy", 389 | "id": 53, 390 | "playtimeSecs": 198, 391 | "link": "/storage/songs/Baby Boy.mp3" 392 | }, 393 | { 394 | "artist": "*NSYNC", 395 | "name": "It's Gonna Be Me", 396 | "id": 54, 397 | "playtimeSecs": 199, 398 | "link": "/storage/songs/It's Gonna Be Me.mp3" 399 | }, 400 | { 401 | "artist": "Usher", 402 | "name": "My Boo", 403 | "id": 55, 404 | "playtimeSecs": 200, 405 | "link": "/storage/songs/My Boo.mp3" 406 | }, 407 | { 408 | "artist": "Kings of Leon", 409 | "name": "Sex on Fire", 410 | "id": 56, 411 | "playtimeSecs": 201, 412 | "link": "/storage/songs/Sex on Fire.mp3" 413 | }, 414 | { 415 | "artist": "Kelly Clarkson", 416 | "name": "Stronger (What Doesn't Kill You)", 417 | "id": 57, 418 | "playtimeSecs": 202, 419 | "link": "/storage/songs/Stronger (What Doesn't Kill You).mp3" 420 | }, 421 | { 422 | "artist": "The Fray", 423 | "name": "Over My Head (Cable Car)", 424 | "id": 58, 425 | "playtimeSecs": 203, 426 | "link": "/storage/songs/Over My Head (Cable Car).mp3" 427 | }, 428 | { 429 | "artist": "The Script", 430 | "name": "For the First Time", 431 | "id": 59, 432 | "playtimeSecs": 204, 433 | "link": "/storage/songs/For the First Time.mp3" 434 | }, 435 | { 436 | "artist": "Avril Lavigne", 437 | "name": "Girlfriend", 438 | "id": 60, 439 | "playtimeSecs": 205, 440 | "link": "/storage/songs/Girlfriend.mp3" 441 | }, 442 | { 443 | "artist": "Alicia Keys", 444 | "name": "Fallin'", 445 | "id": 61, 446 | "playtimeSecs": 206, 447 | "link": "/storage/songs/Fallin'.mp3" 448 | }, 449 | { 450 | "artist": "Kings of Leon", 451 | "name": "Use Somebody", 452 | "id": 62, 453 | "playtimeSecs": 207, 454 | "link": "/storage/songs/Use Somebody.mp3" 455 | }, 456 | { 457 | "artist": "The Fray", 458 | "name": "How to Save a Life", 459 | "id": 63, 460 | "playtimeSecs": 208, 461 | "link": "/storage/songs/How to Save a Life.mp3" 462 | }, 463 | { 464 | "artist": "Gavin DeGraw", 465 | "name": "Not Over You", 466 | "id": 64, 467 | "playtimeSecs": 209, 468 | "link": "/storage/songs/Not Over You.mp3" 469 | }, 470 | { 471 | "artist": "Carrie Underwood", 472 | "name": "Before He Cheats", 473 | "id": 65, 474 | "playtimeSecs": 210, 475 | "link": "/storage/songs/Before He Cheats.mp3" 476 | }, 477 | { 478 | "artist": "Christina Aguilera", 479 | "name": "Dirrty (feat. Redman)", 480 | "id": 66, 481 | "playtimeSecs": 211, 482 | "link": "/storage/songs/Dirrty (feat. Redman).mp3" 483 | }, 484 | { 485 | "artist": "Usher", 486 | "name": "Caught Up", 487 | "id": 67, 488 | "playtimeSecs": 212, 489 | "link": "/storage/songs/Caught Up.mp3" 490 | }, 491 | { 492 | "artist": "The Calling", 493 | "name": "Wherever You Will Go", 494 | "id": 68, 495 | "playtimeSecs": 213, 496 | "link": "/storage/songs/Wherever You Will Go.mp3" 497 | }, 498 | { 499 | "artist": "The Fray", 500 | "name": "You Found Me", 501 | "id": 69, 502 | "playtimeSecs": 214, 503 | "link": "/storage/songs/You Found Me.mp3" 504 | }, 505 | { 506 | "artist": "Destiny's Child", 507 | "name": "Independent Women, Pt. 1", 508 | "id": 70, 509 | "playtimeSecs": 215, 510 | "link": "/storage/songs/Independent Women, Pt. 1.mp3" 511 | }, 512 | { 513 | "artist": "Kelly Clarkson", 514 | "name": "Because of You", 515 | "id": 71, 516 | "playtimeSecs": 216, 517 | "link": "/storage/songs/Because of You.mp3" 518 | }, 519 | { 520 | "artist": "P!nk", 521 | "name": "Get the Party Started", 522 | "id": 72, 523 | "playtimeSecs": 217, 524 | "link": "/storage/songs/Get the Party Started.mp3" 525 | }, 526 | { 527 | "artist": "Good Charlotte", 528 | "name": "The Anthem", 529 | "id": 73, 530 | "playtimeSecs": 218, 531 | "link": "/storage/songs/The Anthem.mp3" 532 | }, 533 | { 534 | "artist": "Gavin DeGraw", 535 | "name": "I Don't Want to Be", 536 | "id": 74, 537 | "playtimeSecs": 219, 538 | "link": "/storage/songs/I Don't Want to Be.mp3" 539 | }, 540 | { 541 | "artist": "OutKast", 542 | "name": "The Way You Move - Club Mix", 543 | "id": 75, 544 | "playtimeSecs": 220, 545 | "link": "/storage/songs/The Way You Move (feat. Sleepy Brown) - Club Mix.mp3" 546 | }, 547 | { 548 | "artist": "Leona Lewis", 549 | "name": "Better in Time", 550 | "id": 76, 551 | "playtimeSecs": 221, 552 | "link": "/storage/songs/Better in Time.mp3" 553 | }, 554 | { 555 | "artist": "Daughtry", 556 | "name": "Over You", 557 | "id": 77, 558 | "playtimeSecs": 222, 559 | "link": "/storage/songs/Over You.mp3" 560 | }, 561 | { 562 | "artist": "Boys Like Girls", 563 | "name": "The Great Escape", 564 | "id": 78, 565 | "playtimeSecs": 223, 566 | "link": "/storage/songs/The Great Escape.mp3" 567 | }, 568 | { 569 | "artist": "John Mayer", 570 | "name": "Your Body Is a Wonderland", 571 | "id": 79, 572 | "playtimeSecs": 224, 573 | "link": "/storage/songs/Your Body Is a Wonderland.mp3" 574 | }, 575 | { 576 | "artist": "Five For Fighting", 577 | "name": "Superman (It's Not Easy)", 578 | "id": 80, 579 | "playtimeSecs": 225, 580 | "link": "/storage/songs/Superman (It's Not Easy).mp3" 581 | } 582 | ] -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const fs = require("fs"); 4 | const util = require("util"); 5 | const url = require("url"); 6 | const dal_playlists_1 = require("./dal/dal-playlists"); 7 | const logger_1 = require("./logger"); 8 | const express = require("express"); 9 | const cors = require("cors"); 10 | const bodyParser = require("body-parser"); 11 | const dal_songs_1 = require("./dal/dal-songs"); 12 | // convert fs.readFile into Promise version of same 13 | const readFile = util.promisify(fs.readFile); 14 | // getting the configuration from file for now 15 | async function getConfig() { 16 | let content = await readFile("./config.json", "utf8"); 17 | return JSON.parse(content); 18 | } 19 | async function main() { 20 | let playlists = new dal_playlists_1.PlaylistDal(); 21 | let conf = await getConfig(); 22 | let mongoHost = process.env["MONGO_SERVICE_HOST"] || "localhost"; 23 | let mongoPort = process.env["MONGO_SERVICE_PORT_TCP"] || "27017"; 24 | let mongoDbName = process.env["MONGO_SERVICE_DB_NAME"] || "demo1"; 25 | let logger = new logger_1.Logger(conf.logLevel); 26 | let mongoConnStr = "mongodb://" + mongoHost + ":" + mongoPort + "/" + mongoDbName; 27 | logger.info("connecting to db:", mongoConnStr); 28 | let songs = new dal_songs_1.SongDal(logger, mongoConnStr); 29 | songs.delete(); 30 | songs.populate("songs.json"); 31 | logger.info("loglevel:", logger.logLevel); 32 | logger.debug("running with configuration: ", JSON.stringify(conf)); 33 | // no authentication yet, set our user as #2 34 | let currentUserId = "2"; 35 | logger.debug("running with configuration: ", JSON.stringify(conf)); 36 | // now using a cors header (allow origin) 37 | let app = express(); 38 | app.use(bodyParser.json()); 39 | // let corsOptions: cors.CorsOptions = { 40 | // optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204 41 | // origin: "http://localhost:3001", 42 | // }; 43 | app.use(cors()); 44 | app.get("/v1/playlists", (req, res) => { 45 | let urlParts = url.parse(req.url, true); 46 | let query = urlParts.query; 47 | let plists = []; 48 | if (req.query.id) { 49 | let id = req.query.id; 50 | let plist = playlists.getPlaylistById(id); 51 | plists = [plist]; 52 | } 53 | else if (req.query.userId) { 54 | let userId = req.query.userId; 55 | let owned = req.query.onlyOwned === "true"; 56 | if (!owned) { 57 | owned = false; 58 | } 59 | plists = playlists.getPlaylistsByUser(userId, owned); 60 | } 61 | if (plists) { 62 | res.send(plists); 63 | } 64 | }); 65 | app.post("/v1/playlists/:id/", (req, res) => { 66 | let urlParts = url.parse(req.url, true); 67 | let listId = req.params.id; 68 | let songId = urlParts.query.songId; 69 | let err = playlists.addItemToPlaylist(currentUserId, listId, songId); 70 | if (err) { 71 | res.status(401).send(err.message); 72 | } 73 | res.end("success"); 74 | }); 75 | app.delete("/v1/playlists/:id/", (req, res) => { 76 | let urlParts = url.parse(req.url, true); 77 | let listId = req.params.id; 78 | let songId = urlParts.query.songId; 79 | let err = playlists.removeItemFromPlaylist(currentUserId, listId, songId); 80 | if (err) { 81 | res.status(401).send(err.message); 82 | } 83 | res.end("success"); 84 | }); 85 | // add a playlist 86 | app.post("/v1/playlists", (req, res) => { 87 | let listItem = req.body; 88 | if (listItem) { 89 | let id = req.query.id; 90 | let plist = playlists.addNewPlaylist(listItem.name, listItem.creatorId); 91 | res.send(plist); 92 | } 93 | else { 94 | logger.error("Playlists API post: no list item in body!"); 95 | res.status(404).send("No Item to add"); 96 | } 97 | }); 98 | // delete a playlist 99 | app.delete("/v1/playlists", (req, res) => { 100 | let urlParts = url.parse(req.url, true); 101 | let listId = urlParts.query.id; 102 | if (listId) { 103 | let err = playlists.delPlaylist(currentUserId, listId); 104 | if (err) { 105 | res.status(401).send(err.message); 106 | } 107 | res.end("success"); 108 | } 109 | else { 110 | logger.error("Playlists API delete: no id in query!"); 111 | res.status(404).send("No Item found to delete"); 112 | } 113 | }); 114 | app.get("/v1/songs", async (req, res) => { 115 | let urlParts = url.parse(req.url, true); 116 | let id = req.query.id; 117 | if (id) { 118 | let song = await songs.getSongById(id); 119 | res.send(song); 120 | } 121 | else if (req.query.q) { 122 | let query = req.query.q; 123 | res.send(await songs.getSongSearch(query)); 124 | } 125 | else { 126 | res.send(await songs.getAllSongs()); 127 | } 128 | }); 129 | app.listen(3002, () => { 130 | logger.info("Service listening on port 3002!"); 131 | }); 132 | } 133 | main(); 134 | //# sourceMappingURL=app.js.map -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/app.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"app.js","sourceRoot":"","sources":["app.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/app.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as util from "util"; 3 | import * as url from "url"; 4 | import { PlaylistDal } from "./dal/dal-playlists"; 5 | import { Logger } from "./logger"; 6 | import { IConfiguration } from "./types/config"; 7 | import { ListItem } from "./types/list-item"; 8 | import * as express from "express"; 9 | import * as cors from "cors"; 10 | import { Playlist } from "playlist"; 11 | import bodyParser = require("body-parser"); 12 | import { SongDal } from "./dal/dal-songs"; 13 | // convert fs.readFile into Promise version of same 14 | const readFile = util.promisify(fs.readFile); 15 | 16 | // getting the configuration from file for now 17 | async function getConfig(): Promise { 18 | let content: any = await readFile("./config.json", "utf8"); 19 | return JSON.parse(content) as IConfiguration; 20 | } 21 | 22 | async function main() { 23 | let playlists: PlaylistDal = new PlaylistDal(); 24 | let conf = await getConfig(); 25 | 26 | let mongoHost = process.env["MONGO_SERVICE_HOST"] || "localhost"; 27 | let mongoPort = process.env["MONGO_SERVICE_PORT_TCP"] || "27017"; 28 | let mongoDbName = process.env["MONGO_SERVICE_DB_NAME"] || "demo1"; 29 | let logger = new Logger(conf.logLevel); 30 | 31 | let mongoConnStr = 32 | "mongodb://" + mongoHost + ":" + mongoPort + "/" + mongoDbName; 33 | logger.info("connecting to db:", mongoConnStr); 34 | 35 | let songs: SongDal = new SongDal(logger, mongoConnStr); 36 | 37 | songs.delete(); 38 | songs.populate("songs.json"); 39 | logger.info("loglevel:", logger.logLevel); 40 | logger.debug("running with configuration: ", JSON.stringify(conf)); 41 | 42 | // no authentication yet, set our user as #2 43 | let currentUserId = "2"; 44 | 45 | logger.debug("running with configuration: ", JSON.stringify(conf)); 46 | 47 | // now using a cors header (allow origin) 48 | let app: express.Express = express(); 49 | 50 | app.use(bodyParser.json()); 51 | 52 | app.use(cors()); 53 | app.get( 54 | "/v1/playlists", 55 | (req: express.Request, res: express.Response): any => { 56 | let urlParts = url.parse(req.url, true); 57 | let query = urlParts.query; 58 | let plists: Playlist[] = []; 59 | if (req.query.id) { 60 | let id: string = req.query.id; 61 | let plist = playlists.getPlaylistById(id); 62 | plists = [plist]; 63 | } else if (req.query.userId) { 64 | let userId = req.query.userId; 65 | let owned: boolean = req.query.onlyOwned === "true"; 66 | if (!owned) { 67 | owned = false; 68 | } 69 | plists = playlists.getPlaylistsByUser(userId, owned); 70 | } 71 | if (plists) { 72 | res.send(plists); 73 | } 74 | } 75 | ); 76 | 77 | app.post( 78 | "/v1/playlists/:id/", 79 | (req: express.Request, res: express.Response): any => { 80 | let urlParts = url.parse(req.url, true); 81 | let listId: string = req.params.id as string; 82 | let songId: string = urlParts.query.songId as string; 83 | let err = playlists.addItemToPlaylist(currentUserId, listId, songId); 84 | if (err) { 85 | res.status(401).send(err.message); 86 | } 87 | res.end("success"); 88 | } 89 | ); 90 | 91 | app.delete( 92 | "/v1/playlists/:id/", 93 | (req: express.Request, res: express.Response): any => { 94 | let urlParts = url.parse(req.url, true); 95 | let listId: string = req.params.id as string; 96 | let songId: string = urlParts.query.songId as string; 97 | let err = playlists.removeItemFromPlaylist(currentUserId, listId, songId); 98 | if (err) { 99 | res.status(401).send(err.message); 100 | } 101 | res.end("success"); 102 | } 103 | ); 104 | // add a playlist 105 | app.post( 106 | "/v1/playlists", 107 | (req: express.Request, res: express.Response): any => { 108 | let listItem: Playlist = req.body; 109 | if (listItem) { 110 | let id: string = req.query.id; 111 | let plist = playlists.addNewPlaylist(listItem.name, listItem.creatorId); 112 | res.send(plist); 113 | } else { 114 | logger.error("Playlists API post: no list item in body!"); 115 | res.status(404).send("No Item to add"); 116 | } 117 | } 118 | ); 119 | 120 | // delete a playlist 121 | app.delete( 122 | "/v1/playlists", 123 | (req: express.Request, res: express.Response): any => { 124 | let urlParts = url.parse(req.url, true); 125 | let listId: string = urlParts.query.id as string; 126 | if (listId) { 127 | let err = playlists.delPlaylist(currentUserId, listId); 128 | if (err) { 129 | res.status(401).send(err.message); 130 | } 131 | res.end("success"); 132 | } else { 133 | logger.error("Playlists API delete: no id in query!"); 134 | res.status(404).send("No Item found to delete"); 135 | } 136 | } 137 | ); 138 | 139 | app.get("/v1/songs", async (req: express.Request, res: express.Response) => { 140 | let urlParts = url.parse(req.url, true); 141 | 142 | let id: string = req.query.id; 143 | if (id) { 144 | let song = await songs.getSongById(id); 145 | res.send(song); 146 | } else if (req.query.q) { 147 | let query = req.query.q; 148 | res.send(await songs.getSongSearch(query)); 149 | } else { 150 | res.send(await songs.getAllSongs()); 151 | } 152 | }); 153 | 154 | app.listen(3002, () => { 155 | logger.info("Service listening on port 3002!"); 156 | }); 157 | } 158 | main(); 159 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "logLevel": "debug" 3 | } -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/dal/dal-playlists.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const playlist_1 = require("../types/playlist"); 4 | const uuidv4 = require("uuid/v4"); 5 | class PlaylistDal { 6 | constructor() { 7 | this.map = new Map(); 8 | this.populate(); 9 | } 10 | populate() { 11 | this.map["1"] = { 12 | id: "1", 13 | name: "PopHits2000", 14 | songIds: ["111"], 15 | creationTime: Date.parse("21-07-2011T11:48:33"), 16 | lastModifiedTime: Date.parse("21-07-2011T11:48:33"), 17 | creatorId: "102239082092063283638", 18 | isPublic: true, 19 | playedCounter: 21 20 | }; 21 | this.map["2"] = { 22 | id: "2", 23 | name: "User1-Hits2001", 24 | songIds: [], 25 | creationTime: Date.parse("21-07-2011T11:48:33"), 26 | lastModifiedTime: Date.parse("21-07-2011T11:48:33"), 27 | creatorId: "1", 28 | isPublic: false, 29 | playedCounter: 21 30 | }; 31 | this.map["3"] = { 32 | id: "3", 33 | name: "User2-Hits2002", 34 | songIds: [], 35 | creationTime: Date.parse("21-07-2011T11:48:33"), 36 | lastModifiedTime: Date.parse("21-07-2011T11:48:33"), 37 | creatorId: "2", 38 | isPublic: false, 39 | playedCounter: 21 40 | }; 41 | } 42 | getPlaylistById(id) { 43 | return this.map[id]; 44 | } 45 | getPlaylistsByUser(uid, onlyOwned) { 46 | let retList = []; 47 | for (let key in this.map) { 48 | let plist = this.map[key]; 49 | if (plist.creatorId === uid || (plist.isPublic && !onlyOwned)) { 50 | retList.push(plist); 51 | } 52 | } 53 | return retList; 54 | } 55 | addNewPlaylist(listName, creatorId) { 56 | let listItem = new playlist_1.Playlist(listName, creatorId); 57 | let newListId = uuidv4(); //"" + (this.count() + 1); 58 | listItem.id = newListId; 59 | listItem.songIds = []; 60 | this.map[newListId] = listItem; 61 | return newListId; 62 | } 63 | hasAccess(listId, userId) { 64 | let list = this.map[listId]; 65 | return list.creatorId === userId; 66 | } 67 | addItemToPlaylist(currentUserId, listId, songId) { 68 | if (!this.map[listId]) { 69 | return new Error("Playlist not found"); 70 | } 71 | // if (!this.hasAccess(listId, currentUserId)) { 72 | // return new Error("User not authorized to change the playlist"); 73 | // } 74 | this.map[listId].songIds.push(songId); 75 | } 76 | removeItemFromPlaylist(currentUserId, listId, songId) { 77 | if (!this.map[listId]) { 78 | return new Error("Playlist not found"); 79 | } 80 | // if (!this.hasAccess(listId, currentUserId)) { 81 | // return new Error("User not authorized to change the playlist"); 82 | // } 83 | let plist = this.map[listId]; 84 | let array = plist.songIds; 85 | let index = array.indexOf(songId); 86 | if (index > -1) { 87 | array.splice(index, 1); 88 | } 89 | this.map[listId].songIds = array; 90 | } 91 | delPlaylist(currentUserId, listId) { 92 | if (!this.map[listId]) { 93 | return new Error("Playlist not found"); 94 | } 95 | // if (!this.hasAccess(listId, currentUserId)) { 96 | // return new Error("User not authorized to change the playlist"); 97 | // } 98 | delete this.map[listId]; 99 | } 100 | count() { 101 | return Object.keys(this.map).length; 102 | } 103 | } 104 | exports.PlaylistDal = PlaylistDal; 105 | //# sourceMappingURL=dal-playlists.js.map -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/dal/dal-playlists.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"dal-playlists.js","sourceRoot":"","sources":["dal-playlists.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/dal/dal-playlists.ts: -------------------------------------------------------------------------------- 1 | import { Playlist, ISongDesc } from "../types/playlist"; 2 | const uuidv4 = require("uuid/v4"); 3 | 4 | export class PlaylistDal { 5 | public map: Map = new Map(); 6 | 7 | constructor() { 8 | this.populate(); 9 | } 10 | 11 | public populate() { 12 | this.map["1"] = { 13 | id: "1", 14 | name: "PopHits2000", 15 | songIds: ["111"], 16 | creationTime: Date.parse("21-07-2011T11:48:33"), 17 | lastModifiedTime: Date.parse("21-07-2011T11:48:33"), 18 | creatorId: "102239082092063283638", 19 | isPublic: true, 20 | playedCounter: 21 21 | }; 22 | this.map["2"] = { 23 | id: "2", 24 | name: "User1-Hits2001", 25 | songIds: [], 26 | creationTime: Date.parse("21-07-2011T11:48:33"), 27 | lastModifiedTime: Date.parse("21-07-2011T11:48:33"), 28 | creatorId: "1", 29 | isPublic: false, 30 | playedCounter: 21 31 | }; 32 | 33 | this.map["3"] = { 34 | id: "3", 35 | name: "User2-Hits2002", 36 | songIds: [], 37 | creationTime: Date.parse("21-07-2011T11:48:33"), 38 | lastModifiedTime: Date.parse("21-07-2011T11:48:33"), 39 | creatorId: "2", 40 | isPublic: false, 41 | playedCounter: 21 42 | }; 43 | } 44 | 45 | public getPlaylistById(id: string): Playlist { 46 | return this.map[id]; 47 | } 48 | 49 | public getPlaylistsByUser(uid: string, onlyOwned: boolean): Playlist[] { 50 | let retList: Playlist[] = []; 51 | for (let key in this.map) { 52 | let plist = this.map[key]; 53 | if (plist.creatorId === uid || (plist.isPublic && !onlyOwned)) { 54 | retList.push(plist); 55 | } 56 | } 57 | return retList; 58 | } 59 | 60 | public addNewPlaylist(listName: string, creatorId: string): string { 61 | let listItem = new Playlist(listName, creatorId); 62 | let newListId = uuidv4(); //"" + (this.count() + 1); 63 | listItem.id = newListId; 64 | listItem.songIds = []; 65 | this.map[newListId] = listItem; 66 | return newListId; 67 | } 68 | 69 | private hasAccess(listId: string, userId: string): boolean { 70 | let list: Playlist = this.map[listId]; 71 | return list.creatorId === userId; 72 | } 73 | public addItemToPlaylist( 74 | currentUserId: string, 75 | listId: string, 76 | songId: string 77 | ): Error { 78 | if (!this.map[listId]) { 79 | return new Error("Playlist not found"); 80 | } 81 | // if (!this.hasAccess(listId, currentUserId)) { 82 | // return new Error("User not authorized to change the playlist"); 83 | // } 84 | this.map[listId].songIds.push(songId); 85 | } 86 | 87 | public removeItemFromPlaylist( 88 | currentUserId: string, 89 | listId: string, 90 | songId: string 91 | ): Error { 92 | if (!this.map[listId]) { 93 | return new Error("Playlist not found"); 94 | } 95 | // if (!this.hasAccess(listId, currentUserId)) { 96 | // return new Error("User not authorized to change the playlist"); 97 | // } 98 | let plist: Playlist = this.map[listId]; 99 | let array = plist.songIds; 100 | let index = array.indexOf(songId); 101 | if (index > -1) { 102 | array.splice(index, 1); 103 | } 104 | this.map[listId].songIds = array; 105 | } 106 | 107 | public delPlaylist(currentUserId: string, listId: string): Error { 108 | if (!this.map[listId]) { 109 | return new Error("Playlist not found"); 110 | } 111 | // if (!this.hasAccess(listId, currentUserId)) { 112 | // return new Error("User not authorized to change the playlist"); 113 | // } 114 | delete this.map[listId]; 115 | } 116 | 117 | public count(): number { 118 | return Object.keys(this.map).length; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/dal/dal-songs.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const mongoose = require("mongoose"); 4 | const fs = require("fs"); 5 | class SongDal { 6 | constructor(logger, mongoConnStr) { 7 | this.map = new Map(); 8 | // Define a schema 9 | this.songsModelSchema = new mongoose.Schema({ 10 | name: String, 11 | id: String, 12 | artist: String 13 | }); 14 | this.songModel = mongoose.model("Songs", this.songsModelSchema); 15 | // Set up default mongoose connection 16 | // let mongoDB = "mongodb://" + host + ":" + port + "/demo1"; 17 | mongoose.connect(mongoConnStr, { useNewUrlParser: true }); 18 | // Get Mongoose to use the global promise library 19 | mongoose.Promise = global.Promise; 20 | // Get the default connection 21 | this.db = mongoose.connection; 22 | this.logger = logger; 23 | // Bind connection to error event (to get notification of connection errors) 24 | this.db.on("error", err => { 25 | this.logger.error("MongoDB connection error:", err); 26 | }); 27 | } 28 | // populate adds all songs to the database, it is meant for demo purposes only 29 | async populate(jsonFilePath) { 30 | this.map = new Map(); 31 | this.songModel = mongoose.model("Songs", this.songsModelSchema); 32 | let contents = fs.readFileSync(jsonFilePath); 33 | let songArr = JSON.parse(contents.toString()); 34 | let res = await this.songModel.insertMany(songArr); 35 | return res.length > 0; 36 | } 37 | async delete() { 38 | this.db.dropCollection("songs"); 39 | } 40 | mongo2song(doc) { 41 | let song = { 42 | id: doc["id"], 43 | playtimeSecs: doc["playtimeSecs"] || 0, 44 | name: doc["name"] || "", 45 | artist: doc["artist"] || "", 46 | link: doc["link"] || "", 47 | fullName: doc["name"] + " " + doc["artist"] 48 | }; 49 | return song; 50 | } 51 | async getAllSongs() { 52 | return this.getSongSearch(".*"); 53 | } 54 | async getSongSearch(q) { 55 | let songs = []; 56 | // ignore this huge security issue for the sake of the sample... 57 | let re = new RegExp(q, "i"); 58 | // search for the partial string in the collection as artist 59 | let retList = await this.songModel 60 | .find({ 61 | $or: [{ artist: re }, { name: re }] 62 | }) 63 | .exec(); 64 | for (let i = 0; i < retList.length; i++) { 65 | let doc = retList[i]; 66 | let song = this.mongo2song(doc); 67 | songs.push(song); 68 | } 69 | return songs; 70 | } 71 | async getSongById(id1) { 72 | let res = await this.songModel.find({ id: id1 }).exec(); 73 | let doc = res[0]; 74 | if (!res || !res[0]) 75 | return undefined; 76 | let song = this.mongo2song(doc); 77 | return song; 78 | } 79 | async deleteSongById(id1) { 80 | let res = await this.songModel.deleteOne({ id: id1 }).exec(); 81 | return res.ok == 1; 82 | } 83 | async addSong(name, artist, link, playtimeSecs) { 84 | let song1 = { 85 | id: 0, 86 | playtimeSecs: playtimeSecs, 87 | name: name || "", 88 | artist: artist || "", 89 | link: link || "" 90 | }; 91 | let res = await this.songModel.create(song1); 92 | return this.mongo2song(res); 93 | } 94 | async stop() { 95 | await mongoose.disconnect(); 96 | } 97 | } 98 | exports.SongDal = SongDal; 99 | //# sourceMappingURL=dal-songs.js.map -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/dal/dal-songs.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"dal-songs.js","sourceRoot":"","sources":["dal-songs.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/dal/dal-songs.ts: -------------------------------------------------------------------------------- 1 | import { ISong } from "../types/song"; 2 | import mongoose = require("mongoose"); 3 | import { Logger } from "../logger"; 4 | import * as fs from "fs"; 5 | 6 | export class SongDal { 7 | public map: Map = new Map(); 8 | private db: mongoose.Connection; 9 | private logger: Logger; 10 | public songModel: mongoose.Model; 11 | private songsModelSchema: mongoose.Schema; 12 | constructor(logger: Logger, mongoConnStr: string) { 13 | // Define a schema 14 | this.songsModelSchema = new mongoose.Schema({ 15 | name: String, 16 | id: String, 17 | artist: String 18 | }); 19 | 20 | this.songModel = mongoose.model("Songs", this.songsModelSchema); 21 | // Set up default mongoose connection 22 | 23 | // let mongoDB = "mongodb://" + host + ":" + port + "/demo1"; 24 | mongoose.connect(mongoConnStr, { useNewUrlParser: true }); 25 | // Get Mongoose to use the global promise library 26 | mongoose.Promise = global.Promise; 27 | // Get the default connection 28 | this.db = mongoose.connection; 29 | this.logger = logger; 30 | // Bind connection to error event (to get notification of connection errors) 31 | this.db.on("error", err => { 32 | this.logger.error("MongoDB connection error:", err); 33 | }); 34 | } 35 | 36 | // populate adds all songs to the database, it is meant for demo purposes only 37 | public async populate(jsonFilePath: string): Promise { 38 | this.map = new Map(); 39 | this.songModel = mongoose.model("Songs", this.songsModelSchema); 40 | let contents = fs.readFileSync(jsonFilePath); 41 | let songArr: ISong[] = JSON.parse(contents.toString()); 42 | 43 | let res = await this.songModel.insertMany(songArr); 44 | return res.length > 0; 45 | } 46 | 47 | public async delete() { 48 | this.db.dropCollection("songs"); 49 | } 50 | 51 | private mongo2song(doc: mongoose.Document): ISong { 52 | let song = { 53 | id: doc["id"], 54 | playtimeSecs: doc["playtimeSecs"] || 0, 55 | name: doc["name"] || "", 56 | artist: doc["artist"] || "", 57 | link: doc["link"] || "", 58 | fullName: doc["name"] + " " + doc["artist"] 59 | }; 60 | return song; 61 | } 62 | public async getAllSongs(): Promise { 63 | return this.getSongSearch(".*"); 64 | } 65 | 66 | public async getSongSearch(q: string): Promise { 67 | let songs: ISong[] = []; 68 | 69 | // ignore this huge security issue for the sake of the sample... 70 | let re = new RegExp(q, "i"); 71 | 72 | // search for the partial string in the collection as artist 73 | let retList = await this.songModel 74 | .find({ 75 | $or: [{ artist: re }, { name: re }] 76 | }) 77 | .exec(); 78 | 79 | for (let i = 0; i < retList.length; i++) { 80 | let doc = retList[i]; 81 | let song = this.mongo2song(doc); 82 | songs.push(song); 83 | } 84 | 85 | return songs; 86 | } 87 | 88 | public async getSongById(id1: string): Promise { 89 | let res = await this.songModel.find({ id: id1 }).exec(); 90 | let doc = res[0]; 91 | if (!res || !res[0]) return undefined; 92 | 93 | let song = this.mongo2song(doc); 94 | return song; 95 | } 96 | 97 | public async deleteSongById(id1: string): Promise { 98 | let res = await this.songModel.deleteOne({ id: id1 }).exec(); 99 | return res.ok == 1; 100 | } 101 | 102 | public async addSong( 103 | name: string, 104 | artist: string, 105 | link: string, 106 | playtimeSecs: number 107 | ): Promise { 108 | let song1: ISong = { 109 | id: 0, 110 | playtimeSecs: playtimeSecs, 111 | name: name || "", 112 | artist: artist || "", 113 | link: link || "" 114 | }; 115 | 116 | let res = await this.songModel.create(song1); 117 | return this.mongo2song(res); 118 | } 119 | 120 | public async stop() { 121 | await mongoose.disconnect(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/dal/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/dal/etcd.ts1: -------------------------------------------------------------------------------- 1 | import { Etcd3 } from "etcd3"; 2 | import { Logger, LogLevelEnum } from "../logger"; 3 | import { ServiceConfig } from "./service-config"; 4 | 5 | export class Etcd { 6 | private client: Etcd3; 7 | constructor(etcdHostAddress: string) { 8 | this.client = new Etcd3({ 9 | hosts: etcdHostAddress, 10 | }); 11 | } 12 | public async getConfig(): Promise { 13 | let scfg = new ServiceConfig(); 14 | let logLvl = await this.client.get("loglevel").string(); 15 | scfg.logLevel = this.getLogLevelEnumVal(logLvl); 16 | return scfg; 17 | } 18 | 19 | public async setLogLevel(newLevel: string) { 20 | await this.client.put("loglevel").value(newLevel).exec(); 21 | } 22 | 23 | public async populate() { 24 | // log level is initially info 25 | await this.client.put("loglevel").value("info").exec(); 26 | } 27 | 28 | public async setWatchers(config: ServiceConfig) { 29 | // let lvlVal = await this.client.get("loglevel").string(); 30 | // logger.logLevel = lvlVal; 31 | // logger.info("loglevel:", lvlVal); 32 | 33 | let watcher = await this.client.watch() 34 | .key("loglevel") 35 | .create(); 36 | 37 | watcher 38 | .on("disconnected", () => console.log("disconnected...")) 39 | .on("connected", () => console.log("successfully reconnected!")) 40 | .on("put", 41 | (res) => { 42 | let newLogLevel = res.value.toString(); 43 | console.log("loglevel got set to:", newLogLevel); 44 | config.logLevel = this.getLogLevelEnumVal(newLogLevel); 45 | config.configurationChanged(); 46 | }); 47 | } 48 | 49 | private getLogLevelEnumVal(newLogLevel: string): LogLevelEnum { 50 | let logLevel = LogLevelEnum[newLogLevel]; 51 | // for (let k in LogLevelEnum) { 52 | // if (LogLevelEnum[k] === newLogLevel) { 53 | // logLevel = Number(k); 54 | // } 55 | // } 56 | return logLevel; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/dal/redis-cache.ts1: -------------------------------------------------------------------------------- 1 | 2 | import * as util from "util"; 3 | import * as Redis from "redis"; 4 | import { Logger } from "../logger"; 5 | 6 | export class DalRedis { 7 | public async getAsync(key: string) { return null; } 8 | public async setAsync(key: string, value: string) { return null; } 9 | 10 | private client: Redis.RedisClient; 11 | 12 | constructor(logger: Logger, host: string, port: number) { 13 | let client = Redis.createClient(port, host, null); 14 | // if you'd like to select database 3, instead of 0 (default), call 15 | // client.select(3, function() { /* ... */ }); 16 | 17 | client.on("error", (err) => { 18 | logger.error("Error in redis data layer: ", err); 19 | }); 20 | 21 | client.set("mykey", "a-String"); 22 | // client.hset("hash key", "hashtest 1", "some value", Redis.print); 23 | // client.hset("hash key", "hashtest 2", "some other value", Redis.print); 24 | // client.hkeys("hash key", (err, replies) => { 25 | // console.log(replies.length + " replies:"); 26 | // replies.forEach((reply, i) => { 27 | // console.log(" " + i + ": " + reply); 28 | // }); 29 | // client.quit(); 30 | // }); 31 | 32 | this.getAsync = util.promisify(client.get).bind(client); 33 | this.setAsync = util.promisify(client.set).bind(client); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/dal/service-config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const events_1 = require("events"); 4 | class ServiceConfig extends events_1.EventEmitter { 5 | configurationChanged() { 6 | this.emit("changed"); 7 | } 8 | } 9 | exports.ServiceConfig = ServiceConfig; 10 | //# sourceMappingURL=service-config.js.map -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/dal/service-config.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"service-config.js","sourceRoot":"","sources":["service-config.ts"],"names":[],"mappings":";;AAAA,mCAAsC;AAGtC,MAAa,aAAc,SAAQ,qBAAY;IAGpC,oBAAoB;QACvB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACzB,CAAC;CACJ;AAND,sCAMC"} -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/dal/service-config.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "events"; 2 | import { LogLevelEnum } from "../logger"; 3 | 4 | export class ServiceConfig extends EventEmitter { 5 | public logLevel: LogLevelEnum; 6 | 7 | public configurationChanged() { 8 | this.emit("changed"); 9 | } 10 | } -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var LogLevelEnum; 4 | (function (LogLevelEnum) { 5 | LogLevelEnum[LogLevelEnum["debug"] = 0] = "debug"; 6 | LogLevelEnum[LogLevelEnum["info"] = 1] = "info"; 7 | LogLevelEnum[LogLevelEnum["warn"] = 2] = "warn"; 8 | LogLevelEnum[LogLevelEnum["error"] = 3] = "error"; 9 | })(LogLevelEnum = exports.LogLevelEnum || (exports.LogLevelEnum = {})); 10 | class Logger { 11 | constructor(logLevel) { 12 | let theLogLevel = LogLevelEnum[logLevel]; 13 | this.logLevel = theLogLevel; 14 | } 15 | getDate() { 16 | let date = new Date(); 17 | let dStr = date.toLocaleDateString() + " " + date.toLocaleTimeString(); 18 | return dStr; 19 | } 20 | debug(fmt, ...additions) { 21 | if (this.logLevel > LogLevelEnum.debug) { 22 | return; 23 | } 24 | console.debug(this.getDate() + " [Debug] " + fmt, ...additions); 25 | } 26 | info(fmt, ...additions) { 27 | if (this.logLevel > LogLevelEnum.info) { 28 | return; 29 | } 30 | console.info(this.getDate() + " [Info] " + fmt, ...additions); 31 | } 32 | warn(fmt, ...additions) { 33 | if (this.logLevel > LogLevelEnum.warn) { 34 | return; 35 | } 36 | console.warn(this.getDate() + " [Warn ] " + fmt, ...additions); 37 | } 38 | error(fmt, ...additions) { 39 | if (this.logLevel > LogLevelEnum.error) { 40 | return; 41 | } 42 | console.error(this.getDate() + " [Error] " + fmt, ...additions); 43 | } 44 | } 45 | exports.Logger = Logger; 46 | //# sourceMappingURL=logger.js.map -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/logger.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"logger.js","sourceRoot":"","sources":["logger.ts"],"names":[],"mappings":";;AACA,IAAY,YAKX;AALD,WAAY,YAAY;IACvB,iDAAK,CAAA;IACL,+CAAI,CAAA;IACJ,+CAAI,CAAA;IACJ,iDAAK,CAAA;AACN,CAAC,EALW,YAAY,GAAZ,oBAAY,KAAZ,oBAAY,QAKvB;AAED,MAAa,MAAM;IAGlB,YAAY,QAAgB;QAC3B,IAAI,WAAW,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;QACzC,IAAI,CAAC,QAAQ,GAAG,WAAW,CAAC;IAC7B,CAAC;IAEO,OAAO;QACd,IAAI,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;QACtB,IAAI,IAAI,GAAG,IAAI,CAAC,kBAAkB,EAAE,GAAG,GAAG,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAC;QACvE,OAAO,IAAI,CAAC;IACb,CAAC;IACM,KAAK,CAAC,GAAW,EAAE,GAAG,SAAS;QACrC,IAAI,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC,KAAK,EAAE;YAAE,OAAO;SAAE;QACnD,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,WAAW,GAAG,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC;IACjE,CAAC;IACM,IAAI,CAAC,GAAW,EAAE,GAAG,SAAS;QACpC,IAAI,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC,IAAI,EAAE;YAAE,OAAO;SAAE;QAElD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,UAAU,GAAG,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC;IAC/D,CAAC;IACM,IAAI,CAAC,GAAW,EAAE,GAAG,SAAS;QACpC,IAAI,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC,IAAI,EAAE;YAAE,OAAO;SAAE;QAElD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,WAAW,GAAG,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC;IAChE,CAAC;IACM,KAAK,CAAC,GAAW,EAAE,GAAG,SAAS;QACrC,IAAI,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC,KAAK,EAAE;YAAE,OAAO;SAAE;QAEnD,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,WAAW,GAAG,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC;IACjE,CAAC;CACD;AAhCD,wBAgCC"} -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/logger.ts: -------------------------------------------------------------------------------- 1 | 2 | export enum LogLevelEnum { 3 | debug, 4 | info, 5 | warn, 6 | error, 7 | } 8 | 9 | export class Logger { 10 | 11 | public logLevel: LogLevelEnum; 12 | constructor(logLevel: string) { 13 | let theLogLevel = LogLevelEnum[logLevel]; 14 | this.logLevel = theLogLevel; 15 | } 16 | 17 | private getDate(): string { 18 | let date = new Date(); 19 | let dStr = date.toLocaleDateString() + " " + date.toLocaleTimeString(); 20 | return dStr; 21 | } 22 | public debug(fmt: string, ...additions): void { 23 | if (this.logLevel > LogLevelEnum.debug) { return; } 24 | console.debug(this.getDate() + " [Debug] " + fmt, ...additions); 25 | } 26 | public info(fmt: string, ...additions): void { 27 | if (this.logLevel > LogLevelEnum.info) { return; } 28 | 29 | console.info(this.getDate() + " [Info] " + fmt, ...additions); 30 | } 31 | public warn(fmt: string, ...additions): void { 32 | if (this.logLevel > LogLevelEnum.warn) { return; } 33 | 34 | console.warn(this.getDate() + " [Warn ] " + fmt, ...additions); 35 | } 36 | public error(fmt: string, ...additions): void { 37 | if (this.logLevel > LogLevelEnum.error) { return; } 38 | 39 | console.error(this.getDate() + " [Error] " + fmt, ...additions); 40 | } 41 | } -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/types/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=config.js.map -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/types/config.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"config.js","sourceRoot":"","sources":["config.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/types/config.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface IConfiguration { 3 | logLevel: string; 4 | } 5 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/types/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/types/list-item.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class ListItem { 4 | } 5 | exports.ListItem = ListItem; 6 | //# sourceMappingURL=list-item.js.map -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/types/list-item.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"list-item.js","sourceRoot":"","sources":["list-item.ts"],"names":[],"mappings":";;AAAA,MAAa,QAAQ;CAIpB;AAJD,4BAIC"} -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/types/list-item.ts: -------------------------------------------------------------------------------- 1 | export class ListItem { 2 | public songId: string; 3 | public id: string; 4 | public listId: string; 5 | } -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/types/playlist.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class Playlist { 4 | constructor(lname, lcreatorId) { 5 | let list = { 6 | id: "", 7 | name: lname, 8 | songIds: [], 9 | creationTime: new Date(), 10 | lastModifiedTime: new Date(), 11 | creatorId: lcreatorId, 12 | isPublic: false, 13 | playedCounter: 0, 14 | }; 15 | return list; 16 | } 17 | } 18 | exports.Playlist = Playlist; 19 | //# sourceMappingURL=playlist.js.map -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/types/playlist.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"playlist.js","sourceRoot":"","sources":["playlist.ts"],"names":[],"mappings":";;AAGA,MAAa,QAAQ;IASjB,YAAY,KAAa,EAAE,UAAkB;QAEzC,IAAI,IAAI,GAAa;YACjB,EAAE,EAAE,EAAE;YACN,IAAI,EAAE,KAAK;YACX,OAAO,EAAE,EAAE;YACX,YAAY,EAAE,IAAI,IAAI,EAAE;YACxB,gBAAgB,EAAE,IAAI,IAAI,EAAE;YAC5B,SAAS,EAAE,UAAU;YACrB,QAAQ,EAAE,KAAK;YACf,aAAa,EAAE,CAAC;SACnB,CAAC;QACF,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ;AAvBD,4BAuBC"} -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/types/playlist.ts: -------------------------------------------------------------------------------- 1 | export interface ISongDesc { 2 | id: string; 3 | } 4 | export class Playlist { 5 | public id: string; 6 | public name: string; 7 | public songIds: string[]; 8 | public creationTime: Date; 9 | public lastModifiedTime: Date; 10 | public creatorId: string; 11 | public isPublic: boolean; 12 | public playedCounter: number; 13 | constructor(lname: string, lcreatorId: string) { 14 | 15 | let list: Playlist = { 16 | id: "", 17 | name: lname, 18 | songIds: [], 19 | creationTime: new Date(), 20 | lastModifiedTime: new Date(), 21 | creatorId: lcreatorId, 22 | isPublic: false, 23 | playedCounter: 0, 24 | }; 25 | return list; 26 | } 27 | } -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/types/song.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=song.js.map -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/types/song.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"song.js","sourceRoot":"","sources":["song.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/src/types/song.ts: -------------------------------------------------------------------------------- 1 | export interface ISong { 2 | id: number; 3 | name: string; 4 | playtimeSecs: number; 5 | link: string; 6 | artist: string; 7 | } 8 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/test/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/test/playlist-api-spec.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const request = require("request-promise-native"); 4 | const chai = require("chai"); 5 | const baseUrl = "http://localhost:3002/"; 6 | describe("Playlist Server Interface", () => { 7 | describe("playlist API", () => { 8 | it("GET playlist by id", async () => { 9 | let res = await request.get(baseUrl + "v1/playlists?id=2"); 10 | let rObj = JSON.parse(res); 11 | chai.assert(res, "no response recieved!"); 12 | chai.assert(rObj.length > 0, "no data returned"); 13 | chai.assert(rObj[0].id == 2, "wrong object returned"); 14 | }); 15 | async function createPlaylist() { 16 | let plist; 17 | return await request.post({ 18 | headers: { "content-type": "application/json" }, 19 | url: baseUrl + "v1/playlists", 20 | body: JSON.stringify(plist) 21 | }); 22 | } 23 | it("adds a playlist", async () => { 24 | let id = await createPlaylist(); 25 | chai.expect(id.length).to.be.greaterThan(8); 26 | }); 27 | it("adds a song to playlist", async () => { 28 | await request.post({ 29 | headers: { "content-type": "application/json" }, 30 | url: baseUrl + "v1/playlists/2?songId=3" 31 | }); 32 | await request.delete({ 33 | headers: { "content-type": "application/json" }, 34 | url: baseUrl + "v1/playlists/2?songId=3" 35 | }); 36 | }); 37 | it("removes a song from playlist", async () => { 38 | let resCreate = await request.post({ 39 | headers: { "content-type": "application/json" }, 40 | url: baseUrl + "v1/playlists/3?songId=3" 41 | }); 42 | chai.expect(resCreate).to.eq("success"); 43 | let res = await request.delete({ 44 | headers: { "content-type": "application/json" }, 45 | url: baseUrl + "v1/playlists/3?songId=3" 46 | }); 47 | chai.expect(res).to.eq("success"); 48 | }); 49 | it("deletes a playlist", async () => { 50 | let listId = await createPlaylist(); 51 | let res = await request.delete(baseUrl + "v1/playlists?id=" + listId); 52 | chai.expect(res).to.eq("success"); 53 | }); 54 | }); 55 | }); 56 | //# sourceMappingURL=playlist-api-spec.test.js.map -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/test/playlist-api-spec.test.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"playlist-api-spec.test.js","sourceRoot":"","sources":["playlist-api-spec.test.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/test/playlist-api-spec.test.ts: -------------------------------------------------------------------------------- 1 | import request = require("request-promise-native"); 2 | import chai = require("chai"); 3 | const baseUrl = "http://localhost:3002/"; 4 | 5 | describe("Playlist Server Interface", () => { 6 | describe("playlist API", () => { 7 | it("GET playlist by id", async () => { 8 | let res = await request.get(baseUrl + "v1/playlists?id=2"); 9 | let rObj = JSON.parse(res); 10 | chai.assert(res, "no response recieved!"); 11 | chai.assert(rObj.length > 0, "no data returned"); 12 | chai.assert(rObj[0].id == 2, "wrong object returned"); 13 | }); 14 | 15 | async function createPlaylist() { 16 | let plist: { 17 | id: "3"; 18 | name: "stam1"; 19 | songIds: null; 20 | creationTime: null; 21 | lastModifiedTime: null; 22 | creatorId: "me"; 23 | isPublic: true; 24 | playedCounter: 0; 25 | }; 26 | return await request.post({ 27 | headers: { "content-type": "application/json" }, 28 | url: baseUrl + "v1/playlists", 29 | body: JSON.stringify(plist) 30 | }); 31 | } 32 | 33 | it("adds a playlist", async () => { 34 | let id = await createPlaylist(); 35 | chai.expect(id.length).to.be.greaterThan(8); 36 | }); 37 | 38 | it("adds a song to playlist", async () => { 39 | await request.post({ 40 | headers: { "content-type": "application/json" }, 41 | url: baseUrl + "v1/playlists/2?songId=3" 42 | }); 43 | 44 | await request.delete({ 45 | headers: { "content-type": "application/json" }, 46 | url: baseUrl + "v1/playlists/2?songId=3" 47 | }); 48 | }); 49 | 50 | it("removes a song from playlist", async () => { 51 | let resCreate = await request.post({ 52 | headers: { "content-type": "application/json" }, 53 | url: baseUrl + "v1/playlists/3?songId=3" 54 | }); 55 | chai.expect(resCreate).to.eq("success"); 56 | 57 | let res = await request.delete({ 58 | headers: { "content-type": "application/json" }, 59 | url: baseUrl + "v1/playlists/3?songId=3" 60 | }); 61 | chai.expect(res).to.eq("success"); 62 | }); 63 | 64 | it("deletes a playlist", async () => { 65 | let listId = await createPlaylist(); 66 | let res = await request.delete(baseUrl + "v1/playlists?id=" + listId); 67 | chai.expect(res).to.eq("success"); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/test/playlist-db-spec.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const dal_playlists_1 = require("../src/dal/dal-playlists"); 4 | const dal_songs_1 = require("../src/dal/dal-songs"); 5 | const chai = require("chai"); 6 | const logger_1 = require("../src/logger"); 7 | const mongodb_memory_server_1 = require("mongodb-memory-server"); 8 | // const sinon = require("sinon"); 9 | // const chaiSinon = require("chai-sinon"); 10 | describe("Backend Server Data Access", () => { 11 | describe("Playlist Data Access", () => { 12 | it("returns a playlist for an existing listId", () => { 13 | let db = new dal_playlists_1.PlaylistDal(); 14 | let myTestListId = db.addNewPlaylist("testlist1", "333"); 15 | let plist = db.getPlaylistById(myTestListId); 16 | chai.expect(plist, "no list was returned").to.not.be.undefined; 17 | chai 18 | .expect(plist.name, "retrieved list has wrong name") 19 | .to.equal("testlist1"); 20 | db.delPlaylist("2", myTestListId); 21 | }); 22 | it("returns list results for user", () => { 23 | let db = new dal_playlists_1.PlaylistDal(); 24 | let myTestListId = db.addNewPlaylist("testlist1", "333"); 25 | let myTestListId1 = db.addNewPlaylist("testlist2", "333"); 26 | let plists = db.getPlaylistsByUser("333", true); 27 | chai 28 | .expect(plists.length) 29 | .to.equal(2, "our test user should have 2 lists"); 30 | db.delPlaylist("2", myTestListId); 31 | db.delPlaylist("2", myTestListId1); 32 | }); 33 | it("adds a Playlist", () => { 34 | let db = new dal_playlists_1.PlaylistDal(); 35 | let myTestListId = db.addNewPlaylist("testlist1", "2"); 36 | chai.expect(db.getPlaylistById(myTestListId)).to.not.be.undefined; 37 | db.delPlaylist("2", myTestListId); 38 | }); 39 | it("adds an item to list", () => { 40 | let db = new dal_playlists_1.PlaylistDal(); 41 | let myTestListId = db.addNewPlaylist("testlist1", "2"); 42 | db.addItemToPlaylist("2", myTestListId, "23"); 43 | let playlist = db.getPlaylistById(myTestListId); 44 | chai 45 | .expect(playlist.songIds) 46 | .to.contain("23", "item isn't in playlist after adding it"); 47 | db.delPlaylist("2", myTestListId); 48 | }); 49 | it("deletes a list item", () => { 50 | let db = new dal_playlists_1.PlaylistDal(); 51 | let myTestListId = db.addNewPlaylist("testlist1", "2"); 52 | db.addItemToPlaylist("2", myTestListId, "23"); 53 | db.removeItemFromPlaylist("2", myTestListId, "23"); 54 | let playlist = db.getPlaylistById(myTestListId); 55 | chai 56 | .expect(playlist.songIds) 57 | .to.not.contain("23", "item exists in list after deletion"); 58 | db.delPlaylist("2", myTestListId); 59 | }); 60 | it("deletes a Playlist", () => { 61 | let db = new dal_playlists_1.PlaylistDal(); 62 | let myTestListId = db.addNewPlaylist("testlist1", "2"); 63 | db.delPlaylist("2", myTestListId); 64 | chai.expect(db.getPlaylistById(myTestListId), "deletion did not succeed") 65 | .to.be.undefined; 66 | }); 67 | }); 68 | describe("Songs Data Access", async () => { 69 | let songs; 70 | let mongod; 71 | before(async () => { 72 | // create an in memory mongo instance just for testing: 73 | mongod = new mongodb_memory_server_1.MongoMemoryServer(); 74 | //connect to our in mem mong instance: 75 | let logger = new logger_1.Logger("debug"); 76 | const uri = await mongod.getConnectionString(); 77 | songs = new dal_songs_1.SongDal(logger, uri); 78 | // populate our in memory database with a dummy song list: 79 | await songs.populate(__dirname + "\\..\\src\\songs.json"); 80 | }); 81 | after(async () => { 82 | await mongod.stop(); 83 | await songs.stop(); 84 | }); 85 | it("can search for songs", async () => { 86 | let res = await songs.getSongSearch("ala"); 87 | chai.expect(res.length).to.equal(1); 88 | chai.expect(res[0].artist).to.equal("Alanis"); 89 | }); 90 | it("can find a song by id", async () => { 91 | let res = await songs.getSongById("111"); 92 | chai.expect(res.name).to.equal("Ironic"); 93 | chai.expect(res.artist).to.equal("Alanis"); 94 | }); 95 | it("can delete a song", async () => { 96 | let song = await songs.getSongById("111"); 97 | chai.expect(song).to.exist; 98 | let res = await songs.deleteSongById("111"); 99 | chai.expect(res).to.be.true; 100 | let song1 = await songs.getSongById("111"); 101 | chai.expect(song1).not.to.exist; 102 | }); 103 | it("can add a song", async () => { 104 | let res = await songs.addSong("blank song", "the busts", "http://nourl.com/download/this.mp3", 300); 105 | chai.expect(res.name).to.equal("blank song"); 106 | chai.expect(res.artist).to.equal("the busts"); 107 | }); 108 | }); 109 | }); 110 | //# sourceMappingURL=playlist-db-spec.test.js.map -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/test/playlist-db-spec.test.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"playlist-db-spec.test.js","sourceRoot":"","sources":["playlist-db-spec.test.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/test/playlist-db-spec.test.ts: -------------------------------------------------------------------------------- 1 | import { PlaylistDal } from "../src/dal/dal-playlists"; 2 | import { SongDal } from "../src/dal/dal-songs"; 3 | import { ListItem } from "../src/types/list-item"; 4 | import chai = require("chai"); 5 | import { Playlist } from "../src/types/playlist"; 6 | import { Logger } from "../src/logger"; 7 | import { MongoMemoryServer } from "mongodb-memory-server"; 8 | import { Mongoose } from "mongoose"; 9 | const expect = chai.expect; 10 | 11 | describe("Backend Server Data Access", () => { 12 | describe("Playlist Data Access", () => { 13 | it("returns a playlist for an existing listId", () => { 14 | let db = new PlaylistDal(); 15 | let myTestListId = db.addNewPlaylist("testlist1", "333"); 16 | let plist = db.getPlaylistById(myTestListId); 17 | 18 | expect(plist, "no list was returned").to.not.be.undefined; 19 | expect(plist.name, "retrieved list has wrong name").to.equal("testlist1"); 20 | 21 | db.delPlaylist("2", myTestListId); 22 | }); 23 | 24 | it("returns list results for user", () => { 25 | let db = new PlaylistDal(); 26 | let myTestListId = db.addNewPlaylist("testlist1", "333"); 27 | let myTestListId1 = db.addNewPlaylist("testlist2", "333"); 28 | let plists = db.getPlaylistsByUser("333", true); 29 | 30 | expect(plists.length).to.equal(2, "our test user should have 2 lists"); 31 | 32 | db.delPlaylist("2", myTestListId); 33 | db.delPlaylist("2", myTestListId1); 34 | }); 35 | 36 | it("adds a Playlist", () => { 37 | let db = new PlaylistDal(); 38 | let myTestListId = db.addNewPlaylist("testlist1", "2"); 39 | 40 | expect(db.getPlaylistById(myTestListId)).to.not.be.undefined; 41 | db.delPlaylist("2", myTestListId); 42 | }); 43 | 44 | it("adds an item to list", () => { 45 | let db = new PlaylistDal(); 46 | let myTestListId = db.addNewPlaylist("testlist1", "2"); 47 | db.addItemToPlaylist("2", myTestListId, "23"); 48 | 49 | let playlist = db.getPlaylistById(myTestListId); 50 | 51 | expect(playlist.songIds).to.contain( 52 | "23", 53 | "item isn't in playlist after adding it" 54 | ); 55 | 56 | db.delPlaylist("2", myTestListId); 57 | }); 58 | 59 | it("deletes a list item", () => { 60 | let db = new PlaylistDal(); 61 | let myTestListId = db.addNewPlaylist("testlist1", "2"); 62 | db.addItemToPlaylist("2", myTestListId, "23"); 63 | 64 | db.removeItemFromPlaylist("2", myTestListId, "23"); 65 | let playlist = db.getPlaylistById(myTestListId); 66 | 67 | expect(playlist.songIds).to.not.contain( 68 | "23", 69 | "item exists in list after deletion" 70 | ); 71 | 72 | db.delPlaylist("2", myTestListId); 73 | }); 74 | 75 | it("deletes a Playlist", () => { 76 | let db = new PlaylistDal(); 77 | let myTestListId = db.addNewPlaylist("testlist1", "2"); 78 | 79 | db.delPlaylist("2", myTestListId); 80 | expect(db.getPlaylistById(myTestListId), "deletion did not succeed").to.be 81 | .undefined; 82 | }); 83 | }); 84 | 85 | describe("Songs Data Access", async () => { 86 | let songs: SongDal; 87 | let mongod: MongoMemoryServer; 88 | before(async () => { 89 | // create an in memory mongo instance just for testing: 90 | mongod = new MongoMemoryServer(); 91 | 92 | //connect to our in mem mong instance: 93 | let logger = new Logger("debug"); 94 | const uri = await mongod.getConnectionString(); 95 | songs = new SongDal(logger, uri); 96 | 97 | // populate our in memory database with a dummy song list: 98 | await songs.populate(__dirname + "\\..\\src\\songs.json"); 99 | }); 100 | 101 | after(async () => { 102 | await mongod.stop(); 103 | await songs.stop(); 104 | }); 105 | 106 | it("can search for songs", async () => { 107 | let res = await songs.getSongSearch("ala"); 108 | expect(res.length).to.equal(1); 109 | expect(res[0].artist).to.equal("Alanis"); 110 | }); 111 | 112 | it("can find a song by id", async () => { 113 | let res = await songs.getSongById("111"); 114 | expect(res.name).to.equal("Ironic"); 115 | expect(res.artist).to.equal("Alanis"); 116 | }); 117 | 118 | it("can delete a song", async () => { 119 | let song = await songs.getSongById("111"); 120 | expect(song).to.exist; 121 | let res = await songs.deleteSongById("111"); 122 | expect(res).to.be.true; 123 | let song1 = await songs.getSongById("111"); 124 | expect(song1).not.to.exist; 125 | }); 126 | 127 | it("can add a song", async () => { 128 | let res = await songs.addSong( 129 | "blank song", 130 | "the busts", 131 | "http://nourl.com/download/this.mp3", 132 | 300 133 | ); 134 | expect(res.name).to.equal("blank song"); 135 | expect(res.artist).to.equal("the busts"); 136 | }); 137 | }); 138 | }); 139 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2017", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | 8 | "baseUrl": ".", 9 | "paths": { 10 | "*": [ 11 | "node_modules/*", 12 | "src/types/*", 13 | "test/*" 14 | ] 15 | } 16 | }, 17 | "include": [ 18 | "src/**/*", 19 | "test/**/*" 20 | ] 21 | } -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/BackEnd/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:latest", 3 | "rules": { 4 | "ordered-imports": false, 5 | "no-inferrable-types": false, 6 | "prefer-const": false, 7 | "eofline": false, 8 | "max-line-length": false, 9 | "member-ordering": false, 10 | "no-console": false, 11 | "object-literal-sort-keys": false, 12 | "no-implicit-dependencies": false, 13 | "indent": false, 14 | "forin": false, 15 | "no-string-literal": false, 16 | "prefer-for-of": false, 17 | "no-unused-expression": false 18 | } 19 | } -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.13-slim AS build-env 2 | 3 | # Create app directory 4 | RUN mkdir -p /opt/my-musik/ 5 | WORKDIR /opt/my-musik/ 6 | 7 | COPY ./package.json . 8 | COPY ./tsconfig.json . 9 | COPY ./tslint.json . 10 | 11 | # Install app dependencies 12 | RUN npm install typescript -g 13 | RUN npm install --no-optional 14 | RUN npm install -g @vue/cli 15 | 16 | # Bundle app source 17 | COPY ./src ./src 18 | COPY ./public ./public 19 | 20 | RUN npm run build 21 | 22 | 23 | # final stage 24 | FROM node:8.13-slim 25 | RUN npm install npm -g 26 | RUN mkdir -p /opt/my-musik/ 27 | WORKDIR /opt/my-musik/ 28 | COPY --from=build-env /opt/my-musik/dist ./dist 29 | COPY --from=build-env /opt/my-musik/package.json ./ 30 | 31 | RUN npm install --no-optional --only=prod 32 | RUN npm install node-static -g 33 | EXPOSE 3001 34 | 35 | CMD [ "static", "./dist", "-a", "0.0.0.0", "-p", "3001" ] -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "songs-ui", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve --port 3001", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@types/request": "^2.48.1", 12 | "bootstrap-vue": "^2.0.4", 13 | "register-service-worker": "^1.5.2", 14 | "request": "^2.88.0", 15 | "vue": "^2.5.21", 16 | "vue-class-component": "^6.0.0", 17 | "vue-property-decorator": "^7.0.0", 18 | "vue-router": "^3.0.1", 19 | "vuejs-auto-complete": "^0.9.0" 20 | }, 21 | "devDependencies": { 22 | "@vue/cli-plugin-pwa": "^3.3.0", 23 | "@vue/cli-plugin-typescript": "^3.3.0", 24 | "@vue/cli-service": "^3.3.0", 25 | "typescript": "^3.0.0", 26 | "vue-template-compiler": "^2.5.21" 27 | }, 28 | "postcss": { 29 | "plugins": { 30 | "autoprefixer": {} 31 | } 32 | }, 33 | "browserslist": [ 34 | "> 1%", 35 | "last 2 versions", 36 | "not ie <= 8" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/public/favicon.ico -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/img/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/img/icons/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | demo 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "short_name": "demo", 4 | "icons": [ 5 | { 6 | "src": "./img/icons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "./img/icons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": "./index.html", 17 | "display": "standalone", 18 | "background_color": "#000000", 19 | "theme_color": "#4DBA87" 20 | } 21 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/run.bat: -------------------------------------------------------------------------------- 1 | npm run serve -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | 43 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/assets/backspace16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/src/assets/backspace16.png -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/assets/delb16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/src/assets/delb16.png -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/assets/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/assets/googleButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/src/assets/googleButton.png -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/src/assets/logo.png -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/assets/musical-note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Node.js-Testing-Best-Practices/b5fa9b185bbc973f024d3eb899dee242b0ba648f/4.1 - Sample App & Testing/FrontEnd/src/assets/musical-note.png -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 72 | 73 | 81 | 82 | 83 | 99 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/components/Songs.vue: -------------------------------------------------------------------------------- 1 | 84 | 85 | 243 | 244 | 245 | 296 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/components/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | import router from './router'; 4 | import './registerServiceWorker'; 5 | 6 | Vue.config.productionTip = false; 7 | 8 | new Vue({ 9 | router, 10 | render: (h) => h(App), 11 | }).$mount('#app'); 12 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/registerServiceWorker.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-console */ 2 | 3 | import { register } from 'register-service-worker'; 4 | 5 | if (process.env.NODE_ENV === 'production') { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | ready() { 8 | console.log( 9 | 'App is being served from cache by a service worker.\n' + 10 | 'For more details, visit https://goo.gl/AFskqB', 11 | ); 12 | }, 13 | registered() { 14 | console.log('Service worker has been registered.'); 15 | }, 16 | cached() { 17 | console.log('Content has been cached for offline use.'); 18 | }, 19 | updatefound() { 20 | console.log('New content is downloading.'); 21 | }, 22 | updated() { 23 | console.log('New content is available; please refresh.'); 24 | }, 25 | offline() { 26 | console.log('No internet connection found. App is running in offline mode.'); 27 | }, 28 | error(error) { 29 | console.error('Error during service worker registration:', error); 30 | }, 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/router.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import Home from './views/Home.vue'; 4 | 5 | Vue.use(Router); 6 | 7 | export default new Router({ 8 | routes: [ 9 | { 10 | path: '/home', 11 | name: 'home', 12 | component: Home, 13 | props: (route: any) => ({ userId: route.query.id, userName: route.query.name }), 14 | }, 15 | ], 16 | }); 17 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue'; 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue'; 3 | export default Vue; 4 | } 5 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 22 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/src/views/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env" 16 | ], 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "lib": [ 23 | "esnext", 24 | "dom", 25 | "dom.iterable", 26 | "scripthost" 27 | ] 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.tsx", 32 | "src/**/*.vue", 33 | "tests/**/*.ts", 34 | "tests/**/*.tsx" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /4.1 - Sample App & Testing/FrontEnd/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "warning", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "linterOptions": { 7 | "exclude": [ 8 | "node_modules/**" 9 | ] 10 | }, 11 | "rules": { 12 | "quotemark": [false, "single"], 13 | "indent": [true, "spaces", 2], 14 | "interface-name": false, 15 | "ordered-imports": false, 16 | "object-literal-sort-keys": false, 17 | "no-consecutive-blank-lines": false, 18 | "prefer-const":false, 19 | "trailing-comma": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /4.3 - API tests for sample app/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Main", 11 | "program": "${workspaceFolder}/src/index.js" 12 | }, 13 | { 14 | "type": "node", 15 | "request": "launch", 16 | "name": "Mocha Tests", 17 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 18 | "args": [ 19 | "-u", 20 | "bdd", 21 | "--timeout", 22 | "999999", 23 | "--colors", 24 | "${workspaceFolder}/test" 25 | ], 26 | "internalConsoleOptions": "openOnSessionStart" 27 | }, 28 | { 29 | "type": "node", 30 | "request": "launch", 31 | "name": "Launch Program", 32 | "program": "${workspaceFolder}\\index.js" 33 | }, 34 | { 35 | "name": "Debug Mocha Test", 36 | "type": "node", 37 | "request": "attach", 38 | "address": "localhost", 39 | "port": 9229, 40 | "sourceMaps": false 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /4.3 - API tests for sample app/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "mochaExplorer.files": "test/**/*.js", 3 | "mochaExplorer.require": "", 4 | "mochaExplorer.timeout": 10000 5 | } 6 | -------------------------------------------------------------------------------- /4.3 - API tests for sample app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node ./node_modules/mocha/bin/_mocha test/*spec*.js --timeout 30000 --exit -s 0" 8 | }, 9 | "author": "Amit Bezalel", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "chai": "^4.2.0", 13 | "mocha": "^6.2.1", 14 | "sinon": "^7.5.0" 15 | }, 16 | "dependencies": { 17 | "@types/mocha": "^5.2.7", 18 | "@types/node": "^12.7.12", 19 | "mongodb-memory-server": "^5.2.8", 20 | "request": "^2.88.0", 21 | "request-promise-native": "^1.0.7", 22 | "ts-node": "^8.4.1", 23 | "typescript": "^3.6.4" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /4.3 - API tests for sample app/test/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | InfoTip=This folder is shared online. 3 | IconFile=C:\Program Files\Google\Drive\googledrivesync.exe 4 | IconIndex=16 5 | -------------------------------------------------------------------------------- /4.3 - API tests for sample app/test/playlist-api-spec.test.js: -------------------------------------------------------------------------------- 1 | const request = require("request-promise-native"); 2 | const chai = require("chai"); 3 | const baseUrl = "http://localhost:3002/"; 4 | const MongoMemoryServer = require("mongodb-memory-server").MongoMemoryServer; 5 | const cp = require("child_process"); 6 | const expect = chai.expect; 7 | 8 | describe("Playlist Server Interface", () => { 9 | describe("playlist API", () => { 10 | let childProc; 11 | let mongod; 12 | before(async () => { 13 | // create an in memory mongo instance just for testing: 14 | mongod = new MongoMemoryServer(); 15 | 16 | //connect the app to our in mem mongo instance: 17 | const port = await mongod.getPort(); 18 | const name = await mongod.getDbName(); 19 | process.env["MONGO_SERVICE_HOST"] = "127.0.0.1"; 20 | process.env["MONGO_SERVICE_PORT_TCP"] = "" + port; 21 | process.env["MONGO_SERVICE_DB_NAME"] = name; 22 | 23 | childProc = cp.exec( 24 | "node src/app.js", 25 | { cwd: "../4.1 - Sample App & Testing/BackEnd" }, 26 | (err, stdout, stderr) => { 27 | if (err) { 28 | console.error(`exec error: ${err}`); 29 | return; 30 | } 31 | } 32 | ); 33 | childProc.stdout.on("data", function(data) { 34 | console.log(data); 35 | }); 36 | 37 | //childProc.stdout.pipe(process.stdout); 38 | 39 | for (let i = 0; i < 10; i++) { 40 | try { 41 | let res = await request.get(baseUrl + "v1/playlists?id=2"); 42 | //if the server responds, break out, if not retry up to 10 times... 43 | break; 44 | } catch (ex) {} 45 | } 46 | }); 47 | 48 | after(async () => { 49 | console.log("killing service: ", childProc.pid); 50 | childProc.kill("SIGKILL"); 51 | console.log("stopping in mem DB"); 52 | await mongod.stop(); 53 | }); 54 | 55 | it("GET playlist by id", async () => { 56 | let res = await request.get(baseUrl + "v1/playlists?id=2"); 57 | let rObj = JSON.parse(res); 58 | chai.assert(res, "no response recieved!"); 59 | chai.assert(rObj.length > 0, "no data returned"); 60 | chai.assert(rObj[0].id == 2, "wrong object returned"); 61 | }); 62 | 63 | async function createPlaylist() { 64 | let plist = { 65 | id: "3", 66 | name: "stam1", 67 | songIds: null, 68 | creationTime: null, 69 | lastModifiedTime: null, 70 | creatorId: "me", 71 | isPublic: true, 72 | playedCounter: 0 73 | }; 74 | // let promise = new Promise(function(resolve, reject) { 75 | return await request.post({ 76 | headers: { "content-type": "application/json" }, 77 | url: baseUrl + "v1/playlists", 78 | body: JSON.stringify(plist) 79 | }); 80 | } 81 | 82 | it("adds a playlist", async () => { 83 | let id = await createPlaylist(); 84 | // id should be a uuid string: 85 | expect(id.length).to.be.greaterThan(8); 86 | }); 87 | 88 | it("adds a song to playlist (without await)", done => { 89 | request.post( 90 | { 91 | headers: { "content-type": "application/json" }, 92 | url: baseUrl + "v1/playlists/2?songId=3" 93 | }, 94 | (error, response, body) => { 95 | chai.assert(response, "no response recieved!"); 96 | chai.assert(response.statusCode === 200, "error in status code"); 97 | request.get( 98 | baseUrl + "v1/playlists?id=2", 99 | (error, response, body) => { 100 | resp = JSON.parse(body); 101 | expect(resp).to.not.be.undefined; 102 | expect(resp[0]).to.not.be.undefined; 103 | expect(resp[0].songIds).to.not.be.undefined; 104 | let firstSong = resp[0].songIds[0]; 105 | expect(firstSong).to.eq("3"); 106 | request.delete( 107 | { 108 | headers: { "content-type": "application/json" }, 109 | url: baseUrl + "v1/playlists/2?songId=3" 110 | }, 111 | () => { 112 | done(); 113 | } 114 | ); 115 | } 116 | ); 117 | } 118 | ); 119 | }); 120 | 121 | it("adds a song to playlist", async () => { 122 | await request.post({ 123 | headers: { "content-type": "application/json" }, 124 | url: baseUrl + "v1/playlists/2?songId=3" 125 | }); 126 | let resp = await request.get(baseUrl + "v1/playlists?id=2"); 127 | resp = JSON.parse(resp); 128 | 129 | expect(resp).to.not.be.undefined; 130 | expect(resp[0]).to.not.be.undefined; 131 | expect(resp[0].songIds).to.not.be.undefined; 132 | let firstSong = resp[0].songIds[0]; 133 | expect(firstSong).to.eq("3"); 134 | 135 | await request.delete({ 136 | headers: { "content-type": "application/json" }, 137 | url: baseUrl + "v1/playlists/2?songId=3" 138 | }); 139 | }); 140 | 141 | it("removes a song from playlist", async () => { 142 | let resCreate = await request.post({ 143 | headers: { "content-type": "application/json" }, 144 | url: baseUrl + "v1/playlists/3?songId=3" 145 | }); 146 | chai.expect(resCreate).to.eq("success"); 147 | 148 | let res = await request.delete({ 149 | headers: { "content-type": "application/json" }, 150 | url: baseUrl + "v1/playlists/3?songId=3" 151 | }); 152 | chai.expect(res).to.eq("success"); 153 | }); 154 | 155 | it("deletes a playlist", async () => { 156 | let listId = await createPlaylist(); 157 | let res = await request.delete(baseUrl + "v1/playlists?id=" + listId); 158 | chai.expect(res).to.eq("success"); 159 | }); 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /4.4 - UI tests for sample app/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Main", 11 | "program": "${workspaceFolder}/src/index.js" 12 | }, 13 | { 14 | "type": "node", 15 | "request": "launch", 16 | "name": "Mocha Tests", 17 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 18 | "args": [ 19 | "-u", 20 | "bdd", 21 | "--timeout", 22 | "999999", 23 | "--colors", 24 | "${workspaceFolder}/test" 25 | ], 26 | "internalConsoleOptions": "openOnSessionStart" 27 | }, 28 | { 29 | "type": "node", 30 | "request": "launch", 31 | "name": "Launch Program", 32 | "program": "${workspaceFolder}\\index.js" 33 | }, 34 | { 35 | "name": "Debug Mocha Test", 36 | "type": "node", 37 | "request": "attach", 38 | "address": "localhost", 39 | "port": 9229, 40 | "sourceMaps": false 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /4.4 - UI tests for sample app/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "mochaExplorer.files": "test/**/*.js", 3 | "mochaExplorer.require": "", 4 | "mochaExplorer.timeout": 10000 5 | } 6 | -------------------------------------------------------------------------------- /4.4 - UI tests for sample app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppeteer-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node ./node_modules/mocha/bin/_mocha test/*spec*.js --exit -s 0" 8 | }, 9 | "author": "Amit Bezalel", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "chai": "^4.2.0", 13 | "mocha": "^6.1.4", 14 | "sinon": "^7.5.0" 15 | }, 16 | "dependencies": { 17 | "puppeteer": "^1.15.0", 18 | "selenium-webdriver": "^4.0.0-alpha.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /4.4 - UI tests for sample app/src/index.js: -------------------------------------------------------------------------------- 1 | let puppeteer = require("puppeteer"); 2 | 3 | async function main() { 4 | let browser = await puppeteer.launch({ 5 | headless: true 6 | }); 7 | let page = await browser.newPage(); 8 | await page.goto("https://old.reddit.com/r/csharp/"); 9 | await page.waitForSelector(".thing"); 10 | let things = await page.$$(".thing"); 11 | 12 | console.log("got %d topics", things.length); 13 | for (let i = 0; i < things.length; i++) { 14 | // await page.goto("https://export.shopify.com"); 15 | // await page.waitForSelector(".section"); 16 | // let sections = page.$$("section"); 17 | let thing = things[i]; 18 | 19 | let topic = await thing.$("a.title"); 20 | let topicTitle = await page.evaluate(topic => topic.innerText, topic); 21 | console.log("topic #" + i + " :" + topicTitle); 22 | // button.click(); 23 | // await page.waitForSelector("#ExpertResutls"); 24 | // const lis = await page.$$("#ExpertResutls > li"); 25 | // for (let li of lis) { 26 | // let name = await li.$eval('h2', h2 => h2.innerText); 27 | // console.log("name", name); 28 | // } 29 | } 30 | } 31 | 32 | main(); 33 | -------------------------------------------------------------------------------- /4.4 - UI tests for sample app/test/playlist-ui-cypress-spec.js: -------------------------------------------------------------------------------- 1 | // const puppeteer = require("puppeteer"); 2 | // const expect = require("chai").expect; 3 | 4 | // describe("Song List Editor in chrome (Puppeteer)", async function() { 5 | // let browser = null; 6 | // this.timeout(50000); 7 | // before(async function() { 8 | // try { 9 | // browser = await puppeteer.launch({ 10 | // headless: false 11 | // }); 12 | // } catch (err) { 13 | // console.log(err); 14 | // } 15 | // }); 16 | // after(async () => { 17 | // browser.close(); 18 | // }); 19 | // it("should add a song to an existing list", async function() { 20 | // let page = await browser.newPage(); 21 | // await page.goto("http://www.google.com/ncr"); 22 | // await page.keyboard.type("puppeteer"); 23 | // await page.keyboard.press("Enter"); 24 | 25 | // // Wait for redirection 26 | // await page.waitForNavigation({ waitUntil: "networkidle2" }); 27 | // const pageTitle = await page.title(); 28 | // console.log(pageTitle); 29 | // expect(pageTitle).to.equal("puppeteer - Google Search"); 30 | // }); 31 | 32 | // after(() => { 33 | // browser.quit(); 34 | // }); 35 | // }); 36 | -------------------------------------------------------------------------------- /4.4 - UI tests for sample app/test/playlist-ui-puppeteer-spec.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require("puppeteer"); 2 | const expect = require("chai").expect; 3 | 4 | describe("Song List Editor in chrome (Puppeteer)", async function() { 5 | let browser = null; 6 | before(async function() { 7 | try { 8 | browser = await puppeteer.launch({ 9 | headless: false 10 | }); 11 | } catch (err) { 12 | console.log(err); 13 | } 14 | }); 15 | 16 | after(async () => { 17 | browser.close(); 18 | }); 19 | 20 | it("should add a song to an existing list", async function() { 21 | let page = await browser.newPage(); 22 | await page.goto("localhost:3001/"); 23 | let searchBox = await page.$(".autocomplete"); 24 | console.log(searchBox); 25 | await searchBox.click(); 26 | await searchBox.type("ala"); 27 | await page.keyboard.press("ArrowDown"); 28 | await page.waitFor(300); 29 | await page.keyboard.press("Enter"); 30 | await page.keyboard.press("Tab"); 31 | await page.keyboard.press("ArrowDown"); 32 | await page.keyboard.press("ArrowDown"); 33 | await page.keyboard.press("Enter"); 34 | 35 | await page.keyboard.press("Tab"); 36 | await page.keyboard.press("Enter"); 37 | 38 | let addSongButton = await page.$("#addSongButton"); 39 | await addSongButton.click(); 40 | 41 | //allow some time for page to refresh with updated data 42 | await page.waitFor(100); 43 | var text = await page.evaluate(() => 44 | Array.from( 45 | document.querySelectorAll(".card-text"), 46 | element => element.textContent 47 | ) 48 | ); 49 | console.log("text = " + text); 50 | expect(text[text.length - 1].trim()).to.eq("Ironic / Alanis"); 51 | // await page.waitFor(3000); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /4.4 - UI tests for sample app/test/playlist-ui-selenium-spec.js: -------------------------------------------------------------------------------- 1 | const { Builder, By, Key, until } = require("selenium-webdriver"); 2 | const expect = require("chai").expect; 3 | 4 | async function getTextNode(webEl) { 5 | let text = await webEl.getText(); 6 | return text.split("\n"); 7 | } 8 | 9 | async function testAddSongToList(driver, searchStr, expectedStr) { 10 | await driver.get("http://localhost:3001/"); 11 | 12 | await driver.wait(until.titleIs("demo")); 13 | 14 | let searchBox = await driver.findElement(By.className("autocomplete")); 15 | await searchBox.click(); 16 | 17 | await driver 18 | .actions() 19 | .sendKeys(searchStr) 20 | .sendKeys(Key.ARROW_DOWN, Key.ARROW_DOWN) 21 | .perform(); 22 | 23 | await driver 24 | .actions() 25 | .move({ duration: 500, origin: searchBox, x: 0, y: 20 }) 26 | .click() 27 | .perform(); 28 | 29 | let listPicker = await driver.findElement(By.id("pickListCombo")); 30 | await listPicker.click(); 31 | // await listPicker.keyDown(); 32 | await listPicker.sendKeys("u"); 33 | await listPicker.sendKeys(Key.ENTER); 34 | 35 | let addSongButton = await driver.findElement(By.id("addSongButton")); 36 | console.log(addSongButton); 37 | await addSongButton.click(); 38 | 39 | let elems = await driver.findElements(By.className("card-body")); 40 | let text = await getTextNode(elems[1]); 41 | 42 | expect(text[text.length - 2]).to.contain(expectedStr); 43 | } 44 | describe("Song List Editor in chrome (Selenium)", async function() { 45 | let driver = null; 46 | this.timeout(50000); 47 | before(async function() { 48 | driver = await new Builder().forBrowser("chrome").build(); //internet explorer 49 | }); 50 | 51 | it("should add a song to an existing list", async () => { 52 | return testAddSongToList(driver, "yeah", "Yeah! / Usher"); 53 | }); 54 | 55 | after(() => { 56 | driver.quit(); 57 | }); 58 | }); 59 | 60 | describe("Song List Editor in firefox (Selenium)", async function() { 61 | let driver = null; 62 | this.timeout(50000); 63 | 64 | before(async function() { 65 | driver = new Builder().forBrowser("firefox").build(); 66 | }); 67 | 68 | after(() => { 69 | driver.quit(); 70 | }); 71 | 72 | it("should add a song to an existing list", async function() { 73 | return testAddSongToList(driver, "lit", "Lithium / Nirvana"); 74 | }); 75 | 76 | // it("should not add a song which doesn't exist in the system", async () => {}); 77 | }); 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node.js-Testing-Best-Practices 2 | Node.js Testing Best Practices, published by [Packt] 3 | 4 | This is the code repository for Node.js Testing Best Practices, published by [Packt](https://www.packtpub.com/?utm_source=github). It contains all the supporting project files necessary to work through the video course from start to finish. 5 | 6 | ## Prerequisites: 7 | * built with NodeJS 12.6 (may work with lower versions, but not tested) 8 | 9 | 10 | ## Usage: 11 | * clone the whole repository to disk, or download the zip 12 | * run "npm install" in each directory 13 | * run "npm test" in each of the directories to run the tests 14 | 15 | ### Some specific examples require launching something first: 16 | * In 3.4 there is a runsrv.bat file that runs the json-server, which has to be installed globally (npm i -g json-server) 17 | * In 4.1 there is a run.bat file in each directory (frontEnd and backEnd) 18 | --------------------------------------------------------------------------------