Update dependencies and fix tests

This commit is contained in:
T. R. Bernstein
2025-10-03 23:10:54 +02:00
parent 29b8f24e10
commit e61be73f35
13 changed files with 5705 additions and 8904 deletions

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm test
npx lint-staged
pnpm test
pnpx lint-staged

View File

@@ -31,21 +31,21 @@ ENV NODE_ENV="production"
COPY package-lock.json package.json ./
RUN apk update && apk add python3=3.11.10-r1 build-base=0.5-r3 && npm ci
RUN apk update && apk add python3=3.11.10-r1 build-base=0.5-r3 && pnpm i
COPY . .
CMD [ "npm", "start" ]
CMD [ "pnpm", "start" ]
```
</details>
You can proceed [to building the application](https://docs.docker.com/get-started/workshop/02_our_app/#build-the-apps-image).
### npm
### pnpm
```bash
npm install -g soul-cli
pnpm install -g @tabshift/soul-cli
```
### 1. Running Soul
@@ -180,8 +180,8 @@ git clone https://github.com/thevahidal/soul # Clone project
cp .env.sample .env # Duplicate sample environment variables
vim .env # Update the environment variables
npm install # Install dependencies
npm run dev # Start the dev server
pnpm install # Install dependencies
pnpm run dev # Start the dev server
```
## Testing
@@ -193,7 +193,7 @@ npm run dev # Start the dev server
5. Use the following command to run the tests:
```
npm run test
pnpm run test
```
Make sure to replace the placeholders with the appropriate values for your environment.

