diff --git a/packages/nocodb/package.json b/packages/nocodb/package.json index 7e8767021a..170bd5ec97 100644 --- a/packages/nocodb/package.json +++ b/packages/nocodb/package.json @@ -21,7 +21,7 @@ "local:test:graphql": "cross-env DATABASE_URL=mysql://root:password@localhost:3306/sakila TS_NODE_PROJECT=tsconfig.json mocha -r ts-node/register src/__tests__/graphql.test.ts --recursive --timeout 10000 --exit", "test:graphql": "cross-env TS_NODE_PROJECT=tsconfig.json mocha -r ts-node/register src/__tests__/graphql.test.ts --recursive --timeout 10000 --exit", "test:grpc": "cross-env TS_NODE_PROJECT=tsconfig.json mocha -r ts-node/register src/__tests__/grpc.test.ts --recursive --timeout 10000 --exit", - "local:test:rest": "cross-env DATABASE_URL=mysql://root:password@localhost:3306/sakila TS_NODE_PROJECT=tsconfig.json mocha -r ts-node/register src/__tests__/rest.test.ts --recursive --timeout 10000 --exit", + "local:test:rest": "cross-env DATABASE_URL=mysql://root:password@localhost:3306/sakila TS_NODE_PROJECT=tsconfig.json mocha -r ts-node/register src/__tests__/unit/rest/index.test.ts --recursive --timeout 10000 --exit", "test:rest": "cross-env TS_NODE_PROJECT=tsconfig.json mocha -r ts-node/register src/__tests__/rest.test.ts --recursive --timeout 10000 --exit", "test1": "run-s build test:*", "test:lint": "tslint --project . && prettier \"src/**/*.ts\" --list-different", diff --git a/packages/nocodb/src/__tests__/unit/rest/dbConfig.ts b/packages/nocodb/src/__tests__/unit/rest/dbConfig.ts new file mode 100644 index 0000000000..ecef55bc1f --- /dev/null +++ b/packages/nocodb/src/__tests__/unit/rest/dbConfig.ts @@ -0,0 +1,24 @@ +import NcConfigFactory from '../../../lib/utils/NcConfigFactory'; + +const dbName = `test_meta`; +process.env[`DATABASE_URL`] = `mysql2://root:password@localhost:3306/${dbName}`; + +const dbConfig = NcConfigFactory.urlToDbConfig( + NcConfigFactory.extractXcUrlFromJdbc(process.env[`DATABASE_URL`]) +); +dbConfig.connection.database = 'sakila'; +dbConfig.meta = { + tn: 'nc_evolutions', + dbAlias: 'db', + api: { + type: 'rest', + prefix: '', + graphqlDepthLimit: 10, + }, + inflection: { + tn: 'camelize', + column_name: 'camelize', + }, +} as any; + +export { dbConfig, dbName }; diff --git a/packages/nocodb/src/__tests__/unit/rest/index.test.ts b/packages/nocodb/src/__tests__/unit/rest/index.test.ts new file mode 100644 index 0000000000..84efc00b31 --- /dev/null +++ b/packages/nocodb/src/__tests__/unit/rest/index.test.ts @@ -0,0 +1,16 @@ +import 'mocha'; +import authTests from './tests/auth.test'; +import NcConfigFactory from '../../../lib/utils/NcConfigFactory'; + +const dbName = `test_meta`; + +process.env.NODE_ENV = 'test'; +process.env.TEST = 'test'; +process.env[`DATABASE_URL`] = `mysql2://root:password@localhost:3306/${dbName}`; + +const dbConfig = NcConfigFactory.urlToDbConfig( + NcConfigFactory.extractXcUrlFromJdbc(process.env[`DATABASE_URL`]) +); +dbConfig.connection.database = 'sakila'; + +authTests(); diff --git a/packages/nocodb/src/__tests__/unit/rest/server.ts b/packages/nocodb/src/__tests__/unit/rest/server.ts new file mode 100644 index 0000000000..ce7be9e299 --- /dev/null +++ b/packages/nocodb/src/__tests__/unit/rest/server.ts @@ -0,0 +1,18 @@ +import { Noco } from '../../../lib'; +import express from 'express'; +import { dbConfig, dbName } from './dbConfig'; + +const knex = require('knex'); + +export default async function () { + try { + await knex(dbConfig).raw(`DROP DATABASE ${dbName}`); + await knex(dbConfig).raw(`CREATE DATABASE ${dbName}`); + } catch {} + + const server = express(); + server.enable('trust proxy'); + server.use(await Noco.forceInit()); + + return server; +} diff --git a/packages/nocodb/src/__tests__/unit/rest/tests/auth.test.ts b/packages/nocodb/src/__tests__/unit/rest/tests/auth.test.ts new file mode 100644 index 0000000000..b46ec67fb0 --- /dev/null +++ b/packages/nocodb/src/__tests__/unit/rest/tests/auth.test.ts @@ -0,0 +1,196 @@ +import { expect } from 'chai'; +import 'mocha'; +import request from 'supertest'; +import server from '../server'; + +const EMAIL_ID = 'abc@g.com'; +const VALID_PASSWORD = 'Abc@1234'; + +function authTests() { + let app; + let token; + + before(async function () { + app = await server(); + }); + + it('Signup with valid email', function (done) { + request(app) + .post('/api/v1/auth/user/signup') + .send({ email: EMAIL_ID, password: VALID_PASSWORD }) + .expect(200, (err, res) => { + if (err) { + expect(res.status).to.equal(400); + } else { + const token = res.body.token; + expect(token).to.be.a('string'); + } + done(); + }); + }); + + it('Signup with invalid email', (done) => { + request(app) + .post('/api/v1/auth/user/signup') + .send({ email: 'test', password: VALID_PASSWORD }) + .expect(400, done); + }); + + it('Signup with invalid passsword', (done) => { + request(app) + .post('/api/v1/auth/user/signup') + .send({ email: EMAIL_ID, password: 'weakpass' }) + .expect(400, done); + }); + + it('Signin with valid credentials', function (done) { + request(app) + .post('/api/v1/auth/user/signin') + .send({ email: EMAIL_ID, password: VALID_PASSWORD }) + .expect(200, async function (err, res) { + if (err) { + console.log(res.error); + return done(err); + } + token = res.body.token; + expect(token).to.be.a('string'); + // todo: Verify token + done(); + }); + }); + + it('Signup without email and password', (done) => { + request(app) + .post('/api/v1/auth/user/signin') + // pass empty data in request + .send({}) + .expect(400, done); + }); + + it('Signin with invalid credentials', function (done) { + request(app) + .post('/api/v1/auth/user/signin') + .send({ email: 'abc@abc.com', password: VALID_PASSWORD }) + .expect(400, done); + }); + + it('Signin with invalid password', function (done) { + request(app) + .post('/api/v1/auth/user/signin') + .send({ email: EMAIL_ID, password: 'wrongPassword' }) + .expect(400, done); + }); + + it('me without token', function (done) { + request(app) + .get('/api/v1/auth/user/me') + .unset('xc-auth') + .expect(200, (err, res) => { + if (err) { + console.log(err, res); + done(err); + return; + } + + if (!res.body?.roles?.guest) { + done('User should be guest'); + return; + } + + done(); + }); + }); + + it('me with token', function (done) { + request(app) + .get('/api/v1/auth/user/me') + .set('xc-auth', token) + .expect(200, function (err, res) { + if (err) { + return done(err); + } + const email = res.body.email; + expect(email).to.equal(EMAIL_ID); + done(); + }); + }); + + it('Forgot password with a non-existing email id', function (done) { + request(app) + .post('/api/v1/auth/password/forgot') + .send({ email: 'nonexisting@email.com' }) + .expect(400, done); + }); + + // todo: fix mailer issues + // it('Forgot password with an existing email id', function () {}); + + it('Change password', function (done) { + request(app) + .post('/api/v1/auth/password/change') + .set('xc-auth', token) + .send({ + currentPassword: VALID_PASSWORD, + newPassword: 'NEW' + VALID_PASSWORD, + }) + .expect(200, done); + }); + + it('Change password - after logout', function (done) { + request(app) + .post('/api/v1/auth/password/change') + .unset('xc-auth') + .send({ + currentPassword: VALID_PASSWORD, + newPassword: 'NEW' + VALID_PASSWORD, + }) + .expect(500, function (_err, _res) { + done(); + }); + }); + + // todo: + it('Reset Password with an invalid token', function (done) { + request(app) + .post('/api/v1/auth/password/reset/someRandomValue') + .send({ email: EMAIL_ID }) + .expect(400, done); + }); + + it('Email validate with an invalid token', function (done) { + request(app) + .post('/api/v1/auth/email/validate/someRandomValue') + .send({ email: EMAIL_ID }) + .expect(400, done); + }); + + // todo: + // it('Email validate with a valid token', function (done) { + // // request(app) + // // .post('/auth/email/validate/someRandomValue') + // // .send({email: EMAIL_ID}) + // // .expect(500, done); + // }); + + // todo: + // it('Forgot password validate with a valid token', function (done) { + // // request(app) + // // .post('/auth/token/validate/someRandomValue') + // // .send({email: EMAIL_ID}) + // // .expect(500, done); + // }); + + // todo: + // it('Reset Password with an valid token', function (done) { + // // request(app) + // // .post('/auth/password/reset/someRandomValue') + // // .send({password: 'anewpassword'}) + // // .expect(500, done); + // }); + + // todo: refresh token api +} + +export default function () { + describe('Auth', authTests); +} diff --git a/packages/nocodb/src/__tests__/unit/rest/tests/helpers/user.ts b/packages/nocodb/src/__tests__/unit/rest/tests/helpers/user.ts new file mode 100644 index 0000000000..10296c2049 --- /dev/null +++ b/packages/nocodb/src/__tests__/unit/rest/tests/helpers/user.ts @@ -0,0 +1,10 @@ +import request from 'supertest'; + +const createUser = async (app, email, password) => { + const response = await request(app) + .post('/api/v1/auth/user/signup') + .send({ email, password }); + return { token: response.body.token }; +}; + +export { createUser }; diff --git a/packages/nocodb/src/lib/Noco.ts b/packages/nocodb/src/lib/Noco.ts index 7e5bc855a3..82d9808f04 100644 --- a/packages/nocodb/src/lib/Noco.ts +++ b/packages/nocodb/src/lib/Noco.ts @@ -66,6 +66,11 @@ export default class Noco { return `${siteUrl}${Noco._this?.config?.dashboardPath}`; } + public static async forceInit(): Promise { + Noco._this = new Noco(); + return Noco._this.init(); + } + public static async init( args?: { progressCallback?: Function;