some frontend unit tests

This commit is contained in:
Danny Coates 2018-02-20 20:31:27 -08:00
parent 4929437283
commit 78728ce4ca
No known key found for this signature in database
GPG Key ID: 4C442633C62E00CB
24 changed files with 708 additions and 417 deletions

View File

@ -6,4 +6,5 @@ assets
docs
public
test
coverage
coverage
.nyc_output

3
.nsprc
View File

@ -1,3 +0,0 @@
{
"exceptions": ["https://nodesecurity.io/advisories/534"]
}

View File

@ -1,2 +1,3 @@
dist
assets/*.js
coverage

View File

@ -145,9 +145,6 @@ function download(id, keychain) {
if (authHeader) {
keychain.nonce = parseNonce(authHeader);
}
if (xhr.status === 404) {
return reject(new Error('notfound'));
}
if (xhr.status !== 200) {
return reject(new Error(xhr.status));
}

View File

@ -188,7 +188,7 @@ export default function(state, emitter) {
}
console.error(err);
state.transfer = null;
const location = err.message === 'notfound' ? '/404' : '/error';
const location = err.message === '404' ? '/404' : '/error';
if (location === '/error') {
state.raven.captureException(err);
metrics.stoppedDownload({ size, err });

View File

@ -59,7 +59,7 @@ export default class FileReceiver extends Nanobus {
return;
}
async download() {
async download(noSave = false) {
this.state = 'downloading';
this.emit('progress', this.progress);
try {
@ -78,11 +78,13 @@ export default class FileReceiver extends Nanobus {
if (this.cancelled) {
throw new Error(0);
}
await saveFile({
plaintext,
name: decodeURIComponent(this.fileInfo.name),
type: this.fileInfo.type
});
if (!noSave) {
await saveFile({
plaintext,
name: decodeURIComponent(this.fileInfo.name),
type: this.fileInfo.type
});
}
this.msg = 'downloadFinish';
this.state = 'complete';
return;

View File

@ -16,7 +16,7 @@ export default class OwnedFile {
this.ownerToken = obj.ownerToken;
this.dlimit = obj.dlimit || 1;
this.dtotal = obj.dtotal || 0;
this.keychain = new Keychain(obj.secretKey);
this.keychain = new Keychain(obj.secretKey, obj.nonce);
this._hasPassword = !!obj.hasPassword;
}
@ -59,6 +59,7 @@ export default class OwnedFile {
if (e.message === '404') {
this.dtotal = this.dlimit;
}
// ignore other errors
}
}

View File

@ -24,5 +24,5 @@ test:
override:
- npm run build
- npm run lint
- npm run test:ci
- npm test
- nsp check

410
package-lock.json generated
View File

@ -386,6 +386,12 @@
"integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
"dev": true
},
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==",
"dev": true
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -683,6 +689,17 @@
"babel-runtime": "6.26.0"
}
},
"babel-plugin-istanbul": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz",
"integrity": "sha1-Z2DN2Xf0EdPhdbsGTyvDJ9mbK24=",
"dev": true,
"requires": {
"find-up": "2.1.0",
"istanbul-lib-instrument": "1.9.2",
"test-exclude": "4.2.0"
}
},
"babel-plugin-syntax-async-functions": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz",
@ -3942,6 +3959,29 @@
"webpack-sources": "1.1.0"
}
},
"extract-zip": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.6.tgz",
"integrity": "sha1-EpDt6NINCHK0Kf0/NRyhKOxe+Fw=",
"dev": true,
"requires": {
"concat-stream": "1.6.0",
"debug": "2.6.9",
"mkdirp": "0.5.0",
"yauzl": "2.4.1"
},
"dependencies": {
"mkdirp": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz",
"integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=",
"dev": true,
"requires": {
"minimist": "0.0.8"
}
}
}
},
"fast-deep-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz",
@ -3975,6 +4015,15 @@
"websocket-driver": "0.7.0"
}
},
"fd-slicer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
"integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
"dev": true,
"requires": {
"pend": "1.2.0"
}
},
"figures": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
@ -6027,12 +6076,6 @@
"integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==",
"dev": true
},
"immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=",
"dev": true
},
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@ -6720,6 +6763,27 @@
"isarray": "1.0.0"
}
},
"istanbul-lib-coverage": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.2.tgz",
"integrity": "sha512-tZYA0v5A7qBSsOzcebJJ/z3lk3oSzH62puG78DbBA1+zupipX2CakDyiPV3pOb8He+jBwVimuwB0dTnh38hX0w==",
"dev": true
},
"istanbul-lib-instrument": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.2.tgz",
"integrity": "sha512-nz8t4HQ2206a/3AXi+NHFWEa844DMpPsgbcUteJbt1j8LX1xg56H9rOMnhvcvVvPbW60qAIyrSk44H8ZDqaSSA==",
"dev": true,
"requires": {
"babel-generator": "6.26.1",
"babel-template": "6.26.0",
"babel-traverse": "6.26.0",
"babel-types": "6.26.0",
"babylon": "6.18.0",
"istanbul-lib-coverage": "1.1.2",
"semver": "5.5.0"
}
},
"jest-get-type": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-21.2.0.tgz",
@ -6936,53 +7000,6 @@
"integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=",
"dev": true
},
"jszip": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.5.tgz",
"integrity": "sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ==",
"dev": true,
"requires": {
"core-js": "2.3.0",
"es6-promise": "3.0.2",
"lie": "3.1.1",
"pako": "1.0.6",
"readable-stream": "2.0.6"
},
"dependencies": {
"core-js": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz",
"integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=",
"dev": true
},
"es6-promise": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz",
"integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=",
"dev": true
},
"process-nextick-args": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
"dev": true
},
"readable-stream": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
"integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
"dev": true,
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "1.0.0",
"process-nextick-args": "1.0.7",
"string_decoder": "0.10.31",
"util-deprecate": "1.0.2"
}
}
}
},
"just-extend": {
"version": "1.1.27",
"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz",
@ -7084,15 +7101,6 @@
"type-check": "0.3.2"
}
},
"lie": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
"integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=",
"dev": true,
"requires": {
"immediate": "3.0.6"
}
},
"lint-staged": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-6.1.1.tgz",
@ -7509,6 +7517,12 @@
"integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=",
"dev": true
},
"loglevelnext": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/loglevelnext/-/loglevelnext-1.0.3.tgz",
"integrity": "sha512-OCxd/b78TijTB4b6zVqLbMrxhebyvdZKwqpL0VHUZ0pYhavXaPD4l6Xrr4n5xqTYWiqtb0i7ikSoJY/myQ/Org==",
"dev": true
},
"lolex": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.2.tgz",
@ -10672,6 +10686,12 @@
"resolved": "https://registry.npmjs.org/pelo/-/pelo-0.1.0.tgz",
"integrity": "sha512-+oVixa69fxS/X+3s1DaSJVQLG/ukHfjK2pHCmpIgjRChp73lnAfbqOYZ0MEo5C5yVkYeUJSoWAcRK0lx0hvOjQ=="
},
"pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
"dev": true
},
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
@ -13876,6 +13896,12 @@
"ipaddr.js": "1.5.2"
}
},
"proxy-from-env": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz",
"integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=",
"dev": true
},
"proxyquire": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-1.8.0.tgz",
@ -13967,6 +13993,22 @@
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
},
"puppeteer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.1.0.tgz",
"integrity": "sha512-Up+EiDVtc+EueFUzEFi4JqTlkXdC4pjMng0W9s/uqg2HP7+EHJiIe8OtwP7I+Jk7rI1kS5WIVpqx46aihnLuJQ==",
"dev": true,
"requires": {
"debug": "2.6.9",
"extract-zip": "1.6.6",
"https-proxy-agent": "2.1.1",
"mime": "1.4.1",
"progress": "2.0.0",
"proxy-from-env": "1.0.0",
"rimraf": "2.6.2",
"ws": "3.3.3"
}
},
"q": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
@ -14809,29 +14851,6 @@
"integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=",
"dev": true
},
"selenium-webdriver": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz",
"integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==",
"dev": true,
"requires": {
"jszip": "3.1.5",
"rimraf": "2.6.2",
"tmp": "0.0.30",
"xml2js": "0.4.17"
},
"dependencies": {
"tmp": {
"version": "0.0.30",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz",
"integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=",
"dev": true,
"requires": {
"os-tmpdir": "1.0.2"
}
}
}
},
"selfsigned": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.2.tgz",
@ -16025,6 +16044,109 @@
"integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=",
"dev": true
},
"test-exclude": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.0.tgz",
"integrity": "sha512-8hMFzjxbPv6xSlwGhXSvOMJ/vTy3bkng+2pxmf6E1z6VF7I9nIyNfvHtaw+NBPgvz647gADBbMSbwLfZYppT/w==",
"dev": true,
"requires": {
"arrify": "1.0.1",
"micromatch": "2.3.11",
"object-assign": "4.1.1",
"read-pkg-up": "1.0.1",
"require-main-filename": "1.0.1"
},
"dependencies": {
"find-up": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
"integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
"dev": true,
"requires": {
"path-exists": "2.1.0",
"pinkie-promise": "2.0.1"
}
},
"load-json-file": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {
"graceful-fs": "4.1.11",
"parse-json": "2.2.0",
"pify": "2.3.0",
"pinkie-promise": "2.0.1",
"strip-bom": "2.0.0"
}
},
"parse-json": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
"integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
"dev": true,
"requires": {
"error-ex": "1.3.1"
}
},
"path-exists": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
"integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
"dev": true,
"requires": {
"pinkie-promise": "2.0.1"
}
},
"path-type": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
"integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
"dev": true,
"requires": {
"graceful-fs": "4.1.11",
"pify": "2.3.0",
"pinkie-promise": "2.0.1"
}
},
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
},
"read-pkg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
"integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
"dev": true,
"requires": {
"load-json-file": "1.1.0",
"normalize-package-data": "2.4.0",
"path-type": "1.1.0"
}
},
"read-pkg-up": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
"integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
"dev": true,
"requires": {
"find-up": "1.1.2",
"read-pkg": "1.1.0"
}
},
"strip-bom": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
"integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
"dev": true,
"requires": {
"is-utf8": "0.2.1"
}
}
}
},
"testpilot-ga": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/testpilot-ga/-/testpilot-ga-0.3.0.tgz",
@ -16300,6 +16422,12 @@
}
}
},
"ultron": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==",
"dev": true
},
"unassert": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/unassert/-/unassert-1.5.1.tgz",
@ -16471,6 +16599,12 @@
"querystring": "0.2.0"
}
},
"url-join": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz",
"integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=",
"dev": true
},
"url-parse": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.2.0.tgz",
@ -16734,22 +16868,24 @@
}
},
"webpack-dev-middleware": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz",
"integrity": "sha512-FCrqPy1yy/sN6U/SaEZcHKRXGlqU0DUaEBL45jkUYoB8foVb6wCnbIJ1HKIx+qUFTW+3JpVcCJCxZ8VATL4e+A==",
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-2.0.6.tgz",
"integrity": "sha512-tj5LLD9r4tDuRIDa5Mu9lnY2qBBehAITv6A9irqXhw/HQquZgTx3BCd57zYbU2gMDnncA49ufK2qVQSbaKJwOw==",
"dev": true,
"requires": {
"loud-rejection": "1.6.0",
"memory-fs": "0.4.1",
"mime": "1.6.0",
"mime": "2.2.0",
"path-is-absolute": "1.0.1",
"range-parser": "1.2.0",
"time-stamp": "2.0.0"
"url-join": "2.0.5",
"webpack-log": "1.1.2"
},
"dependencies": {
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.2.0.tgz",
"integrity": "sha512-0Qz9uF1ATtl8RKJG4VRfOymh7PyEor6NbrI/61lRfuRe4vx9SNATrvAeTj2EWVRKjEQGskrzWkJBBY5NbaVHIA==",
"dev": true
}
}
@ -16873,6 +17009,12 @@
}
}
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"dev": true
},
"os-locale": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
@ -16969,6 +17111,19 @@
"has-flag": "2.0.0"
}
},
"webpack-dev-middleware": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz",
"integrity": "sha512-FCrqPy1yy/sN6U/SaEZcHKRXGlqU0DUaEBL45jkUYoB8foVb6wCnbIJ1HKIx+qUFTW+3JpVcCJCxZ8VATL4e+A==",
"dev": true,
"requires": {
"memory-fs": "0.4.1",
"mime": "1.6.0",
"path-is-absolute": "1.0.1",
"range-parser": "1.2.0",
"time-stamp": "2.0.0"
}
},
"which-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
@ -17007,6 +17162,55 @@
}
}
},
"webpack-log": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-1.1.2.tgz",
"integrity": "sha512-B53SD4N4BHpZdUwZcj4st2QT7gVfqZtqHDruC1N+K2sciq0Rt/3F1Dx6RlylVkcrToMLTaiaeT48k9Lq4iDVDA==",
"dev": true,
"requires": {
"chalk": "2.3.1",
"log-symbols": "2.2.0",
"loglevelnext": "1.0.3",
"uuid": "3.1.0"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
"dev": true,
"requires": {
"color-convert": "1.9.1"
}
},
"chalk": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz",
"integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==",
"dev": true,
"requires": {
"ansi-styles": "3.2.0",
"escape-string-regexp": "1.0.5",
"supports-color": "5.2.0"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"supports-color": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz",
"integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"webpack-manifest-plugin": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-1.3.2.tgz",
@ -17178,6 +17382,17 @@
"mkdirp": "0.5.1"
}
},
"ws": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
"integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
"dev": true,
"requires": {
"async-limiter": "1.0.0",
"safe-buffer": "5.1.1",
"ultron": "1.1.1"
}
},
"x-is-function": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/x-is-function/-/x-is-function-1.0.4.tgz",
@ -17264,6 +17479,15 @@
"camelcase": "4.1.0"
}
},
"yauzl": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
"integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=",
"dev": true,
"requires": {
"fd-slicer": "1.0.1"
}
},
"yo-yoify": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/yo-yoify/-/yo-yoify-4.3.0.tgz",

View File

@ -9,6 +9,7 @@
"private": true,
"scripts": {
"precommit": "lint-staged",
"prepush": "npm test",
"clean": "rimraf dist",
"build": "npm run clean && webpack -p",
"lint": "npm-run-all lint:*",
@ -23,8 +24,9 @@
"changelog": "github-changes -o mozilla -r send --only-pulls --use-commit-body --no-merges",
"contributors": "git shortlog -s | awk -F\\t '{print $2}' > CONTRIBUTORS",
"release": "npm-run-all contributors changelog",
"test": "mocha test/unit",
"test:ci": "nyc mocha --reporter=min test/unit",
"test": "npm-run-all test:*",
"test:backend": "nyc mocha --reporter=min test/unit",
"test:frontend": "cross-env NODE_ENV=development node test/frontend/runner.js && nyc report --reporter=html",
"start": "cross-env NODE_ENV=development webpack-dev-server",
"prod": "node server/prod.js",
"cover": "nyc --reporter=html mocha test/unit"
@ -43,7 +45,7 @@
},
"nyc": {
"reporter": [
"text-summary"
"text"
],
"cache": true
},
@ -53,6 +55,7 @@
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-plugin-istanbul": "^4.1.5",
"babel-plugin-yo-yoify": "^1.0.2",
"babel-preset-env": "^1.6.1",
"babel-preset-es2015": "^6.24.1",
@ -88,11 +91,11 @@
"postcss-loader": "^2.1.0",
"prettier": "^1.10.2",
"proxyquire": "^1.8.0",
"puppeteer": "^1.1.0",
"raven-js": "^3.22.2",
"redis-mock": "^0.21.0",
"require-from-string": "^2.0.1",
"rimraf": "^2.6.2",
"selenium-webdriver": "^3.6.0",
"sinon": "^4.3.0",
"string-hash": "^1.1.3",
"stylelint": "^9.0.0",
@ -102,6 +105,7 @@
"testpilot-ga": "^0.3.0",
"val-loader": "^1.1.0",
"webpack": "^3.11.0",
"webpack-dev-middleware": "^2.0.6",
"webpack-dev-server": "2.9.1",
"webpack-manifest-plugin": "^1.3.2",
"webpack-unassert-loader": "^1.2.0"

View File

@ -2,11 +2,13 @@ const assets = require('../common/assets');
const locales = require('../common/locales');
const routes = require('./routes');
const pages = require('./routes/pages');
const tests = require('../test/frontend/routes');
module.exports = function(app, devServer) {
assets.setMiddleware(devServer.middleware);
locales.setMiddleware(devServer.middleware);
routes(app);
tests(app);
// webpack-dev-server routes haven't been added yet
// so wait for next tick to add 404 handler
process.nextTick(() => app.use(pages.notfound));

View File

@ -1,2 +1,8 @@
env:
browser: true
browser: true
parserOptions:
sourceType: module
rules:
node/no-unsupported-features: off

View File

@ -1,22 +0,0 @@
const webdriver = require('selenium-webdriver');
const path = require('path');
const until = webdriver.until;
const driver = new webdriver.Builder().forBrowser('firefox').build();
driver.get(path.join('file:///', __dirname, '/frontend.test.html'));
driver.wait(until.titleIs('Mocha Tests'));
driver.wait(until.titleMatches(/^[0-9]$/));
driver.getTitle().then(title => {
driver.quit().then(() => {
if (title === '0') {
console.log('Frontend tests have passed.');
} else {
throw new Error(
'Frontend tests are failing. ' +
'Please open the frontend.test.html file in a browser.'
);
}
});
});

View File

@ -1,22 +0,0 @@
class FakeFile extends Blob {
constructor(name, data, opt) {
super(data, opt);
this.name = name;
}
}
window.Raven = {
captureException: function(err) {
console.error(err, err.stack);
}
};
window.FakeFile = FakeFile;
window.FileSender = require('../../app/fileSender');
window.FileReceiver = require('../../app/fileReceiver');
window.sinon = require('sinon');
window.server = window.sinon.fakeServer.create();
window.assert = require('assert');
const utils = require('../../app/utils');
window.b64ToArray = utils.b64ToArray;
window.arrayToB64 = utils.arrayToB64;

View File

@ -1,24 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Mocha Tests</title>
<link rel="stylesheet" href="../../node_modules/mocha/mocha.css">
<script src="bundle.js"></script>
<meta charset="utf-8"/>
</head>
<body>
<div id="mocha"></div>
<script src="../../node_modules/mocha/mocha.js"></script>
<script>mocha.setup('bdd')</script>
<script src="frontend.test.js"></script>
<script>
mocha.checkLeaks();
mocha.globals(['jQuery']);
mocha.run(function(err) {
document.title = err;
});
</script>
</body>
</html>

View File

@ -1,230 +0,0 @@
const FileSender = window.FileSender;
const FileReceiver = window.FileReceiver;
const FakeFile = window.FakeFile;
const assert = window.assert;
const server = window.server;
const b64ToArray = window.b64ToArray;
const sinon = window.sinon;
let file;
let encryptedIV;
let secretKey;
let originalBlob;
describe('File Sender', function() {
before(function() {
server.respondImmediately = true;
server.respondWith('POST', '/upload', function(request) {
const reader = new FileReader();
reader.readAsArrayBuffer(request.requestBody.get('data'));
reader.onload = function(event) {
file = this.result;
};
const responseObj = JSON.parse(request.requestHeaders['X-File-Metadata']);
request.respond(
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({
url: 'some url',
id: responseObj.id,
delete: responseObj.delete
})
);
});
});
it('Should get a loading event emission', function() {
const file = new FakeFile('hello_world.txt', ['This is some data.']);
const fs = new FileSender(file);
let testLoading = true;
fs.on('loading', isStillLoading => {
assert(!(!testLoading && isStillLoading));
testLoading = isStillLoading;
});
return fs
.upload()
.then(info => {
assert(info);
assert(!testLoading);
})
.catch(err => {
console.log(err, err.stack);
assert.fail();
});
});
it('Should get a encrypting event emission', function() {
const file = new FakeFile('hello_world.txt', ['This is some data.']);
const fs = new FileSender(file);
let testEncrypting = true;
fs.on('encrypting', isStillEncrypting => {
assert(!(!testEncrypting && isStillEncrypting));
testEncrypting = isStillEncrypting;
});
return fs
.upload()
.then(info => {
assert(info);
assert(!testEncrypting);
})
.catch(err => {
console.log(err, err.stack);
assert.fail();
});
});
it('Should encrypt a file properly', function(done) {
const newFile = new FakeFile('hello_world.txt', ['This is some data.']);
const fs = new FileSender(newFile);
fs.upload().then(info => {
const key = info.secretKey;
secretKey = info.secretKey;
const IV = info.fileId;
encryptedIV = info.fileId;
const readRaw = new FileReader();
readRaw.onload = function(event) {
const rawArray = new Uint8Array(this.result);
originalBlob = rawArray;
window.crypto.subtle
.importKey(
'jwk',
{
kty: 'oct',
k: key,
alg: 'A128GCM',
ext: true
},
{
name: 'AES-GCM'
},
true,
['encrypt', 'decrypt']
)
.then(cryptoKey => {
window.crypto.subtle
.encrypt(
{
name: 'AES-GCM',
iv: b64ToArray(IV),
tagLength: 128
},
cryptoKey,
rawArray
)
.then(encrypted => {
assert(
new Uint8Array(encrypted).toString() ===
new Uint8Array(file).toString()
);
done();
});
});
};
readRaw.readAsArrayBuffer(newFile);
});
});
});
describe('File Receiver', function() {
class FakeXHR {
constructor() {
this.response = file;
this.status = 200;
}
static setup() {
FakeXHR.prototype.open = sinon.spy();
FakeXHR.prototype.send = function() {
this.onload();
};
FakeXHR.prototype.originalXHR = window.XMLHttpRequest;
FakeXHR.prototype.getResponseHeader = function() {
return JSON.stringify({
filename: 'hello_world.txt',
id: encryptedIV
});
};
window.XMLHttpRequest = FakeXHR;
}
static restore() {
// originalXHR is a sinon FakeXMLHttpRequest, since
// fakeServer.create() is called in frontend.bundle.js
window.XMLHttpRequest.prototype.originalXHR.restore();
}
}
const cb = function(done) {
if (
file === undefined ||
encryptedIV === undefined ||
secretKey === undefined
) {
assert.fail(
'Please run file sending tests before trying to receive the files.'
);
done();
}
FakeXHR.setup();
done();
};
before(cb);
after(function() {
FakeXHR.restore();
});
it('Should decrypt properly', function() {
const fr = new FileReceiver();
location.hash = secretKey;
return fr
.download()
.then(([decrypted, name]) => {
assert(name);
assert(
new Uint8Array(decrypted).toString() ===
new Uint8Array(originalBlob).toString()
);
})
.catch(err => {
console.log(err, err.stack);
assert.fail();
});
});
it('Should emit decrypting events', function() {
const fr = new FileReceiver();
location.hash = secretKey;
let testDecrypting = true;
fr.on('decrypting', isStillDecrypting => {
assert(!(!testDecrypting && isStillDecrypting));
testDecrypting = isStillDecrypting;
});
return fr
.download()
.then(([decrypted, name]) => {
assert(decrypted);
assert(name);
assert(!testDecrypting);
})
.catch(err => {
console.log(err, err.stack);
assert.fail();
});
});
});

16
test/frontend/index.js Normal file
View File

@ -0,0 +1,16 @@
const fs = require('fs');
const path = require('path');
function kv(f) {
return `require('./tests/${f}')`;
}
module.exports = function() {
const files = fs.readdirSync(path.join(__dirname, 'tests'));
const code = files.map(kv).join(';\n');
return {
code,
dependencies: files.map(f => require.resolve('./tests/' + f)),
cacheable: false
};
};

47
test/frontend/routes.js Normal file
View File

@ -0,0 +1,47 @@
const html = require('choo/html');
const assets = require('../../common/assets');
module.exports = function(app) {
app.get('/mocha.css', function(req, res) {
res.sendFile(require.resolve('mocha/mocha.css'));
});
app.get('/mocha.js', function(req, res) {
res.sendFile(require.resolve('mocha/mocha.js'));
});
app.get('/test', function(req, res) {
res.send(
html`
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="/mocha.css" />
<script src="/mocha.js"></script>
<script>
const reporters = mocha.constructor.reporters;
function Combo(runner) {
reporters.HTML.call(this, runner)
reporters.JSON.call(this, runner)
}
Object.setPrototypeOf(Combo.prototype, reporters.HTML.prototype)
mocha.setup({
ui: 'bdd',
reporter: Combo
})
</script>
<script src="/jsconfig.js"></script>
<script src="${assets.get('runtime.js')}"></script>
<script src="${assets.get('vendor.js')}"></script>
<script src="${assets.get('tests.js')}"></script>
</head>
<body>
<div id="mocha"></div>
<script>
mocha.checkLeaks();
const runner = mocha.run();
</script>
</body>
</html>
`.toString()
);
});
};

63
test/frontend/runner.js Normal file
View File

@ -0,0 +1,63 @@
/* eslint-disable no-undef, no-process-exit */
const fs = require('fs');
const path = require('path');
const mkdirp = require('mkdirp');
const puppeteer = require('puppeteer');
const webpack = require('webpack');
const config = require('../../webpack.config');
const middleware = require('webpack-dev-middleware');
const express = require('express');
const devRoutes = require('../../server/dev');
const app = express();
const wpm = middleware(webpack(config), { logLevel: 'silent' });
app.use(wpm);
devRoutes(app, { middleware: wpm });
function onConsole(msg) {
// excluding 'log' because mocha uses it to write the json output
if (msg.type() !== 'log') {
console.error(msg.text());
}
}
const server = app.listen(async function() {
let exitCode = -1;
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
page.on('console', onConsole);
page.on('pageerror', console.log.bind(console));
await page.goto(`http://127.0.0.1:${server.address().port}/test`);
await page.waitFor(() => typeof runner.testResults !== 'undefined', {
timeout: 5000
});
const results = await page.evaluate(() => runner.testResults);
const coverage = await page.evaluate(() => __coverage__);
if (coverage) {
const dir = path.resolve(__dirname, '../../.nyc_output');
mkdirp.sync(dir);
fs.writeFileSync(
path.resolve(dir, 'frontend.json'),
JSON.stringify(coverage)
);
}
const stats = results.stats;
exitCode = stats.failures;
console.log(`${stats.passes} passing (${stats.duration}ms)\n`);
if (stats.failures) {
console.log('Failures:\n');
for (const f of results.failures) {
console.log(`${f.fullTitle}`);
console.log(` ${f.err.stack}\n`);
}
}
} catch (e) {
console.log(e);
} finally {
browser.close();
server.close(() => {
process.exit(exitCode);
});
}
});