8162
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -28,33 +28,33 @@
"homepage": "https://github.com/tabshift-gh/soul-server#readme",
"dependencies": {
"bcrypt": "^5.1.1",
"better-sqlite3": "^8.1.0",
"body-parser": "^1.20.2",
"check-password-strength": "^2.0.7",
"cookie-parser": "^1.4.6",
"better-sqlite3": "^12.4.1",
"body-parser": "^1.20.3",
"check-password-strength": "^2.0.10",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"express-rate-limit": "^6.7.0",
"dotenv": "^16.6.1",
"express": "^4.21.2",
"express-rate-limit": "^6.11.2",
"express-winston": "^4.2.0",
"joi": "^17.8.3",
"joi": "^17.13.3",
"jsonwebtoken": "^9.0.2",
"soul-studio": "^0.0.1",
"swagger-ui-express": "^4.6.1",
"winston": "^3.8.2",
"ws": "^8.12.1",
"yargs": "^17.7.1"
"swagger-ui-express": "^4.6.3",
"winston": "^3.18.3",
"ws": "^8.18.3",
"yargs": "^17.7.2"
},
"devDependencies": {
"cross-env": "^7.0.3",
"eslint": "^8.54.0",
"eslint-config-prettier": "^9.0.0",
"eslint": "^8.57.1",
"eslint-config-prettier": "^9.1.2",
"husky": "^8.0.3",
"jest": "^29.4.3",
"nodemon": "^3.1.3",
"jest": "^29.7.0",
"nodemon": "^3.1.10",
"prettier": "3.1.0",
"supertest": "^6.3.3",
"swagger-autogen": "^2.23.1"
"supertest": "^6.3.4",
"swagger-autogen": "^2.23.7"
},
"jest": {
"testEnvironment": "node",

4792
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

6
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,6 @@
ignoredBuiltDependencies:
- '@scarf/scarf'
onlyBuiltDependencies:
- bcrypt
- better-sqlite3

View File

@@ -1,364 +1,437 @@
const supertest = require('supertest')
const supertest = require("supertest");
const app = require('../index')
const config = require('../config')
const { generateToken } = require('../utils')
const { testData } = require('../tests/testData')
jest.useFakeTimers();
jest.spyOn(global, "setInterval");
const app = require("../index");
const config = require("../config");
const { generateToken } = require("../utils");
const { testData } = require("../tests/testData");
const requestWithSupertest = supertest(app)
const requestWithSupertest = supertest(app);
describe('Auth Endpoints', () => {
describe('User Endpoints', () => {
it('POST /tables/_users/rows should register a user', async () => {
describe("Auth Endpoints", () => {
if (!config.auth) return;
describe("User Endpoints", () => {
it("POST /tables/_users/rows should register a user", async () => {
const accessToken = await generateToken(
{ username: 'John', userId: 1, isSuperuser: true },
{
username: config.initialUserUsername,
userId: 1,
isSuperuser: true,
},
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.post('/api/tables/_users/rows')
.set('Cookie', [`accessToken=${accessToken}`])
.post("/api/tables/_users/rows")
.set("Cookie", [`accessToken=${accessToken}`])
.send({
fields: {
username: testData.users.user1.username,
password: testData.strongPassword
}
})
username: testData.users.frozenUser1.username,
password: testData.strongPassword,
},
});
expect(res.status).toEqual(201)
expect(res.type).toEqual(expect.stringContaining('json'))
expect(res.status).toEqual(201);
expect(res.type).toEqual(expect.stringContaining("json"));
expect(res.body).toHaveProperty('message')
expect(res.body.message).toBe('Row Inserted')
expect(res.body).toHaveProperty("message");
expect(res.body.message).toBe("Row Inserted");
expect(res.body).not.toHaveProperty('password')
expect(res.body).not.toHaveProperty('hashed_password')
expect(res.body).not.toHaveProperty('salt')
})
expect(res.body).not.toHaveProperty("password");
expect(res.body).not.toHaveProperty("hashed_password");
expect(res.body).not.toHaveProperty("salt");
});
it('POST /tables/_users/rows should throw 400 error if username is not passed', async () => {
it("POST /tables/_users/rows should throw 400 error if username is not passed", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: config.initialUserUsername, isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.post('/api/tables/_users/rows')
.set('Cookie', [`accessToken=${accessToken}`])
.post("/api/tables/_users/rows")
.set("Cookie", [`accessToken=${accessToken}`])
.send({
fields: { password: testData.strongPassword }
})
fields: { password: testData.strongPassword },
});
expect(res.status).toEqual(400)
expect(res.body.message).toBe('username is required')
expect(res.status).toEqual(400);
expect(res.body.message).toBe("username is required");
expect(res.body).not.toHaveProperty('password')
expect(res.body).not.toHaveProperty('hashed_password')
expect(res.body).not.toHaveProperty('salt')
})
expect(res.body).not.toHaveProperty("password");
expect(res.body).not.toHaveProperty("hashed_password");
expect(res.body).not.toHaveProperty("salt");
});
it('POST /tables/_users/rows should throw 400 error if the password is not strong', async () => {
it("POST /tables/_users/rows should throw 400 error if the password is not strong", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: config.initialUserUsername, isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.post('/api/tables/_users/rows')
.set('Cookie', [`accessToken=${accessToken}`])
.post("/api/tables/_users/rows")
.set("Cookie", [`accessToken=${accessToken}`])
.send({
fields: {
username: testData.users.user2.username,
password: testData.weakPassword
}
})
username: testData.users.weakPasswordUser.username,
password: testData.weakPassword,
},
});
expect(res.status).toEqual(400)
expect(res.status).toEqual(400);
expect(res.body.message).toBe(
'This password is weak, it should be at least 8 characters long and contain a combination of lowercase letters, uppercase letters, numbers, and special characters'
)
"This password is weak, it should be at least 8 characters long and contain a combination of lowercase letters, uppercase letters, numbers, and special characters",
);
expect(res.body).not.toHaveProperty('password')
expect(res.body).not.toHaveProperty('hashed_password')
expect(res.body).not.toHaveProperty('salt')
})
expect(res.body).not.toHaveProperty("password");
expect(res.body).not.toHaveProperty("hashed_password");
expect(res.body).not.toHaveProperty("salt");
});
it('POST /tables/_users/rows should throw 409 error if the username is taken', async () => {
it("POST /tables/_users/rows should throw 409 error if the username is taken", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: config.initialUserUsername, isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.post('/api/tables/_users/rows')
.set('Cookie', [`accessToken=${accessToken}`])
.post("/api/tables/_users/rows")
.set("Cookie", [`accessToken=${accessToken}`])
.send({
fields: {
username: testData.users.user1.username,
password: testData.strongPassword
}
})
username: testData.users.frozenUser1.username,
password: testData.strongPassword,
},
});
expect(res.status).toEqual(409)
expect(res.body.message).toBe('This username is taken')
expect(res.status).toEqual(409);
expect(res.body.message).toBe("This username is taken");
expect(res.body).not.toHaveProperty('password')
expect(res.body).not.toHaveProperty('hashed_password')
expect(res.body).not.toHaveProperty('salt')
})
expect(res.body).not.toHaveProperty("password");
expect(res.body).not.toHaveProperty("hashed_password");
expect(res.body).not.toHaveProperty("salt");
});
it('GET /tables/_users/rows should return list of users', async () => {
it("GET /tables/_users/rows should return list of users", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: config.initialUserUsername, isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.get('/api/tables/_users/rows')
.set('Cookie', [`accessToken=${accessToken}`])
.get("/api/tables/_users/rows")
.set("Cookie", [`accessToken=${accessToken}`]);
expect(res.status).toEqual(200)
expect(res.body.data[0]).toHaveProperty('id')
expect(res.body.data[0]).toHaveProperty('username')
expect(res.body.data[0]).toHaveProperty('is_superuser')
expect(res.body.data[0]).toHaveProperty('createdAt')
expect(res.status).toEqual(200);
expect(res.body.data[0]).toHaveProperty("id");
expect(res.body.data[0]).toHaveProperty("username");
expect(res.body.data[0]).toHaveProperty("is_superuser");
expect(res.body.data[0]).toHaveProperty("createdAt");
expect(res.body.data[0]).not.toHaveProperty('password')
expect(res.body.data[0]).not.toHaveProperty('hashed_password')
expect(res.body.data[0]).not.toHaveProperty('salt')
})
expect(res.body.data[0]).not.toHaveProperty("password");
expect(res.body.data[0]).not.toHaveProperty("hashed_password");
expect(res.body.data[0]).not.toHaveProperty("salt");
});
it('GET /tables/_users/rows/:id should retrive a single user', async () => {
it("GET /tables/_users/rows/:id should retrive a single user", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: config.initialUserUsername, isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.get('/api/tables/_users/rows/1')
.set('Cookie', [`accessToken=${accessToken}`])
.get("/api/tables/_users/rows/1")
.set("Cookie", [`accessToken=${accessToken}`]);
expect(res.status).toEqual(200)
expect(res.body.data[0]).toHaveProperty('id')
expect(res.body.data[0]).toHaveProperty('username')
expect(res.body.data[0]).toHaveProperty('is_superuser')
expect(res.body.data[0]).toHaveProperty('createdAt')
expect(res.status).toEqual(200);
expect(res.body.data[0]).toHaveProperty("id");
expect(res.body.data[0]).toHaveProperty("username");
expect(res.body.data[0]).toHaveProperty("is_superuser");
expect(res.body.data[0]).toHaveProperty("createdAt");
expect(res.body.data[0]).not.toHaveProperty('password')
expect(res.body.data[0]).not.toHaveProperty('hashed_password')
expect(res.body.data[0]).not.toHaveProperty('salt')
})
expect(res.body.data[0]).not.toHaveProperty("password");
expect(res.body.data[0]).not.toHaveProperty("hashed_password");
expect(res.body.data[0]).not.toHaveProperty("salt");
});
it('PUT /tables/_users/rows/:id should update a user', async () => {
it("PUT /tables/_users/rows/:id should update a user", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: config.initialUserUsername, isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.put('/api/tables/_users/rows/1')
.set('Cookie', [`accessToken=${accessToken}`])
const createUserRes = await requestWithSupertest
.post("/api/tables/_users/rows")
.set("Cookie", [`accessToken=${accessToken}`])
.send({
fields: {
username: testData.users.user3.username
}
})
username: testData.users.changeNameUser.username,
password: testData.strongPassword,
},
});
expect(res.status).toEqual(200)
expect(res.type).toEqual(expect.stringContaining('json'))
expect(res.body).toHaveProperty('message')
expect(res.body.message).toBe('Row updated')
expect(res.body).not.toHaveProperty('password')
expect(res.body).not.toHaveProperty('hashed_password')
expect(res.body).not.toHaveProperty('salt')
})
it('DELETE /tables/_users/rows/:id should remove a user', async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
config.tokenSecret,
'1H'
)
expect(createUserRes.status).toEqual(201);
const res = await requestWithSupertest
.delete('/api/tables/_users/rows/2')
.set('Cookie', [`accessToken=${accessToken}`])
.put("/api/tables/_users/rows/" + createUserRes.body.rowId)
.set("Cookie", [`accessToken=${accessToken}`])
.send({
fields: {
username: testData.users.changeNameUser.username + "2",
},
});
expect(res.status).toEqual(400)
expect(res.body.message).toBe('FOREIGN KEY constraint failed')
expect(res.status).toEqual(200);
expect(res.type).toEqual(expect.stringContaining("json"));
expect(res.body).not.toHaveProperty('password')
expect(res.body).not.toHaveProperty('hashed_password')
expect(res.body).not.toHaveProperty('salt')
})
})
expect(res.body).toHaveProperty("message");
expect(res.body.message).toBe("Row updated");
describe('Obtain Access Token Endpoint', () => {
it('POST /auth/token/obtain should return an access token and refresh token values and a success message', async () => {
const res = await requestWithSupertest.post('/api/auth/token/obtain').send({
fields: {
username: testData.users.user1.username,
password: testData.strongPassword
}
})
expect(res.body).not.toHaveProperty("password");
expect(res.body).not.toHaveProperty("hashed_password");
expect(res.body).not.toHaveProperty("salt");
});
expect(res.status).toEqual(201)
expect(res.type).toEqual(expect.stringContaining('json'))
expect(res.body).toHaveProperty('message')
expect(res.body.message).toBe('Success')
it("DELETE /tables/_users/rows/:id should remove a user", async () => {
const accessToken = await generateToken(
{ username: config.initialUserUsername, isSuperuser: true },
config.tokenSecret,
"1H",
);
expect(res.headers['set-cookie']).toBeDefined()
expect(res.headers['set-cookie']).toEqual(
const createUserRes = await requestWithSupertest
.post("/api/tables/_users/rows")
.set("Cookie", [`accessToken=${accessToken}`])
.send({
fields: {
username: testData.users.changeNameUser.username,
password: testData.strongPassword,
},
});
expect(createUserRes.status).toEqual(201);
const res = await requestWithSupertest
.delete("/api/tables/_users/rows/" + createUserRes.body.rowId)
.set("Cookie", [`accessToken=${accessToken}`]);
expect(res.status).toEqual(400);
expect(res.body.message).toBe("FOREIGN KEY constraint failed");
expect(res.body).not.toHaveProperty("password");
expect(res.body).not.toHaveProperty("hashed_password");
expect(res.body).not.toHaveProperty("salt");
});
});
describe("Obtain Access Token Endpoint", () => {
it("POST /auth/token/obtain should return an access token and refresh token values and a success message", async () => {
const res = await requestWithSupertest
.post("/api/auth/token/obtain")
.send({
fields: {
username: testData.users.frozenUser1.username,
password: testData.strongPassword,
},
});
expect(res.status).toEqual(201);
expect(res.type).toEqual(expect.stringContaining("json"));
expect(res.body).toHaveProperty("message");
expect(res.body.message).toBe("Success");
expect(res.headers["set-cookie"]).toBeDefined();
expect(res.headers["set-cookie"]).toEqual(
expect.arrayContaining([
expect.stringContaining('refreshToken='),
expect.stringContaining('accessToken=')
])
)
expect.stringContaining("refreshToken="),
expect.stringContaining("accessToken="),
]),
);
expect(res.body).not.toHaveProperty('password')
expect(res.body).not.toHaveProperty('hashed_password')
expect(res.body).not.toHaveProperty('salt')
})
expect(res.body).not.toHaveProperty("password");
expect(res.body).not.toHaveProperty("hashed_password");
expect(res.body).not.toHaveProperty("salt");
});
it('POST /auth/token/obtain should throw a 401 error if the username does not exist in the DB', async () => {
const res = await requestWithSupertest.post('/api/auth/token/obtain').send({
fields: {
username: testData.invalidUsername,
password: testData.strongPassword
}
})
it("POST /auth/token/obtain should throw a 401 error if the username does not exist in the DB", async () => {
const res = await requestWithSupertest
.post("/api/auth/token/obtain")
.send({
fields: {
username: testData.invalidUsername,
password: testData.strongPassword,
},
});
expect(res.status).toEqual(401)
expect(res.type).toEqual(expect.stringContaining('json'))
expect(res.body).toHaveProperty('message')
expect(res.body.message).toBe('Invalid username or password')
expect(res.status).toEqual(401);
expect(res.type).toEqual(expect.stringContaining("json"));
expect(res.body).toHaveProperty("message");
expect(res.body.message).toBe("Invalid username or password");
expect(res.body).not.toHaveProperty('password')
expect(res.body).not.toHaveProperty('hashed_password')
expect(res.body).not.toHaveProperty('salt')
})
expect(res.body).not.toHaveProperty("password");
expect(res.body).not.toHaveProperty("hashed_password");
expect(res.body).not.toHaveProperty("salt");
});
it('POST /auth/token/obtain should throw a 401 error if the password is invalid', async () => {
const res = await requestWithSupertest.post('/api/auth/token/obtain').send({
fields: {
username: testData.users.user1.username,
password: testData.invalidPassword
}
})
it("POST /auth/token/obtain should throw a 401 error if the password is invalid", async () => {
const res = await requestWithSupertest
.post("/api/auth/token/obtain")
.send({
fields: {
username: testData.users.frozenUser1.username,
password: testData.invalidPassword,
},
});
expect(res.status).toEqual(401)
expect(res.type).toEqual(expect.stringContaining('json'))
expect(res.body).toHaveProperty('message')
expect(res.body.message).toBe('Invalid username or password')
expect(res.status).toEqual(401);
expect(res.type).toEqual(expect.stringContaining("json"));
expect(res.body).toHaveProperty("message");
expect(res.body.message).toBe("Invalid username or password");
expect(res.body).not.toHaveProperty('password')
expect(res.body).not.toHaveProperty('hashed_password')
expect(res.body).not.toHaveProperty('salt')
})
})
expect(res.body).not.toHaveProperty("password");
expect(res.body).not.toHaveProperty("hashed_password");
expect(res.body).not.toHaveProperty("salt");
});
});
describe('Refresh Access Token Endpoint', () => {
it('GET /auth/token/refresh should refresh the access and refresh tokens', async () => {
describe("Refresh Access Token Endpoint", () => {
it("GET /auth/token/refresh should refresh the access and refresh tokens", async () => {
const refreshToken = await generateToken(
{ username: 'John', userId: 1, isSuperuser: true },
{
username: config.initialUserUsername,
userId: 1,
isSuperuser: true,
},
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.get('/api/auth/token/refresh')
.set('Cookie', [`refreshToken=${refreshToken}`])
.get("/api/auth/token/refresh")
.set("Cookie", [`refreshToken=${refreshToken}`]);
expect(res.status).toEqual(200)
expect(res.type).toEqual(expect.stringContaining('json'))
expect(res.body).toHaveProperty('message')
expect(res.body.message).toBe('Success')
expect(res.status).toEqual(200);
expect(res.type).toEqual(expect.stringContaining("json"));
expect(res.body).toHaveProperty("message");
expect(res.body.message).toBe("Success");
expect(res.headers['set-cookie']).toBeDefined()
expect(res.headers['set-cookie']).toEqual(
expect(res.headers["set-cookie"]).toBeDefined();
expect(res.headers["set-cookie"]).toEqual(
expect.arrayContaining([
expect.stringContaining('refreshToken='),
expect.stringContaining('accessToken=')
])
)
})
})
expect.stringContaining("refreshToken="),
expect.stringContaining("accessToken="),
]),
);
});
});
describe('Change Password Endpoint', () => {
it('PUT /auth/change-password/ should change a password', async () => {
const accessToken = await generateToken(
{ username: 'John', userId: 2, isSuperuser: true },
describe("Change Password Endpoint", () => {
it("PUT /auth/change-password/ should change a password", async () => {
let accessToken = await generateToken(
{
username: config.initialUserUsername,
userId: 1,
isSuperuser: true,
},
config.tokenSecret,
'1H'
)
"1H",
);
const createUserRes = await requestWithSupertest
.post("/api/tables/_users/rows")
.set("Cookie", [`accessToken=${accessToken}`])
.send({
fields: {
username: testData.users.changePasswordUser.username,
password: testData.strongPassword,
},
});
expect(createUserRes.status).toEqual(201);
accessToken = await generateToken(
{
username: config.initialUserUsername,
userId: createUserRes.body.rowId,
isSuperuser: true,
},
config.tokenSecret,
"1H",
);
const res = await requestWithSupertest
.put('/api/auth/change-password')
.set('Cookie', [`accessToken=${accessToken}`])
.put("/api/auth/change-password")
.set("Cookie", [`accessToken=${accessToken}`])
.send({
fields: {
currentPassword: testData.strongPassword,
newPassword: testData.strongPassword2
}
})
newPassword: testData.strongPassword2,
},
});
expect(res.status).toEqual(200)
expect(res.type).toEqual(expect.stringContaining('json'))
expect(res.body).toHaveProperty('message')
expect(res.body.message).toBe('Password updated successfully')
expect(res.status).toEqual(200);
expect(res.type).toEqual(expect.stringContaining("json"));
expect(res.body).toHaveProperty("message");
expect(res.body.message).toBe("Password updated successfully");
expect(res.body).not.toHaveProperty('password')
expect(res.body).not.toHaveProperty('hashed_password')
expect(res.body).not.toHaveProperty('salt')
expect(res.body).not.toHaveProperty("password");
expect(res.body).not.toHaveProperty("hashed_password");
expect(res.body).not.toHaveProperty("salt");
// check if the password is really updated
const res2 = await requestWithSupertest.post('/api/auth/token/obtain').send({
fields: {
username: testData.users.user1.username,
password: testData.strongPassword2
}
})
const res2 = await requestWithSupertest
.post("/api/auth/token/obtain")
.send({
fields: {
username: testData.users.changePasswordUser.username,
password: testData.strongPassword2,
},
});
expect(res2.status).toEqual(201)
expect(res2.type).toEqual(expect.stringContaining('json'))
expect(res2.body).toHaveProperty('message')
expect(res2.body.message).toBe('Success')
})
expect(res2.status).toEqual(201);
expect(res2.type).toEqual(expect.stringContaining("json"));
expect(res2.body).toHaveProperty("message");
expect(res2.body.message).toBe("Success");
});
it('PUT /auth/change-password/ should throw 401 error if the current password is not valid', async () => {
it("PUT /auth/change-password/ should throw 401 error if the current password is not valid", async () => {
const accessToken = await generateToken(
{ username: 'John', userId: 2, isSuperuser: true },
{
username: config.initialUserUsername,
userId: 2,
isSuperuser: true,
},
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.put('/api/auth/change-password')
.set('Cookie', [`accessToken=${accessToken}`])
.put("/api/auth/change-password")
.set("Cookie", [`accessToken=${accessToken}`])
.send({
fields: {
currentPassword: testData.invalidPassword,
newPassword: testData.strongPassword2
}
})
newPassword: testData.strongPassword2,
},
});
expect(res.status).toEqual(401)
expect(res.type).toEqual(expect.stringContaining('json'))
expect(res.body).toHaveProperty('message')
expect(res.body.message).toBe('Invalid current password')
expect(res.status).toEqual(401);
expect(res.type).toEqual(expect.stringContaining("json"));
expect(res.body).toHaveProperty("message");
expect(res.body.message).toBe("Invalid current password");
expect(res.body).not.toHaveProperty('password')
expect(res.body).not.toHaveProperty('hashed_password')
expect(res.body).not.toHaveProperty('salt')
})
})
})
expect(res.body).not.toHaveProperty("password");
expect(res.body).not.toHaveProperty("hashed_password");
expect(res.body).not.toHaveProperty("salt");
});
});
});

