diff --git a/sites/backend/nodemon.json b/sites/backend/nodemon.json index 04f0edbc23c..4c9390fe930 100644 --- a/sites/backend/nodemon.json +++ b/sites/backend/nodemon.json @@ -1,4 +1,5 @@ { "verbose": true, - "ignore": ["tests/**.test.mjs"] + "ignore": ["tests/**.test.mjs"], + "watch": ["src/**"] } diff --git a/sites/backend/prisma/schema.prisma b/sites/backend/prisma/schema.prisma index 17d1ca62998..a5bc71feab7 100644 --- a/sites/backend/prisma/schema.prisma +++ b/sites/backend/prisma/schema.prisma @@ -48,6 +48,7 @@ model User { email String github String @default("") ihash String + img String @default("https://freesewing.org/avatar.svg") initial String imperial Boolean @default(false) language String @default("en") diff --git a/sites/backend/prisma/schema.sqlite b/sites/backend/prisma/schema.sqlite index 103a26d7ed8..27145196bb6 100644 Binary files a/sites/backend/prisma/schema.sqlite and b/sites/backend/prisma/schema.sqlite differ diff --git a/sites/backend/src/models/user.mjs b/sites/backend/src/models/user.mjs index a97822f6819..705e6975e95 100644 --- a/sites/backend/src/models/user.mjs +++ b/sites/backend/src/models/user.mjs @@ -306,6 +306,7 @@ UserModel.prototype.safeUpdate = async function (data) { process.exit() return this.setResponse(500, 'updateUserFailed') } + await this.reveal() return this.setResponse(200) } @@ -331,8 +332,6 @@ UserModel.prototype.unsafeUpdate = async function (body) { if ([true, false].includes(body.newsletter)) data.newsletter = body.newsletter // Password if (typeof body.password === 'string') data.password = body.password // Will be cloaked below - // Patron - if ([0, 2, 4, 8].includes(body.patron)) data.patron = body.patron // Username if (typeof body.username === 'string') { const available = await this.isLusernameAvailable(body.username) @@ -410,11 +409,15 @@ UserModel.prototype.unsafeUpdate = async function (body) { UserModel.prototype.asAccount = function () { return { id: this.record.id, + bio: this.clear.bio, consent: this.record.consent, createdAt: this.record.createdAt, data: this.clear.data, email: this.clear.email, + github: this.clear.github, + imperial: this.record.imperial, initial: this.clear.initial, + language: this.record.language, lastLogin: this.record.lastLogin, newsletter: this.record.newsletter, patron: this.record.patron, diff --git a/sites/backend/tests/account.mjs b/sites/backend/tests/account.mjs index a47d1812988..375aa44f470 100644 --- a/sites/backend/tests/account.mjs +++ b/sites/backend/tests/account.mjs @@ -1,6 +1,4 @@ -export const accountTests = async (config, store, chai) => { - const expect = chai.expect - +export const accountTests = async (chai, config, expect, store) => { /* consent Int @default(0) data String @default("{}") diff --git a/sites/backend/tests/apikey.mjs b/sites/backend/tests/apikey.mjs index 9fd098d9ebb..0c63a3ca0fd 100644 --- a/sites/backend/tests/apikey.mjs +++ b/sites/backend/tests/apikey.mjs @@ -1,6 +1,4 @@ -export const apikeyTests = async (config, store, chai) => { - const expect = chai.expect - +export const apikeyTests = async (chai, config, expect, store) => { describe(`${store.icon('key')} API Key create/read/delete`, () => { step(`${store.icon('key', 'jwt')} Create API Key (jwt)`, (done) => { chai diff --git a/sites/backend/tests/index.mjs b/sites/backend/tests/index.mjs index 034fcab39f2..a7ec91441ee 100644 --- a/sites/backend/tests/index.mjs +++ b/sites/backend/tests/index.mjs @@ -1,41 +1,16 @@ -import dotenv from 'dotenv' -import chai from 'chai' -import http from 'chai-http' -import { verifyConfig } from '../src/config.mjs' -import { randomString } from '../src/utils/crypto.mjs' import { userTests } from './user.mjs' import { accountTests } from './account.mjs' import { apikeyTests } from './apikey.mjs' import { setup } from './shared.mjs' -dotenv.config() - -const config = verifyConfig() -const expect = chai.expect -chai.use(http) - -// Account data -const store = { - account: { - email: `test_${randomString()}@${config.tests.domain}`, - language: 'en', - password: randomString(), - }, - icons: { - user: '🧑 ', - jwt: '🎫 ', - key: '🎟️ ', - }, -} -store.icon = (icon1, icon2 = false) => store.icons[icon1] + (icon2 ? store.icons[icon2] : '') - -// Run tests -const runTests = async (config, store, chai) => { - await setup(config, store, chai) - await userTests(config, store, chai) - await apikeyTests(config, store, chai) - //await accountTests(config, store, chai) +const runTests = async (...params) => { + await userTests(...params) + await apikeyTests(...params) + await accountTests(...params) } -// Do the work -runTests(config, store, chai) +// Load initial data required for tests +const { chai, config, expect, store } = await setup() + +// Note run the tests using this data +runTests(chai, config, expect, store) diff --git a/sites/backend/tests/shared.mjs b/sites/backend/tests/shared.mjs index ea40ce6c81f..ee1de9b8795 100644 --- a/sites/backend/tests/shared.mjs +++ b/sites/backend/tests/shared.mjs @@ -1,94 +1,84 @@ -export const setup = async function (config, store, chai) { - const expect = chai.expect - const icon = '🚀' +import dotenv from 'dotenv' +import axios from 'axios' +import chai from 'chai' +import http from 'chai-http' +import { verifyConfig } from '../src/config.mjs' +import { randomString } from '../src/utils/crypto.mjs' - // Shared state - describe(`${icon} Initial setup of user accounts`, async function () { - step(`${icon} Should signup new user ${store.account.email}`, (done) => { - chai - .request(config.api) - .post('/signup') - .send({ - email: store.account.email, - language: store.account.language, - unittest: true, - }) - .end((err, res) => { - expect(res.status).to.equal(201) - expect(res.type).to.equal('application/json') - expect(res.charset).to.equal('utf-8') - expect(res.body.result).to.equal(`success`) - expect(res.body.email).to.equal(store.account.email) - store.account.confirmation = res.body.confirmation - done() - }) - }) +dotenv.config() - step(`${icon} Should confirm new user (${store.account.email})`, (done) => { - chai - .request(config.api) - .post(`/confirm/signup/${store.account.confirmation}`) - .send({ consent: 1 }) - .end((err, res) => { - expect(res.status).to.equal(200) - expect(res.type).to.equal('application/json') - expect(res.charset).to.equal('utf-8') - expect(res.body.result).to.equal(`success`) - expect(typeof res.body.token).to.equal(`string`) - expect(typeof res.body.account.id).to.equal(`number`) - store.account.token = res.body.token - store.account.username = res.body.account.username - store.account.userid = res.body.account.id - done() - }) - }) +const config = verifyConfig() +const expect = chai.expect +chai.use(http) - step(`${icon} Should create API Key`, (done) => { - chai - .request(config.api) - .post('/apikey/jwt') - .set('Authorization', 'Bearer ' + store.account.token) - .send({ - name: 'Test API key', - level: 4, - expiresIn: 60, - }) - .end((err, res) => { - expect(res.status).to.equal(201) - expect(res.type).to.equal('application/json') - expect(res.charset).to.equal('utf-8') - expect(res.body.result).to.equal(`created`) - expect(typeof res.body.apikey.key).to.equal('string') - expect(typeof res.body.apikey.secret).to.equal('string') - expect(typeof res.body.apikey.expiresAt).to.equal('string') - expect(res.body.apikey.level).to.equal(4) - store.apikey = res.body.apikey - done() - }) - }) +export const setup = async () => { + // Initial store contents + const store = { + chai, + expect, + config, + account: { + email: `test_${randomString()}@${config.tests.domain}`, + language: 'en', + password: randomString(), + }, + icons: { + user: '🧑 ', + jwt: '🎫 ', + key: '🎟️ ', + }, + } + store.icon = (icon1, icon2 = false) => store.icons[icon1] + (icon2 ? store.icons[icon2] : '') - step(`${store.icon('user')} Should set the initial password`, (done) => { - chai - .request(config.api) - .put('/account/jwt') - .set('Authorization', 'Bearer ' + store.account.token) - .send({ - password: store.account.password, - }) - .end((err, res) => { - expect(res.status).to.equal(200) - expect(res.type).to.equal('application/json') - expect(res.charset).to.equal('utf-8') - expect(res.body.result).to.equal(`success`) - expect(res.body.account.email).to.equal(store.account.email) - expect(res.body.account.username).to.equal(store.account.username) - expect(res.body.account.lusername).to.equal(store.account.username.toLowerCase()) - expect(typeof res.body.account.id).to.equal(`number`) - store.token = res.body.token - done() - }) + // Get confirmation ID + let result + try { + result = await axios.post(`${store.config.api}/signup`, { + email: store.account.email, + language: store.account.language, + unittest: true, }) - }) + } catch (err) { + console.log('Failed at first setup request', err) + process.exit() + } + store.account.confirmation = result.data.confirmation + + // Confirm account + try { + result = await axios.post(`${store.config.api}/confirm/signup/${store.account.confirmation}`, { + consent: 1, + }) + } catch (err) { + console.log('Failed at account confirmation request', err) + process.exit() + } + store.account.token = result.data.token + store.account.username = result.data.account.username + store.account.userid = result.data.account.id + + // Create API key + try { + result = await axios.post( + `${store.config.api}/apikey/jwt`, + { + name: 'Test API key', + level: 4, + expiresIn: 60, + }, + { + headers: { + authorization: `Bearer ${store.account.token}`, + }, + } + ) + } catch (err) { + console.log('Failed at API key creation request', err) + process.exit() + } + store.account.apikey = result.data.apikey + + return { chai, config, expect, store } } export const teardown = async function (store) { diff --git a/sites/backend/tests/user.mjs b/sites/backend/tests/user.mjs index a0fa2fe0368..253d583d30d 100644 --- a/sites/backend/tests/user.mjs +++ b/sites/backend/tests/user.mjs @@ -1,9 +1,6 @@ -export const userTests = async (config, store, chai) => { - const expect = chai.expect - - describe(`${store.icon('user')} Signup flow and authentication`, async function () { - it(`${store.icon('user')} Should return 400 on signup without body`, function (done) { - this.store = store +export const userTests = async (chai, config, expect, store) => { + describe(`${store.icon('user')} Signup flow and authentication`, () => { + it(`${store.icon('user')} Should return 400 on signup without body`, (done) => { chai .request(config.api) .post('/signup') @@ -78,6 +75,23 @@ export const userTests = async (config, store, chai) => { }) }) + // Note that password was not set at account creation + step(`${store.icon('user')} Should set the password`, (done) => { + chai + .request(config.api) + .put('/account/jwt') + .set('Authorization', 'Bearer ' + store.account.token) + .send({ + password: store.account.password, + }) + .end((err, res) => { + expect(res.status).to.equal(200) + expect(res.type).to.equal('application/json') + expect(res.charset).to.equal('utf-8') + done() + }) + }) + step(`${store.icon('user')} Should login with username and password`, (done) => { chai .request(config.api) @@ -192,6 +206,7 @@ export const userTests = async (config, store, chai) => { done() }) }) + step(`${store.icon('user', 'jwt')} Should load account (jwt)`, (done) => { chai .request(config.api)