View File

@ -0,0 +1,26 @@
import assert from 'assert';
import * as api from '../../../app/api';
import Keychain from '../../../app/keychain';
const encoder = new TextEncoder();
const plaintext = encoder.encode('hello world!');
const metadata = {
name: 'test.txt',
type: 'text/plain'
};
describe('API', function() {
describe('uploadFile', function() {
it('returns file info on success', async function() {
const keychain = new Keychain();
const encrypted = await keychain.encryptFile(plaintext);
const meta = await keychain.encryptMetadata(metadata);
const verifierB64 = await keychain.authKeyB64();
const up = api.uploadFile(encrypted, meta, verifierB64, keychain);
const result = await up.result;
assert.ok(result.url);
assert.ok(result.id);
assert.ok(result.ownerToken);
});
});
});

View File

@ -0,0 +1,17 @@
import assert from 'assert';
import FileSender from '../../../app/fileSender';
// FileSender uses a File in real life but a Blob works for testing
const blob = new Blob(['hello world!'], { type: 'text/plain' });
blob.name = 'text.txt';
describe('FileSender', function() {
describe('upload', function() {
it('returns an OwnedFile on success', async function() {
const fs = new FileSender(blob);
const file = await fs.upload();
assert.ok(file.id);
assert.equal(file.name, blob.name);
});
});
});