View File

@@ -1,54 +1,67 @@
const { rowService, authService } = require('../../services')
const { apiConstants, dbConstants, responseMessages, authConstants } = require('../../constants')
const config = require('../../config')
const { hashPassword, checkPasswordStrength, comparePasswords } = require('../../utils')
const { rowService, authService } = require("../../services");
const {
apiConstants,
dbConstants,
responseMessages,
authConstants,
} = require("../../constants");
const config = require("../../config");
const {
hashPassword,
checkPasswordStrength,
comparePasswords,
} = require("../../utils");
const { USERS_TABLE, USERS_ROLES_TABLE, tableFields } = dbConstants
const { USERS_TABLE, USERS_ROLES_TABLE, tableFields } = dbConstants;
const { SALT_ROUNDS } = authConstants
const { SALT_ROUNDS } = authConstants;
const { successMessage, errorMessage, infoMessage } = responseMessages
const { successMessage, errorMessage, infoMessage } = responseMessages;
const updateSuperuser = async (fields) => {
const { id, password, is_superuser } = fields
let newHashedPassword, newSalt
let fieldsString = ''
const { id, password, is_superuser } = fields;
let newHashedPassword, newSalt;
let fieldsString = "";
try {
// find the user by using the id field
const users = authService.getUsersById({ userId: id })
const users = authService.getUsersById({ userId: id });
// abort if the id is invalid
if (users.length === 0) {
console.log(errorMessage.USER_NOT_FOUND_ERROR)
process.exit(1)
console.log(errorMessage.USER_NOT_FOUND_ERROR);
process.exit(1);
}
// check if the is_superuser field is passed
if (is_superuser !== undefined) {
fieldsString = `${tableFields.IS_SUPERUSER} = '${is_superuser}'`
fieldsString = `${tableFields.IS_SUPERUSER} = '${is_superuser}'`;
}
// if the password is sent from the CLI, update it
if (password) {
// check if the password is weak
if (
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
checkPasswordStrength(password)
)
[
apiConstants.PASSWORD.TOO_WEAK,
apiConstants.PASSWORD.WEAK,
].includes(checkPasswordStrength(password))
) {
console.log(errorMessage.WEAK_PASSWORD_ERROR)
process.exit(1)
console.log(errorMessage.WEAK_PASSWORD_ERROR);
process.exit(1);
}
//hash the password
const { hashedPassword, salt } = await hashPassword(password, SALT_ROUNDS)
newHashedPassword = hashedPassword
newSalt = salt
const { hashedPassword, salt } = await hashPassword(
password,
SALT_ROUNDS,
);
newHashedPassword = hashedPassword;
newSalt = salt;
fieldsString = `${fieldsString ? fieldsString + ', ' : ''} ${
fieldsString = `${fieldsString ? fieldsString + ", " : ""} ${
tableFields.HASHED_PASSWORD
} = '${newHashedPassword}', ${tableFields.SALT} = '${newSalt}'`
} = '${newHashedPassword}', ${tableFields.SALT} = '${newSalt}'`;
}
// update the user
@@ -56,15 +69,15 @@ const updateSuperuser = async (fields) => {
tableName: USERS_TABLE,
lookupField: tableFields.ID,
fieldsString,
pks: `${id}`
})
pks: `${id}`,
});
console.log(successMessage.USER_UPDATE_SUCCESS)
process.exit(1)
console.log(successMessage.USER_UPDATE_SUCCESS);
process.exit(1);
} catch (error) {
console.log(error)
console.log(error);
}
}
};
const registerUser = async (req, res) => {
/*
@@ -80,22 +93,28 @@ const registerUser = async (req, res) => {
}
*/
const { username, password, ...optionalFields } = req.body.fields
const { username, password, ...optionalFields } = req.body.fields;
try {
if (!username) {
return res.status(400).send({ message: errorMessage.USERNAME_REQUIRED_ERROR })
return res
.status(400)
.send({ message: errorMessage.USERNAME_REQUIRED_ERROR });
}
if (!password) {
return res.status(400).send({ message: errorMessage.PASSWORD_REQUIRED_ERROR })
return res
.status(400)
.send({ message: errorMessage.PASSWORD_REQUIRED_ERROR });
}
// check if the username is taken
const users = authService.getUsersByUsername({ username })
const users = authService.getUsersByUsername({ username });
if (users.length > 0) {
return res.status(409).send({ message: errorMessage.USERNAME_TAKEN_ERROR })
return res
.status(409)
.send({ message: errorMessage.USERNAME_TAKEN_ERROR });
/*
#swagger.responses[409] = {
@@ -109,13 +128,14 @@ const registerUser = async (req, res) => {
// check if the password is weak
if (
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
checkPasswordStrength(password)
)
[
apiConstants.PASSWORD.TOO_WEAK,
apiConstants.PASSWORD.WEAK,
].includes(checkPasswordStrength(password))
) {
return res.status(400).send({
message: errorMessage.WEAK_PASSWORD_ERROR
})
message: errorMessage.WEAK_PASSWORD_ERROR,
});
/*
#swagger.responses[400] = {
@@ -128,7 +148,10 @@ const registerUser = async (req, res) => {
}
// hash the password
const { salt, hashedPassword } = await hashPassword(password, SALT_ROUNDS)
const { salt, hashedPassword } = await hashPassword(
password,
SALT_ROUNDS,
);
// create the user
const newUser = rowService.save({
@@ -137,18 +160,18 @@ const registerUser = async (req, res) => {
username,
salt,
hashed_password: hashedPassword,
is_superuser: 'false',
...optionalFields
}
})
is_superuser: "false",
...optionalFields,
},
});
// find the default role from the DB
const defaultRole = authService.getDefaultRole()
const defaultRole = authService.getDefaultRole();
if (defaultRole.length <= 0) {
return res.status(500).send({
message: errorMessage.DEFAULT_ROLE_NOT_CREATED_ERROR
})
message: errorMessage.DEFAULT_ROLE_NOT_CREATED_ERROR,
});
/*
#swagger.responses[500] = {
description: 'Server error',
@@ -162,10 +185,16 @@ const registerUser = async (req, res) => {
// create a role for the user
rowService.save({
tableName: USERS_ROLES_TABLE,
fields: { user_id: newUser.lastInsertRowid, role_id: defaultRole[0].id }
})
fields: {
user_id: newUser.lastInsertRowid,
role_id: defaultRole[0].id,
},
});
res.status(201).send({ message: successMessage.ROW_INSERTED })
res.status(201).send({
message: successMessage.ROW_INSERTED,
rowId: newUser.lastInsertRowid,
});
/*
#swagger.responses[201] = {
@@ -176,10 +205,10 @@ const registerUser = async (req, res) => {
}
*/
} catch (error) {
console.log(error)
res.status(500).send({ message: errorMessage.SERVER_ERROR })
console.log(error);
res.status(500).send({ message: errorMessage.SERVER_ERROR });
}
}
};
const changePassword = async (req, res) => {
/*
@@ -197,24 +226,31 @@ const changePassword = async (req, res) => {
}
*/
const userInfo = req.user
const { currentPassword, newPassword } = req.body.fields
const userInfo = req.user;
const { currentPassword, newPassword } = req.body.fields;
try {
// get the user from the Db
const users = authService.getUsersById({ userId: userInfo.userId })
const users = authService.getUsersById({ userId: userInfo.userId });
if (users.length <= 0) {
return res.status(401).send({ message: errorMessage.USER_NOT_FOUND_ERROR })
return res
.status(401)
.send({ message: errorMessage.USER_NOT_FOUND_ERROR });
}
const user = users[0]
const user = users[0];
// check if the users current password is valid
const isMatch = await comparePasswords(currentPassword, user.hashed_password)
const isMatch = await comparePasswords(
currentPassword,
user.hashed_password,
);
if (!isMatch) {
return res.status(401).send({ message: errorMessage.INVALID_CURRENT_PASSWORD_ERROR })
return res
.status(401)
.send({ message: errorMessage.INVALID_CURRENT_PASSWORD_ERROR });
/*
#swagger.responses[401] = {
description: 'User not found error',
@@ -227,13 +263,14 @@ const changePassword = async (req, res) => {
// check if the new password is strong
if (
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
checkPasswordStrength(newPassword)
)
[
apiConstants.PASSWORD.TOO_WEAK,
apiConstants.PASSWORD.WEAK,
].includes(checkPasswordStrength(newPassword))
) {
return res.status(400).send({
message: errorMessage.WEAK_PASSWORD_ERROR
})
message: errorMessage.WEAK_PASSWORD_ERROR,
});
/*
#swagger.responses[400] = {
@@ -246,23 +283,26 @@ const changePassword = async (req, res) => {
}
// hash the password
const { salt, hashedPassword } = await hashPassword(newPassword, SALT_ROUNDS)
const { salt, hashedPassword } = await hashPassword(
newPassword,
SALT_ROUNDS,
);
user.salt = salt
user.hashed_password = hashedPassword
user.salt = salt;
user.hashed_password = hashedPassword;
// update the user
rowService.update({
tableName: USERS_TABLE,
lookupField: tableFields.ID,
fieldsString: `${tableFields.HASHED_PASSWORD} = '${hashedPassword}', ${tableFields.SALT} = '${salt}'`,
pks: `${user.id}`
})
pks: `${user.id}`,
});
res.status(200).send({
message: successMessage.PASSWORD_UPDATE_SUCCESS,
data: { id: user.id, username: user.username }
})
data: { id: user.id, username: user.username },
});
/*
#swagger.responses[200] = {
@@ -273,51 +313,60 @@ const changePassword = async (req, res) => {
}
*/
} catch (error) {
res.status(500).send({ message: errorMessage.SERVER_ERROR })
res.status(500).send({ message: errorMessage.SERVER_ERROR });
}
}
};
const createInitialUser = async () => {
// extract some fields from the environment variables or from the CLI
const { initialUserUsername: username, initialUserPassword: password } = config
const { initialUserUsername: username, initialUserPassword: password } =
config;
try {
// check if there are users in the DB
const users = authService.getAllUsers()
const users = authService.getAllUsers();
if (users.length <= 0) {
// check if initial users username is passed from the env or CLI
if (!username) {
console.error(errorMessage.INITIAL_USER_USERNAME_NOT_PASSED_ERROR)
process.exit(1)
console.error(
errorMessage.INITIAL_USER_USERNAME_NOT_PASSED_ERROR,
);
process.exit(1);
}
// check if initial users password is passed from the env or CLI
if (!password) {
console.error(errorMessage.INITIAL_USER_PASSWORD_NOT_PASSED_ERROR)
process.exit(1)
console.error(
errorMessage.INITIAL_USER_PASSWORD_NOT_PASSED_ERROR,
);
process.exit(1);
}
// check if the usernmae is taken
const users = authService.getUsersByUsername({ username })
const users = authService.getUsersByUsername({ username });
if (users.length > 0) {
console.error(errorMessage.USERNAME_TAKEN_ERROR)
process.exit(1)
console.error(errorMessage.USERNAME_TAKEN_ERROR);
process.exit(1);
}
// check if the password is strong
if (
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
checkPasswordStrength(password)
)
[
apiConstants.PASSWORD.TOO_WEAK,
apiConstants.PASSWORD.WEAK,
].includes(checkPasswordStrength(password))
) {
console.error(errorMessage.WEAK_PASSWORD_ERROR)
process.exit(1)
console.error(errorMessage.WEAK_PASSWORD_ERROR);
process.exit(1);
}
// hash the password
const { hashedPassword, salt } = await hashPassword(password, SALT_ROUNDS)
const { hashedPassword, salt } = await hashPassword(
password,
SALT_ROUNDS,
);
// create the initial user
const { lastInsertRowid: userId } = rowService.save({
@@ -326,38 +375,38 @@ const createInitialUser = async () => {
username,
hashed_password: hashedPassword,
salt,
is_superuser: 'false'
}
})
is_superuser: "false",
},
});
// get the default role from the DB
const roles = authService.getDefaultRole()
const roles = authService.getDefaultRole();
if (roles.length <= 0) {
console.log(errorMessage.DEFAULT_ROLE_NOT_CREATED_ERROR)
process.exit(1)
console.log(errorMessage.DEFAULT_ROLE_NOT_CREATED_ERROR);
process.exit(1);
}
const defaultRoleId = roles[0].id
const defaultRoleId = roles[0].id;
// create a _users_role for the initial user
rowService.save({
tableName: USERS_ROLES_TABLE,
fields: { user_id: userId, role_id: defaultRoleId }
})
fields: { user_id: userId, role_id: defaultRoleId },
});
console.log(successMessage.INITIAL_USER_CREATED_SUCCESS)
console.log(successMessage.INITIAL_USER_CREATED_SUCCESS);
} else {
console.log(infoMessage.INITIAL_USER_ALREADY_CREATED)
console.log(infoMessage.INITIAL_USER_ALREADY_CREATED);
}
} catch (error) {
console.log(error)
console.log(error);
}
}
};
module.exports = {
updateSuperuser,
registerUser,
changePassword,
createInitialUser
}
createInitialUser,
};

