From 2ee4fd21e4db969c6c3886af8c770b4c4af0b739 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 5 May 2021 17:49:34 +0100 Subject: [PATCH] Updating testing system across the board after playing around with it, having the worker tests run when top level test is ran, fixing environment in worker when testing, removing the use of redis (replacing with ioredis-mock) when in test. --- packages/auth/package.json | 3 + packages/auth/src/environment.js | 9 +++ packages/auth/src/redis/index.js | 18 ++++- packages/auth/src/redis/utils.js | 8 +- packages/auth/yarn.lock | 74 ++++++++++++++++++- packages/server/src/api/index.js | 11 ++- .../server/src/api/routes/tests/row.spec.js | 6 +- packages/server/src/automations/triggers.js | 3 +- packages/worker/package.json | 9 ++- packages/worker/scripts/jestSetup.js | 5 ++ .../worker/src/api/controllers/admin/email.js | 11 ++- .../worker/src/api/controllers/admin/users.js | 2 +- .../worker/src/api/routes/tests/auth.spec.js | 49 ++++++++++++ .../worker/src/api/routes/tests/email.spec.js | 7 +- .../src/api/routes/tests/realEmail.spec.js | 4 +- .../worker/src/api/routes/tests/users.spec.js | 52 +++++++++++++ .../tests/utilities/TestConfiguration.js | 26 +++++++ .../src/api/routes/tests/utilities/index.js | 15 +++- packages/worker/src/environment.js | 2 +- packages/worker/src/index.js | 4 - packages/worker/src/utilities/templates.js | 2 +- packages/worker/yarn.lock | 67 ++++++++++++++++- 22 files changed, 353 insertions(+), 34 deletions(-) create mode 100644 packages/worker/scripts/jestSetup.js create mode 100644 packages/worker/src/api/routes/tests/auth.spec.js create mode 100644 packages/worker/src/api/routes/tests/users.spec.js diff --git a/packages/auth/package.json b/packages/auth/package.json index 72ea0bc3ba..dfbb63c431 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -15,5 +15,8 @@ "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "uuid": "^8.3.2" + }, + "devDependencies": { + "ioredis-mock": "^5.5.5" } } diff --git a/packages/auth/src/environment.js b/packages/auth/src/environment.js index 4f195337b6..5b0b141019 100644 --- a/packages/auth/src/environment.js +++ b/packages/auth/src/environment.js @@ -1,7 +1,16 @@ +function isTest() { + return ( + process.env.NODE_ENV === "jest" || + process.env.NODE_ENV === "cypress" || + process.env.JEST_WORKER_ID != null + ) +} + module.exports = { JWT_SECRET: process.env.JWT_SECRET, COUCH_DB_URL: process.env.COUCH_DB_URL, SALT_ROUNDS: process.env.SALT_ROUNDS, REDIS_URL: process.env.REDIS_URL, REDIS_PASSWORD: process.env.REDIS_PASSWORD, + isTest, } diff --git a/packages/auth/src/redis/index.js b/packages/auth/src/redis/index.js index 0cb92c4622..dc670c07fb 100644 --- a/packages/auth/src/redis/index.js +++ b/packages/auth/src/redis/index.js @@ -1,9 +1,12 @@ -const Redis = require("ioredis") +const env = require("../environment") +// ioredis mock is all in memory +const Redis = env.isTest() ? require("ioredis-mock") : require("ioredis") const { addDbPrefix, removeDbPrefix, getRedisOptions } = require("./utils") const CLUSTERED = false -let CLIENT +// for testing just generate the client once +let CLIENT = env.isTest() ? new Redis(getRedisOptions()) : null /** * Inits the system, will error if unable to connect to redis cluster (may take up to 10 seconds) otherwise @@ -12,6 +15,10 @@ let CLIENT */ function init() { return new Promise((resolve, reject) => { + // testing uses a single in memory client + if (env.isTest()) { + return resolve(CLIENT) + } // if a connection existed, close it and re-create it if (CLIENT) { CLIENT.disconnect() @@ -108,7 +115,12 @@ class RedisWrapper { if (response != null && response.key) { response.key = key } - return JSON.parse(response) + // if its not an object just return the response + try { + return JSON.parse(response) + } catch (err) { + return response + } } async store(key, value, expirySeconds = null) { diff --git a/packages/auth/src/redis/utils.js b/packages/auth/src/redis/utils.js index a29f786c41..bd4a762e1d 100644 --- a/packages/auth/src/redis/utils.js +++ b/packages/auth/src/redis/utils.js @@ -3,6 +3,8 @@ const env = require("../environment") const SLOT_REFRESH_MS = 2000 const CONNECT_TIMEOUT_MS = 10000 const SEPARATOR = "-" +const REDIS_URL = !env.REDIS_URL ? "localhost:6379" : env.REDIS_URL +const REDIS_PASSWORD = !env.REDIS_PASSWORD ? "budibase" : env.REDIS_PASSWORD exports.Databases = { PW_RESETS: "pwReset", @@ -10,20 +12,20 @@ exports.Databases = { } exports.getRedisOptions = (clustered = false) => { - const [host, port] = env.REDIS_URL.split(":") + const [host, port] = REDIS_URL.split(":") const opts = { connectTimeout: CONNECT_TIMEOUT_MS, } if (clustered) { opts.redisOptions = {} opts.redisOptions.tls = {} - opts.redisOptions.password = env.REDIS_PASSWORD + opts.redisOptions.password = REDIS_PASSWORD opts.slotsRefreshTimeout = SLOT_REFRESH_MS opts.dnsLookup = (address, callback) => callback(null, address) } else { opts.host = host opts.port = port - opts.password = env.REDIS_PASSWORD + opts.password = REDIS_PASSWORD } return { opts, host, port } } diff --git a/packages/auth/yarn.lock b/packages/auth/yarn.lock index 0dbdaadf8d..a3c95bf42c 100644 --- a/packages/auth/yarn.lock +++ b/packages/auth/yarn.lock @@ -46,6 +46,11 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + base64url@3.x.x: version "3.0.1" resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" @@ -63,6 +68,14 @@ bcryptjs@^2.4.3: resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" @@ -85,6 +98,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -154,6 +172,20 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fengari-interop@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/fengari-interop/-/fengari-interop-0.1.2.tgz#f7731dcdd2ff4449073fb7ac3c451a8841ce1e87" + integrity sha512-8iTvaByZVoi+lQJhHH9vC+c/Yaok9CwOqNQZN6JrVpjmWwW4dDkeblBXhnHC+BoI6eF4Cy5NKW3z6ICEjvgywQ== + +fengari@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/fengari/-/fengari-0.1.4.tgz#72416693cd9e43bd7d809d7829ddc0578b78b0bb" + integrity sha512-6ujqUuiIYmcgkGz8MGAdERU57EIluGGPSUgGPTsco657EHa+srq0S3/YUl/r9kx1+D+d4rGfYObd+m8K22gB1g== + dependencies: + readline-sync "^1.4.9" + sprintf-js "^1.1.1" + tmp "^0.0.33" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -233,6 +265,17 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +ioredis-mock@^5.5.5: + version "5.5.5" + resolved "https://registry.yarnpkg.com/ioredis-mock/-/ioredis-mock-5.5.5.tgz#dec9fedd238c6ab9f56c026fc366533144f8a256" + integrity sha512-7SxCAwNtDLC8IFDptqIhOC7ajp3fciVtCrXOEOkpyjPboAGRQkJbnpNPy1NYORoWi+0/iOtUPUQckSKtSQj4DA== + dependencies: + fengari "^0.1.4" + fengari-interop "^0.1.2" + lodash "^4.17.21" + minimatch "^3.0.4" + standard-as-callback "^2.1.0" + ioredis@^4.27.1: version "4.27.1" resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.27.1.tgz#4ef947b455a1b995baa4b0d7e2c4e4f75f746421" @@ -379,7 +422,7 @@ lodash.once@^4.0.0: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= -lodash@^4.14.0: +lodash@^4.14.0, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -401,6 +444,13 @@ mime@^1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -426,6 +476,11 @@ oauth@0.9.x: resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE= +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + p-map@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" @@ -534,6 +589,11 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== +readline-sync@^1.4.9: + version "1.4.10" + resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b" + integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw== + redis-commands@1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89" @@ -592,6 +652,11 @@ semver@^5.6.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== +sprintf-js@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" + integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== + sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -617,6 +682,13 @@ string-template@~1.0.0: resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96" integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y= +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js index 5d16134eb4..84ea0aaf20 100644 --- a/packages/server/src/api/index.js +++ b/packages/server/src/api/index.js @@ -5,13 +5,16 @@ const compress = require("koa-compress") const zlib = require("zlib") const { mainRoutes, staticRoutes } = require("./routes") const pkg = require("../../package.json") -const bullboard = require("bull-board") -const expressApp = require("express")() +const env = require("../environment") -expressApp.use("/bulladmin", bullboard.router) +if (!env.isTest()) { + const bullboard = require("bull-board") + const expressApp = require("express")() + + expressApp.use("/bulladmin", bullboard.router) +} const router = new Router() -const env = require("../environment") router .use( diff --git a/packages/server/src/api/routes/tests/row.spec.js b/packages/server/src/api/routes/tests/row.spec.js index 6a1c309c39..fb8ff96efa 100644 --- a/packages/server/src/api/routes/tests/row.spec.js +++ b/packages/server/src/api/routes/tests/row.spec.js @@ -402,14 +402,16 @@ describe("/rows", () => { name: "test", description: "test", attachment: [{ - url: "/test/thing", + key: `/assets/${config.getAppId()}/attachment/test/thing.csv`, }], tableId: table._id, }) // the environment needs configured for this await setup.switchToSelfHosted(async () => { const enriched = await outputProcessing(config.getAppId(), table, [row]) - expect(enriched[0].attachment[0].url).toBe(`/app-assets/assets/${config.getAppId()}/test/thing`) + expect(enriched[0].attachment[0].url).toBe( + `/prod-budi-app-assets/assets/${config.getAppId()}/attachment/test/thing.csv` + ) }) }) }) diff --git a/packages/server/src/automations/triggers.js b/packages/server/src/automations/triggers.js index 77eb32377a..f303856779 100644 --- a/packages/server/src/automations/triggers.js +++ b/packages/server/src/automations/triggers.js @@ -1,6 +1,7 @@ const CouchDB = require("../db") const emitter = require("../events/index") -const Queue = require("bull") +const env = require("../environment") +const Queue = env.isTest() ? require("../utilities/queue/inMemoryQueue") : require("bull") const { setQueues, BullAdapter } = require("bull-board") const { getAutomationParams } = require("../db/utils") const { coerce } = require("../utilities/rowProcessor") diff --git a/packages/worker/package.json b/packages/worker/package.json index e8db71fc46..1e478f8d5d 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -14,7 +14,8 @@ "scripts": { "run:docker": "node src/index.js", "dev:stack:init": "node ./scripts/dev/manage.js init", - "dev:builder": "npm run dev:stack:init && nodemon src/index.js" + "dev:builder": "npm run dev:stack:init && nodemon src/index.js", + "test": "jest --runInBand" }, "author": "Budibase", "license": "AGPL-3.0-or-later", @@ -50,5 +51,11 @@ "nodemon": "^2.0.7", "pouchdb-adapter-memory": "^7.2.2", "supertest": "^6.1.3" + }, + "jest": { + "testEnvironment": "node", + "setupFiles": [ + "./scripts/jestSetup.js" + ] } } diff --git a/packages/worker/scripts/jestSetup.js b/packages/worker/scripts/jestSetup.js new file mode 100644 index 0000000000..4e3b05b8f9 --- /dev/null +++ b/packages/worker/scripts/jestSetup.js @@ -0,0 +1,5 @@ +const env = require("../src/environment") + +env._set("NODE_ENV", "jest") +env._set("JWT_SECRET", "test-jwtsecret") +env._set("LOG_LEVEL", "silent") \ No newline at end of file diff --git a/packages/worker/src/api/controllers/admin/email.js b/packages/worker/src/api/controllers/admin/email.js index 0a29468133..04e85e7f44 100644 --- a/packages/worker/src/api/controllers/admin/email.js +++ b/packages/worker/src/api/controllers/admin/email.js @@ -1,8 +1,17 @@ const { sendEmail } = require("../../../utilities/email") +const CouchDB = require("../../../db") +const authPkg = require("@budibase/auth") + +const GLOBAL_DB = authPkg.StaticDatabases.GLOBAL.name exports.sendEmail = async ctx => { const { groupId, email, userId, purpose } = ctx.request.body - const response = await sendEmail(email, purpose, { groupId, userId }) + let user + if (userId) { + const db = new CouchDB(GLOBAL_DB) + user = await db.get(userId) + } + const response = await sendEmail(email, purpose, { groupId, user }) ctx.body = { ...response, message: `Email sent to ${email}.`, diff --git a/packages/worker/src/api/controllers/admin/users.js b/packages/worker/src/api/controllers/admin/users.js index cd307bbc6b..075b82ec6c 100644 --- a/packages/worker/src/api/controllers/admin/users.js +++ b/packages/worker/src/api/controllers/admin/users.js @@ -126,7 +126,7 @@ exports.find = async ctx => { exports.invite = async ctx => { const { email } = ctx.request.body - const existing = await getGlobalUserByEmail(FIRST_USER_EMAIL) + const existing = await getGlobalUserByEmail(email) if (existing) { ctx.throw(400, "Email address already in use.") } diff --git a/packages/worker/src/api/routes/tests/auth.spec.js b/packages/worker/src/api/routes/tests/auth.spec.js new file mode 100644 index 0000000000..ea7ab829a3 --- /dev/null +++ b/packages/worker/src/api/routes/tests/auth.spec.js @@ -0,0 +1,49 @@ +const setup = require("./utilities") + +jest.mock("nodemailer") +const sendMailMock = setup.emailMock() + +describe("/api/admin/auth", () => { + let request = setup.getRequest() + let config = setup.getConfig() + let code + + beforeAll(async () => { + await config.init() + }) + + afterAll(setup.afterAll) + + it("should be able to generate password reset email", async () => { + // initially configure settings + await config.saveSmtpConfig() + await config.saveSettingsConfig() + await config.createUser("test@test.com") + const res = await request + .post(`/api/admin/auth/reset`) + .send({ + email: "test@test.com", + }) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body).toEqual({ message: "Please check your email for a reset link." }) + expect(sendMailMock).toHaveBeenCalled() + const emailCall = sendMailMock.mock.calls[0][0] + // after this URL there should be a code + const parts = emailCall.html.split("http://localhost:10000/reset/") + code = parts[1].split("\"")[0] + expect(code).toBeDefined() + }) + + it("should allow resetting user password with code", async () => { + const res = await request + .post(`/api/admin/auth/reset/update`) + .send({ + password: "newpassword", + resetCode: code, + }) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body).toEqual({ message: "password reset successfully." }) + }) +}) \ No newline at end of file diff --git a/packages/worker/src/api/routes/tests/email.spec.js b/packages/worker/src/api/routes/tests/email.spec.js index 797b0326ed..11bdb3fb1f 100644 --- a/packages/worker/src/api/routes/tests/email.spec.js +++ b/packages/worker/src/api/routes/tests/email.spec.js @@ -2,13 +2,8 @@ const setup = require("./utilities") const { EmailTemplatePurpose } = require("../../../constants") // mock the email system -const sendMailMock = jest.fn() jest.mock("nodemailer") -const nodemailer = require("nodemailer") -nodemailer.createTransport.mockReturnValue({ - sendMail: sendMailMock, - verify: jest.fn() -}) +const sendMailMock = setup.emailMock() describe("/api/admin/email", () => { let request = setup.getRequest() diff --git a/packages/worker/src/api/routes/tests/realEmail.spec.js b/packages/worker/src/api/routes/tests/realEmail.spec.js index c96b8ab561..f593b2cc09 100644 --- a/packages/worker/src/api/routes/tests/realEmail.spec.js +++ b/packages/worker/src/api/routes/tests/realEmail.spec.js @@ -16,11 +16,13 @@ describe("/api/admin/email", () => { async function sendRealEmail(purpose) { await config.saveEtherealSmtpConfig() await config.saveSettingsConfig() + const user = await config.getUser("test@test.com") const res = await request .post(`/api/admin/email/send`) .send({ email: "test@test.com", purpose, + userId: user._id, }) .set(config.defaultHeaders()) .expect("Content-Type", /json/) @@ -55,6 +57,6 @@ describe("/api/admin/email", () => { }) it("should be able to send a password recovery email", async () => { - const res = await sendRealEmail(EmailTemplatePurpose.PASSWORD_RECOVERY) + await sendRealEmail(EmailTemplatePurpose.PASSWORD_RECOVERY) }) }) \ No newline at end of file diff --git a/packages/worker/src/api/routes/tests/users.spec.js b/packages/worker/src/api/routes/tests/users.spec.js new file mode 100644 index 0000000000..135d29f0f5 --- /dev/null +++ b/packages/worker/src/api/routes/tests/users.spec.js @@ -0,0 +1,52 @@ +const setup = require("./utilities") + +jest.mock("nodemailer") +const sendMailMock = setup.emailMock() + +describe("/api/admin/users", () => { + let request = setup.getRequest() + let config = setup.getConfig() + let code + + beforeAll(async () => { + await config.init() + }) + + afterAll(setup.afterAll) + + it("should be able to generate an invitation", async () => { + // initially configure settings + await config.saveSmtpConfig() + await config.saveSettingsConfig() + const res = await request + .post(`/api/admin/users/invite`) + .send({ + email: "invite@test.com", + }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body).toEqual({ message: "Invitation has been sent." }) + expect(sendMailMock).toHaveBeenCalled() + const emailCall = sendMailMock.mock.calls[0][0] + // after this URL there should be a code + const parts = emailCall.html.split("http://localhost:10000/invite/") + code = parts[1].split("\"")[0] + expect(code).toBeDefined() + }) + + it("should be able to create new user from invite", async () => { + const res = await request + .post(`/api/admin/users/invite/accept`) + .send({ + password: "newpassword", + inviteCode: code, + }) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body._id).toBeDefined() + const user = await config.getUser("invite@test.com") + expect(user).toBeDefined() + expect(user._id).toEqual(res.body._id) + }) +}) \ No newline at end of file diff --git a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js index 0f97b50c82..d6ef6744f3 100644 --- a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js +++ b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js @@ -4,6 +4,7 @@ const supertest = require("supertest") const { jwt } = require("@budibase/auth").auth const { Cookies } = require("@budibase/auth").constants const { Configs, LOGO_URL } = require("../../../../constants") +const { getGlobalUserByEmail } = require("@budibase/auth").utils class TestConfiguration { constructor(openServer = true) { @@ -53,6 +54,12 @@ class TestConfiguration { ) } + async end() { + if (this.server) { + await this.server.close() + } + } + defaultHeaders() { const user = { _id: "us_uuid1", @@ -65,6 +72,25 @@ class TestConfiguration { } } + async getUser(email) { + return getGlobalUserByEmail(email) + } + + async createUser(email = "test@test.com", password = "test") { + const user = await this.getUser(email) + if (user) { + return user + } + await this._req( + { + email, + password, + }, + null, + controllers.users.save + ) + } + async deleteConfig(type) { try { const cfg = await this._req( diff --git a/packages/worker/src/api/routes/tests/utilities/index.js b/packages/worker/src/api/routes/tests/utilities/index.js index d63b837f6d..90f95de49b 100644 --- a/packages/worker/src/api/routes/tests/utilities/index.js +++ b/packages/worker/src/api/routes/tests/utilities/index.js @@ -7,9 +7,9 @@ exports.beforeAll = () => { request = config.getRequest() } -exports.afterAll = () => { +exports.afterAll = async () => { if (config) { - config.end() + await config.end() } request = null config = null @@ -28,3 +28,14 @@ exports.getConfig = () => { } return config } + +exports.emailMock = () => { + // mock the email system + const sendMailMock = jest.fn() + const nodemailer = require("nodemailer") + nodemailer.createTransport.mockReturnValue({ + sendMail: sendMailMock, + verify: jest.fn() + }) + return sendMailMock +} diff --git a/packages/worker/src/environment.js b/packages/worker/src/environment.js index 0adfe2afae..04c010ce16 100644 --- a/packages/worker/src/environment.js +++ b/packages/worker/src/environment.js @@ -11,7 +11,7 @@ function isTest() { } let LOADED = false -if (!LOADED && isDev()) { +if (!LOADED && isDev() && !isTest()) { require("dotenv").config() LOADED = true } diff --git a/packages/worker/src/index.js b/packages/worker/src/index.js index 8b67181fcc..f94846b370 100644 --- a/packages/worker/src/index.js +++ b/packages/worker/src/index.js @@ -12,10 +12,6 @@ const api = require("./api") const app = new Koa() -if (!env.SELF_HOSTED) { - throw "Currently this service only supports use in self hosting" -} - // set up top level koa middleware app.use(koaBody({ multipart: true })) diff --git a/packages/worker/src/utilities/templates.js b/packages/worker/src/utilities/templates.js index 4a7ca6fc7c..e4d6b7d396 100644 --- a/packages/worker/src/utilities/templates.js +++ b/packages/worker/src/utilities/templates.js @@ -45,7 +45,7 @@ exports.getSettingsTemplateContext = async (purpose, code = null) => { case EmailTemplatePurpose.INVITATION: context[TemplateBindings.INVITE_CODE] = code context[TemplateBindings.REGISTRATION_URL] = checkSlashesInUrl( - `${URL}/registration/${code}` + `${URL}/invite/${code}` ) break } diff --git a/packages/worker/yarn.lock b/packages/worker/yarn.lock index 150a8685f1..ac60b6c188 100644 --- a/packages/worker/yarn.lock +++ b/packages/worker/yarn.lock @@ -287,7 +287,7 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/auth@0.0.1": +"@budibase/auth@^0.18.6": version "0.18.6" resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.18.6.tgz#d893005962afd9425f10e2ac8d1d495047d0d44e" integrity sha512-pdyqR8G240lToMe2OZNpw2YzuRwOlOT+cAfVHPMBxJJKF0VvZ0K500NoSUINEQPr4IfWpPSu6CQhq+ROf4pMXA== @@ -2505,6 +2505,20 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fengari-interop@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/fengari-interop/-/fengari-interop-0.1.2.tgz#f7731dcdd2ff4449073fb7ac3c451a8841ce1e87" + integrity sha512-8iTvaByZVoi+lQJhHH9vC+c/Yaok9CwOqNQZN6JrVpjmWwW4dDkeblBXhnHC+BoI6eF4Cy5NKW3z6ICEjvgywQ== + +fengari@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/fengari/-/fengari-0.1.4.tgz#72416693cd9e43bd7d809d7829ddc0578b78b0bb" + integrity sha512-6ujqUuiIYmcgkGz8MGAdERU57EIluGGPSUgGPTsco657EHa+srq0S3/YUl/r9kx1+D+d4rGfYObd+m8K22gB1g== + dependencies: + readline-sync "^1.4.9" + sprintf-js "^1.1.1" + tmp "^0.0.33" + fetch-cookie@0.10.1: version "0.10.1" resolved "https://registry.yarnpkg.com/fetch-cookie/-/fetch-cookie-0.10.1.tgz#5ea88f3d36950543c87997c27ae2aeafb4b5c4d4" @@ -3130,6 +3144,17 @@ inline-process-browser@^1.0.0: falafel "^1.0.1" through2 "^0.6.5" +ioredis-mock@^5.5.5: + version "5.5.5" + resolved "https://registry.yarnpkg.com/ioredis-mock/-/ioredis-mock-5.5.5.tgz#dec9fedd238c6ab9f56c026fc366533144f8a256" + integrity sha512-7SxCAwNtDLC8IFDptqIhOC7ajp3fciVtCrXOEOkpyjPboAGRQkJbnpNPy1NYORoWi+0/iOtUPUQckSKtSQj4DA== + dependencies: + fengari "^0.1.4" + fengari-interop "^0.1.2" + lodash "^4.17.21" + minimatch "^3.0.4" + standard-as-callback "^2.1.0" + ioredis@^4.27.1: version "4.27.1" resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.27.1.tgz#4ef947b455a1b995baa4b0d7e2c4e4f75f746421" @@ -3146,6 +3171,22 @@ ioredis@^4.27.1: redis-parser "^3.0.0" standard-as-callback "^2.1.0" +ioredis@^4.27.2: + version "4.27.2" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.27.2.tgz#6a79bca05164482da796f8fa010bccefd3bf4811" + integrity sha512-7OpYymIthonkC2Jne5uGWXswdhlua1S1rWGAERaotn0hGJWTSURvxdHA9G6wNbT/qKCloCja/FHsfKXW8lpTmg== + dependencies: + cluster-key-slot "^1.1.0" + debug "^4.3.1" + denque "^1.1.0" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + p-map "^2.1.0" + redis-commands "1.7.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -4403,7 +4444,7 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "^3.0.0" -lodash@^4.14.0, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.7.0: +lodash@^4.14.0, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -4894,6 +4935,11 @@ optionator@^0.8.1: type-check "~0.3.2" word-wrap "~1.2.3" +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + p-cancelable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" @@ -5521,6 +5567,11 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" +readline-sync@^1.4.9: + version "1.4.10" + resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b" + integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw== + recast@^0.10.1: version "0.10.43" resolved "https://registry.yarnpkg.com/recast/-/recast-0.10.43.tgz#b95d50f6d60761a5f6252e15d80678168491ce7f" @@ -6045,6 +6096,11 @@ split2@^3.1.1: dependencies: readable-stream "^3.0.0" +sprintf-js@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" + integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -6327,6 +6383,13 @@ tiny-queue@^0.2.0: resolved "https://registry.yarnpkg.com/tiny-queue/-/tiny-queue-0.2.1.tgz#25a67f2c6e253b2ca941977b5ef7442ef97a6046" integrity sha1-JaZ/LG4lOyypQZd7XvdELvl6YEY= +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"