From 44e6df636a37967a38066d0ea45b15ba67b95d69 Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Fri, 26 Mar 2021 13:13:32 +0100 Subject: [PATCH] fix backend tests --- deploy/app.spec.js | 4 + deploy/auth/index.spec.js | 4 +- deploy/package.json | 2 +- deploy/saneUrl/index.spec.js | 436 +++++++++++++++++------------------ deploy/saneUrl/store.js | 3 +- deploy/saneUrl/store.spec.js | 189 +++++++++++---- 6 files changed, 357 insertions(+), 281 deletions(-) diff --git a/deploy/app.spec.js b/deploy/app.spec.js index 9796fecc6..65b492f71 100644 --- a/deploy/app.spec.js +++ b/deploy/app.spec.js @@ -60,6 +60,10 @@ describe('authentication', () => { }) after(() => { + delete require.cache[require.resolve('./saneUrl')] + delete require.cache[require.resolve('./datasets')] + delete require.cache[require.resolve('./user')] + delete require.cache[require.resolve('./constants')] server.close() }) diff --git a/deploy/auth/index.spec.js b/deploy/auth/index.spec.js index 6773ad36b..a75affe6f 100644 --- a/deploy/auth/index.spec.js +++ b/deploy/auth/index.spec.js @@ -16,7 +16,9 @@ describe('auth/index.js', () => { exports: hbpOidcStub } require.cache[require.resolve('./hbp-oidc-v2')] = { - exports: hbpOidcV2Stub + exports: { + bootstrapApp: hbpOidcV2Stub + } } }) diff --git a/deploy/package.json b/deploy/package.json index 29c7b1ba2..334fe6153 100644 --- a/deploy/package.json +++ b/deploy/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "start": "node server.js", - "test": "DISABLE_LIMITER=1 node -r dotenv/config ./node_modules/.bin/mocha './**/*.spec.js' --exclude 'node_modules/*' --timeout 60000", + "test": "DISABLE_LIMITER=1 node -r dotenv/config ./node_modules/.bin/mocha './**/*.spec.js' --exclude 'node_modules/*' --timeout 60000 --exit", "mocha": "mocha" }, "keywords": [], diff --git a/deploy/saneUrl/index.spec.js b/deploy/saneUrl/index.spec.js index cb0038819..80970b18b 100644 --- a/deploy/saneUrl/index.spec.js +++ b/deploy/saneUrl/index.spec.js @@ -1,7 +1,10 @@ const sinon = require('sinon') +const got = require('got') const cookie = require('cookie') -const { Store, NotFoundError } = require('./store') +const { expect } = require('chai') +const express = require('express') +let NotFoundError const userStore = require('../user/store') const savedUserDataPayload = { @@ -20,200 +23,177 @@ const saveUserDataStub = sinon .stub(userStore, 'saveUserData') .returns(Promise.resolve()) -const express = require('express') -const router = require('./index') -const got = require('got') -const { expect } = require('chai') - -const app = express() -let user -app.use('', (req, res, next) => { - req.user = user - next() -}, router) - -const name = `nameme` - -const payload = { - ver: '0.0.1', - queryString: 'test_test' -} describe('> saneUrl/index.js', () => { - - let getTokenStub + const name = `nameme`, + payload = { + ver: '0.0.1', + queryString: 'test_test' + } + + let SaneUrlStoreObjStub, + getStub, + setStub + before(() => { - getTokenStub = sinon - .stub(Store.prototype, 'getToken') - .returns(Promise.resolve(`--fake-token--`)) + const SaneUrlStore = require('./store') + NotFoundError = SaneUrlStore.NotFoundError + + getStub = sinon.stub() + setStub = sinon.stub() + class StubbedStoreObj { + constructor(){ + this.get = getStub + this.set = setStub + } + } + SaneUrlStoreObjStub = sinon.stub(SaneUrlStore, 'Store').value(StubbedStoreObj) }) - after(() => { - getTokenStub.restore() + SaneUrlStoreObjStub.restore() + }) + + afterEach(() => { + getStub.resetHistory() + getStub.resetBehavior() + setStub.resetHistory() + setStub.resetBehavior() }) describe('> router', () => { - let server, setStub + let server, user before(() => { - - setStub = sinon - .stub(Store.prototype, 'set') - .returns(Promise.resolve()) + const router = require('./index') + const app = express() + app.use('', (req, res, next) => { + req.user = user + next() + }, router) + server = app.listen(50000) }) - - afterEach(() => { - setStub.resetHistory() - }) - after(() => { + console.log('closing server') + server.close() - setStub.restore() }) - it('> works', async () => { + describe('> works', () => { + const body = { ...payload } - const getStub = sinon - .stub(Store.prototype, 'get') - .returns(Promise.resolve(JSON.stringify(body))) - const { body: respBody } = await got(`http://localhost:50000/${name}`) - expect(getStub.calledWith(name)).to.be.true - expect(respBody).to.equal(JSON.stringify(body)) - getStub.restore() - }) - - it('> get on expired returns 404', async () => { - const body = { - ...payload, - expiry: Date.now() - 1e3 * 60 - } - const getStub = sinon - .stub(Store.prototype, 'get') - .returns(Promise.resolve(JSON.stringify(body))) - - const { statusCode } = await got(`http://localhost:50000/${name}`, { - throwHttpErrors: false + beforeEach(() => { + setStub.returns(Promise.resolve()) + getStub.returns(Promise.resolve(JSON.stringify(body))) }) - expect(statusCode).to.equal(404) - expect(getStub.calledWith(name)).to.be.true - getStub.restore() - }) - - it('> get on expired with txt html header sets cookie and redirect', async () => { - - const body = { - ...payload, - expiry: Date.now() - 1e3 * 60 - } - const getStub = sinon - .stub(Store.prototype, 'get') - .returns(Promise.resolve(JSON.stringify(body))) - - const { statusCode, headers } = await got(`http://localhost:50000/${name}`, { - headers: { - 'accept': 'text/html' - }, - followRedirect: false + afterEach(() => { + setStub.resetHistory() + setStub.resetBehavior() + getStub.resetHistory() + getStub.resetBehavior() }) - expect(statusCode).to.be.greaterThan(300) - expect(statusCode).to.be.lessThan(303) - - expect(getStub.calledWith(name)).to.be.true - getStub.restore() - const c = cookie.parse(...headers['set-cookie']) - expect(!!c['iav-error']).to.be.true + it('> works', async () => { + const { body: respBody } = await got(`http://localhost:50000/${name}`) + expect(getStub.calledWith(name)).to.be.true + expect(respBody).to.equal(JSON.stringify(body)) + }) }) - describe('> set', () => { - - it('> checks if the name is available', async () => { - - const getStub = sinon - .stub(Store.prototype, 'get') - .returns(Promise.reject(new NotFoundError())) - - await got(`http://localhost:50000/${name}`, { - method: 'POST', - headers: { - 'Content-type': 'application/json' - }, - body: JSON.stringify(payload) - }) - - const [ storedName, _ ] = setStub.args[0] + describe('> expired', () => { + beforeEach(() => { + const body = { + ...payload, + expiry: Date.now() - 1e3 * 60 + } - expect(storedName).to.equal(name) - expect(getStub.called).to.be.true - expect(setStub.called).to.be.true - - getStub.restore() + getStub.returns(Promise.resolve(JSON.stringify(body))) }) - - it('> if file exist, will return 409 conflict', async () => { - - const getStub = sinon - .stub(Store.prototype, 'get') - .returns(Promise.resolve('{}')) - + it('> get on expired returns 404', async () => { + const { statusCode } = await got(`http://localhost:50000/${name}`, { - method: 'POST', - headers: { - 'Content-type': 'application/json' - }, - body: JSON.stringify(payload), throwHttpErrors: false }) - - expect(statusCode).to.equal(409) - expect(getStub.called).to.be.true - expect(setStub.called).to.be.false - - getStub.restore() + expect(statusCode).to.equal(404) + expect(getStub.calledWith(name)).to.be.true }) - - it('> if other error, will return 500', async () => { - - const getStub = sinon - .stub(Store.prototype, 'get') - .returns(Promise.reject(new Error(`other errors`))) - - const { statusCode } = await got(`http://localhost:50000/${name}`, { - method: 'POST', + it('> get on expired with txt html header sets cookie and redirect', async () => { + + const { statusCode, headers } = await got(`http://localhost:50000/${name}`, { headers: { - 'Content-type': 'application/json' + 'accept': 'text/html' }, - body: JSON.stringify(payload), - throwHttpErrors: false + followRedirect: false }) + expect(statusCode).to.be.greaterThan(300) + expect(statusCode).to.be.lessThan(303) - expect(statusCode).to.equal(500) - expect(getStub.called).to.be.true - expect(setStub.called).to.be.false - - getStub.restore() + expect(getStub.calledWith(name)).to.be.true + + const c = cookie.parse(...headers['set-cookie']) + expect(!!c['iav-error']).to.be.true }) + }) + + describe('> set', () => { + + describe('> error', () => { + describe('> entry exists', () => { + beforeEach(() => { + getStub.returns(Promise.resolve('{}')) + }) - describe('> set with unauthenticated user', () => { - let getStub - - before(() => { - getStub = sinon - .stub(Store.prototype, 'get') - .returns(Promise.reject(new NotFoundError())) + it('> returns 409 conflict', async () => { + const { statusCode } = await got(`http://localhost:50000/${name}`, { + method: 'POST', + headers: { + 'Content-type': 'application/json' + }, + body: JSON.stringify(payload), + throwHttpErrors: false + }) + + expect(statusCode).to.equal(409) + expect(getStub.called).to.be.true + expect(setStub.called).to.be.false + }) }) - after(() => { - getStub.restore() + describe('> other error', () => { + beforeEach(() => { + getStub.callsFake(async () => { + throw new Error(`other errors`) + }) + }) + it('> returns 500', async () => { + const { statusCode } = await got(`http://localhost:50000/${name}`, { + method: 'POST', + headers: { + 'Content-type': 'application/json' + }, + body: JSON.stringify(payload), + throwHttpErrors: false + }) + + expect(statusCode).to.equal(500) + expect(getStub.called).to.be.true + expect(setStub.called).to.be.false + }) }) - - it('> set with anonymous user has user undefined and expiry as defined', async () => { - + }) + + describe('> success', () => { + beforeEach(() => { + getStub.callsFake(async () => { + throw new NotFoundError() + }) + }) + it('> checks if the name is available', async () => { + debugger await got(`http://localhost:50000/${name}`, { method: 'POST', headers: { @@ -222,88 +202,88 @@ 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) - }) - }) - - describe('> set with authenticated user', () => { - - before(() => { - getStub = sinon - .stub(Store.prototype, 'get') - .returns(Promise.reject(new NotFoundError())) + expect(storedName).to.equal(name) + expect(getStub.called).to.be.true + expect(setStub.called).to.be.true }) - after(() => { - getStub.restore() - }) - - 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) + describe('> anony user', () => { + beforeEach(() => { + user = null }) + + it('> set with anonymous user has user undefined and expiry as defined', async () => { - 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 + 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('> authenticated user', () => { + beforeEach(() => { + user = { + id: 'test/1', + name: 'hello world' + } + }) + it('> userId set, expiry unset', async () => { - 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) + 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 }) - - 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.js b/deploy/saneUrl/store.js index dd3cafd14..3db348b3c 100644 --- a/deploy/saneUrl/store.js +++ b/deploy/saneUrl/store.js @@ -126,7 +126,6 @@ class Store { } async init() { - this.openIdClient = await getClient() this.keys = { [HBP_OIDC_V2_REFRESH_TOKEN_KEY]: (await this.redisUtil.asyncGet(HBP_OIDC_V2_REFRESH_TOKEN_KEY)) || HBP_V2_REFRESH_TOKEN, [HBP_OIDC_V2_ACCESS_TOKEN_KEY]: (await this.redisUtil.asyncGet(HBP_OIDC_V2_ACCESS_TOKEN_KEY)) || HBP_V2_ACCESS_TOKEN, @@ -317,7 +316,7 @@ class Store { rs.push(null) const uploadToSeafile = async () => { await this.seafileHandle.uploadFile({ - pathToFile: `/saneurl/${id}`, + filename: id, readStream: rs, }, { repoId: this.seafileRepoId, diff --git a/deploy/saneUrl/store.spec.js b/deploy/saneUrl/store.spec.js index 2f860eabf..c48446443 100644 --- a/deploy/saneUrl/store.spec.js +++ b/deploy/saneUrl/store.spec.js @@ -1,10 +1,17 @@ -const { Store } = require('./store') +const objStorateRootUrl = `http://fake.obj` +process.env['OBJ_STORAGE_ROOT_URL'] = objStorateRootUrl const sinon = require('sinon') + +const mockClient = { + refresh: sinon.stub() +} +const HbpOidcv2 = require('../auth/hbp-oidc-v2') +const OIDC = require('../auth/oidc') + +const { Store } = require('./store') const { expect } = require("chai") const nock = require('nock') -const fakeToken = `token-123-token` -const objStorateRootUrl = `http://fake.obj` const objName = `objname` const objContent = `objContent` @@ -12,73 +19,157 @@ describe('> store.js', () => { describe('> Store', () => { - let getTokenSpy, store - before(() => { + let store, + getClientStub, + jwtDecodeStub - getTokenSpy = sinon - .stub(Store.prototype, 'getToken') - .returns(Promise.resolve(fakeToken)) - - store = new Store({ objStorateRootUrl }) + before(() => { + getClientStub = sinon.stub(HbpOidcv2, 'getClient').returns(Promise.resolve(mockClient)) + jwtDecodeStub = sinon.stub(OIDC, 'jwtDecode') }) after(() => { - getTokenSpy.restore() nock.restore() + getClientStub.restore() + jwtDecodeStub.restore() + store.dispose() }) afterEach(() => { - getTokenSpy.resetHistory() + getClientStub.resetHistory() + jwtDecodeStub.resetHistory() }) - it('> spy works', async () => { - expect(getTokenSpy.called).to.be.true - - const token = await store.getToken() - expect(token).to.equal(fakeToken) - }) + describe('> get', () => { + let tryGetFromSwiftObjStub, + tryGetFromSeafileStub, + doRefreshTokensStub, + initStub, + result + before(async () => { + doRefreshTokensStub = sinon.stub(Store.prototype, 'doRefreshTokens').returns(Promise.resolve()) + tryGetFromSwiftObjStub = sinon.stub(Store.prototype, 'tryGetFromSwiftObj').returns(Promise.resolve(objContent)) + tryGetFromSeafileStub = sinon.stub(Store.prototype, 'tryGetFromSeafile').returns(Promise.resolve(objContent + objContent)) + initStub = sinon.stub(Store.prototype, 'init').returns(Promise.resolve()) + jwtDecodeStub.returns({ + exp: 1337 + }) + store = new Store() + result = await store.get(objName) + }) - it('> spy gets reset', async () => { - expect(getTokenSpy.notCalled).to.be.true - }) + after(() => { + doRefreshTokensStub.restore() + tryGetFromSwiftObjStub.restore() + tryGetFromSeafileStub.restore() + initStub.restore() + store.dispose() + }) - it('> get works', async () => { - const scope = nock(objStorateRootUrl) - .get(`/${objName}`) - .reply(200, objContent) + it('> first tries to get from tryGetFromSwiftObj', () => { + expect(tryGetFromSwiftObjStub.called).to.be.true + }) - const content = await store.get(objName) - expect(content).to.equal(objContent) - expect(scope.isDone()).to.be.true + it('> does not try to fetch from tryGetFromSeafile', () => { + expect(tryGetFromSeafileStub.called).to.be.false + }) + it('> returns value is as expected', () => { + expect(result).to.equal(objContent) + }) }) - it('> set works', async () => { + describe('> set', () => { + let initStub, + fakeSeafileHandle = { + uploadFile: sinon.stub() + }, + fakeRepoId, + refreshSeafileHandleStub + + describe('> if no need to refresh', () => { + + before(async () => { + initStub = sinon.stub(Store.prototype, 'init').callsFake(async function(){ + this.seafileHandle = fakeSeafileHandle + this.seafileRepoId = fakeRepoId + return this.seafileHandle + }) + + store = new Store() + fakeSeafileHandle.uploadFile.returns(Promise.resolve()) + refreshSeafileHandleStub = sinon.stub(Store.prototype, 'refreshSeafileHandle').returns(Promise.resolve()) + await store.set('key', 'value') + }) + + after(() => { + fakeSeafileHandle.uploadFile.resetHistory() + fakeSeafileHandle.uploadFile.resetBehavior() + refreshSeafileHandleStub.restore() + initStub.restore() + store.dispose() + }) + + it('> calls this.seafileHandle.uploadFile', () => { + expect(fakeSeafileHandle.uploadFile.called).to.be.true + }) + it('> calls this.seafileHandle.uploadFile only once', () => { + expect(fakeSeafileHandle.uploadFile.calledOnce).to.be.true + }) - const scope = nock(objStorateRootUrl) - .put(`/${objName}`) - .reply(200) + it('> does not call refreshSeafileHandle', () => { + expect(refreshSeafileHandleStub.called).to.be.false + }) - scope.on('request', (req, int, body) => { - expect(body).to.equal(objContent) + it('> calls this.seafileHandle.uploadFile with the correct arguments', done => { + const arg = fakeSeafileHandle.uploadFile.args + const [ arg1, arg2 ] = arg[0] + expect(arg2.repoId).to.equal(fakeRepoId, 'expecting repoId to match') + expect(arg2.dir).to.equal('/saneurl/', 'expecting path to be saneurl') + expect(arg1.filename).to.equal(`key`, 'not so important... expecting filename to match') + + let output = '' + arg1.readStream.on('data', chunk => { + output += chunk + }) + arg1.readStream.on('end', () => { + arg1.readStream.destroy() + expect(output).to.equal('value', 'expecitng value of uptload to match') + done() + }) + }) }) - await store.set(objName, objContent) - expect(scope.isDone()).to.be.true - }) + describe('> if need to refresh', () => { + + before(async () => { + initStub = sinon.stub(Store.prototype, 'init').callsFake(async function(){ + this.seafileHandle = fakeSeafileHandle + this.seafileRepoId = fakeRepoId + return this.seafileHandle + }) + + store = new Store() + fakeSeafileHandle.uploadFile.onCall(0).returns(Promise.reject()) + fakeSeafileHandle.uploadFile.onCall(1).returns(Promise.resolve()) + refreshSeafileHandleStub = sinon.stub(Store.prototype, 'refreshSeafileHandle').returns(Promise.resolve()) + await store.set('key', 'value') + }) + + after(() => { + fakeSeafileHandle.uploadFile.resetHistory() + fakeSeafileHandle.uploadFile.resetBehavior() + refreshSeafileHandleStub.restore() + initStub.restore() + store.dispose() + }) + it('> calls this.seafileHandle.uploadFile twice', () => { + expect(fakeSeafileHandle.uploadFile.calledTwice).to.be.true + }) - it('> set retries if at first fails', async () => { - let index = 0 - const scope = nock(objStorateRootUrl) - .put(`/${objName}`) - .twice() - .reply((_uri, _reqBody, cb) => { - cb(null, [ index % 2 === 0 ? 401 : 200 ]) - index ++ + it('> calls refreshSeafileStub', () => { + expect(refreshSeafileHandleStub.called).to.be.true }) - - await store.set(objName, objContent) - expect(scope.isDone()).to.be.true - expect(getTokenSpy.called).to.be.true + }) }) }) }) -- GitLab