View File

@@ -1,24 +1,27 @@
const supertest = require('supertest')
const supertest = require("supertest");
const app = require('../index')
const requestWithSupertest = supertest(app)
jest.useFakeTimers();
jest.spyOn(global, "setInterval");
describe('Root Endpoints', () => {
it('GET / should return server version and timestamp', async () => {
const res = await requestWithSupertest.get('/api')
expect(res.status).toEqual(200)
expect(res.type).toEqual(expect.stringContaining('json'))
expect(res.body).toHaveProperty('message')
expect(res.body).toHaveProperty('data')
expect(res.body.data).toHaveProperty('version')
expect(res.body.data).toHaveProperty('timestamp')
})
})
const app = require("../index");
const requestWithSupertest = supertest(app);
describe('Health Endpoints', () => {
it('GET /health should return server version and timestamp', async () => {
const res = await requestWithSupertest.get('/api/health')
expect(res.status).toEqual(200)
expect(res.type).toEqual(expect.stringContaining('text'))
})
})
describe("Root Endpoints", () => {
it("GET / should return server version and timestamp", async () => {
const res = await requestWithSupertest.get("/api");
expect(res.status).toEqual(200);
expect(res.type).toEqual(expect.stringContaining("json"));
expect(res.body).toHaveProperty("message");
expect(res.body).toHaveProperty("data");
expect(res.body.data).toHaveProperty("version");
expect(res.body.data).toHaveProperty("timestamp");
});
});
describe("Health Endpoints", () => {
it("GET /health should return server version and timestamp", async () => {
const res = await requestWithSupertest.get("/api/health");
expect(res.status).toEqual(200);
expect(res.type).toEqual(expect.stringContaining("text"));
});
});