View File

@ -0,0 +1,41 @@
import assert from 'assert';
import Keychain from '../../../app/keychain';
describe('Keychain', function() {
describe('setPassword', function() {
it('changes the authKey', async function() {
const k = new Keychain();
const original = await k.authKeyB64();
k.setPassword('foo', 'some://url');
const pwd = await k.authKeyB64();
assert.notEqual(pwd, original);
});
});
describe('encrypt / decrypt file', function() {
it('can decrypt text it encrypts', async function() {
const enc = new TextEncoder();
const dec = new TextDecoder();
const text = 'hello world!';
const k = new Keychain();
const ciphertext = await k.encryptFile(enc.encode(text));
assert.notEqual(dec.decode(ciphertext), text);
const plaintext = await k.decryptFile(ciphertext);
assert.equal(dec.decode(plaintext), text);
});
});
describe('encrypt / decrypt metadata', function() {
it('can decrypt metadata it encrypts', async function() {
const k = new Keychain();
const meta = {
name: 'foo',
type: 'bar/baz'
};
const ciphertext = await k.encryptMetadata(meta);
const result = await k.decryptMetadata(ciphertext);
assert.equal(result.name, meta.name);
assert.equal(result.type, meta.type);
});
});
});

View File

@ -0,0 +1,133 @@
import assert from 'assert';
import FileSender from '../../../app/fileSender';
import FileReceiver from '../../../app/fileReceiver';
const headless = /Headless/.test(navigator.userAgent);
const noSave = !headless; // only run the saveFile code if headless
// FileSender uses a File in real life but a Blob works for testing
const blob = new Blob(['hello world!'], { type: 'text/plain' });
blob.name = 'test.txt';
describe('Upload / Download flow', function() {
it('can only download once by default', async function() {
const fs = new FileSender(blob);
const file = await fs.upload();
const fr = new FileReceiver({
secretKey: file.toJSON().secretKey,
id: file.id,
nonce: file.keychain.nonce,
requiresPassword: false
});
await fr.getMetadata();
await fr.download(noSave);
try {
await fr.download(noSave);
assert.fail('downloaded again');
} catch (e) {
assert.equal(e.message, '404');
}
});
it('downloads with the correct password', async function() {
const fs = new FileSender(blob);
const file = await fs.upload();
await file.setPassword('magic');
const fr = new FileReceiver({
secretKey: file.toJSON().secretKey,
id: file.id,
url: file.url,
nonce: file.keychain.nonce,
requiresPassword: true,
password: 'magic'
});
await fr.getMetadata();
await fr.download(noSave);
assert.equal(fr.state, 'complete');
});
it('blocks invalid passwords from downloading', async function() {
const fs = new FileSender(blob);
const file = await fs.upload();
await file.setPassword('magic');
const fr = new FileReceiver({
secretKey: file.toJSON().secretKey,
id: file.id,
url: file.url,
nonce: file.keychain.nonce,
requiresPassword: true,
password: 'password'
});
try {
await fr.getMetadata();
assert.fail('got metadata with bad password');
} catch (e) {
assert.equal(e.message, '401');
}
try {
// We can't decrypt without IV from metadata
// but let's try to download anyway
await fr.download();
assert.fail('downloaded file with bad password');
} catch (e) {
assert.equal(e.message, '401');
}
});
it('retries a bad nonce', async function() {
const fs = new FileSender(blob);
const file = await fs.upload();
const fr = new FileReceiver({
secretKey: file.toJSON().secretKey,
id: file.id,
nonce: null, // oops
requiresPassword: false
});
await fr.getMetadata();
assert.equal(fr.fileInfo.name, blob.name);
});
it('can allow multiple downloads', async function() {
const fs = new FileSender(blob);
const file = await fs.upload();
const fr = new FileReceiver({
secretKey: file.toJSON().secretKey,
id: file.id,
nonce: file.keychain.nonce,
requiresPassword: false
});
await file.changeLimit(2);
await fr.getMetadata();
await fr.download(noSave);
await file.updateDownloadCount();
assert.equal(file.dtotal, 1);
await fr.download(noSave);
await file.updateDownloadCount();
assert.equal(file.dtotal, 2);
try {
await fr.download(noSave);
assert.fail('downloaded too many times');
} catch (e) {
assert.equal(e.message, '404');
}
});
it('can delete the file before download', async function() {
const fs = new FileSender(blob);
const file = await fs.upload();
const fr = new FileReceiver({
secretKey: file.toJSON().secretKey,
id: file.id,
nonce: file.keychain.nonce,
requiresPassword: false
});
await file.del();
try {
await fr.getMetadata();
assert.fail('file still exists');
} catch (e) {
assert.equal(e.message, '404');
}
});
});

View File

@ -11,12 +11,19 @@ const regularJSOptions = {
plugins: ['yo-yoify']
};
const entry = {
vendor: ['babel-polyfill', 'fluent'],
app: ['./app/main.js'],
style: ['./app/main.css']
};
if (IS_DEV) {
entry.tests = ['./test/frontend/index.js'];
regularJSOptions.plugins.push('istanbul');
}
module.exports = {
entry: {
vendor: ['babel-polyfill', 'fluent'],
app: ['./app/main.js'],
style: ['./app/main.css']
},
entry,
output: {
filename: '[name].[chunkhash:8].js',
path: path.resolve(__dirname, 'dist'),
@ -126,6 +133,10 @@ module.exports = {
'./build/fluent_loader'
]
},
{
test: require.resolve('./test/frontend/index.js'),
use: ['babel-loader', 'val-loader']
},
{
test: require.resolve('./build/generate_asset_map.js'),
use: ['babel-loader', 'val-loader']