From 96f036b720db46e204a4ef31a2a3020d46a96756 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 72ea0bc3b..dfbb63c43 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 4f195337b..5b0b14101 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 0cb92c462..dc670c07f 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 a29f786c4..bd4a762e1 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 0dbdaadf8..a3c95bf42 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 5d16134eb..84ea0aaf2 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") + +if (!env.isTest()) { + const bullboard = require("bull-board") + const expressApp = require("express")() -expressApp.use("/bulladmin", bullboard.router) + 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 6a1c309c3..fb8ff96ef 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 77eb32377..f30385677 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 e8db71fc4..1e478f8d5 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 000000000..4e3b05b8f --- /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 0a2946813..04e85e7f4 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 cd307bbc6..075b82ec6 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 000000000..ea7ab829a --- /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 797b0326e..11bdb3fb1 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 c96b8ab56..f593b2cc0 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 000000000..135d29f0f --- /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 0f97b50c8..d6ef6744f 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 d63b837f6..90f95de49 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 0adfe2afa..04c010ce1 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 8b67181fc..f94846b37 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 4a7ca6fc7..e4d6b7d39 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 150a8685f..ac60b6c18 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"