View File

@@ -1,330 +1,338 @@
const { not } = require('joi')
const supertest = require('supertest')
const { not } = require("joi");
const supertest = require("supertest");
const app = require('../index')
const config = require('../config')
const { generateToken } = require('../utils')
jest.useFakeTimers();
jest.spyOn(global, "setInterval");
const app = require("../index");
const config = require("../config");
const { generateToken } = require("../utils");
const requestWithSupertest = supertest(app)
const requestWithSupertest = supertest(app);
function queryString(params) {
const queryString = Object.keys(params)
.map((key) => `${key}=${params[key]}`)
.join('&')
.join("&");
return queryString
return queryString;
}
describe('Rows Endpoints', () => {
it('GET /tables/:name/rows should return a list of all rows', async () => {
describe("Rows Endpoints", () => {
it("GET /tables/:name/rows should return a list of all rows", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.get('/api/tables/users/rows')
.set('Cookie', [`accessToken=${accessToken}`])
.get("/api/tables/users/rows")
.set("Cookie", [`accessToken=${accessToken}`]);
expect(res.status).toEqual(200)
expect(res.type).toEqual(expect.stringContaining('json'))
expect(res.body).toHaveProperty('data')
expect(res.body.data).toEqual(expect.any(Array))
expect(res.body.data[0]).toHaveProperty('id')
expect(res.body.data[0]).toHaveProperty('firstName')
expect(res.body.data[0]).toHaveProperty('lastName')
})
expect(res.status).toEqual(200);
expect(res.type).toEqual(expect.stringContaining("json"));
expect(res.body).toHaveProperty("data");
expect(res.body.data).toEqual(expect.any(Array));
expect(res.body.data[0]).toHaveProperty("id");
expect(res.body.data[0]).toHaveProperty("firstName");
expect(res.body.data[0]).toHaveProperty("lastName");
});
it('GET /tables/:name/rows?_limit=8&_schema=firstName,lastName&_ordering:-firstName&_page=2: should query the rows by the provided query params', async () => {
it("GET /tables/:name/rows?_limit=8&_schema=firstName,lastName&_ordering:-firstName&_page=2: should query the rows by the provided query params", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const params = {
_search: 'a',
_ordering: '-firstName',
_schema: 'firstName,lastName',
_search: "a",
_ordering: "-firstName",
_schema: "firstName,lastName",
_limit: 8,
_page: 2
}
const query = queryString(params)
_page: 2,
};
const query = queryString(params);
const res = await requestWithSupertest
.get(`/api/tables/users/rows?${query}`)
.set('Cookie', [`accessToken=${accessToken}`])
.set("Cookie", [`accessToken=${accessToken}`]);
expect(res.status).toEqual(200)
expect(res.type).toEqual(expect.stringContaining('json'))
expect(res.body).toHaveProperty('data')
expect(res.body.data).toEqual(expect.any(Array))
expect(res.body.data[0]).toHaveProperty('firstName')
expect(res.body.data[0]).toHaveProperty('lastName')
expect(res.status).toEqual(200);
expect(res.type).toEqual(expect.stringContaining("json"));
expect(res.body).toHaveProperty("data");
expect(res.body.data).toEqual(expect.any(Array));
expect(res.body.data[0]).toHaveProperty("firstName");
expect(res.body.data[0]).toHaveProperty("lastName");
expect(res.body.next).toEqual(
`/tables/users/rows?${queryString({
...params,
_page: params._page + 1
}).toString()}`
)
_page: params._page + 1,
}).toString()}`,
);
expect(res.body.previous).toEqual(
`/tables/users/rows?${queryString({
...params,
_page: params._page - 1
}).toString()}`
)
})
_page: params._page - 1,
}).toString()}`,
);
});
it('GET /tables/:name/rows: should return a null field', async () => {
it("GET /tables/:name/rows: should return a null field", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.get('/api/tables/users/rows?_filters=firstName__null,lastName__notnull')
.set('Cookie', [`accessToken=${accessToken}`])
.get(
"/api/tables/users/rows?_filters=firstName__null,lastName__notnull",
)
.set("Cookie", [`accessToken=${accessToken}`]);
expect(res.status).toEqual(200)
expect(res.body.data[0].firstName).toBeNull()
expect(res.body.data[0].lastName).not.toBeNull()
})
expect(res.status).toEqual(200);
expect(res.body.data[0].firstName).toBeNull();
expect(res.body.data[0].lastName).not.toBeNull();
});
it('GET /tables/:name/rows: should successfully retrieve users created after 2010-01-01 00:00:00.', async () => {
it("GET /tables/:name/rows: should successfully retrieve users created after 2010-01-01 00:00:00.", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const date = '2010-01-01 00:00:00'
const date = "2010-01-01 00:00:00";
const res = await requestWithSupertest
.get(`/api/tables/users/rows?_filters=createdAt__gte:${date}`)
.set('Cookie', [`accessToken=${accessToken}`])
.set("Cookie", [`accessToken=${accessToken}`]);
res.body.data.map((user) => {
const createdAt = new Date(user.createdAt)
const referenceDate = new Date(date)
expect(createdAt.getTime()).toBeGreaterThan(referenceDate.getTime())
})
const createdAt = new Date(user.createdAt);
const referenceDate = new Date(date);
expect(createdAt.getTime()).toBeGreaterThan(
referenceDate.getTime(),
);
});
expect(res.status).toEqual(200)
expect(res.body.data[0]).toHaveProperty('id')
expect(res.body.data[0]).toHaveProperty('firstName')
expect(res.body.data[0]).toHaveProperty('lastName')
expect(res.body.data[0]).toHaveProperty('createdAt')
})
expect(res.status).toEqual(200);
expect(res.body.data[0]).toHaveProperty("id");
expect(res.body.data[0]).toHaveProperty("firstName");
expect(res.body.data[0]).toHaveProperty("lastName");
expect(res.body.data[0]).toHaveProperty("createdAt");
});
it('GET /tables/:name/rows: should successfully retrieve users created before 2008-01-20 00:00:00.', async () => {
it("GET /tables/:name/rows: should successfully retrieve users created before 2008-01-20 00:00:00.", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const date = '2008-01-20 00:00:00'
const date = "2008-01-20 00:00:00";
const res = await requestWithSupertest
.get(`/api/tables/users/rows?_filters=createdAt__lte:${date}`)
.set('Cookie', [`accessToken=${accessToken}`])
.set("Cookie", [`accessToken=${accessToken}`]);
res.body.data.map((user) => {
const createdAt = new Date(user.createdAt)
const referenceDate = new Date(date)
expect(createdAt.getTime()).toBeLessThan(referenceDate.getTime())
})
const createdAt = new Date(user.createdAt);
const referenceDate = new Date(date);
expect(createdAt.getTime()).toBeLessThan(referenceDate.getTime());
});
expect(res.status).toEqual(200)
expect(res.body.data[0]).toHaveProperty('id')
expect(res.body.data[0]).toHaveProperty('firstName')
expect(res.body.data[0]).toHaveProperty('lastName')
expect(res.body.data[0]).toHaveProperty('createdAt')
})
expect(res.status).toEqual(200);
expect(res.body.data[0]).toHaveProperty("id");
expect(res.body.data[0]).toHaveProperty("firstName");
expect(res.body.data[0]).toHaveProperty("lastName");
expect(res.body.data[0]).toHaveProperty("createdAt");
});
it('GET /tables/:name/rows: should successfully retrieve users created at 2013-01-08 00:00:00', async () => {
it("GET /tables/:name/rows: should successfully retrieve users created at 2013-01-08 00:00:00", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const date = '2013-01-08 00:00:00'
const date = "2013-01-08 00:00:00";
const res = await requestWithSupertest
.get(`/api/tables/users/rows?_filters=createdAt__eq:${date}`)
.set('Cookie', [`accessToken=${accessToken}`])
.set("Cookie", [`accessToken=${accessToken}`]);
res.body.data.map((user) => {
const createdAt = new Date(user.createdAt)
const referenceDate = new Date(date)
expect(createdAt.getTime()).toEqual(referenceDate.getTime())
})
const createdAt = new Date(user.createdAt);
const referenceDate = new Date(date);
expect(createdAt.getTime()).toEqual(referenceDate.getTime());
});
expect(res.status).toEqual(200)
expect(res.body.data[0]).toHaveProperty('id')
expect(res.body.data[0]).toHaveProperty('firstName')
expect(res.body.data[0]).toHaveProperty('lastName')
expect(res.body.data[0]).toHaveProperty('createdAt')
})
expect(res.status).toEqual(200);
expect(res.body.data[0]).toHaveProperty("id");
expect(res.body.data[0]).toHaveProperty("firstName");
expect(res.body.data[0]).toHaveProperty("lastName");
expect(res.body.data[0]).toHaveProperty("createdAt");
});
it('GET /tables/:name/rows: should successfully retrieve users created at 2007-01-08 00:00:00', async () => {
it("GET /tables/:name/rows: should successfully retrieve users created at 2007-01-08 00:00:00", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const date = '2007-01-08 00:00:00'
const date = "2007-01-08 00:00:00";
const res = await requestWithSupertest
.get(`/api/tables/users/rows?_filters=createdAt__eq:${date}`)
.set('Cookie', [`accessToken=${accessToken}`])
.set("Cookie", [`accessToken=${accessToken}`]);
//There are no users that are created at 2007-01-08 00:00:00 so the API should return empty data
expect(res.body.data).toHaveLength(0)
expect(res.status).toEqual(200)
})
expect(res.body.data).toHaveLength(0);
expect(res.status).toEqual(200);
});
it('GET /tables/:name/rows: should successfully retrieve users that are not created at 2021-01-08 00:00:00', async () => {
it("GET /tables/:name/rows: should successfully retrieve users that are not created at 2021-01-08 00:00:00", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const date = '2021-01-08 00:00:00'
const date = "2021-01-08 00:00:00";
const res = await requestWithSupertest
.get(`/api/tables/users/rows?_filters=createdAt__neq:${date}`)
.set('Cookie', [`accessToken=${accessToken}`])
.set("Cookie", [`accessToken=${accessToken}`]);
res.body.data.map((user) => {
const createdAt = new Date(user.createdAt)
const referenceDate = new Date(date)
expect(createdAt.getTime()).not.toEqual(referenceDate.getTime())
})
const createdAt = new Date(user.createdAt);
const referenceDate = new Date(date);
expect(createdAt.getTime()).not.toEqual(referenceDate.getTime());
});
expect(res.status).toEqual(200)
expect(res.body.data[0]).toHaveProperty('id')
expect(res.body.data[0]).toHaveProperty('firstName')
expect(res.body.data[0]).toHaveProperty('lastName')
expect(res.body.data[0]).toHaveProperty('createdAt')
})
expect(res.status).toEqual(200);
expect(res.body.data[0]).toHaveProperty("id");
expect(res.body.data[0]).toHaveProperty("firstName");
expect(res.body.data[0]).toHaveProperty("lastName");
expect(res.body.data[0]).toHaveProperty("createdAt");
});
it('POST /tables/:name/rows should insert a new row and return the lastInsertRowid', async () => {
it("POST /tables/:name/rows should insert a new row and return the lastInsertRowid", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.post('/api/tables/users/rows')
.set('Cookie', [`accessToken=${accessToken}`])
.send({ fields: { firstName: 'Jane', lastName: 'Doe' } })
.post("/api/tables/users/rows")
.set("Cookie", [`accessToken=${accessToken}`])
.send({ fields: { firstName: "Jane", lastName: "Doe" } });
expect(res.status).toEqual(201)
expect(res.type).toEqual(expect.stringContaining('json'))
expect(res.body).toHaveProperty('data')
})
expect(res.status).toEqual(201);
expect(res.type).toEqual(expect.stringContaining("json"));
expect(res.body).toHaveProperty("data");
});
it('GET /tables/:name/rows/:pks should return a row by its primary key', async () => {
it("GET /tables/:name/rows/:pks should return a row by its primary key", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.get('/api/tables/users/rows/1')
.set('Cookie', [`accessToken=${accessToken}`])
.get("/api/tables/users/rows/1")
.set("Cookie", [`accessToken=${accessToken}`]);
expect(res.status).toEqual(200)
expect(res.type).toEqual(expect.stringContaining('json'))
expect(res.body).toHaveProperty('data')
expect(res.body.data[0]).toHaveProperty('id')
expect(res.body.data[0]).toHaveProperty('firstName')
expect(res.body.data[0]).toHaveProperty('lastName')
})
expect(res.status).toEqual(200);
expect(res.type).toEqual(expect.stringContaining("json"));
expect(res.body).toHaveProperty("data");
expect(res.body.data[0]).toHaveProperty("id");
expect(res.body.data[0]).toHaveProperty("firstName");
expect(res.body.data[0]).toHaveProperty("lastName");
});
it('PUT /tables/:name/rows/:pks should update a row by its primary key and return the number of changes', async () => {
it("PUT /tables/:name/rows/:pks should update a row by its primary key and return the number of changes", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.put('/api/tables/users/rows/1')
.set('Cookie', [`accessToken=${accessToken}`])
.send({ fields: { firstName: 'Jane', lastName: 'Doe' } })
expect(res.status).toEqual(200)
expect(res.type).toEqual(expect.stringContaining('json'))
})
.put("/api/tables/users/rows/1")
.set("Cookie", [`accessToken=${accessToken}`])
.send({ fields: { firstName: "Jane", lastName: "Doe" } });
expect(res.status).toEqual(200);
expect(res.type).toEqual(expect.stringContaining("json"));
});
it('DELETE /tables/:name/rows/:pks should delete a row by its primary key and return the number of changes', async () => {
it("DELETE /tables/:name/rows/:pks should delete a row by its primary key and return the number of changes", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.delete('/api/tables/users/rows/1')
.set('Cookie', [`accessToken=${accessToken}`])
expect(res.status).toEqual(200)
expect(res.type).toEqual(expect.stringContaining('json'))
})
.delete("/api/tables/users/rows/1")
.set("Cookie", [`accessToken=${accessToken}`]);
expect(res.status).toEqual(200);
expect(res.type).toEqual(expect.stringContaining("json"));
});
it('POST /tables/:name/rows should insert a new row if any of the value of the object being inserted is null', async () => {
it("POST /tables/:name/rows should insert a new row if any of the value of the object being inserted is null", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.post('/api/tables/users/rows')
.post("/api/tables/users/rows")
.send({
fields: {
firstName: null,
lastName: 'Doe',
lastName: "Doe",
email: null,
username: 'Jane'
}
username: "Jane",
},
})
.set('Cookie', [`accessToken=${accessToken}`])
expect(res.status).toEqual(201)
expect(res.type).toEqual(expect.stringContaining('json'))
expect(res.body).toHaveProperty('data')
})
.set("Cookie", [`accessToken=${accessToken}`]);
expect(res.status).toEqual(201);
expect(res.type).toEqual(expect.stringContaining("json"));
expect(res.body).toHaveProperty("data");
});
it('GET /tables/:name/rows should return values if any of the IDs from the array match the user ID.', async () => {
it("GET /tables/:name/rows should return values if any of the IDs from the array match the user ID.", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.get('/api/tables/users/rows?_filters=id:[2,3]')
.set('Cookie', [`accessToken=${accessToken}`])
expect(res.status).toEqual(200)
expect(res.body).toHaveProperty('data')
expect(res.body.data).toEqual(expect.any(Array))
expect(res.body.data.length).toEqual(2)
})
.get("/api/tables/users/rows?_filters=id:[2,3]")
.set("Cookie", [`accessToken=${accessToken}`]);
expect(res.status).toEqual(200);
expect(res.body).toHaveProperty("data");
expect(res.body.data).toEqual(expect.any(Array));
expect(res.body.data.length).toEqual(2);
});
it('GET /tables/:name/rows should return values if the provided ID matches the user ID.', async () => {
it("GET /tables/:name/rows should return values if the provided ID matches the user ID.", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.get('/api/tables/users/rows?_filters=id:2,firstName:Michael,lastName:Lee')
.set('Cookie', [`accessToken=${accessToken}`])
expect(res.status).toEqual(200)
expect(res.body).toHaveProperty('data')
expect(res.body.data).toEqual(expect.any(Array))
expect(res.body.data.length).toEqual(1)
})
})
.get(
"/api/tables/users/rows?_filters=id:2,firstName:Michael,lastName:Lee",
)
.set("Cookie", [`accessToken=${accessToken}`]);
expect(res.status).toEqual(200);
expect(res.body).toHaveProperty("data");
expect(res.body.data).toEqual(expect.any(Array));
expect(res.body.data.length).toEqual(1);
});
});

