diff --git a/deploy/auth/util_noenv.spec.js b/deploy/auth/util.noenv-spec.js similarity index 100% rename from deploy/auth/util_noenv.spec.js rename to deploy/auth/util.noenv-spec.js diff --git a/deploy/package.json b/deploy/package.json index 168ec3c3b1211a2e6e0f9f4a7eb6345d29ce7b2d..75b84d789ed5e2071f232e156a247c394d7e068f 100644 --- a/deploy/package.json +++ b/deploy/package.json @@ -6,8 +6,8 @@ "scripts": { "start": "node server.js", "test": "npm run testEnv && npm run testNoEnv", - "testEnv": "node -r dotenv/config ./node_modules/.bin/mocha ./test/mocha.test.js --timeout 60000", - "testNoEnv": "node ./node_modules/.bin/mocha ./test/mocha.test.noenv.js --timeout 60000", + "testEnv": "DISABLE_LIMITER=1 node -r dotenv/config ./node_modules/.bin/mocha ./**/*.spec.js --timeout 60000", + "testNoEnv": "node ./node_modules/.bin/mocha ./**/*.noenv-spec.js --timeout 60000", "mocha": "mocha", "mocha-env": "node -r dotenv/config ./node_modules/.bin/mocha" }, diff --git a/deploy/saneUrl/index.js b/deploy/saneUrl/index.js index 56f88513d019662b2f51e666e35376d38f686fa3..c46d25a294ce5440039205d3fca02a72093abc05 100644 --- a/deploy/saneUrl/index.js +++ b/deploy/saneUrl/index.js @@ -20,6 +20,7 @@ const { REDIS_PASSWORD, HOSTNAME, + HOST_PATHNAME, DISABLE_LIMITER, } = process.env @@ -41,6 +42,18 @@ const passthrough = (_, __, next) => next() const acceptHtmlProg = /text\/html/i +const getFile = async name => { + + const value = await store.get(name) + const json = JSON.parse(value) + const { expiry } = json + if ( expiry && ((Date.now() - expiry) > 0) ) { + throw new NotFoundError(`File expired`) + } + + return value +} + router.get('/:name', DISABLE_LIMITER ? passthrough : limiter, async (req, res) => { const { name } = req.params const { headers } = req @@ -48,55 +61,68 @@ router.get('/:name', DISABLE_LIMITER ? passthrough : limiter, async (req, res) = const redirectFlag = acceptHtmlProg.test(headers['accept']) try { - const value = await store.get(name) + const value = await getFile(name) const json = JSON.parse(value) - const { expiry, queryString } = json - if ( expiry && ((Date.now() - expiry) > 0) ) { - return res.status(404).end() - } + const { queryString } = json + + const REAL_HOSTNAME = `${HOSTNAME}${HOST_PATHNAME}/` - if (redirectFlag) res.redirect(`${HOSTNAME}/?${queryString}`) + if (redirectFlag) res.redirect(`${REAL_HOSTNAME}/?${queryString}`) else res.status(200).send(value) } catch (e) { if (e instanceof NotFoundError) return res.status(404).end() - res.status(500).send(e.toString()) + else return res.status(500).send(e.toString()) } }) -router.post('/:name', DISABLE_LIMITER ? passthrough : limiter, bodyParser.json(), async (req, res) => { - const { name } = req.params - const { body, user } = req - - try { - const payload = { - ...body, - userId: user && user.id, - expiry: !user && (Date.now() + 1e3 * 60 * 60 * 72) +router.post('/:name', + DISABLE_LIMITER ? passthrough : limiter, + async (req, res, next) => { + const { name } = req.params + try { + await getFile(name) + return res.status(409).send(`filename already exists`) + } catch (e) { + if (e instanceof NotFoundError) return next() + else return res.status(500).send(e) } - - await store.set(name, JSON.stringify(payload)) - res.status(200).end() - + }, + bodyParser.json(), + async (req, res) => { + const { name } = req.params + const { body, user } = req + try { - if (!user) return - const { savedCustomLinks = [], ...rest } = await readUserData(user) - await saveUserData(user, { - ...rest, - savedCustomLinks: [ - ...savedCustomLinks, - name - ] - }) + const payload = { + ...body, + userId: user && user.id, + expiry: !user && (Date.now() + 1e3 * 60 * 60 * 72) + } + + await store.set(name, JSON.stringify(payload)) + res.status(200).end() + + try { + if (!user) return + const { savedCustomLinks = [], ...rest } = await readUserData(user) + await saveUserData(user, { + ...rest, + savedCustomLinks: [ + ...savedCustomLinks, + name + ] + }) + } catch (e) { + console.error(`reading/writing user data error ${user && user.id}, ${name}`, e) + } } catch (e) { - console.error(`reading/writing user data error ${user && user.id}, ${name}`, e) + console.error(`saneUrl /POST error`, e) + const { statusCode, statusMessage } = e + res.status(statusCode || 500).send(statusMessage || 'Error encountered.') } - } catch (e) { - console.error(`saneUrl /POST error`, e) - const { statusCode, statusMessage } = e - res.status(statusCode || 500).send(statusMessage || 'Error encountered.') } -}) +) router.use((_, res) => { res.status(405).send('Not implemneted') diff --git a/deploy/saneUrl/index.spec.js b/deploy/saneUrl/index.spec.js index 39162a25cbd9029a4bbb40c252b4e51ca3b0cd59..25056f2a14fa8c0d5973f83a40166489ff6e34ef 100644 --- a/deploy/saneUrl/index.spec.js +++ b/deploy/saneUrl/index.spec.js @@ -1,9 +1,5 @@ const sinon = require('sinon') -const { Store } = require('./store') - -sinon - .stub(Store.prototype, 'getToken') - .returns(Promise.resolve(`--fake-token--`)) +const { Store, NotFoundError } = require('./store') const userStore = require('../user/store') @@ -44,6 +40,17 @@ const payload = { describe('> saneUrl/index.js', () => { + let getTokenStub + before(() => { + getTokenStub = sinon + .stub(Store.prototype, 'getToken') + .returns(Promise.resolve(`--fake-token--`)) + }) + + after(() => { + getTokenStub.restore() + }) + describe('> router', () => { let server, setStub @@ -61,6 +68,7 @@ describe('> saneUrl/index.js', () => { after(() => { server.close() + setStub.restore() }) it('> works', async () => { @@ -94,25 +102,13 @@ describe('> saneUrl/index.js', () => { getStub.restore() }) - it('> set works', async () => { - - await got(`http://localhost:50000/${name}`, { - method: 'POST', - headers: { - 'Content-type': 'application/json' - }, - body: JSON.stringify(payload) - }) - - const [ storedName, _ ] = setStub.args[0] - - expect(storedName).to.equal(name) - expect(setStub.called).to.be.true - }) + describe('> set', () => { - describe('> set with unauthenticated user', () => { + it('> checks if the name is available', async () => { - it('> set with anonymous user has user undefined and expiry as defined', async () => { + const getStub = sinon + .stub(Store.prototype, 'get') + .returns(Promise.reject(new NotFoundError())) await got(`http://localhost:50000/${name}`, { method: 'POST', @@ -122,78 +118,167 @@ describe('> saneUrl/index.js', () => { body: JSON.stringify(payload) }) - expect(setStub.called).to.be.true - const [ _, storedContent] = setStub.args[0] - const { userId, expiry } = JSON.parse(storedContent) - expect(!!userId).to.be.false - expect(!!expiry).to.be.true + const [ storedName, _ ] = setStub.args[0] - // there will be some discrepencies, but the server lag should not exceed 5 seconds - expect( 1e3 * 60 * 60 * 72 - expiry + Date.now() ).to.be.lessThan(1e3 * 5) - }) - }) + expect(storedName).to.equal(name) + expect(getStub.called).to.be.true + expect(setStub.called).to.be.true - describe('> set with authenticated user', () => { - - before(() => { - user = { - id: 'test/1', - name: 'hello world' - } + getStub.restore() }) - afterEach(() => { - readUserDataStub.resetHistory() - saveUserDataStub.resetHistory() - }) - after(() => { - user = null - readUserDataStub.restore() - saveUserDataStub.restore() - }) + it('> if file exist, will return 409 conflict', async () => { - it('> userId set, expiry unset', async () => { + const getStub = sinon + .stub(Store.prototype, 'get') + .returns(Promise.resolve('{}')) - await got(`http://localhost:50000/${name}`, { + const { statusCode } = await got(`http://localhost:50000/${name}`, { method: 'POST', headers: { 'Content-type': 'application/json' }, - body: JSON.stringify(payload) + body: JSON.stringify(payload), + throwHttpErrors: false }) - expect(setStub.called).to.be.true - const [ _, storedContent] = setStub.args[0] - const { userId, expiry } = JSON.parse(storedContent) - expect(!!userId).to.be.true - expect(!!expiry).to.be.false - - expect( userId ).to.equal('test/1') + expect(statusCode).to.equal(409) + expect(getStub.called).to.be.true + expect(setStub.called).to.be.false + + getStub.restore() }) - it('> readUserDataset saveUserDataset data stubs called', async () => { + it('> if other error, will return 500', async () => { + + const getStub = sinon + .stub(Store.prototype, 'get') + .returns(Promise.reject(new Error(`other errors`))) - await got(`http://localhost:50000/${name}`, { + const { statusCode } = await got(`http://localhost:50000/${name}`, { method: 'POST', headers: { 'Content-type': 'application/json' }, - body: JSON.stringify(payload) + body: JSON.stringify(payload), + throwHttpErrors: false + }) + + expect(statusCode).to.equal(500) + expect(getStub.called).to.be.true + expect(setStub.called).to.be.false + + getStub.restore() + }) + + describe('> set with unauthenticated user', () => { + let getStub + + before(() => { + getStub = sinon + .stub(Store.prototype, 'get') + .returns(Promise.reject(new NotFoundError())) + }) + + after(() => { + getStub.restore() }) - expect(readUserDataStub.called).to.be.true - expect(readUserDataStub.calledWith(user)).to.be.true + it('> set with anonymous user has user undefined and expiry as defined', async () => { + + await got(`http://localhost:50000/${name}`, { + method: 'POST', + headers: { + 'Content-type': 'application/json' + }, + body: JSON.stringify(payload) + }) + + expect(setStub.called).to.be.true + const [ _, storedContent] = setStub.args[0] + const { userId, expiry } = JSON.parse(storedContent) + expect(!!userId).to.be.false + expect(!!expiry).to.be.true + + // there will be some discrepencies, but the server lag should not exceed 5 seconds + expect( 1e3 * 60 * 60 * 72 - expiry + Date.now() ).to.be.lessThan(1e3 * 5) + }) + }) + + describe('> set with authenticated user', () => { + + before(() => { + getStub = sinon + .stub(Store.prototype, 'get') + .returns(Promise.reject(new NotFoundError())) + }) + + after(() => { + getStub.restore() + }) - expect(saveUserDataStub.called).to.be.true - expect(saveUserDataStub.calledWith(user, { - ...savedUserDataPayload, - savedCustomLinks: [ - ...savedUserDataPayload.savedCustomLinks, - name - ] - })).to.be.true + before(() => { + user = { + id: 'test/1', + name: 'hello world' + } + }) + + afterEach(() => { + readUserDataStub.resetHistory() + saveUserDataStub.resetHistory() + }) + + after(() => { + user = null + readUserDataStub.restore() + saveUserDataStub.restore() + }) + + it('> userId set, expiry unset', async () => { + + await got(`http://localhost:50000/${name}`, { + method: 'POST', + headers: { + 'Content-type': 'application/json' + }, + body: JSON.stringify(payload) + }) + + expect(setStub.called).to.be.true + const [ _, storedContent] = setStub.args[0] + const { userId, expiry } = JSON.parse(storedContent) + expect(!!userId).to.be.true + expect(!!expiry).to.be.false + + expect( userId ).to.equal('test/1') + }) + + it('> readUserDataset saveUserDataset data stubs called', async () => { + + await got(`http://localhost:50000/${name}`, { + method: 'POST', + headers: { + 'Content-type': 'application/json' + }, + body: JSON.stringify(payload) + }) + + expect(readUserDataStub.called).to.be.true + expect(readUserDataStub.calledWith(user)).to.be.true + + expect(saveUserDataStub.called).to.be.true + expect(saveUserDataStub.calledWith(user, { + ...savedUserDataPayload, + savedCustomLinks: [ + ...savedUserDataPayload.savedCustomLinks, + name + ] + })).to.be.true + }) }) }) }) }) + diff --git a/deploy/saneUrl/store.spec.js b/deploy/saneUrl/store.spec.js index fc9a37eba3827e61c1e9d507fed0f51577a0a91a..2f860eabfcf4d9d89ad65f960d25fbe91973d89f 100644 --- a/deploy/saneUrl/store.spec.js +++ b/deploy/saneUrl/store.spec.js @@ -1,4 +1,4 @@ -const { NotFoundError, Store } = require('./store') +const { Store } = require('./store') const sinon = require('sinon') const { expect } = require("chai") const nock = require('nock') @@ -11,12 +11,21 @@ const objContent = `objContent` describe('> store.js', () => { describe('> Store', () => { - const getTokenSpy = sinon - .stub(Store.prototype, 'getToken') - .returns(Promise.resolve(fakeToken)) + + let getTokenSpy, store + before(() => { + + getTokenSpy = sinon + .stub(Store.prototype, 'getToken') + .returns(Promise.resolve(fakeToken)) - const store = new Store({ objStorateRootUrl }) + store = new Store({ objStorateRootUrl }) + }) + after(() => { + getTokenSpy.restore() + nock.restore() + }) afterEach(() => { getTokenSpy.resetHistory() }) diff --git a/deploy/test/mocha.test.js b/deploy/test/mocha.test.js deleted file mode 100644 index 55fac2baae75d7f4f0cfa8854780d7d738b97b09..0000000000000000000000000000000000000000 --- a/deploy/test/mocha.test.js +++ /dev/null @@ -1,3 +0,0 @@ -require('../auth/util.spec') -require('../datasets/query.spec') -require('../datasets/util.spec') diff --git a/deploy/test/mocha.test.noenv.js b/deploy/test/mocha.test.noenv.js deleted file mode 100644 index 36a918fcdf251fd5bcf380d8859f9c711925496b..0000000000000000000000000000000000000000 --- a/deploy/test/mocha.test.noenv.js +++ /dev/null @@ -1 +0,0 @@ -require("../auth/util_noenv.spec") \ No newline at end of file