diff --git a/api/package.json b/api/package.json index 1ad96dc5..8013a477 100644 --- a/api/package.json +++ b/api/package.json @@ -13,7 +13,7 @@ "start:debug": "nest start --debug --watch", "start:prod": "node dist/api/src/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", - "test": "jest --config ./test/jest-config.json -i --detectOpenHandles" + "test": "jest --config ./test/jest-config.json -i" }, "dependencies": { "@aws-sdk/client-ses": "^3.651.1", @@ -56,6 +56,7 @@ "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", "jest": "^29.5.0", + "jest-cucumber": "4.5.0", "prettier": "^3.0.0", "source-map-support": "^0.5.21", "supertest": "^7.0.0", diff --git a/api/test/auth/authentication.spec.ts b/api/test/auth/authentication.spec.ts deleted file mode 100644 index ca39a978..00000000 --- a/api/test/auth/authentication.spec.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { TestManager } from '../utils/test-manager'; - -import { User } from '@shared/entities/users/user.entity'; - -describe('Authentication', () => { - let testManager: TestManager; - - beforeAll(async () => { - testManager = await TestManager.createTestManager(); - }); - - afterEach(async () => { - await testManager.clearDatabase(); - }); - - afterAll(async () => { - await testManager.close(); - }); - describe('Sign Up', () => { - test.skip(`it should throw validation errors`, async () => {}); - test(`it should throw email already exist error`, async () => { - const user = await testManager.mocks().createUser({}); - const response = await testManager - .request() - .post('/authentication/signup') - .send({ - email: user.email, - password: user.password, - }); - expect(response.status).toBe(409); - expect(response.body.message).toEqual( - `Email ${user.email} already exists`, - ); - }); - test(`it should sign up a new user`, async () => { - const newUser = { email: 'test@test.com', password: '12345678' }; - await testManager.request().post('/authentication/signup').send({ - email: newUser.email, - password: newUser.password, - }); - const user = await testManager - .getDataSource() - .getRepository(User) - .findOne({ - where: { email: newUser.email }, - }); - expect(user.id).toBeDefined(); - expect(user.email).toEqual(newUser.email); - }); - }); - describe('Sign In', () => { - test(`it should throw an error if no user exists with provided credentials`, async () => { - const response = await testManager - .request() - .post('/authentication/login') - .send({ - email: 'non-existing@user.com', - password: '12345567', - }); - expect(response.status).toBe(401); - expect(response.body.message).toEqual('Invalid credentials'); - }); - test(`it should throw an error if password is incorrect`, async () => { - const user = await testManager.mocks().createUser({}); - const response = await testManager - .request() - .post('/authentication/login') - .send({ - email: user.email, - password: 'wrongpassword', - }); - expect(response.status).toBe(401); - expect(response.body.message).toEqual('Invalid credentials'); - }); - test(`it should sign in a user`, async () => { - const user = await testManager.mocks().createUser({}); - const response = await testManager - .request() - .post('/authentication/login') - .send({ - email: user.email, - password: user.password, - }); - expect(response.status).toBe(201); - expect(response.body.accessToken).toBeDefined(); - }); - }); -}); diff --git a/api/test/auth/password-recovery.spec.ts b/api/test/auth/password-recovery.spec.ts deleted file mode 100644 index 49a3ddba..00000000 --- a/api/test/auth/password-recovery.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { TestManager } from '../utils/test-manager'; -import { User } from '@shared/entities/users/user.entity'; -import { MockEmailService } from '../utils/mocks/mock-email.service'; -import { IEmailServiceToken } from '@api/modules/notifications/email/email-service.interface'; - -describe('Password Recovery', () => { - let testManager: TestManager; - let testUser: User; - let mockEmailService: MockEmailService; - - beforeAll(async () => { - testManager = await TestManager.createTestManager(); - mockEmailService = - testManager.moduleFixture.get(IEmailServiceToken); - }); - beforeEach(async () => { - const { user } = await testManager.setUpTestUser(); - testUser = user; - jest.clearAllMocks(); - }); - afterEach(async () => { - await testManager.clearDatabase(); - }); - it('an email should be sent if a user with provided email has been found', async () => { - const response = await testManager - .request() - .post(`/authentication/recover-password`) - .send({ email: testUser.email }); - - expect(response.status).toBe(201); - expect(mockEmailService.sendMail).toHaveBeenCalledTimes(1); - }); - it('should return 200 if user has not been found but no mail should be sent', async () => { - const response = await testManager - .request() - .post(`/authentication/recover-password`) - .send({ email: 'no-user@test.com' }); - - expect(response.status).toBe(201); - expect(mockEmailService.sendMail).toHaveBeenCalledTimes(0); - }); -}); diff --git a/api/test/e2e/features/password-recovery.feature b/api/test/e2e/features/password-recovery.feature new file mode 100644 index 00000000..00a7f99c --- /dev/null +++ b/api/test/e2e/features/password-recovery.feature @@ -0,0 +1,12 @@ +Feature: Password Recovery + + Scenario: An email should be sent if a user is found + Given a user exists with valid credentials + When the user requests password recovery + Then the user should receive a 201 status code + And an email should be sent + + Scenario: No email should be sent if the user is not found + When the user requests password recovery with an invalid email + Then the user should receive a 201 status code + And no email should be sent diff --git a/api/test/e2e/features/sign-in.feature b/api/test/e2e/features/sign-in.feature new file mode 100644 index 00000000..0efc33a4 --- /dev/null +++ b/api/test/e2e/features/sign-in.feature @@ -0,0 +1,18 @@ +Feature: Sign In + + Scenario: A user tries to sign in with non-existing credentials + When a user attempts to sign in with non-existing credentials + Then the user should receive a 401 status code + And the response message should be "Invalid credentials" + + Scenario: A user tries to sign in with an incorrect password + Given a user exists with valid credentials + When a user attempts to sign in with an incorrect password + Then the user should receive a 401 status code + And the response message should be "Invalid credentials" + + Scenario: A user successfully signs in + Given a user exists with valid credentials + When a user attempts to sign in with valid credentials + Then the user should receive a 201 status code + And the access token should be defined diff --git a/api/test/e2e/features/sign-up.feature b/api/test/e2e/features/sign-up.feature new file mode 100644 index 00000000..6743e93d --- /dev/null +++ b/api/test/e2e/features/sign-up.feature @@ -0,0 +1,12 @@ +Feature: User Sign Up + + Scenario: A user cannot sign up with an already registered email + Given a user exists with valid credentials + When the user attempts to sign up with the same email + Then the user should receive a 409 status code + And the response message should be "Email already exists" + + Scenario: A user successfully signs up with a new email + When a user attempts to sign up with valid credentials + Then the user should be registered successfully + And the user should have a valid ID and email diff --git a/api/test/e2e/steps/password-recovery.steps.ts b/api/test/e2e/steps/password-recovery.steps.ts new file mode 100644 index 00000000..d1b30932 --- /dev/null +++ b/api/test/e2e/steps/password-recovery.steps.ts @@ -0,0 +1,94 @@ +import { defineFeature, loadFeature } from 'jest-cucumber'; +import { Response } from 'supertest'; +import { User } from '@shared/entities/users/user.entity'; +import { IEmailServiceToken } from '@api/modules/notifications/email/email-service.interface'; +import { MockEmailService } from 'api/test/utils/mocks/mock-email.service'; +import { TestManager } from 'api/test/utils/test-manager'; + +const feature = loadFeature('./test/e2e/features/password-recovery.feature'); + +defineFeature(feature, (test) => { + let testManager: TestManager; + let testUser: User; + let mockEmailService: MockEmailService; + + beforeAll(async () => { + testManager = await TestManager.createTestManager(); + mockEmailService = + testManager.moduleFixture.get(IEmailServiceToken); + }); + + beforeEach(async () => { + await testManager.clearDatabase(); + const { user } = await testManager.setUpTestUser(); + testUser = user; + jest.clearAllMocks(); + }); + + afterAll(async () => { + await testManager.close(); + }); + + test('An email should be sent if a user is found', ({ + given, + when, + then, + and, + }) => { + let response: Response; + + given('a user exists with valid credentials', async () => { + testUser = await testManager.mocks().createUser({ + email: 'test@test.com', + password: 'password123', + }); + }); + + when('the user requests password recovery', async () => { + response = await testManager + .request() + .post(`/authentication/recover-password`) + .send({ email: testUser.email }); + }); + + then( + /the user should receive a (\d+) status code/, + async (statusCode: string) => { + expect(response.status).toBe(Number.parseInt(statusCode)); + }, + ); + + and('an email should be sent', async () => { + expect(mockEmailService.sendMail).toHaveBeenCalledTimes(1); + }); + }); + + test('No email should be sent if the user is not found', ({ + when, + then, + and, + }) => { + let response: Response; + + when( + 'the user requests password recovery with an invalid email', + async () => { + response = await testManager + .request() + .post(`/authentication/recover-password`) + .send({ email: 'no-user@test.com' }); + }, + ); + + then( + /the user should receive a (\d+) status code/, + async (statusCode: string) => { + expect(response.status).toBe(Number.parseInt(statusCode)); + }, + ); + + and('no email should be sent', async () => { + expect(mockEmailService.sendMail).toHaveBeenCalledTimes(0); + }); + }); +}); diff --git a/api/test/e2e/steps/sign-in.steps.ts b/api/test/e2e/steps/sign-in.steps.ts new file mode 100644 index 00000000..43e27dd9 --- /dev/null +++ b/api/test/e2e/steps/sign-in.steps.ts @@ -0,0 +1,114 @@ +import { defineFeature, loadFeature } from 'jest-cucumber'; +import { Response } from 'supertest'; +import { TestManager } from 'api/test/utils/test-manager'; +import { User } from '@shared/entities/users/user.entity'; + +const feature = loadFeature('./test/e2e/features/sign-in.feature'); + +defineFeature(feature, (test) => { + let testManager: TestManager; + + beforeAll(async () => { + testManager = await TestManager.createTestManager(); + }); + + beforeEach(async () => { + await testManager.clearDatabase(); + }); + + afterAll(async () => { + await testManager.close(); + }); + + test('A user tries to sign in with non-existing credentials', ({ + when, + then, + and, + }) => { + let response: Response; + + when( + 'a user attempts to sign in with non-existing credentials', + async () => { + response = await testManager + .request() + .post('/authentication/login') + .send({ email: 'non-existing@user.com', password: '12345567' }); + }, + ); + + then( + /^the user should receive a (\d+) status code$/, + (statusCode: string) => { + expect(response.status).toBe(Number.parseInt(statusCode, 10)); + }, + ); + + and(/^the response message should be "(.*)"$/, (message: string) => { + expect(response.body.message).toEqual(message); + }); + }); + + test('A user tries to sign in with an incorrect password', ({ + given, + when, + then, + and, + }) => { + let user: User; + let response: Response; + + given('a user exists with valid credentials', async () => { + user = await testManager + .mocks() + .createUser({ email: 'test@test.com', password: '12345678' }); + }); + + when('a user attempts to sign in with an incorrect password', async () => { + response = await testManager + .request() + .post('/authentication/login') + .send({ email: user.email, password: 'wrongpassword' }); + }); + + then( + /^the user should receive a (\d+) status code$/, + (statusCode: string) => { + expect(response.status).toBe(Number.parseInt(statusCode, 10)); + }, + ); + + and(/^the response message should be "(.*)"$/, (message: string) => { + expect(response.body.message).toEqual(message); + }); + }); + + test('A user successfully signs in', ({ given, when, then, and }) => { + let user: User; + let response: Response; + + given('a user exists with valid credentials', async () => { + user = await testManager + .mocks() + .createUser({ email: 'test@test.com', password: '12345678' }); + }); + + when('a user attempts to sign in with valid credentials', async () => { + response = await testManager + .request() + .post('/authentication/login') + .send({ email: user.email, password: user.password }); + }); + + then( + /^the user should receive a (\d+) status code$/, + (statusCode: string) => { + expect(response.status).toBe(Number.parseInt(statusCode, 10)); + }, + ); + + and('the access token should be defined', () => { + expect(response.body.accessToken).toBeDefined(); + }); + }); +}); diff --git a/api/test/e2e/steps/sign-up.steps.ts b/api/test/e2e/steps/sign-up.steps.ts new file mode 100644 index 00000000..b125d7ce --- /dev/null +++ b/api/test/e2e/steps/sign-up.steps.ts @@ -0,0 +1,94 @@ +import { defineFeature, loadFeature } from 'jest-cucumber'; +import { Response } from 'supertest'; +import { User } from '@shared/entities/users/user.entity'; +import { TestManager } from 'api/test/utils/test-manager'; + +const feature = loadFeature('./test/e2e/features/sign-up.feature'); + +defineFeature(feature, (test) => { + let testManager: TestManager; + + beforeAll(async () => { + testManager = await TestManager.createTestManager(); + }); + + beforeEach(async () => { + await testManager.clearDatabase(); + }); + + afterAll(async () => { + await testManager.close(); + }); + + test('A user cannot sign up with an already registered email', ({ + given, + when, + then, + and, + }) => { + let existingUser: User; + let response: Response; + + given('a user exists with valid credentials', async () => { + existingUser = await testManager.mocks().createUser({ + email: 'existing@test.com', + password: 'password123', + }); + }); + + when('the user attempts to sign up with the same email', async () => { + response = await testManager + .request() + .post('/authentication/signup') + .send({ email: existingUser.email, password: 'password123' }); + }); + + then( + /the user should receive a (\d+) status code/, + async (statusCode: string) => { + expect(response.status).toBe(Number.parseInt(statusCode)); + }, + ); + + and('the response message should be "Email already exists"', async () => { + expect(response.body.message).toEqual( + `Email ${existingUser.email} already exists`, + ); + }); + }); + + test('A user successfully signs up with a new email', ({ + when, + then, + and, + }) => { + let newUser: { email: string; password: string }; + let createdUser: User; + let response: Response; + + when('a user attempts to sign up with valid credentials', async () => { + newUser = { email: 'newuser@test.com', password: '12345678' }; + response = await testManager + .request() + .post('/authentication/signup') + .send({ + email: newUser.email, + password: newUser.password, + }); + }); + + then('the user should be registered successfully', async () => { + expect(response.status).toBe(201); + }); + + and('the user should have a valid ID and email', async () => { + createdUser = await testManager + .getDataSource() + .getRepository(User) + .findOne({ where: { email: newUser.email } }); + + expect(createdUser.id).toBeDefined(); + expect(createdUser.email).toEqual(newUser.email); + }); + }); +}); diff --git a/api/test/jest-config.json b/api/test/jest-config.json index 84771cdc..2b0e1ce8 100644 --- a/api/test/jest-config.json +++ b/api/test/jest-config.json @@ -1,12 +1,9 @@ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": "../", - "roots": [ - "/src/", - "/test/" - ], + "roots": ["/src/", "/test/"], "testEnvironment": "node", - "testRegex": ".spec.ts$", + "testRegex": ".steps.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, diff --git a/api/test/utils/mocks/entity-mocks.ts b/api/test/utils/mocks/entity-mocks.ts index f5876e61..7748b856 100644 --- a/api/test/utils/mocks/entity-mocks.ts +++ b/api/test/utils/mocks/entity-mocks.ts @@ -1,4 +1,4 @@ -import { DataSource, DeepPartial } from 'typeorm'; +import { DataSource } from 'typeorm'; import { User } from '@shared/entities/users/user.entity'; import { genSalt, hash } from 'bcrypt'; @@ -8,13 +8,12 @@ export const createUser = async ( ): Promise => { const salt = await genSalt(); const usedPassword = additionalData?.password ?? '12345678'; - const defaultData: DeepPartial = { + const user = { email: 'test@user.com', + ...additionalData, password: await hash(usedPassword, salt), }; - const user = { ...defaultData, ...additionalData }; - await dataSource.getRepository(User).save(user); return { ...user, password: usedPassword } as User; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 90e584d0..36cac8a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -143,6 +143,9 @@ importers: jest: specifier: ^29.5.0 version: 29.7.0(@types/node@20.16.5)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)) + jest-cucumber: + specifier: 4.5.0 + version: 4.5.0(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.16.5)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4))) prettier: specifier: ^3.0.0 version: 3.3.3 @@ -543,6 +546,12 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@cucumber/gherkin@28.0.0': + resolution: {integrity: sha512-Ee6zJQq0OmIUPdW0mSnsCsrWA2PZAELNDPICD2pLfs0Oz7RAPgj80UsD2UCtqyAhw2qAR62aqlktKUlai5zl/A==} + + '@cucumber/messages@24.1.0': + resolution: {integrity: sha512-hxVHiBurORcobhVk80I9+JkaKaNXkW6YwGOEFIh/2aO+apAN+5XJgUUWjng9NwqaQrW1sCFuawLB1AuzmBaNdQ==} + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1202,6 +1211,9 @@ packages: '@types/supertest@6.0.2': resolution: {integrity: sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==} + '@types/uuid@9.0.8': + resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} + '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -2793,6 +2805,20 @@ packages: ts-node: optional: true + jest-cucumber@4.5.0: + resolution: {integrity: sha512-EGVqkeE6xM/wnpWuLuB3AMQs4vNkLDwOuH3bsH2AigphAqDp+k3E+AIh0FAKhJ/1IjLTfZKyupIPRlYN62YZ+A==} + peerDependencies: + '@types/jest': '>=29.5.12' + jest: '>=29.7.0' + vitest: '>=1.4.0' + peerDependenciesMeta: + '@types/jest': + optional: true + jest: + optional: true + vitest: + optional: true + jest-diff@29.7.0: resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3636,6 +3662,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + reflect-metadata@0.2.1: + resolution: {integrity: sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==} + deprecated: This version has a critical bug in fallback handling. Please upgrade to reflect-metadata@0.2.2 or newer. + reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} @@ -4247,6 +4277,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true @@ -5017,6 +5051,17 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@cucumber/gherkin@28.0.0': + dependencies: + '@cucumber/messages': 24.1.0 + + '@cucumber/messages@24.1.0': + dependencies: + '@types/uuid': 9.0.8 + class-transformer: 0.5.1 + reflect-metadata: 0.2.1 + uuid: 9.0.1 + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': dependencies: eslint: 8.57.0 @@ -5932,6 +5977,8 @@ snapshots: '@types/methods': 1.1.4 '@types/superagent': 8.1.9 + '@types/uuid@9.0.8': {} + '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.33': @@ -6996,8 +7043,8 @@ snapshots: '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.5.4) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.0) eslint-plugin-react: 7.35.2(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) @@ -7020,37 +7067,37 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.1.0 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.5.4) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): + eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -7061,7 +7108,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -7930,6 +7977,16 @@ snapshots: - babel-plugin-macros - supports-color + jest-cucumber@4.5.0(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.16.5)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4))): + dependencies: + '@cucumber/gherkin': 28.0.0 + callsites: 3.1.0 + glob: 10.4.5 + uuid: 10.0.0 + optionalDependencies: + '@types/jest': 29.5.12 + jest: 29.7.0(@types/node@20.16.5)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)) + jest-diff@29.7.0: dependencies: chalk: 4.1.2 @@ -8850,6 +8907,8 @@ snapshots: dependencies: picomatch: 2.3.1 + reflect-metadata@0.2.1: {} + reflect-metadata@0.2.2: {} reflect.getprototypeof@1.0.6: @@ -9481,6 +9540,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@10.0.0: {} + uuid@9.0.1: {} v8-compile-cache-lib@3.0.1: {}