View File

@@ -1,92 +1,94 @@
const supertest = require('supertest')
const supertest = require("supertest");
const app = require('../index')
const { generateToken } = require('../utils')
const config = require('../config')
jest.useFakeTimers();
jest.spyOn(global, "setInterval");
const app = require("../index");
const { generateToken } = require("../utils");
const config = require("../config");
const requestWithSupertest = supertest(app)
const requestWithSupertest = supertest(app);
describe('Tables Endpoints', () => {
it('GET /tables should return a list of all tables', async () => {
describe("Tables Endpoints", () => {
it("GET /tables should return a list of all tables", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.get('/api/tables')
.set('Cookie', [`accessToken=${accessToken}`])
.get("/api/tables")
.set("Cookie", [`accessToken=${accessToken}`]);
expect(res.status).toEqual(200)
expect(res.type).toEqual(expect.stringContaining('json'))
expect(res.body).toHaveProperty('data')
expect(res.body.data).toEqual(expect.any(Array))
expect(res.body.data[0]).toHaveProperty('name')
})
expect(res.status).toEqual(200);
expect(res.type).toEqual(expect.stringContaining("json"));
expect(res.body).toHaveProperty("data");
expect(res.body.data).toEqual(expect.any(Array));
expect(res.body.data[0]).toHaveProperty("name");
});
it('POST /tables should create a new table and return generated schema', async () => {
it("POST /tables should create a new table and return generated schema", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.post('/api/tables')
.post("/api/tables")
.send({
name: 'pets',
name: "pets",
autoAddCreatedAt: true,
autoAddUpdatedAt: false,
schema: [
{
name: 'owner',
type: 'INTEGER',
name: "owner",
type: "INTEGER",
foreignKey: {
table: 'users',
column: 'id',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
}
table: "users",
column: "id",
onDelete: "CASCADE",
onUpdate: "CASCADE",
},
},
{
name: 'name',
type: 'TEXT',
notNull: true
name: "name",
type: "TEXT",
notNull: true,
},
{
name: 'petId',
name: "petId",
unique: true,
type: 'INTEGER'
}
]
type: "INTEGER",
},
],
})
.set('Cookie', [`accessToken=${accessToken}`])
.set("Cookie", [`accessToken=${accessToken}`]);
expect(res.status).toEqual(201)
expect(res.type).toEqual(expect.stringContaining('json'))
expect(res.body).toHaveProperty('data')
expect(res.body.data).toHaveProperty('name')
expect(res.body.data).toHaveProperty('schema')
expect(res.body.data.schema).toEqual(expect.any(Array))
expect(res.body.data.schema[0]).toHaveProperty('name')
expect(res.body.data.schema[0]).toHaveProperty('cid')
})
expect(res.status).toEqual(201);
expect(res.type).toEqual(expect.stringContaining("json"));
expect(res.body).toHaveProperty("data");
expect(res.body.data).toHaveProperty("name");
expect(res.body.data).toHaveProperty("schema");
expect(res.body.data.schema).toEqual(expect.any(Array));
expect(res.body.data.schema[0]).toHaveProperty("name");
expect(res.body.data.schema[0]).toHaveProperty("cid");
});
it('GET /tables/:name should return schema of the table', async () => {
it("GET /tables/:name should return schema of the table", async () => {
const accessToken = await generateToken(
{ username: 'John', isSuperuser: true },
{ username: "John", isSuperuser: true },
config.tokenSecret,
'1H'
)
"1H",
);
const res = await requestWithSupertest
.get('/api/tables/users')
.set('Cookie', [`accessToken=${accessToken}`])
.get("/api/tables/users")
.set("Cookie", [`accessToken=${accessToken}`]);
expect(res.status).toEqual(200)
expect(res.type).toEqual(expect.stringContaining('json'))
expect(res.body).toHaveProperty('data')
expect(res.body.data).toEqual(expect.any(Array))
})
})
expect(res.status).toEqual(200);
expect(res.type).toEqual(expect.stringContaining("json"));
expect(res.body).toHaveProperty("data");
expect(res.body.data).toEqual(expect.any(Array));
});
});

