diff --git a/staff/jose-pozo/project/api/coverage/base.css b/staff/jose-pozo/project/api/coverage/base.css new file mode 100644 index 000000000..f418035b4 --- /dev/null +++ b/staff/jose-pozo/project/api/coverage/base.css @@ -0,0 +1,224 @@ +body, html { + margin:0; padding: 0; + height: 100%; +} +body { + font-family: Helvetica Neue, Helvetica, Arial; + font-size: 14px; + color:#333; +} +.small { font-size: 12px; } +*, *:after, *:before { + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; + } +h1 { font-size: 20px; margin: 0;} +h2 { font-size: 14px; } +pre { + font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; + margin: 0; + padding: 0; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; +} +a { color:#0074D9; text-decoration:none; } +a:hover { text-decoration:underline; } +.strong { font-weight: bold; } +.space-top1 { padding: 10px 0 0 0; } +.pad2y { padding: 20px 0; } +.pad1y { padding: 10px 0; } +.pad2x { padding: 0 20px; } +.pad2 { padding: 20px; } +.pad1 { padding: 10px; } +.space-left2 { padding-left:55px; } +.space-right2 { padding-right:20px; } +.center { text-align:center; } +.clearfix { display:block; } +.clearfix:after { + content:''; + display:block; + height:0; + clear:both; + visibility:hidden; + } +.fl { float: left; } +@media only screen and (max-width:640px) { + .col3 { width:100%; max-width:100%; } + .hide-mobile { display:none!important; } +} + +.quiet { + color: #7f7f7f; + color: rgba(0,0,0,0.5); +} +.quiet a { opacity: 0.7; } + +.fraction { + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 10px; + color: #555; + background: #E8E8E8; + padding: 4px 5px; + border-radius: 3px; + vertical-align: middle; +} + +div.path a:link, div.path a:visited { color: #333; } +table.coverage { + border-collapse: collapse; + margin: 10px 0 0 0; + padding: 0; +} + +table.coverage td { + margin: 0; + padding: 0; + vertical-align: top; +} +table.coverage td.line-count { + text-align: right; + padding: 0 5px 0 20px; +} +table.coverage td.line-coverage { + text-align: right; + padding-right: 10px; + min-width:20px; +} + +table.coverage td span.cline-any { + display: inline-block; + padding: 0 5px; + width: 100%; +} +.missing-if-branch { + display: inline-block; + margin-right: 5px; + border-radius: 3px; + position: relative; + padding: 0 4px; + background: #333; + color: yellow; +} + +.skip-if-branch { + display: none; + margin-right: 10px; + position: relative; + padding: 0 4px; + background: #ccc; + color: white; +} +.missing-if-branch .typ, .skip-if-branch .typ { + color: inherit !important; +} +.coverage-summary { + border-collapse: collapse; + width: 100%; +} +.coverage-summary tr { border-bottom: 1px solid #bbb; } +.keyline-all { border: 1px solid #ddd; } +.coverage-summary td, .coverage-summary th { padding: 10px; } +.coverage-summary tbody { border: 1px solid #bbb; } +.coverage-summary td { border-right: 1px solid #bbb; } +.coverage-summary td:last-child { border-right: none; } +.coverage-summary th { + text-align: left; + font-weight: normal; + white-space: nowrap; +} +.coverage-summary th.file { border-right: none !important; } +.coverage-summary th.pct { } +.coverage-summary th.pic, +.coverage-summary th.abs, +.coverage-summary td.pct, +.coverage-summary td.abs { text-align: right; } +.coverage-summary td.file { white-space: nowrap; } +.coverage-summary td.pic { min-width: 120px !important; } +.coverage-summary tfoot td { } + +.coverage-summary .sorter { + height: 10px; + width: 7px; + display: inline-block; + margin-left: 0.5em; + background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; +} +.coverage-summary .sorted .sorter { + background-position: 0 -20px; +} +.coverage-summary .sorted-desc .sorter { + background-position: 0 -10px; +} +.status-line { height: 10px; } +/* yellow */ +.cbranch-no { background: yellow !important; color: #111; } +/* dark red */ +.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } +.low .chart { border:1px solid #C21F39 } +.highlighted, +.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ + background: #C21F39 !important; +} +/* medium red */ +.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } +/* light red */ +.low, .cline-no { background:#FCE1E5 } +/* light green */ +.high, .cline-yes { background:rgb(230,245,208) } +/* medium green */ +.cstat-yes { background:rgb(161,215,106) } +/* dark green */ +.status-line.high, .high .cover-fill { background:rgb(77,146,33) } +.high .chart { border:1px solid rgb(77,146,33) } +/* dark yellow (gold) */ +.status-line.medium, .medium .cover-fill { background: #f9cd0b; } +.medium .chart { border:1px solid #f9cd0b; } +/* light yellow */ +.medium { background: #fff4c2; } + +.cstat-skip { background: #ddd; color: #111; } +.fstat-skip { background: #ddd; color: #111 !important; } +.cbranch-skip { background: #ddd !important; color: #111; } + +span.cline-neutral { background: #eaeaea; } + +.coverage-summary td.empty { + opacity: .5; + padding-top: 4px; + padding-bottom: 4px; + line-height: 1; + color: #888; +} + +.cover-fill, .cover-empty { + display:inline-block; + height: 12px; +} +.chart { + line-height: 0; +} +.cover-empty { + background: white; +} +.cover-full { + border-right: none !important; +} +pre.prettyprint { + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.com { color: #999 !important; } +.ignore-none { color: #999; font-weight: normal; } + +.wrapper { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -48px; +} +.footer, .push { + height: 48px; +} diff --git a/staff/jose-pozo/project/api/coverage/block-navigation.js b/staff/jose-pozo/project/api/coverage/block-navigation.js new file mode 100644 index 000000000..cc1213023 --- /dev/null +++ b/staff/jose-pozo/project/api/coverage/block-navigation.js @@ -0,0 +1,87 @@ +/* eslint-disable */ +var jumpToCode = (function init() { + // Classes of code we would like to highlight in the file view + var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; + + // Elements to highlight in the file listing view + var fileListingElements = ['td.pct.low']; + + // We don't want to select elements that are direct descendants of another match + var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` + + // Selecter that finds elements on the page to which we can jump + var selector = + fileListingElements.join(', ') + + ', ' + + notSelector + + missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` + + // The NodeList of matching elements + var missingCoverageElements = document.querySelectorAll(selector); + + var currentIndex; + + function toggleClass(index) { + missingCoverageElements + .item(currentIndex) + .classList.remove('highlighted'); + missingCoverageElements.item(index).classList.add('highlighted'); + } + + function makeCurrent(index) { + toggleClass(index); + currentIndex = index; + missingCoverageElements.item(index).scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center' + }); + } + + function goToPrevious() { + var nextIndex = 0; + if (typeof currentIndex !== 'number' || currentIndex === 0) { + nextIndex = missingCoverageElements.length - 1; + } else if (missingCoverageElements.length > 1) { + nextIndex = currentIndex - 1; + } + + makeCurrent(nextIndex); + } + + function goToNext() { + var nextIndex = 0; + + if ( + typeof currentIndex === 'number' && + currentIndex < missingCoverageElements.length - 1 + ) { + nextIndex = currentIndex + 1; + } + + makeCurrent(nextIndex); + } + + return function jump(event) { + if ( + document.getElementById('fileSearch') === document.activeElement && + document.activeElement != null + ) { + // if we're currently focused on the search input, we don't want to navigate + return; + } + + switch (event.which) { + case 78: // n + case 74: // j + goToNext(); + break; + case 66: // b + case 75: // k + case 80: // p + goToPrevious(); + break; + } + }; +})(); +window.addEventListener('keydown', jumpToCode); diff --git a/staff/jose-pozo/project/api/coverage/data/Appointment.js.html b/staff/jose-pozo/project/api/coverage/data/Appointment.js.html new file mode 100644 index 000000000..a3db17526 --- /dev/null +++ b/staff/jose-pozo/project/api/coverage/data/Appointment.js.html @@ -0,0 +1,226 @@ + + + + +
++ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 | 1x + +1x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1x + +1x | import { Schema, model } from 'mongoose' + +const appointment = new Schema({ + date: { + type: Date, + required: true + }, + + time: { + type: Date, + required: true + }, + + service: { + type: Schema.Types.ObjectId, + ref: 'Service', + required: true + }, + + customer: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true + }, + + provider: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true + }, + + status: { + type: String, + enum: ['confirmed', 'pending', 'cancelled'], + default: 'confirmed' + }, + + notes: [{ + type: Schema.Types.ObjectId, + ref: 'Note' + }] +}, { + timestamps: true +}) + +const Appointment = model('Appointment', appointment) + +export default Appointment |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 | 1x + +1x + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1x + +1x + | import { Schema, model } from 'mongoose' + +const note = new Schema({ + text: { + type: String, + }, + + createdAt: { + type: Date, + default: Date.now + }, + + createdBy: { + type: Schema.Types.ObjectId, + ref: 'User' + }, + + relatedTo: { + type: Schema.Types.ObjectId, + refPath: 'relatedToModel' + }, + + relatedToModel: { + type: String, + enum: ['Appointment', 'Customer', 'Service'], + required: true + } +}, { + timestamps: true +}) + +const Note = model('Note', note) + +export default Note + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 | 1x + +1x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1x + +1x | import { Schema, model } from 'mongoose' + +const service = new Schema({ + name: { + type: String, + required: true + }, + + description: { + type: String + }, + + category: { + type: String, + enum: ['Laser treatments', 'Aesthetic treatments'] + }, + + duration: { + type: Number, + enum: [30, 60, 90, 120], + required: true + }, + + price: { + type: Number + }, + + provider: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true + } +}, { + timestamps: true +}) + +const Service = model('Service', service) + +export default Service |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 | 1x + +1x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1x + +1x | import { Schema, model } from 'mongoose' + +const user = new Schema({ + name: { + type: String, + required: true + }, + + surname: { + type: String, + required: true + }, + + email: { + type: String, + required: true, + }, + + password: { + type: String, + + }, + + role: { + type: String, + enum: ['provider', 'customer'], + required: true + }, + + phone: { + type: String + }, + + manager: { + type: Schema.Types.ObjectId, + ref: 'User' + }, + + providers: [{ + type: Schema.Types.ObjectId, + ref: 'User' + }], + + appointments: [{ + type: Schema.Types.ObjectId, + ref: 'Appointment' + }], + + notes: [{ + type: Schema.Types.ObjectId, + ref: 'Note' + }], + + services: [{ + type: Schema.Types.ObjectId, + ref: 'Service' + }] +}, { + timestamps: true +}) + +const User = model('User', user) + +export default User |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
---|---|---|---|---|---|---|---|---|---|
Appointment.js | +
+
+ |
+ 100% | +4/4 | +100% | +0/0 | +100% | +0/0 | +100% | +4/4 | +
Note.js | +
+
+ |
+ 100% | +4/4 | +100% | +0/0 | +100% | +0/0 | +100% | +4/4 | +
Service.js | +
+
+ |
+ 100% | +4/4 | +100% | +0/0 | +100% | +0/0 | +100% | +4/4 | +
User.js | +
+
+ |
+ 100% | +4/4 | +100% | +0/0 | +100% | +0/0 | +100% | +4/4 | +
index.js | +
+
+ |
+ 100% | +8/8 | +100% | +0/0 | +100% | +0/0 | +100% | +8/8 | +
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 | 1x +1x +1x +1x + +1x +1x +1x +1x + + | import User from './User.js' +import Appointment from './Appointment.js' +import Service from './Service.js' +import Note from './Note.js' + +export { User } +export { Appointment } +export { Service } +export { Note } + + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ ++ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 | 1x +1x +1x +1x + +1x +5x +5x + +5x + + +3x +1x + +2x + + +2x +1x + +1x + + + + +1x | import { User } from '../data/index.js' +import { CredentialsError, SystemError } from 'com/errors.js' +import validate from 'com/validate.js' +import bcrypt from 'bcryptjs' + +const authenticateUser = (email, password) => { + validate.email(email) + validate.password(password) + + return User.findOne({ email }).lean() + .catch(error => { throw new SystemError(error.message) }) + .then(user => { + if (!user) + throw new CredentialsError('User not found') + + return bcrypt.compare(password, user.password) + .catch(error => { throw new SystemError(error.message) }) + .then(match => { + if (!match) + throw new CredentialsError('Wrong password') + + return user._id.toString() + }) + }) +} + +export default authenticateUser |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 | 1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + + +1x +1x + +1x + +1x + + + + + + + + + + +1x +1x + + + +1x +1x + +1x + +1x + + +1x +1x + + + +1x +1x + +1x + + + + + + + + + +1x + + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x + + + + | import 'dotenv/config' + +import mongoose from 'mongoose' + +import bcrypt from 'bcryptjs' + +import { expect } from 'chai' + +import { ContentError, CredentialsError } from 'com/errors.js' + +import { User } from '../data/index.js' + +import authenticateUser from './authenticateUser.js' + +const { MONGODB_URL_TEST } = process.env + + +describe('authenticate user', () => { + before(() => mongoose.connect(MONGODB_URL_TEST).then(() => User.deleteMany())) + + beforeEach(() => User.deleteMany()) + + it('succeeds on existing user', () => + bcrypt.hash('1234', 8) + .then(hash => User.create({ + name: 'Jon', + surname: 'Snow', + email: 'jon@snow.com', + password: hash, + role: 'provider' + })) + .then(() => authenticateUser('jon@snow.com', '1234')) + .then((userId => { + expect(userId).to.be.a('string') + expect(userId).to.have.lengthOf(24) + })) + ) + + it('fails on non-existing user', () => { + let errorThrown + + return authenticateUser('jon@snow.com', '1234') + .catch(error => { + errorThrown = error + }) + .finally(() => { + expect(errorThrown).to.be.an.instanceOf(CredentialsError) + expect(errorThrown.message).to.equal('User not found') + }) + }) + + it('fails on wrong password', () => { + let errorThrown + + return bcrypt.hash('1234', 8) + .then(hash => User.create({ + name: 'Jon', + surname: 'Snow', + email: 'jon@snow.com', + password: hash, + role: 'provider' + })) + .then(() => authenticateUser('jon@snow.com', '12345')) + .catch(error => { + errorThrown = error + }) + .finally(() => { + expect(errorThrown).to.be.an.instanceOf(CredentialsError) + expect(errorThrown.message).to.equal('Wrong password') + }) + }) + + it('fails on invalid email', () => { + let errorThrown + + try { + authenticateUser('jonsnow.com', ' 1234') + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('email is not valid') + } + }) + + it('fails on invalid password', () => { + let errorThrown + + try { + authenticateUser('jon@snow.com', '12 34') + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('password is not valid') + } + }) + + after(() => User.deleteMany().then(() => mongoose.disconnect())) +}) + + + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 | 1x +1x +1x + +1x +7x +7x +7x +7x + +7x + + +3x +1x + + +2x + + +2x + + + +2x + + + + + + + + + + + + + +2x +1x + + + + + + + + + + + + + + +1x + | import { User } from '../data/index.js' +import { NotFoundError, SystemError, DuplicityError } from 'com/errors.js' +import validate from 'com/validate.js' + +const createCustomer = (userId, name, surname, email) => { + validate.id(userId, 'userId') + validate.name(name) + validate.name(surname, 'surname') + validate.email(email) + + return User.findById(userId) + .catch(error => { throw new SystemError(error.message) }) + .then(user => { + if (!user) { + throw new NotFoundError('User not found') + } + + return User.findOne({ $and: [{ email }, { manager: user.id }, { role: 'customer' }, { password: '' }] }) + .catch(error => { throw new SystemError(error.message) }) + .then(customer => { + if (customer) { + throw new DuplicityError('Customer already exists') + } + + const newCustomer = { + name, + surname, + email, + password: '', + role: 'customer', + phone: '', + manager: user.id, + providers: [], + appointments: [], + notes: [], + services: [] + } + + return User.create(newCustomer) + .catch(error => { throw new SystemError(error.message) }) + .then(() => newCustomer) + }) + + + }) + +} + + + + + + + +export default createCustomer + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 | 1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + + +1x +1x + +1x + +1x + + + + + + + + + + + +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x + + + +1x +1x + +1x + +1x + + +1x +1x + + + +1x +1x + +1x + +1x + + + + + + + +1x + + + + + + + + + +1x + + +1x +1x + + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x + + + + + + + + + | import 'dotenv/config' + +import mongoose, { Types } from 'mongoose' + +const { ObjectId } = Types + +import bcrypt from 'bcryptjs' + +import { ContentError, NotFoundError, DuplicityError } from 'com/errors.js' + +import { expect } from 'chai' + +import { User } from '../data/index.js' + +import createCustomer from './createCustomer.js' + +const { MONGODB_URL_TEST } = process.env + + +describe('create customer', () => { + before(() => mongoose.connect(MONGODB_URL_TEST).then(() => User.deleteMany())) + + beforeEach(() => User.deleteMany()) + + it('succeeds on create customer', () => + bcrypt.hash('1234', 8) + .then(hash => User.create({ + name: 'Jon', + surname: 'Snow', + email: 'jon@snow.com', + password: hash, + role: 'provider', + })) + .then(user => createCustomer(user.id, 'Alfa', 'Beto', 'alfa@beto.com')) + .then(() => User.findOne({ email: 'alfa@beto.com' })) + .then(customer => { + expect(customer).to.be.an('object') + expect(customer._id).to.be.an.instanceOf(ObjectId) + expect(customer.id).to.be.a('string') + expect(customer.id).to.have.lengthOf(24) + expect(customer.name).to.equal('Alfa') + expect(customer.surname).to.equal('Beto') + expect(customer.email).to.equal('alfa@beto.com') + expect(customer.role).to.equal('customer') + expect(customer.manager).to.be.an.instanceOf(ObjectId) + expect(customer.providers).to.be.an('array') + expect(customer.providers.length).to.equal(0) + expect(customer.appointments).to.be.an('array') + expect(customer.appointments.length).to.equal(0) + expect(customer.notes).to.be.an('array') + expect(customer.notes.length).to.equal(0) + expect(customer.services).to.be.an('array') + expect(customer.services.length).to.equal(0) + }) + ) + + it('fails on non-existing user', () => { + let errorThrown + + return createCustomer(new ObjectId().toString(), 'Alfa', 'Beto', 'alfa@beto.com') + .catch(error => { + errorThrown = error + }) + .finally(() => { + expect(errorThrown).to.be.an.instanceOf(NotFoundError) + expect(errorThrown.message).to.equal('User not found') + }) + }) + + it('fails on duplicity customer', () => { + let errorThrown + + return bcrypt.hash('1234', 8) + .then(hash => { + return User.create({ + name: 'Jon', + surname: 'Snow', + email: 'jon@snow.com', + password: hash, + role: 'provider' + }) + .then(user => { + return User.create({ + name: 'Alfa', + surname: 'Beto', + email: 'alfa@beto.com', + role: 'customer', + manager: user.id + }) + }) + .then((user) => createCustomer(user.id, 'Alfa', 'Beto', 'alfa@beto.com')) + .catch(error => { + errorThrown = error + }) + .finally(() => { + expect(errorThrown).to.be.an.instanceOf(DuplicityError) + expect(errorThrown.message).to.equal('Customer already exists') + }) + }) + }) + + it('fails on invalid userId', () => { + let errorThrown + + try { + createCustomer(1234, 'Alfa', 'Beto', 'alfa@beto.com') + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('userId is not valid') + } + }) + + it('fails on invalid name', () => { + let errorThrown + + try { + createCustomer(new ObjectId().toString(), '1234', 'Beto', 'alfa@beto.com') + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('name is not valid') + } + }) + + it('fails on invalid surname', () => { + let errorThrown + + try { + createCustomer(new ObjectId().toString(), 'Alfa', 1234, 'alfa@beto.com') + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('surname is not valid') + } + }) + + it('fails on invalid email', () => { + let errorThrown + + try { + createCustomer(new ObjectId().toString(), 'Alfa', 'Beto', 1234) + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('email is not valid') + } + }) + + after(() => User.deleteMany().then(() => mongoose.disconnect())) + +}) + + + + + + + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 | 1x +1x +1x + + +1x + +5x +5x + +5x + + +3x + + + + + +2x + + +2x + +1x + + + +1x + + + + + + +1x | import { User } from '../data/index.js' +import { NotFoundError, SystemError } from 'com/errors.js' +import validate from 'com/validate.js' + + +const deleteCustomer = (userId, customerId) => { + + validate.id(userId, 'userId') + validate.id(customerId, 'Customer') + + return User.findById(userId).select('-__v').lean() + .catch(error => { throw new SystemError(error.message) }) + .then(user => { + if (!user) throw new NotFoundError('User not found') + + // return User.updateOne({ _id: userId }, { $pull: { customers: Customer } }) + // .catch(error => { throw new SystemError(error.message) }) + // .then(() => { + + return User.findById(customerId).select('-__v').lean() + .catch(error => { throw new SystemError(error.message) }) + .then(customerId => { + if (!customerId) throw new NotFoundError('Customer not found') + + return User.deleteOne({ _id: customerId }) + .catch(error => { throw new SystemError(error.message) }) + .then(() => { + + return customerId._id + }) + }) + // }) + }) +} + +export default deleteCustomer |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 | 1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + + + +1x +1x + +1x + +1x + + + + + + + + + + + + + + + + +1x + + + +1x +1x + +1x + + +1x +1x + + + +1x +1x + +1x + + + + + + + + + + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x + + + + + + + + | import 'dotenv/config' + +import mongoose, { Types } from 'mongoose' + +const { ObjectId } = Types + +import bcrypt from 'bcryptjs' + +import { ContentError, NotFoundError } from 'com/errors.js' + +import { expect } from 'chai' + +import { User } from '../data/index.js' + +import deleteCustomer from './deleteCustomer.js' + +const { MONGODB_URL_TEST } = process.env + + + +describe('delete customer', () => { + before(() => mongoose.connect(MONGODB_URL_TEST).then(() => User.deleteMany())) + + beforeEach(() => User.deleteMany()) + + it('succeeds on delete user', () => + bcrypt.hash('1234', 8) + .then(hash => Promise.all([User.create({ + name: 'Jon', + surname: 'Snow', + email: 'jon@snow', + password: hash, + role: 'provider', + }), User.create({ + name: 'Alfa', + surname: 'Beto', + email: 'alfa@beto', + role: 'customer' + })])) + .then(([user, customer]) => deleteCustomer(user.id, customer.id)) + .then(customerIdDeleted => User.findById(customerIdDeleted)) + .then((customerIdDeleted) => { + expect(customerIdDeleted).to.be.null + }) + ) + + it('fails on non-existing user', () => { + let errorThrown + + return deleteCustomer(new ObjectId().toString(), new ObjectId().toString()) + .catch(error => errorThrown = error) + .finally(() => { + expect(errorThrown).to.be.an.instanceof(NotFoundError) + expect(errorThrown.message).to.equal('User not found') + }) + }) + + it('fails on non-existing customer', () => { + let errorThrown + + return bcrypt.hash('1234', 8) + .then(hash => User.create({ + name: 'Jon', + surname: 'Snow', + email: 'jon@snow', + password: hash, + role: 'provider', + })) + .then(user => deleteCustomer(user.id, new ObjectId().toString())) + .catch(error => errorThrown = error) + .finally(() => { + expect(errorThrown).to.be.an.instanceof(NotFoundError) + expect(errorThrown.message).to.equal('Customer not found') + }) + }) + + it('fails on invalid userId', () => { + let errorThrown + + try { + deleteCustomer(1234, new ObjectId().toString()) + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('userId is not valid') + } + }) + + it('fails on invalid customerId', () => { + let errorThrown + + try { + deleteCustomer(new ObjectId().toString(), 1234) + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('Customer is not valid') + } + }) + + after(() => User.deleteMany().then(() => mongoose.disconnect())) +}) + + + + + + + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 | 1x +1x +1x + +1x +4x + +4x + + +3x + + +1x + + +1x + +1x + +1x + +1x + + + + + + +1x + + + + + + +1x | import { User } from '../data/index.js' +import { SystemError, NotFoundError } from 'com/errors.js' +import validate from 'com/validate.js' + +const getAllCustomers = (userId) => { + validate.id(userId, 'userId') + + return User.findById(userId).lean() + .catch(error => { throw new SystemError(error.message) }) + .then(user => { + if (!user) throw new NotFoundError('User not found') + + + return User.find({ manager: userId }).select('-__v').lean() + .catch(error => { throw new SystemError(error.message) }) + .then(customers => { + if (!customers.length) throw new NotFoundError('Customers not found') + + customers.forEach(customer => { + + customer.id = customer._id.toString() + + delete customer._id + + // if (customer.manager) { + // customer.manager = customer.manager.toString() + // } + }) + + return customers + }) + + }) + +} + +export default getAllCustomers |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 | 1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + + +1x + + + + + + + +1x + + + + + + + + + + + +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x + + + + + +1x +1x + +1x + +1x + + +1x +1x + + + +1x +1x +1x + + + + + + + + + +1x + + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x + + + + | import 'dotenv/config' + +import mongoose, { Types } from 'mongoose' + +const { ObjectId } = Types + +import bcrypt from 'bcryptjs' + +import { ContentError, NotFoundError } from 'com/errors.js' + +import { expect } from 'chai' + +import { User } from '../data/index.js' + +import getAllCustomers from './getAllCustomers.js' + +const { MONGODB_URL_TEST } = process.env + +describe('get all customers', () => { + + before(() => mongoose.connect(MONGODB_URL_TEST).then(() => User.deleteMany())) + + beforeEach(() => User.deleteMany()) + + it('succeeds on get all customers', () => + bcrypt.hash('1234', 8) + .then(hash => { + return User.create({ + name: 'Jon', + surname: 'Snow', + email: 'jon@snow.com', + password: hash, + role: 'provider' + }) + .then(user => { + return User.create({ + + name: 'Alfa', + surname: 'Beto', + email: 'alfa@beto.com', + role: 'customer', + manager: user.id.toString() + }) + + + .then(() => getAllCustomers(user.id)) + .then(customers => { + expect(customers).to.be.an('array') + expect(customers.length).to.equal(1) + expect(customers[0]).to.be.an('object') + expect(customers[0].id).to.be.a('string') + expect(customers[0].id).to.have.lengthOf(24) + expect(customers[0].name).to.equal('Alfa') + expect(customers[0].surname).to.equal('Beto') + expect(customers[0].email).to.equal('alfa@beto.com') + expect(customers[0].role).to.equal('customer') + expect(customers[0].phone).to.be.undefined + expect(customers[0].manager).to.be.an.instanceOf(ObjectId) + expect(customers[0].providers).to.be.an('array') + expect(customers[0].providers.length).to.equal(0) + expect(customers[0].appointments).to.be.an('array') + expect(customers[0].appointments.length).to.equal(0) + expect(customers[0].notes).to.be.an('array') + expect(customers[0].notes.length).to.equal(0) + expect(customers[0].services).to.be.an('array') + expect(customers[0].services.length).to.equal(0) + }) + }) + }) + ) + + it('fails on non-existing user', () => { + let errorThrown + + return getAllCustomers(new ObjectId().toString()) + .catch(error => { + errorThrown = error + }) + .finally(() => { + expect(errorThrown).to.be.an.instanceOf(NotFoundError) + expect(errorThrown.message).to.equal('User not found') //TODO change alert and fix spec + }) + }) + + it('fails on non-existing customer', () => { + let errorThrown + bcrypt.hash('1234', 8) + .then(hash => User.create({ + name: 'Jon', + surname: 'Snow', + email: 'jon@snow.com', + password: hash, + role: 'provider' + })) + .then(user => getAllCustomers(user.id)) + .catch(error => { + errorThrown = error + }) + .finally(() => { + expect(errorThrown).to.be.an.instanceOf(NotFoundError) + expect(errorThrown.message).to.equal('Customers not found') + }) + }) + + it('fails on invalid userId', () => { + let errorThrown + + try { + getAllCustomers(1234) + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('userId is not valid') + } + }) + + after(() => User.deleteMany().then(() => mongoose.disconnect())) + +}) + + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 | 1x +1x +1x + +1x +5x +5x + +5x + + +3x +1x + +2x + + +2x +1x + +1x + + + + +1x | import { User } from '../data/index.js' +import { NotFoundError, SystemError } from 'com/errors.js' +import validate from 'com/validate.js' + +const getUserName = (userId, targetUserId) => { + validate.id(userId, 'user id') + validate.id(targetUserId, 'target id') + + return User.findById(userId).lean() + .catch(error => { throw new SystemError(error.message) }) + .then(user => { + if (!user) + throw new NotFoundError('User not found') + + return User.findById(targetUserId).lean() + .catch(error => { throw new SystemError(error.message) }) + .then(targetUser => { + if (!targetUser) + throw new NotFoundError('Target user not found') + + return user.name + }) + }) +} + +export default getUserName |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 | 1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + + +1x + +1x + +1x + +1x + + +1x + + + + + + + +1x + + + + + + + + +1x +1x + + + + + +1x +1x + +1x + + + + + + + + + +1x + + +1x +1x + + + + +1x +1x + +1x + + + + + + + + + +1x + + +1x +1x + + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x + + | import 'dotenv/config' + +import mongoose, { Types } from 'mongoose' + +const { ObjectId } = Types + +import bcrypt from 'bcryptjs' + +import { NotFoundError, ContentError } from 'com/errors.js' + +import { expect } from 'chai' + +import { User } from '../data/index.js' + +import getUserName from './getUserName.js' + +const { MONGODB_URL_TEST } = process.env + + +describe('get user name', () => { + + before(() => mongoose.connect(MONGODB_URL_TEST).then(() => User.deleteMany())) + + beforeEach(() => User.deleteMany()) + + it('succeeds on get user name', () => + bcrypt.hash('1234', 8) + .then(hash => { + return User.create({ + name: 'Jon', + surname: 'Snow', + email: 'jon@snow.com', + password: hash, + role: 'provider' + }) + .then(user => { + return User.create({ + name: 'Alfa', + surname: 'Beto', + email: 'alfa@beto.com', + role: 'customer', + manager: user.id + }) + .then(() => getUserName(user.id, user.id)) + .then(userName => { + expect(userName).to.be.a('string') + expect(userName).to.have.lengthOf(3) + }) + }) + })) + + + it('fails on non-existing user', () => { + let errorThrown + + return bcrypt.hash('1234', 8) + .then(hash => User.create({ + name: 'Jon', + surname: 'Snow', + email: 'jon@snow.com', + password: hash, + role: 'provider' + })) + .then((targetUserId) => getUserName(new ObjectId().toString(), targetUserId.id)) + .catch(error => { + errorThrown = error + }) + .finally(() => { + expect(errorThrown).to.be.an.instanceOf(NotFoundError) + expect(errorThrown.message).to.equal('User not found') + }) + }) + + + it('fails on non-existing target', () => { + let errorThrown + + return bcrypt.hash('1234', 8) + .then(hash => User.create({ + name: 'Jon', + surname: 'Snow', + email: 'jon@snow.com', + password: hash, + role: 'provider' + })) + .then((userId) => getUserName(userId.id, new ObjectId().toString())) + .catch(error => { + errorThrown = error + }) + .finally(() => { + expect(errorThrown).to.be.an.instanceOf(NotFoundError) + expect(errorThrown.message).to.equal('Target user not found') + }) + }) + + + it('fails on invalid userId', () => { + let errorThrown + + try { + getUserName(1234, new ObjectId().toString()) + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('user id is not valid') + } + }) + + it('fails on invalid targetUserId', () => { + let errorThrown + + try { + getUserName(new ObjectId().toString(), 1234) + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('target id is not valid') + } + }) + + after(() => User.deleteMany().then(() => mongoose.disconnect())) +}) + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 | 1x +1x +1x + +1x +4x +4x + +4x + + +2x + +1x + + +1x + + + + + + + + + + + + + +1x | import { User } from '../data/index.js' +import { NotFoundError, SystemError } from 'com/errors.js' +import validate from 'com/validate.js' + +const getUserProfile = (userId, targetUserId) => { + validate.id(userId, 'userId') + validate.id(targetUserId, 'targetUserId') + + return User.findById(userId).select('-__v').lean() + .catch(error => { throw new SystemError(error.message) }) + .then(user => { + if (!user) throw new NotFoundError('User not found') + + return User.findById(targetUserId).select('-__v').lean() + .catch(error => { throw new SystemError(error.message) }) + .then(targetUser => { + if (!targetUser) throw new NotFoundError('Target user not found') + + targetUser.id = targetUser._id.toString() + + delete targetUser._id + + return targetUser + }) + + + + }) +} + +export default getUserProfile |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 | 1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + + +1x + +1x + +1x + +1x + + +1x + + + + + + + +1x + + + + + + + + + + + + + + + + + + + + +1x +1x + +1x + + + + + + + + + +1x + + +1x +1x + + + +1x +1x + +1x + + + + + + + + + +1x + + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + + +1x + + | import 'dotenv/config' + +import mongoose, { Types } from 'mongoose' + +const { ObjectId } = Types + +import bcrypt from 'bcryptjs' + +import { NotFoundError, ContentError } from 'com/errors.js' + +import { expect } from 'chai' + +import { User } from '../data/index.js' + +import getUserProfile from './getUserProfile.js' + +const { MONGODB_URL_TEST } = process.env + + +describe('get user profile', () => { + + before(() => mongoose.connect(MONGODB_URL_TEST).then(() => User.deleteMany())) + + beforeEach(() => User.deleteMany()) + + it('succeeds on get user profile', () => + bcrypt.hash('1234', 8) + .then(hash => { + return User.create({ + name: 'Jon', + surname: 'Snow', + email: 'jon@snow.com', + password: hash, + role: 'provider' + }) + .then(user => { + return User.create({ + name: 'Alfa', + surname: 'Beto', + email: 'alfa@beto.com', + role: 'customer', + manager: user.id + }) + .then((userId, targetUserId) => getUserProfile(userId.id, targetUserId.id)) + .then(userProfile => { + expect(userProfile).to.be.an('object') + // expect(userProfile.id).to.be.a('string') + expect(userProfile.name).to.be.a('string') + expect(userProfile.surname).to.be.a('string') + expect(userProfile.email).to.be.a('string') + expect(userProfile.role).to.be.a('string') + expect(userProfile.manager).to.be.an.instanceOf(ObjectId) + }) + }) + }) + ) + + it('fails on non-existing user', () => { + let errorThrown + + return bcrypt.hash('1234', 8) + .then(hash => User.create({ + name: 'Jon', + surname: 'Snow', + email: 'jon@snow.com', + password: hash, + role: 'provider' + })) + .then((targetUserId) => getUserProfile(new ObjectId().toString(), targetUserId.id)) + .catch(error => { + errorThrown = error + }) + .finally(() => { + expect(errorThrown).to.be.an.instanceOf(NotFoundError) + expect(errorThrown.message).to.equal('User not found') + }) + }) + + it('fails on non-existing target', () => { + let errorThrown + + return bcrypt.hash('1234', 8) + .then(hash => User.create({ + name: 'Jon', + surname: 'Snow', + email: 'jon@snow.com', + password: hash, + role: 'provider' + })) + .then((userId) => getUserProfile(userId.id, new ObjectId().toString())) + .catch(error => { + errorThrown = error + }) + .finally(() => { + expect(errorThrown).to.be.an.instanceOf(NotFoundError) + expect(errorThrown.message).to.equal('Target user not found') + }) + }) + + it('fails on invalid userId', () => { + let errorThrown + + try { + getUserProfile(1234, new ObjectId().toString()) + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('userId is not valid') + } + }) + + it('fails on invalid targetId', () => { + let errorThrown + + try { + getUserProfile(new ObjectId().toString(), 1234) + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('targetUserId is not valid') + } + }) + + + after(() => User.deleteMany().then(() => mongoose.disconnect())) +}) + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
---|---|---|---|---|---|---|---|---|---|
authenticateUser.js | +
+
+ |
+ 88.23% | +15/17 | +100% | +4/4 | +60% | +3/5 | +88.23% | +15/17 | +
authenticateUser.spec.js | +
+
+ |
+ 100% | +41/41 | +100% | +0/0 | +100% | +20/20 | +100% | +41/41 | +
createCustomer.js | +
+
+ |
+ 85% | +17/20 | +75% | +3/4 | +71.42% | +5/7 | +85% | +17/20 | +
createCustomer.spec.js | +
+
+ |
+ 100% | +73/73 | +100% | +0/0 | +100% | +24/24 | +100% | +73/73 | +
deleteCustomer.js | +
+
+ |
+ 83.33% | +15/18 | +100% | +4/4 | +57.14% | +4/7 | +81.25% | +13/16 | +
deleteCustomer.spec.js | +
+
+ |
+ 100% | +39/39 | +100% | +0/0 | +100% | +21/21 | +100% | +39/39 | +
getAllCustomers.js | +
+
+ |
+ 83.33% | +15/18 | +75% | +3/4 | +66.66% | +4/6 | +87.5% | +14/16 | +
getAllCustomers.spec.js | +
+
+ |
+ 100% | +54/54 | +100% | +0/0 | +100% | +20/20 | +100% | +54/54 | +
getUserName.js | +
+
+ |
+ 87.5% | +14/16 | +100% | +4/4 | +60% | +3/5 | +87.5% | +14/16 | +
getUserName.spec.js | +
+
+ |
+ 100% | +44/44 | +100% | +0/0 | +100% | +23/23 | +100% | +44/44 | +
getUserProfile.js | +
+
+ |
+ 72.22% | +13/18 | +75% | +3/4 | +60% | +3/5 | +68.75% | +11/16 | +
getUserProfile.spec.js | +
+
+ |
+ 87.5% | +42/48 | +100% | +0/0 | +95.65% | +22/23 | +87.5% | +42/48 | +
registerUser.js | +
+
+ |
+ 85% | +17/20 | +50% | +1/2 | +71.42% | +5/7 | +85% | +17/20 | +
registerUser.spec.js | +
+
+ |
+ 100% | +68/68 | +100% | +0/0 | +100% | +20/20 | +100% | +68/68 | +
updateCustomer.js | +
+
+ |
+ 18.51% | +5/27 | +0% | +0/10 | +0% | +0/5 | +19.23% | +5/26 | +
updateCustomer.spec.js | +
+
+ |
+ 89.65% | +52/58 | +100% | +0/0 | +77.77% | +14/18 | +89.65% | +52/58 | +
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 | 1x +1x +1x +1x + +1x +6x +6x +6x +6x +6x + +6x + + +2x + + + +2x + + +2x + + + + + + + + + + + +2x +1x + + + + + + + + + +1x | import { User } from '../data/index.js' +import { DuplicityError, SystemError } from 'com/errors.js' +import bcrypt from 'bcryptjs' +import validate from 'com/validate.js' + +const registerUser = (name, surname, email, password, passwordRepeat) => { + validate.name(name) + validate.name(surname, 'surname') + validate.email(email) + validate.password(password) + validate.passwordsMatch(password, passwordRepeat) + + return User.findOne({ $and: [{ email }, { role: 'provider' }] }) + .catch(error => { throw new SystemError(error.message) }) + .then(user => { + if (user) { + throw new DuplicityError('User already exists') + } + + return bcrypt.hash(password, 8) + .catch(error => { throw new SystemError(error.message) }) + .then(hash => { + const newUser = { + name, + surname, + email, + password: hash, + role: 'provider', + phone: '', + appointments: [], + notes: [], + services: [] + } + + return User.create(newUser) + .catch(error => { throw new SystemError(error.message) }) + .then(() => { }) + }) + }) + + + + +} + +export default registerUser |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 | 1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + + +1x +1x + +1x + +1x + + +1x + + +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x + + + + +1x +1x + +1x + +1x + + + + + + + + + +1x + + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x + + | import 'dotenv/config' + +import mongoose, { Types } from 'mongoose' + +const { ObjectId } = Types + +import bcrypt from 'bcryptjs' + +import { DuplicityError, ContentError } from 'com/errors.js' + +import { expect } from 'chai' + +import { User } from '../data/index.js' + +import registerUser from './registerUser.js' + +const { MONGODB_URL_TEST } = process.env + + +describe('register user', () => { + before(() => mongoose.connect(MONGODB_URL_TEST).then(() => User.deleteMany())) + + beforeEach(() => User.deleteMany()) + + it('succeeds on register user', () => + bcrypt.hash('1234', 8) + .then(hash => { + return registerUser('Jon', 'Snow', 'jon@snow.com', '1234', '1234') + .then(() => User.findOne({ email: 'jon@snow.com' })) + .then(user => { + expect(user).to.be.an('object') + expect(user._id).to.be.an.instanceOf(ObjectId) + expect(user.id).to.be.a('string') + expect(user.id).to.have.lengthOf(24) + expect(user.name).to.equal('Jon') + expect(user.surname).to.equal('Snow') + expect(user.email).to.equal('jon@snow.com') + expect(user.password).to.have.lengthOf(60) + expect(user.role).to.equal('provider') + expect(user.phone).to.equal('') + expect(user.appointments).to.be.an('array') + expect(user.appointments.length).to.equal(0) + expect(user.notes).to.be.an('array') + expect(user.notes.length).to.equal(0) + expect(user.services).to.be.an('array') + expect(user.services.length).to.equal(0) + return bcrypt.compare('1234', user.password) + .then(valid => { expect(valid).to.be.true }) + }) + }) + ) + + it('fails on existing user', () => { + let errorThrown + + return bcrypt.hash('1234', 8) + .then(hash => { + User.create({ + name: 'Jon', + surname: 'Snow', + email: 'jon@snow.com', + password: hash, + role: 'provider' + }) + }) + .then(() => registerUser('Jon', 'Snow', 'jon@snow.com', '1234', '1234')) + .catch(error => { + errorThrown = error + }) + .finally(() => { + expect(errorThrown).to.be.an.instanceOf(DuplicityError) + expect(errorThrown.message).to.equal('User already exists') + }) + }) + + it('fails on invalid name', () => { + let errorThrown + + try { + registerUser(1234, 'Snow', 'jon@snow.com', '1234', '1234') + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('name is not valid') + } + }) + + it('fails on invalid surname', () => { + let errorThrown + + try { + registerUser('Jon', 1234, 'jon@snow.com', '1234', '1234') + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('surname is not valid') + } + }) + + it('fails on invalid email', () => { + let errorThrown + + try { + registerUser('Jon', 'Snow', 'jonsow.com', '1234', '1234') + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('email is not valid') + } + }) + + it('fails on invalid password', () => { + let errorThrown + + try { + registerUser('Jon', 'Snow', 'jon@snow.com', 1234, '1234') + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('password is not valid') + } + }) + + after(() => User.deleteMany().then(() => mongoose.disconnect())) +}) + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 | 1x +1x +1x + + +1x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1x | import { User } from '../data/index.js' +import { NotFoundError, SystemError } from 'com/errors.js' +import validate from 'com/validate.js' + + +const updateCustomer = (userId, customerId, customerUpdated) => { + validate.id(userId, 'userId') + validate.id(customerId, 'customerId') + + const fieldsUpdated = {} + + if (customerUpdated.name) { + validate.name(customerUpdated.name, 'name') + fieldsUpdated.name = customerUpdated.name + } + + if (customerUpdated.surname) { + validate.name(customerUpdated.surname, 'surname') + fieldsUpdated.surname = customerUpdated.surname + } + + if (customerUpdated.email) { + validate.email(customerUpdated.email, 'email') + fieldsUpdated.email = customerUpdated.email + } + + if (customerUpdated.phone) { + validate.phone(customerUpdated.phone, 'phone') + fieldsUpdated.phone = customerUpdated.phone + } + + return User.findById(userId) + .catch(error => { throw new SystemError(error.message) }) + .then(user => { + if (!user) throw new NotFoundError('User not found') + + return User.updateOne({ _id: customerId }, { $set: fieldsUpdated }) + .catch(error => { throw new SystemError(error.message) }) + .then(() => { + return customerId + }) + }) + + +} + +export default updateCustomer |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 | 1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + +1x + + +1x +1x + +1x + +1x + + + + + + + + + + + + + + + + + +1x +1x + +1x + + + + + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x +1x + +1x +1x + +1x + +1x +1x + + + +1x + + + + | import 'dotenv/config' + +import mongoose, { connect, Types } from 'mongoose' + +const { ObjectId } = Types + +import bcrypt from 'bcryptjs' + +import { NotFoundError, ContentError } from 'com/errors.js' + +import { expect } from 'chai' + +import { User } from '../data/index.js' + +import updateCustomer from './updateCustomer.js' + +const { MONGODB_URL_TEST } = process.env + + +describe('update customer', () => { + before(() => mongoose.connect(MONGODB_URL_TEST).then(() => User.deleteMany())) + + beforeEach(() => User.deleteMany()) + + it('succeeds on edit customer', () => + User.create({ + name: 'Alfa', + surname: 'Beto', + email: 'alfa@beto', + role: 'customer', + phone: '' + }) + .then(customer => editCustomer(customer.id, { name: 'Manda', surname: 'Rina', email: 'manda@rina.com', phone: '' })) + .then((customerUpdated) => User.findOne()) + .then((user) => { + expect(user.name).to.equal('Manda') + expect(user.surname).to.equal('Rina') + expect(user.email).to.equal('manda@rina.com') + expect(user.phone).to.equal('') + }) + ) + + it('fails on non-existing customer', () => { + let errorThrown + + return editCustomer(new ObjectId().toString(), { name: 'Manda', surname: 'Rina', email: 'manda@rina.com', phone: '' }) + .catch(error => errorThrown = error) + .finally(() => { + expect(errorThrown).to.be.an.instanceof(NotFoundError) + expect(errorThrown.message).to.equal('Customer not found') + }) + }) + + it('fails on invalid userId', () => { + let errorThrown + + try { + editCustomer(1234, { name: 'Manda', surname: 'Rina', email: 'manda@rina.com', phone: '' }) + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('customerId is not valid') + } + }) + + it('fails on invalid name', () => { + let errorThrown + + try { + editCustomer(new ObjectId().toString(), { name: '1234', surname: 'Rina', email: 'manda@rina.com', phone: '' }) + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('name is not valid') + } + }) + + it('fails on invalid surname', () => { + let errorThrown + + try { + editCustomer(new ObjectId().toString(), { name: 'Manda', surname: '1234', email: 'manda@rina.com', phone: '' }) + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('surname is not valid') + } + }) + + it('fails on invalid email', () => { + let errorThrown + + try { + editCustomer(new ObjectId().toString(), { name: 'Manda', surname: 'Rina', email: 'jonsow.com', phone: '' }) + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('email is not valid') + } + }) + + it('fails on invalid phone', () => { + let errorThrown + + try { + editCustomer(new ObjectId().toString(), { name: 'Manda', surname: 'Rina', email: 'manda@rina.com', phone: '' }) + } catch (error) { + errorThrown = error + } finally { + expect(errorThrown).to.be.an.instanceof(ContentError) + expect(errorThrown.message).to.equal('phone is not valid') + } + }) + + after(() => User.deleteMany().then(() => mongoose.disconnect())) +}) + + + |