diff --git a/bun.lockb b/bun.lockb index 6ccbe12..265ae53 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/e2e-tests/login.ts b/e2e-tests/login.ts new file mode 100644 index 0000000..e1e9f42 --- /dev/null +++ b/e2e-tests/login.ts @@ -0,0 +1,23 @@ +import request from 'supertest'; + +export const login = async (app: any) => { + const res = await request(app) + .post('/graphql') + .send({ + query: ` + mutation Login($email: String!, $password: String!) { + login(email: $email, password: $password) + } + `, + variables: { + email: "superuser@example.com", + password: "superpassword" + }, + }); + + if (!res.body.data || !res.body.data.login) { + throw new Error('Login failed or token not returned'); + } + + return res.body.data.login; +}; diff --git a/e2e-tests/setup.ts b/e2e-tests/setup.ts new file mode 100644 index 0000000..e3e5d27 --- /dev/null +++ b/e2e-tests/setup.ts @@ -0,0 +1,68 @@ +import { MongoMemoryServer } from 'mongodb-memory-server'; +import mongoose from 'mongoose'; +import express from 'express'; +import { ApolloServer } from 'apollo-server-express'; +import jwt from 'jsonwebtoken'; +import PluginLoader from '../src/plugins/plugin-loader'; // Adjust the path as needed +import { setMongoServer } from './teardown'; + +const JWT_SECRET = process.env.JWT_SECRET || 'your_jwt_secret'; + +let server: ApolloServer; +let app: express.Application; + +export const setup = async () => { + const mongoServer = await MongoMemoryServer.create(); + const uri = mongoServer.getUri(); + + await mongoose.connect(uri); + + const pluginLoader = new PluginLoader(); + pluginLoader.loadPlugins(); + pluginLoader.initializePlugins(); + + const schema = await pluginLoader.createSchema(); + pluginLoader.registerModels(); + + app = express(); + + server = new ApolloServer({ + schema, + context: async ({ req }) => { + let user = null; + const authHeader = req.headers.authorization || ''; + const token = authHeader.split(' ')[1]; + + if (token) { + try { + const decoded: any = jwt.verify(token, JWT_SECRET); + const UserModel = mongoose.model('User'); // Access the User model dynamically + user = await UserModel.findById(decoded.id); + } catch (error) { + console.error('Error decoding token:', error); + } + } + + // Mock enforcer for testing purposes + const enforcer = { + enforce: (action: string, resource: string) => { + return true; // Allow all actions for testing + }, + }; + + return { + ...pluginLoader.context, + req, + user, + enforcer, + }; + }, + }); + + await server.start(); + server.applyMiddleware({ app, path: '/graphql' }); + + setMongoServer(mongoServer); + + return app; +}; diff --git a/e2e-tests/teardown.ts b/e2e-tests/teardown.ts new file mode 100644 index 0000000..53c487f --- /dev/null +++ b/e2e-tests/teardown.ts @@ -0,0 +1,15 @@ +import mongoose from 'mongoose'; +import { MongoMemoryServer } from 'mongodb-memory-server'; + +let mongoServer: MongoMemoryServer; + +export const setMongoServer = (server: MongoMemoryServer) => { + mongoServer = server; +}; + +export const teardown = async () => { + await mongoose.disconnect(); + if (mongoServer) { + await mongoServer.stop(); + } +}; diff --git a/e2e-tests/user/user.test.ts b/e2e-tests/user/user.test.ts new file mode 100644 index 0000000..3c6860d --- /dev/null +++ b/e2e-tests/user/user.test.ts @@ -0,0 +1,46 @@ +import { describe, it, beforeEach, afterEach } from 'bun:test'; +import { expect } from 'chai'; +import { setup } from '../setup'; +import { teardown } from '../teardown'; +import { initDB, clearDB } from '../utils'; +import { login } from '../login'; +import request from 'supertest'; + +let app: any; +let token: string; + +beforeEach(async () => { + app = await setup(); + await initDB(); + token = await login(app); +}); + +afterEach(async () => { + await clearDB(); + await teardown(); +}); + +describe('User Queries', () => { + it('should return a list of users', async () => { + const res = await request(app) + .post('/graphql') + .set('Authorization', `Bearer ${token}`) + .send({ + query: ` + query { + users { + name + email + } + } + `, + }) + .expect(200); + expect(res.body.data.users).to.have.lengthOf(3); + expect(res.body.data.users).to.deep.equal([ + { name: 'John Doe', email: 'john.doe@example.com' }, + { name: 'Jane Doe', email: 'jane.doe@example.com' }, + { name: 'Super User', email: 'superuser@example.com' }, + ]); + }); +}); diff --git a/e2e-tests/utils.ts b/e2e-tests/utils.ts new file mode 100644 index 0000000..714b216 --- /dev/null +++ b/e2e-tests/utils.ts @@ -0,0 +1,16 @@ +import { UserModel } from '../src/plugins/auth-plugin/models/user'; // Adjust the path as needed +import bcrypt from 'bcrypt'; + +export const initDB = async () => { + //we need users to be able to hit auth and non-auth endpoints + const users = [ + { name: 'John Doe', email: 'john.doe@example.com', password: await bcrypt.hash('password123', 10) }, + { name: 'Jane Doe', email: 'jane.doe@example.com', password: await bcrypt.hash('password123', 10) }, + { name: 'Super User', email: 'superuser@example.com', password: await bcrypt.hash('superpassword', 10), role: 'superuser' }, + ]; + await UserModel.insertMany(users); +}; + +export const clearDB = async () => { + await UserModel.deleteMany({}); +}; diff --git a/package.json b/package.json index 23475d3..1451389 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "lint": "eslint 'src/**/*.{ts,tsx}'", "lint:fix": "eslint 'src/**/*.{ts,tsx}' --fix", "format": "prettier --write 'src/**/*.{ts,tsx,js,jsx,json,css,scss,md}'", - "dev": "nodemon --exec bun run src/index.ts" + "dev": "nodemon --exec bun run src/index.ts", + "test:e2e": "bun test e2e-tests" }, "peerDependencies": { "typescript": "^5.4.5" @@ -33,18 +34,23 @@ "devDependencies": { "@types/bcrypt": "^5.0.2", "@types/bun": "latest", + "@types/chai": "^4.3.16", "@types/express": "^4.17.21", "@types/jsonwebtoken": "^9.0.6", "@types/node": "^20.14.2", + "@types/supertest": "^6.0.2", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", + "chai": "^5.1.1", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "mongodb-memory-server": "^9.3.0", "nodemon": "^3.1.3", "prettier": "^3.3.2", - "ts-mockito": "^2.6.1" + "supertest": "^7.0.0", + "ts-mockito": "^2.6.1", + "ts-node": "^10.9.2" }, "overrides": { "@types/express": "^4.17.21"