View File

@@ -1,9 +1,9 @@
const { dropTestDatabase } = require('.')
const { dropTestDatabase } = require(".");
const globalTearDown = () => {
console.log('Test suite finished')
console.log('Dropping test database...')
dropTestDatabase()
}
const globalTearDown = async () => {
console.log("Test suite finished");
console.log("Dropping test database...");
await dropTestDatabase();
};
module.exports = globalTearDown
module.exports = globalTearDown;

View File

@@ -1,75 +1,105 @@
const testNames = [
{ firstName: 'Emily', lastName: 'William', createdAt: '2008-01-08 00:00:00' },
{ firstName: 'Michael', lastName: 'Lee', createdAt: '2009-01-08 00:00:00' },
{ firstName: 'Sarah', lastName: 'Johnson', createdAt: '2010-01-08 00:00:00' },
{ firstName: 'David', lastName: 'Chen', createdAt: '2011-01-08 00:00:00' },
{
firstName: 'Olivia',
lastName: 'William',
createdAt: '2012-01-08 00:00:00'
firstName: "Emily",
lastName: "William",
createdAt: "2008-01-08 00:00:00",
},
{ firstName: 'William', lastName: 'Kim', createdAt: '2013-01-08 00:00:00' },
{ firstName: 'Sophia', lastName: 'Singh', createdAt: '2013-02-08 00:00:00' },
{ firstName: "Michael", lastName: "Lee", createdAt: "2009-01-08 00:00:00" },
{
firstName: 'James',
lastName: 'Rodriguez',
createdAt: '2013-03-08 00:00:00'
firstName: "Sarah",
lastName: "Johnson",
createdAt: "2010-01-08 00:00:00",
},
{ firstName: 'Ava', lastName: 'Patel', createdAt: '2013-01-04 00:00:00' },
{ firstName: "David", lastName: "Chen", createdAt: "2011-01-08 00:00:00" },
{
firstName: 'Benjamin',
lastName: 'Garcia',
createdAt: '2015-01-08 00:00:00'
firstName: "Olivia",
lastName: "William",
createdAt: "2012-01-08 00:00:00",
},
{ firstName: "William", lastName: "Kim", createdAt: "2013-01-08 00:00:00" },
{
firstName: "Sophia",
lastName: "Singh",
createdAt: "2013-02-08 00:00:00",
},
{
firstName: 'Isabella',
lastName: 'Nguyen',
createdAt: '2014-01-08 00:00:00'
firstName: "James",
lastName: "Rodriguez",
createdAt: "2013-03-08 00:00:00",
},
{ firstName: 'Ethan', lastName: 'Lee', createdAt: '2016-01-08 00:00:00' },
{ firstName: 'Mia', lastName: 'Wilson', createdAt: '2017-01-08 00:00:00' },
{ firstName: "Ava", lastName: "Patel", createdAt: "2013-01-04 00:00:00" },
{
firstName: 'Alexander',
lastName: 'William',
createdAt: '2018-01-08 00:00:00'
firstName: "Benjamin",
lastName: "Garcia",
createdAt: "2015-01-08 00:00:00",
},
{
firstName: 'Charlotte',
lastName: 'Hernandez',
createdAt: '2019-01-08 00:00:00'
firstName: "Isabella",
lastName: "Nguyen",
createdAt: "2014-01-08 00:00:00",
},
{ firstName: 'Liam', lastName: 'Gonzalez', createdAt: '2020-01-08 00:00:00' },
{ firstName: 'Emma', lastName: 'Gomez', createdAt: '2021-01-08 00:00:00' },
{ firstName: 'Noah', lastName: 'Perez', createdAt: '2021-01-08 00:00:00' },
{ firstName: 'Avery', lastName: 'Ramirez', createdAt: '2023-02-08 00:00:00' },
{ firstName: 'Jacob', lastName: 'Turner', createdAt: '2023-03-08 00:00:00' },
{ firstName: "Ethan", lastName: "Lee", createdAt: "2016-01-08 00:00:00" },
{ firstName: "Mia", lastName: "Wilson", createdAt: "2017-01-08 00:00:00" },
{
firstName: 'Abigail',
lastName: 'Williams',
createdAt: '2023-02-10 00:00:00'
firstName: "Alexander",
lastName: "William",
createdAt: "2018-01-08 00:00:00",
},
{ firstName: 'Elijah', lastName: 'Hall', createdAt: '2023-04-02 00:00:00' },
{ firstName: 'Mila', lastName: 'Flores', createdAt: '2023-05-13 00:00:00' },
{
firstName: 'Evelyn',
lastName: 'Morales',
createdAt: '2023-06-05 00:00:00'
firstName: "Charlotte",
lastName: "Hernandez",
createdAt: "2019-01-08 00:00:00",
},
{ firstName: 'Logan', lastName: 'Collins', createdAt: '2023-06-07 00:00:00' },
{ firstName: null, lastName: 'Flores', createdAt: '2023-06-09 00:00:00' }
]
{
firstName: "Liam",
lastName: "Gonzalez",
createdAt: "2020-01-08 00:00:00",
},
{ firstName: "Emma", lastName: "Gomez", createdAt: "2021-01-08 00:00:00" },
{ firstName: "Noah", lastName: "Perez", createdAt: "2021-01-08 00:00:00" },
{
firstName: "Avery",
lastName: "Ramirez",
createdAt: "2023-02-08 00:00:00",
},
{
firstName: "Jacob",
lastName: "Turner",
createdAt: "2023-03-08 00:00:00",
},
{
firstName: "Abigail",
lastName: "Williams",
createdAt: "2023-02-10 00:00:00",
},
{ firstName: "Elijah", lastName: "Hall", createdAt: "2023-04-02 00:00:00" },
{ firstName: "Mila", lastName: "Flores", createdAt: "2023-05-13 00:00:00" },
{
firstName: "Evelyn",
lastName: "Morales",
createdAt: "2023-06-05 00:00:00",
},
{
firstName: "Logan",
lastName: "Collins",
createdAt: "2023-06-07 00:00:00",
},
{ firstName: null, lastName: "Flores", createdAt: "2023-06-09 00:00:00" },
];
const testData = {
strongPassword: 'HeK34#C44DMJ',
strongPassword2: 'Mk22#c9@Cv!K',
weakPassword: '12345678',
invalidUsername: 'invalid_username',
invalidPassword: 'invalid_password',
strongPassword: "HeK34#C44DMJ",
strongPassword2: "Mk22#c9@Cv!K",
weakPassword: "12345678",
invalidUsername: "invalid_username",
invalidPassword: "invalid_password",
users: {
user1: { username: 'Jane' },
user2: { username: 'Mike' },
user3: { username: 'John' }
}
}
frozenUser1: { username: "Jane" },
frozenUser2: { username: "Mary" },
weakPasswordUser: { username: "Penny" },
changeNameUser: { username: "Mike" },
changePasswordUser: { username: "John" },
},
};
module.exports = { testNames, testData }
module.exports = { testNames, testData };