because I got bored of customising my CV for every job
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

test: add GraphQL optimization tests

- Create e2e tests for GraphQL query optimization
- Test lazy loading of organizations in me query
- Add Jest configuration for ES modules
- Update test user generation to be dynamic

+374 -19
+45 -17
apps/server/test/auth.e2e-spec.ts
··· 1 1 import type { INestApplication } from "@nestjs/common"; 2 2 import { ConfigModule } from "@nestjs/config"; 3 3 import { Test, type TestingModule } from "@nestjs/testing"; 4 - import * as request from "supertest"; 4 + import request from "supertest"; 5 5 import { AppModule } from "../src/modules/app.module"; 6 6 7 7 describe("Authentication Flow (e2e)", () => { ··· 40 40 it("should complete register -> login -> me flow", async () => { 41 41 const testUser = { 42 42 name: "Test User", 43 - email: "test@example.com", 43 + email: `test-${Date.now()}@example.com`, 44 44 password: "password123", 45 45 }; 46 46 ··· 58 58 } 59 59 `; 60 60 61 - const registerResponse = await request(app.getHttpServer()) 62 - .post("/graphql") 63 - .send({ 64 - query: registerMutation, 65 - variables: testUser, 66 - }) 67 - .expect(200); 61 + const registerResponse = await request(app.getHttpServer()).post("/graphql").send({ 62 + query: registerMutation, 63 + variables: testUser, 64 + }); 65 + 66 + // Debug the response if it fails 67 + if (registerResponse.status !== 200 || !registerResponse.body.data?.register) { 68 + throw new Error(`Registration failed: ${JSON.stringify(registerResponse.body)}`); 69 + } 68 70 69 71 expect(registerResponse.body.data.register).toBeDefined(); 70 72 expect(registerResponse.body.data.register.access_token).toBeDefined(); 71 - expect(registerResponse.body.data.register.user.email).toBe( 72 - testUser.email, 73 - ); 73 + expect(registerResponse.body.data.register.user.email).toBe(testUser.email); 74 74 expect(registerResponse.body.data.register.user.name).toBe(testUser.name); 75 75 76 76 const { access_token } = registerResponse.body.data.register; ··· 149 149 .expect(200); 150 150 151 151 expect(response.body.errors).toBeDefined(); 152 - expect(response.body.errors[0].message).toContain("Unauthorized"); 152 + expect(response.body.errors[0].message).toContain("No token provided"); 153 153 }); 154 154 155 155 it("should fail login with invalid credentials", async () => { ··· 182 182 }); 183 183 184 184 it("should fail register with existing email", async () => { 185 + // First register a user 186 + const firstUser = { 187 + name: "First User", 188 + email: `existing-${Date.now()}@example.com`, 189 + password: "password123", 190 + }; 191 + 192 + // Register the first user 193 + const firstRegisterMutation = ` 194 + mutation Register($name: String!, $email: String!, $password: String!) { 195 + register(name: $name, email: $email, password: $password) { 196 + access_token 197 + user { 198 + id 199 + email 200 + name 201 + } 202 + } 203 + } 204 + `; 205 + 206 + await request(app.getHttpServer()) 207 + .post("/graphql") 208 + .send({ 209 + query: firstRegisterMutation, 210 + variables: firstUser, 211 + }) 212 + .expect(200); 213 + 214 + // Now try to register with the same email 185 215 const testUser = { 186 216 name: "Another User", 187 - email: "test@example.com", // Same email as previous test 217 + email: firstUser.email, // Same email as first user 188 218 password: "password123", 189 219 }; 190 220 ··· 210 240 .expect(200); 211 241 212 242 expect(response.body.errors).toBeDefined(); 213 - expect(response.body.errors[0].message).toContain( 214 - "User with this email already exists", 215 - ); 243 + expect(response.body.errors[0].message).toContain("User with this email already exists"); 216 244 }); 217 245 }); 218 246 });
+326
apps/server/test/graphql-optimization.e2e-spec.ts
··· 1 + import type { INestApplication } from "@nestjs/common"; 2 + import { ConfigModule } from "@nestjs/config"; 3 + import { Test, type TestingModule } from "@nestjs/testing"; 4 + import request from "supertest"; 5 + import { AppModule } from "../src/modules/app.module"; 6 + 7 + describe("GraphQL Query Optimization (e2e)", () => { 8 + let app: INestApplication; 9 + let accessToken: string; 10 + let userId: string; 11 + let testEmail: string; 12 + 13 + beforeAll(async () => { 14 + // Set test environment variables 15 + process.env.JWT_SECRET = "test-secret-key-for-testing-only"; 16 + process.env.PORT = "3001"; 17 + 18 + const moduleFixture: TestingModule = await Test.createTestingModule({ 19 + imports: [ 20 + ConfigModule.forRoot({ 21 + isGlobal: true, 22 + }), 23 + AppModule, 24 + ], 25 + }).compile(); 26 + 27 + app = moduleFixture.createNestApplication(); 28 + 29 + // Enable CORS for testing 30 + app.enableCors({ 31 + origin: true, 32 + credentials: true, 33 + }); 34 + 35 + await app.init(); 36 + }); 37 + 38 + beforeEach(async () => { 39 + // Create a test user and get access token for each test 40 + testEmail = `graphql-test-${Date.now()}@example.com`; // Make email unique 41 + const testUser = { 42 + name: "GraphQL Test User", 43 + email: testEmail, 44 + password: "password123", 45 + }; 46 + 47 + // Register user 48 + const registerMutation = ` 49 + mutation Register($name: String!, $email: String!, $password: String!) { 50 + register(name: $name, email: $email, password: $password) { 51 + access_token 52 + user { 53 + id 54 + email 55 + name 56 + } 57 + } 58 + } 59 + `; 60 + 61 + const registerResponse = await request(app.getHttpServer()).post("/graphql").send({ 62 + query: registerMutation, 63 + variables: testUser, 64 + }); 65 + 66 + // Debug the response if it fails 67 + if (registerResponse.status !== 200 || !registerResponse.body.data?.register) { 68 + throw new Error(`Registration failed: ${JSON.stringify(registerResponse.body)}`); 69 + } 70 + 71 + accessToken = registerResponse.body.data.register.access_token; 72 + userId = registerResponse.body.data.register.user.id; 73 + }); 74 + 75 + afterAll(async () => { 76 + await app.close(); 77 + }); 78 + 79 + describe("Me Query Optimization", () => { 80 + it("should return basic user info without loading organizations", async () => { 81 + const basicMeQuery = ` 82 + query Me { 83 + me { 84 + id 85 + email 86 + name 87 + } 88 + } 89 + `; 90 + 91 + const response = await request(app.getHttpServer()) 92 + .post("/graphql") 93 + .set("Authorization", `Bearer ${accessToken}`) 94 + .send({ 95 + query: basicMeQuery, 96 + }) 97 + .expect(200); 98 + 99 + // Verify response structure 100 + expect(response.body.data.me).toBeDefined(); 101 + expect(response.body.data.me.id).toBe(userId); 102 + expect(response.body.data.me.email).toBe(testEmail); 103 + expect(response.body.data.me.name).toBe("GraphQL Test User"); 104 + 105 + // Verify organizations field is not present (should be null/undefined) 106 + expect(response.body.data.me.organizations).toBeUndefined(); 107 + }); 108 + 109 + it("should load organizations when explicitly requested", async () => { 110 + const meWithOrganizationsQuery = ` 111 + query Me { 112 + me { 113 + id 114 + email 115 + name 116 + organizations { 117 + id 118 + name 119 + description 120 + } 121 + } 122 + } 123 + `; 124 + 125 + const response = await request(app.getHttpServer()) 126 + .post("/graphql") 127 + .set("Authorization", `Bearer ${accessToken}`) 128 + .send({ 129 + query: meWithOrganizationsQuery, 130 + }) 131 + .expect(200); 132 + 133 + // Verify response structure 134 + expect(response.body.data.me).toBeDefined(); 135 + expect(response.body.data.me.id).toBe(userId); 136 + expect(response.body.data.me.email).toBe(testEmail); 137 + expect(response.body.data.me.name).toBe("GraphQL Test User"); 138 + 139 + // Verify organizations field is present and is an array 140 + expect(response.body.data.me.organizations).toBeDefined(); 141 + expect(Array.isArray(response.body.data.me.organizations)).toBe(true); 142 + }); 143 + 144 + it("should handle partial organization field requests", async () => { 145 + const partialOrganizationsQuery = ` 146 + query Me { 147 + me { 148 + id 149 + name 150 + organizations { 151 + id 152 + name 153 + } 154 + } 155 + } 156 + `; 157 + 158 + const response = await request(app.getHttpServer()) 159 + .post("/graphql") 160 + .set("Authorization", `Bearer ${accessToken}`) 161 + .send({ 162 + query: partialOrganizationsQuery, 163 + }) 164 + .expect(200); 165 + 166 + // Verify response structure 167 + expect(response.body.data.me).toBeDefined(); 168 + expect(response.body.data.me.id).toBe(userId); 169 + expect(response.body.data.me.name).toBe("GraphQL Test User"); 170 + expect(response.body.data.me.organizations).toBeDefined(); 171 + expect(Array.isArray(response.body.data.me.organizations)).toBe(true); 172 + 173 + // Verify organizations don't have description field (not requested) 174 + if (response.body.data.me.organizations.length > 0) { 175 + expect(response.body.data.me.organizations[0].description).toBeUndefined(); 176 + } 177 + }); 178 + 179 + it("should return empty organizations array for user with no organizations", async () => { 180 + const meWithOrganizationsQuery = ` 181 + query Me { 182 + me { 183 + id 184 + email 185 + name 186 + organizations { 187 + id 188 + name 189 + description 190 + } 191 + } 192 + } 193 + `; 194 + 195 + const response = await request(app.getHttpServer()) 196 + .post("/graphql") 197 + .set("Authorization", `Bearer ${accessToken}`) 198 + .send({ 199 + query: meWithOrganizationsQuery, 200 + }) 201 + .expect(200); 202 + 203 + // Verify response structure 204 + expect(response.body.data.me).toBeDefined(); 205 + expect(response.body.data.me.id).toBe(userId); 206 + expect(response.body.data.me.email).toBe(testEmail); 207 + expect(response.body.data.me.name).toBe("GraphQL Test User"); 208 + 209 + // Verify organizations is an empty array (new user has no organizations) 210 + expect(response.body.data.me.organizations).toBeDefined(); 211 + expect(Array.isArray(response.body.data.me.organizations)).toBe(true); 212 + expect(response.body.data.me.organizations.length).toBe(0); 213 + }); 214 + }); 215 + 216 + describe("Query Performance Verification", () => { 217 + it("should not include organization data in basic me query response", async () => { 218 + const basicMeQuery = ` 219 + query Me { 220 + me { 221 + id 222 + email 223 + name 224 + } 225 + } 226 + `; 227 + 228 + const response = await request(app.getHttpServer()) 229 + .post("/graphql") 230 + .set("Authorization", `Bearer ${accessToken}`) 231 + .send({ 232 + query: basicMeQuery, 233 + }) 234 + .expect(200); 235 + 236 + // Verify that the response only contains requested fields 237 + const meData = response.body.data.me; 238 + const expectedFields = ["id", "email", "name"]; 239 + const actualFields = Object.keys(meData); 240 + 241 + // Should only have the requested fields 242 + expect(actualFields).toEqual(expect.arrayContaining(expectedFields)); 243 + expect(actualFields.length).toBe(expectedFields.length); 244 + 245 + // Should not have organizations field 246 + expect(meData.organizations).toBeUndefined(); 247 + }); 248 + 249 + it("should include organization data when organizations field is requested", async () => { 250 + const meWithOrganizationsQuery = ` 251 + query Me { 252 + me { 253 + id 254 + email 255 + name 256 + organizations { 257 + id 258 + name 259 + description 260 + } 261 + } 262 + } 263 + `; 264 + 265 + const response = await request(app.getHttpServer()) 266 + .post("/graphql") 267 + .set("Authorization", `Bearer ${accessToken}`) 268 + .send({ 269 + query: meWithOrganizationsQuery, 270 + }) 271 + .expect(200); 272 + 273 + // Verify that the response includes organizations field 274 + const meData = response.body.data.me; 275 + expect(meData.organizations).toBeDefined(); 276 + expect(Array.isArray(meData.organizations)).toBe(true); 277 + }); 278 + }); 279 + 280 + describe("Error Handling", () => { 281 + it("should return error for invalid token", async () => { 282 + const basicMeQuery = ` 283 + query Me { 284 + me { 285 + id 286 + email 287 + name 288 + } 289 + } 290 + `; 291 + 292 + const response = await request(app.getHttpServer()) 293 + .post("/graphql") 294 + .set("Authorization", "Bearer invalid-token") 295 + .send({ 296 + query: basicMeQuery, 297 + }) 298 + .expect(200); 299 + 300 + expect(response.body.errors).toBeDefined(); 301 + expect(response.body.errors[0].message).toContain("Invalid token"); 302 + }); 303 + 304 + it("should return error for missing authorization header", async () => { 305 + const basicMeQuery = ` 306 + query Me { 307 + me { 308 + id 309 + email 310 + name 311 + } 312 + } 313 + `; 314 + 315 + const response = await request(app.getHttpServer()) 316 + .post("/graphql") 317 + .send({ 318 + query: basicMeQuery, 319 + }) 320 + .expect(200); 321 + 322 + expect(response.body.errors).toBeDefined(); 323 + expect(response.body.errors[0].message).toContain("No token provided"); 324 + }); 325 + }); 326 + });
+3 -2
apps/server/test/jest-e2e.json
··· 6 6 "transform": { 7 7 "^.+\\.(t|j)s$": "ts-jest" 8 8 }, 9 - "moduleNameMapping": { 9 + "moduleNameMapper": { 10 10 "^@/(.*)$": "<rootDir>/../src/$1" 11 - } 11 + }, 12 + "transformIgnorePatterns": ["node_modules/(?!(@faker-js/faker)/)"] 12 13 }