diff --git a/.github/workflows/ci-server.yaml b/.github/workflows/ci-server.yaml index 83dcee198..857dec2fc 100644 --- a/.github/workflows/ci-server.yaml +++ b/.github/workflows/ci-server.yaml @@ -17,7 +17,7 @@ concurrency: cancel-in-progress: true jobs: - server-test: + server-setup: runs-on: ubuntu-latest env: NX_REJECT_UNKNOWN_LOCAL_CACHE: 0 @@ -29,6 +29,10 @@ jobs: POSTGRES_USER: postgres ports: - 5432:5432 + redis: + image: redis + ports: + - 6379:6379 steps: - name: Fetch custom Github Actions and base branch history uses: actions/checkout@v4 @@ -36,7 +40,7 @@ jobs: fetch-depth: 0 - name: Install dependencies uses: ./.github/workflows/actions/yarn-install - - name: Server / Restore Tasks Cache + - name: Server / Restore Task Cache uses: ./.github/workflows/actions/task-cache with: tag: scope:backend @@ -45,14 +49,71 @@ jobs: with: tag: scope:backend tasks: lint,typecheck - - name: Server / Run tests - uses: ./.github/workflows/actions/nx-affected - with: - tag: scope:backend - tasks: test - name: Server / Build run: npx nx build twenty-server - name: Server / Write .env run: npx nx reset:env twenty-server - name: Worker / Run run: MESSAGE_QUEUE_TYPE=sync npx nx worker twenty-server + + server-test: + runs-on: ubuntu-latest + needs: server-setup + env: + NX_REJECT_UNKNOWN_LOCAL_CACHE: 0 + steps: + - name: Fetch custom Github Actions and base branch history + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install dependencies + uses: ./.github/workflows/actions/yarn-install + - name: Server / Restore Task Cache + uses: ./.github/workflows/actions/task-cache + with: + tag: scope:backend + - name: Server / Run Tests + uses: ./.github/workflows/actions/nx-affected + with: + tag: scope:backend + tasks: test + + server-integration-test: + runs-on: ubuntu-latest + needs: server-setup + services: + postgres: + image: twentycrm/twenty-postgres + env: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + ports: + - 5432:5432 + redis: + image: redis + ports: + - 6379:6379 + env: + NX_REJECT_UNKNOWN_LOCAL_CACHE: 0 + steps: + - name: Fetch custom Github Actions and base branch history + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install dependencies + uses: ./.github/workflows/actions/yarn-install + - name: Server / Restore Task Cache + uses: ./.github/workflows/actions/task-cache + with: + tag: scope:backend + - name: Server / Run Integration Tests + uses: ./.github/workflows/actions/nx-affected + with: + tag: scope:backend + tasks: "test:integration" + - name: Server / Upload reset-logs file + if: always() + uses: actions/upload-artifact@v4 + with: + name: reset-logs + path: reset-logs.log diff --git a/.vscode/twenty.code-workspace b/.vscode/twenty.code-workspace index 41549b6f9..ce2498959 100644 --- a/.vscode/twenty.code-workspace +++ b/.vscode/twenty.code-workspace @@ -20,6 +20,10 @@ "name": "packages/twenty-ui", "path": "../packages/twenty-ui" }, + { + "name": "packages/twenty-emails", + "path": "../packages/twenty-emails" + }, { "name": "packages/twenty-postgres", "path": "../packages/twenty-postgres" diff --git a/packages/twenty-server/.env.test b/packages/twenty-server/.env.test index fc51ab9e1..ed0c63d78 100644 --- a/packages/twenty-server/.env.test +++ b/packages/twenty-server/.env.test @@ -1,26 +1,26 @@ +PG_DATABASE_URL=postgres://twenty:twenty@localhost:5432/test + DEBUG_MODE=true -PG_DATABASE_URL=postgres://twenty:twenty@localhost:5432/test?connection_limit=1 -# Use this for docker setup -# PG_DATABASE_URL=postgres://twenty:twenty@postgres:5432/default?connection_limit=1 - -# the URL of the front-end app +DEBUG_PORT=9000 FRONT_BASE_URL=http://localhost:3001 -# random keys used to generate JWT tokens -ACCESS_TOKEN_SECRET=secret_jwt -LOGIN_TOKEN_SECRET=secret_login_tokens -REFRESH_TOKEN_SECRET=secret_refresh_token -FILE_TOKEN_SECRET=secret_file_token +ACCESS_TOKEN_SECRET=replace_me_with_a_random_string_access +LOGIN_TOKEN_SECRET=replace_me_with_a_random_string_login +REFRESH_TOKEN_SECRET=replace_me_with_a_random_string_refresh +SIGN_IN_PREFILLED=true +EXCEPTION_HANDLER_DRIVER=console +SENTRY_DSN=https://ba869cb8fd72d5faeb6643560939cee0@o4505516959793152.ingest.sentry.io/4506660900306944 +DEMO_WORKSPACE_IDS=63db4589-590f-42b3-bdf1-85268b3da02f,8de58f3f-7e86-4a0b-998d-b2cbe314ee3a,4d957b72-0b37-4bad-9468-8dc828ee082d,daa0b739-269e-49b6-9be5-5f0215941489,59c15f6a-909a-4495-9cf4-3ce1b0abbb6a,7202cc9d-92da-4b52-a323-d29d38cd3b4e,5f071b0d-646b-411a-94f1-5d9ba9d5c6ac,7bc10973-897b-4767-ab2f-35cdac3b2aec,4b3ba0be-2d29-4b1e-be66-8ac7eb65d000,edfb500d-cc4e-4f22-8e2b-f139a9758a68,eee459c9-9057-4459-ae0d-d51d14c01635,3dd2f505-0075-4217-ba33-fc4244aeaaa9,3d1a9165-3f3f-494e-a99d-f858eae95144,84db6ded-cfce-4aee-9160-6553b05c8143,96fb1540-269b-4d13-af21-2a8268eff8ca,b2463e69-d121-4ea5-80c9-bba82403e93e,5af30c15-867d-49ed-b939-d4856bed8514,b5677aa1-68fa-4818-aaaa-434a07ae2ed4,1ec7fa9a-d6bf-4fa2-a753-9a235d75ee3f,753a6fa2-df27-4c87-8c90-4da78fcb30dd,2138f2f2-bbe9-41df-b483-687a9075f94e,a885cfef-4636-4c3a-9788-1ff6e6b92df5,5458f7fb-9431-47a2-b7a0-32f31d115e23,6c09929f-11c3-4f92-9508-aa0e6b934d1e,57ae0a2c-7a4e-4c7d-8f43-68548e7f1206,cc7f0b85-6868-4c2d-85c5-3ce9977ea346,21871a7f-f067-45ea-989e-44339bb5ad07,c3efedab-84f5-4656-8297-55964b3d26cb,647dcdd1-4540-4003-9f58-fd84d4d759b7,fc5e6857-8d67-47b8-98f2-edeb0671e326,1ad8d72c-1826-40ed-8b44-d15a1d2aab70,eac6c90a-d25d-4c8c-a053-cfbc7cde0afb,023a70de-a85e-43fc-bbc6-757fbf6562f0,f3f0a7fb-1409-443b-8e39-4e58e628796e,62828804-97d4-40ec-82fa-2992a6ce4a81,af5441fe-b16f-4996-87f4-1a433ec53dd6,e8857860-f7b1-4478-9741-1eb9e7c11f2c,6bca9c44-c8c0-49f8-b0b5-1bb2ca7842b8,d50da092-09df-448f-84ea-3ebddfe1d9f6,9efd5d6d-db64-47d4-9ad3-5e4d8b65ff7f,6f089094-2dd2-4b0e-b5b7-8bb52b93ea8e,299b0822-68e9-4bfa-af35-da799012e80e,a3dd579c-93be-45a0-ad35-f518d8ed45dd,023b1b3e-4891-4061-aae0-f34368644f40,50174445-33c5-4482-bb8c-3ef6c511c8cd,9933c048-07a7-4735-9af2-940c2f9b6683,beadc568-3962-46bd-ad4d-06e23b37615b,0cdafc9f-d4c1-4576-837e-d7f6ec28643d,50bb24ce-1709-4928-a87b-d9d9e147a2ab,7690ed72-910d-4357-8e0e-17aa702b0b94,1ad0d69f-60fa-414f-bf79-4f94c2abba43,946d84a4-db4d-48cb-a5d3-03081b5c7e8e,1a080055-d2bf-4b14-8957-88a7d08769b8,ed343e38-e405-4fae-9486-27b09c98bdad,c8bdef75-a98c-4646-a372-3251340d2dea,87a8c6fa-f93e-4950-aff2-5f956ca1a6ba,604781ba-23c2-4220-a717-b5615431fcd9,31af6841-ad9f-4f28-a637-b5c5e6589447,cf067451-7b88-4ff2-a96d-3fc9c5d6fea0,26a8ad5e-29d9-4e7d-aa1f-e6221e8ea32a,fd14db29-e4df-44a7-9b3f-d00384458122,73b477a8-fcf4-4860-a685-65a0a79b8653,82e0f305-4c6c-4160-be1d-b0de834124e6,e38567ab-a6e2-4a94-99c5-a7db31c0aae8,faf3d6dc-66ff-4c1b-9658-f65a9cd9fcf1,6df6bb90-200e-4290-b73d-9bb374554229,2ff10cf4-a871-404a-9e7b-5ca7a232567e,06c614e2-0f36-4b72-8c82-59631680add2,5e508c81-3453-4185-ae8c-4c9b841f8c15,21b5c371-6010-4b1b-be67-7538eb877efb,54e61442-e291-4eea-8d49-7f11b5f85bd2,b6b7260a-4eea-40b0-9f7f-1dfd4c3cc7a8,e163fe76-30fb-44fb-b51a-50cc78745a21,4da672f2-29b4-4a98-b27c-b39a4aecc858,2fdb0601-c882-4aaf-ad49-ae17e530d47a,49525e1b-1b47-4545-a98c-0ba58778179f,f958ab32-b152-4004-9228-18148f7380f1,0ff5025a-62cd-4a10-a722-79f7cf360f01,642df445-e314-409a-a97d-64fc2aa2a15e,38b0dab5-d4fb-44f9-8cf9-bb35cf82e91d,62054133-f35a-4f64-a2ee-a31e48952835,536dbe8c-af33-4eab-a0a8-8d039a00db40,a04998ba-52c9-4538-b6d9-6d04408dbaf2,89016c7a-3d36-4619-a5c6-4f31795eebf7,7708b9a9-776c-46fc-94a4-dc28e7880958,5c92bc69-b328-4c66-a791-a05dbaf7a6f8,ad580a50-80b4-44be-9bc4-f2b57cd23207,36c0241c-891e-4b74-bd10-5e99df96bbc8,a96842ff-18be-4536-a23d-20d973d91621,0ea549b0-9558-4bdf-9944-5abc707c7660,0186c353-5ed2-4c94-b71a-fc0b48c90288,1508a165-2217-4911-b31c-1ea42a08f097,1731e392-dfdf-4fc4-863b-27ae62b0e374,0b245cea-96a6-4a3a-af6a-ef43496c239c,a844e208-7078-43a2-8bd0-86f31498cd3f,53d112b5-87f2-490b-a788-df1f4624f9ad,0d5794d4-3a52-482b-9a6a-f8185018bad1,df877aa6-231c-47fb-9be0-906e61677356,c56c6d1a-3418-49d2-82ce-bd9370668043,6e0b6f34-3cd0-4aa0-ae1f-25f5545dca68 +MUTATION_MAXIMUM_RECORD_AFFECTED=100 +MESSAGE_QUEUE_TYPE=pg-boss +CACHE_STORAGE_TYPE=redis +REDIS_HOST=127.0.0.1 +REDIS_PORT=6379 +REDIS_USERNAME=default +REDIS_PASSWORD= -# ———————— Optional ———————— -# DEBUG_MODE=false -# SIGN_IN_PREFILLED=false -# ACCESS_TOKEN_EXPIRES_IN=30m -# LOGIN_TOKEN_EXPIRES_IN=15m -# REFRESH_TOKEN_EXPIRES_IN=90d -# FILE_TOKEN_EXPIRES_IN=1d -# FRONT_AUTH_CALLBACK_URL=http://localhost:3001/verify -# AUTH_GOOGLE_ENABLED=false -# MESSAGING_PROVIDER_GMAIL_ENABLED=false -# STORAGE_TYPE=local -# STORAGE_LOCAL_PATH=.local-storage -# MUTATION_MAXIMUM_AFFECTED_RECORDS=100 +AUTH_GOOGLE_ENABLED=false +MESSAGING_PROVIDER_GMAIL_ENABLED=false +CALENDAR_PROVIDER_GOOGLE_ENABLED=false +AUTH_GOOGLE_CALLBACK_URL=http://localhost:3000/auth/google/redirect +AUTH_GOOGLE_APIS_CALLBACK_URL=http://localhost:3000/auth/google-apis/get-access-token +MESSAGING_PROVIDER_GMAIL_CALLBACK_URL=http://localhost:3000/auth/google-gmail/get-access-token diff --git a/packages/twenty-server/.eslintrc.cjs b/packages/twenty-server/.eslintrc.cjs index 348668775..6510f3e04 100644 --- a/packages/twenty-server/.eslintrc.cjs +++ b/packages/twenty-server/.eslintrc.cjs @@ -93,5 +93,11 @@ module.exports = { '@nx/workspace-inject-workspace-repository': 'warn', }, }, + { + files: ['scripts/**/*.ts'], + parserOptions: { + project: ['packages/twenty-server/tsconfig.scripts.json'], + }, + }, ], }; diff --git a/packages/twenty-server/.gitignore b/packages/twenty-server/.gitignore index 63ed409f3..409529962 100644 --- a/packages/twenty-server/.gitignore +++ b/packages/twenty-server/.gitignore @@ -1,3 +1,4 @@ dist/* .local-storage logs/**/* +*.log diff --git a/packages/twenty-server/@types/jest.d.ts b/packages/twenty-server/@types/jest.d.ts new file mode 100644 index 000000000..225e29ff2 --- /dev/null +++ b/packages/twenty-server/@types/jest.d.ts @@ -0,0 +1,17 @@ +import 'jest'; + +declare module '@jest/types' { + namespace Config { + interface ConfigGlobals { + APP_PORT: number; + ACCESS_TOKEN: string; + } + } +} + +declare global { + const APP_PORT: number; + const ACCESS_TOKEN: string; +} + +export {}; diff --git a/packages/twenty-server/jest-integration.config.ts b/packages/twenty-server/jest-integration.config.ts new file mode 100644 index 000000000..10b5cc9e2 --- /dev/null +++ b/packages/twenty-server/jest-integration.config.ts @@ -0,0 +1,34 @@ +import { JestConfigWithTsJest, pathsToModuleNameMapper } from 'ts-jest'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const tsConfig = require('./tsconfig.json'); + +const jestConfig: JestConfigWithTsJest = { + silent: false, + verbose: true, + moduleFileExtensions: ['js', 'json', 'ts'], + rootDir: '.', + testEnvironment: 'node', + testRegex: '.integration-spec.ts$', + modulePathIgnorePatterns: ['/dist'], + globalSetup: '/test/utils/setup-test.ts', + globalTeardown: '/test/utils/teardown-test.ts', + testTimeout: 15000, + moduleNameMapper: { + ...pathsToModuleNameMapper(tsConfig.compilerOptions.paths), + 'twenty-emails': '/../twenty-emails/dist/index.js', + }, + fakeTimers: { + enableGlobally: true, + }, + transform: { + '^.+\\.(t|j)s$': 'ts-jest', + }, + globals: { + APP_PORT: 4000, + ACCESS_TOKEN: + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtMDY4Ny00YzQxLWI3MDctZWQxYmZjYTk3MmE3IiwiaWF0IjoxNzI2NDkyNTAyLCJleHAiOjEzMjQ1MDE2NTAyfQ.zM6TbfeOqYVH5Sgryc2zf02hd9uqUOSL1-iJlMgwzsI', + }, +}; + +export default jestConfig; diff --git a/packages/twenty-server/jest.config.ts b/packages/twenty-server/jest.config.ts index 56bfdd647..00c1b6f06 100644 --- a/packages/twenty-server/jest.config.ts +++ b/packages/twenty-server/jest.config.ts @@ -1,22 +1,27 @@ -module.exports = { +import { JestConfigWithTsJest } from 'ts-jest'; + +const jestConfig: JestConfigWithTsJest = { // to enable logs, comment out the following line silent: true, clearMocks: true, - preset: 'ts-jest', - testEnvironment: 'node', - modulePathIgnorePatterns: ['/dist'], - moduleFileExtensions: ['js', 'json', 'ts'], - moduleNameMapper: { - '^src/(.*)': '/src/$1', - }, + displayName: 'twenty-server', rootDir: './', + testEnvironment: 'node', + transformIgnorePatterns: ['../../node_modules/'], testRegex: '.*\\.spec\\.ts$', transform: { '^.+\\.(t|j)s$': 'ts-jest', }, + moduleNameMapper: { + '^src/(.*)': '/src/$1', + }, + moduleFileExtensions: ['js', 'json', 'ts'], + modulePathIgnorePatterns: ['/dist'], fakeTimers: { enableGlobally: true, }, collectCoverageFrom: ['**/*.(t|j)s'], coverageDirectory: '../coverage', }; + +export default jestConfig; diff --git a/packages/twenty-server/project.json b/packages/twenty-server/project.json index 803e62689..a31ff4fb1 100644 --- a/packages/twenty-server/project.json +++ b/packages/twenty-server/project.json @@ -11,6 +11,16 @@ "commands": ["rimraf dist", "nest build --path ./tsconfig.build.json"] } }, + "test:integration": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/twenty-server", + "commands": [ + "NODE_ENV=test nx database:reset > reset-logs.log && NODE_ENV=test nx jest --config ./jest-integration.config.ts" + ] + }, + "parallel": false + }, "build:packageJson": { "executor": "@nx/js:tsc", "options": { @@ -108,12 +118,11 @@ "command": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register ../../node_modules/.bin/jest --runInBand" } }, - "test:e2e": { + "jest": { "executor": "nx:run-commands", - "dependsOn": ["build"], "options": { "cwd": "packages/twenty-server", - "command": "./scripts/run-integration.sh" + "command": "jest" } }, "database:migrate": { @@ -140,6 +149,16 @@ "parallel": false } }, + "generate:integration-test": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/twenty-server", + "commands": [ + "nx ts-node-no-deps -- ./scripts/generate-integration-tests/index.ts" + ], + "parallel": false + } + }, "database:reset": { "executor": "nx:run-commands", "dependsOn": ["build"], diff --git a/packages/twenty-server/scripts/generate-integration-tests/index.ts b/packages/twenty-server/scripts/generate-integration-tests/index.ts new file mode 100644 index 000000000..5b635d3bc --- /dev/null +++ b/packages/twenty-server/scripts/generate-integration-tests/index.ts @@ -0,0 +1,213 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as process from 'process'; + +import { INTROSPECTION_QUERY } from './introspection-query'; +import { + Field, + InputValue, + IntrospectionResponse, + TypeRef, +} from './introspection.interface'; + +const GRAPHQL_URL = 'http://localhost:3000/graphql'; +const BEARER_TOKEN = + 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtMDY4Ny00YzQxLWI3MDctZWQxYmZjYTk3MmE3IiwiaWF0IjoxNzI2NDkyNTAyLCJleHAiOjEzMjQ1MDE2NTAyfQ.zM6TbfeOqYVH5Sgryc2zf02hd9uqUOSL1-iJlMgwzsI'; +const TEST_OUTPUT_DIR = './test'; + +const fetchGraphQLSchema = async (): Promise => { + const headers = { + Authorization: BEARER_TOKEN, + 'Content-Type': 'application/json', + }; + const response = await fetch(GRAPHQL_URL, { + method: 'POST', + headers, + body: JSON.stringify({ query: INTROSPECTION_QUERY }), + }); + + if (!response.ok) { + throw new Error(`Failed to fetch schema: ${response.statusText}`); + } + + return response.json(); +}; + +const toKebabCase = (name: string): string => { + return name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); +}; + +const unwrapType = (typeInfo: TypeRef): any => { + while (typeInfo.ofType) { + typeInfo = typeInfo.ofType; + } + + return typeInfo; +}; + +const hasRequiredArgs = (args: InputValue[]): boolean => { + return args.some((arg) => unwrapType(arg.type).kind === 'NON_NULL'); +}; + +const generateTestContent = ( + queryName: string, + fields: Field[], +): string | null => { + const fieldNames = fields + .filter((f) => ['SCALAR', 'ENUM'].includes(unwrapType(f.type).kind)) + .map((f) => f.name); + + if (fieldNames.length === 0) { + console.log(`Skipping ${queryName}: No usable fields found.`); + + return null; + } + + const fieldSelection = fieldNames.join('\n '); + const expectSelection = fieldNames + .map((f) => `expect(${queryName}).toHaveProperty('${f}');`) + .join('\n '); + + return `import request from 'supertest'; + +const client = request(\`http://localhost:\${APP_PORT}\`); + +describe('${queryName}Resolver (e2e)', () => { + it('should find many ${queryName}', () => { + const queryData = { + query: \` + query ${queryName} { + ${queryName} { + edges { + node { + ${fieldSelection} + } + } + } + } + \`, + }; + + return client + .post('/graphql') + .set('Authorization', \`Bearer \${ACCESS_TOKEN}\`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.${queryName}; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const ${queryName} = edges[0].node; + + ${expectSelection} + } + }); + }); +}); +`; +}; + +const writeTestFile = ( + queryName: string, + content: string | null, + force = false, +): string => { + if (!content) return 'skipped'; + + const fileName = `${toKebabCase(queryName)}.integration-spec.ts`; + const filePath = path.join(TEST_OUTPUT_DIR, fileName); + + if (fs.existsSync(filePath) && !force) { + return 'skipped'; + } + + fs.writeFileSync(filePath, content); + + return force ? 'updated' : 'created'; +}; + +const generateTests = async (force = false) => { + fs.mkdirSync(TEST_OUTPUT_DIR, { recursive: true }); + const schemaData = await fetchGraphQLSchema(); + const types = schemaData.data.__schema.types; + + const queryTypeName = schemaData.data.__schema.queryType.name; + const queryType = types.find((t: any) => t.name === queryTypeName); + + let createdCount = 0; + let updatedCount = 0; + let totalCount = 0; + + if (!queryType?.fields) { + console.log('No query fields found.'); + + return; + } + + for (const query of queryType.fields) { + const queryName = query.name; + + if (hasRequiredArgs(query.args)) continue; + if (queryName.includes('Duplicates')) continue; + + const queryReturnType = unwrapType(query.type); + + if ( + queryReturnType.kind === 'OBJECT' && + queryReturnType.name.includes('Connection') + ) { + totalCount++; + const connectionTypeInfo = types.find( + (f: any) => f.name === queryReturnType.name, + ); + const edgeTypeInfo = connectionTypeInfo?.fields?.find( + (f: any) => f.name === 'edges', + ); + + if (edgeTypeInfo) { + const returnType = unwrapType(edgeTypeInfo.type); + const returnTypeInfo = types.find( + (t: any) => t.name === returnType.name, + ); + const returnNodeTypeInfo = returnTypeInfo?.fields?.find( + (f: any) => f.name === 'node', + ); + + if (returnNodeTypeInfo) { + const nodeType = unwrapType(returnNodeTypeInfo.type); + const nodeTypeInfo = types.find((t: any) => t.name === nodeType.name); + + if (!nodeTypeInfo?.fields) { + continue; + } + + const content = generateTestContent(queryName, nodeTypeInfo?.fields); + const result = writeTestFile(queryName, content, force); + + if (result === 'created') createdCount++; + if (result === 'updated') updatedCount++; + } + } + } + } + + console.log(`Number of tests created: ${createdCount}/${totalCount}`); + if (force) { + console.log(`Number of tests updated: ${updatedCount}/${totalCount}`); + } +}; + +// Basic command-line argument parsing +const forceArg = process.argv.includes('--force'); + +// Call the function with the parsed argument +generateTests(forceArg); diff --git a/packages/twenty-server/scripts/generate-integration-tests/introspection-query.ts b/packages/twenty-server/scripts/generate-integration-tests/introspection-query.ts new file mode 100644 index 000000000..c93c469ae --- /dev/null +++ b/packages/twenty-server/scripts/generate-integration-tests/introspection-query.ts @@ -0,0 +1,89 @@ +export const INTROSPECTION_QUERY = ` +query IntrospectionQuery { + __schema { + queryType { name } + mutationType { name } + subscriptionType { name } + types { + ...FullType + } + directives { + name + description + locations + args { + ...InputValue + } + } + } +} + +fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + args { + ...InputValue + } + type { + ...TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ...InputValue + } + interfaces { + ...TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ...TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { ...TypeRef } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } +} +`; diff --git a/packages/twenty-server/scripts/generate-integration-tests/introspection.interface.ts b/packages/twenty-server/scripts/generate-integration-tests/introspection.interface.ts new file mode 100644 index 000000000..cec8fd9f5 --- /dev/null +++ b/packages/twenty-server/scripts/generate-integration-tests/introspection.interface.ts @@ -0,0 +1,60 @@ +export interface IntrospectionResponse { + data: { + __schema: Schema; + }; +} + +export interface Schema { + queryType: { name: string }; + mutationType: { name: string | null }; + subscriptionType: { name: string | null }; + types: GraphQLType[]; + directives: Directive[]; +} + +export interface Directive { + name: string; + description: string | null; + locations: string[]; + args: InputValue[]; +} + +export interface GraphQLType { + kind: string; + name: string; + description: string | null; + fields?: Field[]; + inputFields?: InputValue[]; + interfaces?: TypeRef[]; + enumValues?: EnumValue[]; + possibleTypes?: TypeRef[]; +} + +export interface Field { + name: string; + description: string | null; + args: InputValue[]; + type: TypeRef; + isDeprecated: boolean; + deprecationReason: string | null; +} + +export interface InputValue { + name: string; + description: string | null; + type: TypeRef; + defaultValue: string | null; +} + +export interface TypeRef { + kind: string; + name: string | null; + ofType: TypeRef | null; +} + +export interface EnumValue { + name: string; + description: string | null; + isDeprecated: boolean; + deprecationReason: string | null; +} diff --git a/packages/twenty-server/scripts/run-integration.sh b/packages/twenty-server/scripts/run-integration.sh deleted file mode 100755 index 0da23f2de..000000000 --- a/packages/twenty-server/scripts/run-integration.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash -# scripts/run-integration.sh - -DIR="$(cd "$(dirname "$0")" && pwd)" -source $DIR/set-env-test.sh - -npx nx database:reset -npx nx jest --config ./test/jest-e2e.json diff --git a/packages/twenty-server/scripts/set-env-test.sh b/packages/twenty-server/scripts/set-env-test.sh deleted file mode 100755 index 98e7ac7c9..000000000 --- a/packages/twenty-server/scripts/set-env-test.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -# scripts/set-env-test.sh - -# Get script's directory -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Construct the absolute path of .env file in the project root directory -ENV_PATH="${SCRIPT_DIR}/../.env.test" - -# Check if the file exists -if [ -f "${ENV_PATH}" ]; then - echo "🔵 - Loading environment variables from "${ENV_PATH}"..." - # Export env vars - while IFS= read -r line || [ -n "$line" ]; do - if echo "$line" | grep -F = &>/dev/null - then - varname=$(echo "$line" | cut -d '=' -f 1) - varvalue=$(echo "$line" | cut -d '=' -f 2- | cut -d '#' -f 1) - export "$varname"="$varvalue" - fi - done < <(grep -v '^#' "${ENV_PATH}") -else - echo "Error: ${ENV_PATH} does not exist." - exit 1 -fi diff --git a/packages/twenty-server/src/app.module.ts b/packages/twenty-server/src/app.module.ts index 8260087ac..ece68c23e 100644 --- a/packages/twenty-server/src/app.module.ts +++ b/packages/twenty-server/src/app.module.ts @@ -26,23 +26,15 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/ import { ModulesModule } from 'src/modules/modules.module'; import { CoreEngineModule } from './engine/core-modules/core-engine.module'; - @Module({ imports: [ - // Nest.js devtools, use devtools.nestjs.com to debug - // DevtoolsModule.registerAsync({ - // useFactory: (environmentService: EnvironmentService) => ({ - // http: environmentService.get('DEBUG_MODE'), - // port: environmentService.get('DEBUG_PORT'), - // }), - // inject: [EnvironmentService], - // }), ConfigModule.forRoot({ isGlobal: true, + envFilePath: process.env.NODE_ENV === 'test' ? '.env.test' : '.env', }), GraphQLModule.forRootAsync({ driver: YogaDriver, - imports: [CoreEngineModule, GraphQLConfigModule], + imports: [GraphQLConfigModule], useClass: GraphQLConfigService, }), TwentyORMModule, diff --git a/packages/twenty-server/src/database/typeorm/core/core.datasource.ts b/packages/twenty-server/src/database/typeorm/core/core.datasource.ts index 1260f9bcf..0395f8cf2 100644 --- a/packages/twenty-server/src/database/typeorm/core/core.datasource.ts +++ b/packages/twenty-server/src/database/typeorm/core/core.datasource.ts @@ -2,18 +2,24 @@ import { TypeOrmModuleOptions } from '@nestjs/typeorm'; import { config } from 'dotenv'; import { DataSource, DataSourceOptions } from 'typeorm'; -config(); +config({ path: process.env.NODE_ENV === 'test' ? '.env.test' : '.env' }); + +const isJest = process.argv.some((arg) => arg.includes('jest')); export const typeORMCoreModuleOptions: TypeOrmModuleOptions = { url: process.env.PG_DATABASE_URL, type: 'postgres', logging: ['error'], schema: 'core', - entities: ['dist/src/engine/core-modules/**/*.entity{.ts,.js}'], + entities: [ + `${isJest ? '' : 'dist/'}src/engine/core-modules/**/*.entity{.ts,.js}`, + ], synchronize: false, migrationsRun: false, migrationsTableName: '_typeorm_migrations', - migrations: ['dist/src/database/typeorm/core/migrations/*{.ts,.js}'], + migrations: [ + `${isJest ? '' : 'dist/'}src/database/typeorm/core/migrations/*{.ts,.js}`, + ], ssl: process.env.PG_SSL_ALLOW_SELF_SIGNED === 'true' ? { diff --git a/packages/twenty-server/src/database/typeorm/metadata/metadata.datasource.ts b/packages/twenty-server/src/database/typeorm/metadata/metadata.datasource.ts index de67cc736..cdc833973 100644 --- a/packages/twenty-server/src/database/typeorm/metadata/metadata.datasource.ts +++ b/packages/twenty-server/src/database/typeorm/metadata/metadata.datasource.ts @@ -2,18 +2,24 @@ import { TypeOrmModuleOptions } from '@nestjs/typeorm'; import { config } from 'dotenv'; import { DataSource, DataSourceOptions } from 'typeorm'; -config(); +config({ path: process.env.NODE_ENV === 'test' ? '.env.test' : '.env' }); + +const isJest = process.argv.some((arg) => arg.includes('jest')); export const typeORMMetadataModuleOptions: TypeOrmModuleOptions = { url: process.env.PG_DATABASE_URL, type: 'postgres', logging: ['error'], schema: 'metadata', - entities: ['dist/src/engine/metadata-modules/**/*.entity{.ts,.js}'], + entities: [ + `${isJest ? '' : 'dist/'}src/engine/metadata-modules/**/*.entity{.ts,.js}`, + ], synchronize: false, migrationsRun: false, migrationsTableName: '_typeorm_migrations', - migrations: ['dist/src/database/typeorm/metadata/migrations/*{.ts,.js}'], + migrations: [ + `${isJest ? '' : 'dist/'}src/database/typeorm/metadata/migrations/*{.ts,.js}`, + ], ssl: process.env.PG_SSL_ALLOW_SELF_SIGNED === 'true' ? { @@ -24,6 +30,7 @@ export const typeORMMetadataModuleOptions: TypeOrmModuleOptions = { query_timeout: 10000, }, }; + export const connectionSource = new DataSource( typeORMMetadataModuleOptions as DataSourceOptions, ); diff --git a/packages/twenty-server/src/database/typeorm/raw/raw.datasource.ts b/packages/twenty-server/src/database/typeorm/raw/raw.datasource.ts index a50567c43..d39907593 100644 --- a/packages/twenty-server/src/database/typeorm/raw/raw.datasource.ts +++ b/packages/twenty-server/src/database/typeorm/raw/raw.datasource.ts @@ -1,6 +1,6 @@ import { config } from 'dotenv'; import { DataSource, DataSourceOptions } from 'typeorm'; -config(); +config({ path: process.env.NODE_ENV === 'test' ? '.env.test' : '.env' }); const typeORMRawModuleOptions: DataSourceOptions = { url: process.env.PG_DATABASE_URL, diff --git a/packages/twenty-server/src/database/typeorm/typeorm.service.ts b/packages/twenty-server/src/database/typeorm/typeorm.service.ts index a56f3580d..0e8b97094 100644 --- a/packages/twenty-server/src/database/typeorm/typeorm.service.ts +++ b/packages/twenty-server/src/database/typeorm/typeorm.service.ts @@ -4,15 +4,15 @@ import { DataSource } from 'typeorm'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; -import { User } from 'src/engine/core-modules/user/user.entity'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity'; -import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; -import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity'; -import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; +import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity'; import { PostgresCredentials } from 'src/engine/core-modules/postgres-credentials/postgres-credentials.entity'; +import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { User } from 'src/engine/core-modules/user/user.entity'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @Injectable() export class TypeORMService implements OnModuleInit, OnModuleDestroy { diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.module.ts b/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.module.ts index dbfb77932..8b4af6579 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.module.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.module.ts @@ -5,6 +5,6 @@ import { CoreEngineModule } from 'src/engine/core-modules/core-engine.module'; @Module({ imports: [CoreEngineModule], providers: [], - exports: [], + exports: [CoreEngineModule], }) export class GraphQLConfigModule {} diff --git a/packages/twenty-server/src/engine/core-modules/environment/environment.module.ts b/packages/twenty-server/src/engine/core-modules/environment/environment.module.ts index a80214e7d..841f2b0bb 100644 --- a/packages/twenty-server/src/engine/core-modules/environment/environment.module.ts +++ b/packages/twenty-server/src/engine/core-modules/environment/environment.module.ts @@ -1,9 +1,9 @@ import { Global, Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; -import { ConfigurableModuleClass } from 'src/engine/core-modules/environment/environment.module-definition'; import { validate } from 'src/engine/core-modules/environment/environment-variables'; +import { ConfigurableModuleClass } from 'src/engine/core-modules/environment/environment.module-definition'; +import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; @Global() @Module({ @@ -12,6 +12,7 @@ import { validate } from 'src/engine/core-modules/environment/environment-variab isGlobal: true, expandVariables: true, validate, + envFilePath: process.env.NODE_ENV === 'test' ? '.env.test' : '.env', }), ], providers: [EnvironmentService], diff --git a/packages/twenty-server/src/engine/core-modules/environment/interfaces/node-environment.interface.ts b/packages/twenty-server/src/engine/core-modules/environment/interfaces/node-environment.interface.ts index 9bb17d970..31af6fb09 100644 --- a/packages/twenty-server/src/engine/core-modules/environment/interfaces/node-environment.interface.ts +++ b/packages/twenty-server/src/engine/core-modules/environment/interfaces/node-environment.interface.ts @@ -1,4 +1,5 @@ export enum NodeEnvironment { + test = 'test', development = 'development', production = 'production', } diff --git a/packages/twenty-server/src/engine/middlewares/graphql-hydrate-request-from-token.middleware.ts b/packages/twenty-server/src/engine/middlewares/graphql-hydrate-request-from-token.middleware.ts index 0afdd8483..7375c64a8 100644 --- a/packages/twenty-server/src/engine/middlewares/graphql-hydrate-request-from-token.middleware.ts +++ b/packages/twenty-server/src/engine/middlewares/graphql-hydrate-request-from-token.middleware.ts @@ -8,7 +8,6 @@ import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.typ import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { handleExceptionAndConvertToGraphQLError } from 'src/engine/utils/global-exception-handler.util'; import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; - class GraphqlTokenValidationProxy { private tokenService: TokenService; diff --git a/packages/twenty-server/src/utils/generate-front-config.ts b/packages/twenty-server/src/utils/generate-front-config.ts index f6f0f663b..5bc490639 100644 --- a/packages/twenty-server/src/utils/generate-front-config.ts +++ b/packages/twenty-server/src/utils/generate-front-config.ts @@ -2,7 +2,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { config } from 'dotenv'; -config(); +config({ path: process.env.NODE_ENV === 'test' ? '.env.test' : '.env' }); export function generateFrontConfig(): void { const configObject = { diff --git a/packages/twenty-server/test/activities.integration-spec.ts b/packages/twenty-server/test/activities.integration-spec.ts new file mode 100644 index 000000000..01f262c8f --- /dev/null +++ b/packages/twenty-server/test/activities.integration-spec.ts @@ -0,0 +1,67 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('activitiesResolver (integration)', () => { + it('should find many activities', () => { + const queryData = { + query: ` + query activities { + activities { + edges { + node { + title + body + type + reminderAt + dueAt + completedAt + id + createdAt + updatedAt + deletedAt + authorId + assigneeId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.activities; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const activities = edges[0].node; + + expect(activities).toHaveProperty('title'); + expect(activities).toHaveProperty('body'); + expect(activities).toHaveProperty('type'); + expect(activities).toHaveProperty('reminderAt'); + expect(activities).toHaveProperty('dueAt'); + expect(activities).toHaveProperty('completedAt'); + expect(activities).toHaveProperty('id'); + expect(activities).toHaveProperty('createdAt'); + expect(activities).toHaveProperty('updatedAt'); + expect(activities).toHaveProperty('deletedAt'); + expect(activities).toHaveProperty('authorId'); + expect(activities).toHaveProperty('assigneeId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/activity-targets.integration-spec.ts b/packages/twenty-server/test/activity-targets.integration-spec.ts new file mode 100644 index 000000000..cbbeb216f --- /dev/null +++ b/packages/twenty-server/test/activity-targets.integration-spec.ts @@ -0,0 +1,59 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('activityTargetsResolver (integration)', () => { + it('should find many activityTargets', () => { + const queryData = { + query: ` + query activityTargets { + activityTargets { + edges { + node { + id + createdAt + updatedAt + deletedAt + activityId + personId + companyId + opportunityId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.activityTargets; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const activityTargets = edges[0].node; + + expect(activityTargets).toHaveProperty('id'); + expect(activityTargets).toHaveProperty('createdAt'); + expect(activityTargets).toHaveProperty('updatedAt'); + expect(activityTargets).toHaveProperty('deletedAt'); + expect(activityTargets).toHaveProperty('activityId'); + expect(activityTargets).toHaveProperty('personId'); + expect(activityTargets).toHaveProperty('companyId'); + expect(activityTargets).toHaveProperty('opportunityId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/api-keys.integration-spec.ts b/packages/twenty-server/test/api-keys.integration-spec.ts new file mode 100644 index 000000000..a196db086 --- /dev/null +++ b/packages/twenty-server/test/api-keys.integration-spec.ts @@ -0,0 +1,57 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('apiKeysResolver (integration)', () => { + it('should find many apiKeys', () => { + const queryData = { + query: ` + query apiKeys { + apiKeys { + edges { + node { + name + expiresAt + revokedAt + id + createdAt + updatedAt + deletedAt + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.apiKeys; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const apiKeys = edges[0].node; + + expect(apiKeys).toHaveProperty('name'); + expect(apiKeys).toHaveProperty('expiresAt'); + expect(apiKeys).toHaveProperty('revokedAt'); + expect(apiKeys).toHaveProperty('id'); + expect(apiKeys).toHaveProperty('createdAt'); + expect(apiKeys).toHaveProperty('updatedAt'); + expect(apiKeys).toHaveProperty('deletedAt'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/app.e2e-spec.ts b/packages/twenty-server/test/app.e2e-spec.ts deleted file mode 100644 index a8a18c8bc..000000000 --- a/packages/twenty-server/test/app.e2e-spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { INestApplication } from '@nestjs/common'; - -import request from 'supertest'; - -import { createApp } from './utils/create-app'; - -describe('AppController (e2e)', () => { - let app: INestApplication; - - beforeEach(async () => { - [app] = await createApp(); - }); - - afterEach(async () => { - await app.close(); - }); - - it('/healthz (GET)', () => { - return request(app.getHttpServer()) - .get('/healthz') - .expect(200) - .expect((response) => { - expect(response.body).toEqual({ - status: 'ok', - info: { database: { status: 'up' } }, - error: {}, - details: { database: { status: 'up' } }, - }); - }); - }); -}); diff --git a/packages/twenty-server/test/attachments.integration-spec.ts b/packages/twenty-server/test/attachments.integration-spec.ts new file mode 100644 index 000000000..440a6484e --- /dev/null +++ b/packages/twenty-server/test/attachments.integration-spec.ts @@ -0,0 +1,71 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('attachmentsResolver (integration)', () => { + it('should find many attachments', () => { + const queryData = { + query: ` + query attachments { + attachments { + edges { + node { + name + fullPath + type + id + createdAt + updatedAt + deletedAt + authorId + activityId + taskId + noteId + personId + companyId + opportunityId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.attachments; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const attachments = edges[0].node; + + expect(attachments).toHaveProperty('name'); + expect(attachments).toHaveProperty('fullPath'); + expect(attachments).toHaveProperty('type'); + expect(attachments).toHaveProperty('id'); + expect(attachments).toHaveProperty('createdAt'); + expect(attachments).toHaveProperty('updatedAt'); + expect(attachments).toHaveProperty('deletedAt'); + expect(attachments).toHaveProperty('authorId'); + expect(attachments).toHaveProperty('activityId'); + expect(attachments).toHaveProperty('taskId'); + expect(attachments).toHaveProperty('noteId'); + expect(attachments).toHaveProperty('personId'); + expect(attachments).toHaveProperty('companyId'); + expect(attachments).toHaveProperty('opportunityId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/audit-logs.integration-spec.ts b/packages/twenty-server/test/audit-logs.integration-spec.ts new file mode 100644 index 000000000..99a573235 --- /dev/null +++ b/packages/twenty-server/test/audit-logs.integration-spec.ts @@ -0,0 +1,65 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('auditLogsResolver (integration)', () => { + it('should find many auditLogs', () => { + const queryData = { + query: ` + query auditLogs { + auditLogs { + edges { + node { + name + properties + context + objectName + objectMetadataId + recordId + id + createdAt + updatedAt + deletedAt + workspaceMemberId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.auditLogs; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const auditLogs = edges[0].node; + + expect(auditLogs).toHaveProperty('name'); + expect(auditLogs).toHaveProperty('properties'); + expect(auditLogs).toHaveProperty('context'); + expect(auditLogs).toHaveProperty('objectName'); + expect(auditLogs).toHaveProperty('objectMetadataId'); + expect(auditLogs).toHaveProperty('recordId'); + expect(auditLogs).toHaveProperty('id'); + expect(auditLogs).toHaveProperty('createdAt'); + expect(auditLogs).toHaveProperty('updatedAt'); + expect(auditLogs).toHaveProperty('deletedAt'); + expect(auditLogs).toHaveProperty('workspaceMemberId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/auth.integration-spec.ts b/packages/twenty-server/test/auth.integration-spec.ts new file mode 100644 index 000000000..64cdde13c --- /dev/null +++ b/packages/twenty-server/test/auth.integration-spec.ts @@ -0,0 +1,80 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +const auth = { + email: 'tim@apple.dev', + password: 'Applecar2025', +}; + +describe('AuthResolve (integration)', () => { + let loginToken: string; + + it('should challenge with email and password', () => { + const queryData = { + query: ` + mutation Challenge { + challenge(email: "${auth.email}", password: "${auth.password}") { + loginToken { + token + expiresAt + } + } + } + `, + }; + + return client + .post('/graphql') + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.challenge; + + expect(data).toBeDefined(); + expect(data.loginToken).toBeDefined(); + + loginToken = data.loginToken.token; + }); + }); + + it('should verify with login token', () => { + const queryData = { + query: ` + mutation Verify { + verify(loginToken: "${loginToken}") { + tokens { + accessToken { + token + } + } + } + } + `, + }; + + return client + .post('/graphql') + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.verify; + + expect(data).toBeDefined(); + expect(data.tokens).toBeDefined(); + + const accessToken = data.tokens.accessToken; + + expect(accessToken).toBeDefined(); + expect(accessToken.token).toBeDefined(); + }); + }); +}); diff --git a/packages/twenty-server/test/blocklists.integration-spec.ts b/packages/twenty-server/test/blocklists.integration-spec.ts new file mode 100644 index 000000000..d8080b3cd --- /dev/null +++ b/packages/twenty-server/test/blocklists.integration-spec.ts @@ -0,0 +1,55 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('blocklistsResolver (integration)', () => { + it('should find many blocklists', () => { + const queryData = { + query: ` + query blocklists { + blocklists { + edges { + node { + handle + id + createdAt + updatedAt + deletedAt + workspaceMemberId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.blocklists; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const blocklists = edges[0].node; + + expect(blocklists).toHaveProperty('handle'); + expect(blocklists).toHaveProperty('id'); + expect(blocklists).toHaveProperty('createdAt'); + expect(blocklists).toHaveProperty('updatedAt'); + expect(blocklists).toHaveProperty('deletedAt'); + expect(blocklists).toHaveProperty('workspaceMemberId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/calendar-channel-event-associations.integration-spec.ts b/packages/twenty-server/test/calendar-channel-event-associations.integration-spec.ts new file mode 100644 index 000000000..5d03268b8 --- /dev/null +++ b/packages/twenty-server/test/calendar-channel-event-associations.integration-spec.ts @@ -0,0 +1,63 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('calendarChannelEventAssociationsResolver (integration)', () => { + it('should find many calendarChannelEventAssociations', () => { + const queryData = { + query: ` + query calendarChannelEventAssociations { + calendarChannelEventAssociations { + edges { + node { + eventExternalId + id + createdAt + updatedAt + deletedAt + calendarChannelId + calendarEventId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.calendarChannelEventAssociations; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const calendarChannelEventAssociations = edges[0].node; + + expect(calendarChannelEventAssociations).toHaveProperty( + 'eventExternalId', + ); + expect(calendarChannelEventAssociations).toHaveProperty('id'); + expect(calendarChannelEventAssociations).toHaveProperty('createdAt'); + expect(calendarChannelEventAssociations).toHaveProperty('updatedAt'); + expect(calendarChannelEventAssociations).toHaveProperty('deletedAt'); + expect(calendarChannelEventAssociations).toHaveProperty( + 'calendarChannelId', + ); + expect(calendarChannelEventAssociations).toHaveProperty( + 'calendarEventId', + ); + } + }); + }); +}); diff --git a/packages/twenty-server/test/calendar-channels.integration-spec.ts b/packages/twenty-server/test/calendar-channels.integration-spec.ts new file mode 100644 index 000000000..6056af6ac --- /dev/null +++ b/packages/twenty-server/test/calendar-channels.integration-spec.ts @@ -0,0 +1,75 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('calendarChannelsResolver (integration)', () => { + it('should find many calendarChannels', () => { + const queryData = { + query: ` + query calendarChannels { + calendarChannels { + edges { + node { + handle + syncStatus + syncStage + visibility + isContactAutoCreationEnabled + contactAutoCreationPolicy + isSyncEnabled + syncCursor + syncStageStartedAt + throttleFailureCount + id + createdAt + updatedAt + deletedAt + connectedAccountId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.calendarChannels; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const calendarChannels = edges[0].node; + + expect(calendarChannels).toHaveProperty('handle'); + expect(calendarChannels).toHaveProperty('syncStatus'); + expect(calendarChannels).toHaveProperty('syncStage'); + expect(calendarChannels).toHaveProperty('visibility'); + expect(calendarChannels).toHaveProperty( + 'isContactAutoCreationEnabled', + ); + expect(calendarChannels).toHaveProperty('contactAutoCreationPolicy'); + expect(calendarChannels).toHaveProperty('isSyncEnabled'); + expect(calendarChannels).toHaveProperty('syncCursor'); + expect(calendarChannels).toHaveProperty('syncStageStartedAt'); + expect(calendarChannels).toHaveProperty('throttleFailureCount'); + expect(calendarChannels).toHaveProperty('id'); + expect(calendarChannels).toHaveProperty('createdAt'); + expect(calendarChannels).toHaveProperty('updatedAt'); + expect(calendarChannels).toHaveProperty('deletedAt'); + expect(calendarChannels).toHaveProperty('connectedAccountId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/calendar-event-participants.integration-spec.ts b/packages/twenty-server/test/calendar-event-participants.integration-spec.ts new file mode 100644 index 000000000..50e65547e --- /dev/null +++ b/packages/twenty-server/test/calendar-event-participants.integration-spec.ts @@ -0,0 +1,65 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('calendarEventParticipantsResolver (integration)', () => { + it('should find many calendarEventParticipants', () => { + const queryData = { + query: ` + query calendarEventParticipants { + calendarEventParticipants { + edges { + node { + handle + displayName + isOrganizer + responseStatus + id + createdAt + updatedAt + deletedAt + calendarEventId + personId + workspaceMemberId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.calendarEventParticipants; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const calendarEventParticipants = edges[0].node; + + expect(calendarEventParticipants).toHaveProperty('handle'); + expect(calendarEventParticipants).toHaveProperty('displayName'); + expect(calendarEventParticipants).toHaveProperty('isOrganizer'); + expect(calendarEventParticipants).toHaveProperty('responseStatus'); + expect(calendarEventParticipants).toHaveProperty('id'); + expect(calendarEventParticipants).toHaveProperty('createdAt'); + expect(calendarEventParticipants).toHaveProperty('updatedAt'); + expect(calendarEventParticipants).toHaveProperty('deletedAt'); + expect(calendarEventParticipants).toHaveProperty('calendarEventId'); + expect(calendarEventParticipants).toHaveProperty('personId'); + expect(calendarEventParticipants).toHaveProperty('workspaceMemberId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/comments.integration-spec.ts b/packages/twenty-server/test/comments.integration-spec.ts new file mode 100644 index 000000000..0f89ba549 --- /dev/null +++ b/packages/twenty-server/test/comments.integration-spec.ts @@ -0,0 +1,57 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('commentsResolver (integration)', () => { + it('should find many comments', () => { + const queryData = { + query: ` + query comments { + comments { + edges { + node { + body + id + createdAt + updatedAt + deletedAt + authorId + activityId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.comments; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const comments = edges[0].node; + + expect(comments).toHaveProperty('body'); + expect(comments).toHaveProperty('id'); + expect(comments).toHaveProperty('createdAt'); + expect(comments).toHaveProperty('updatedAt'); + expect(comments).toHaveProperty('deletedAt'); + expect(comments).toHaveProperty('authorId'); + expect(comments).toHaveProperty('activityId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/companies.integration-spec.ts b/packages/twenty-server/test/companies.integration-spec.ts new file mode 100644 index 000000000..63c7f9eec --- /dev/null +++ b/packages/twenty-server/test/companies.integration-spec.ts @@ -0,0 +1,67 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('companiesResolver (integration)', () => { + it('should find many companies', () => { + const queryData = { + query: ` + query companies { + companies { + edges { + node { + name + employees + idealCustomerProfile + position + id + createdAt + updatedAt + deletedAt + accountOwnerId + tagline + workPolicy + visaSponsorship + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.companies; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const companies = edges[0].node; + + expect(companies).toHaveProperty('name'); + expect(companies).toHaveProperty('employees'); + expect(companies).toHaveProperty('idealCustomerProfile'); + expect(companies).toHaveProperty('position'); + expect(companies).toHaveProperty('id'); + expect(companies).toHaveProperty('createdAt'); + expect(companies).toHaveProperty('updatedAt'); + expect(companies).toHaveProperty('deletedAt'); + expect(companies).toHaveProperty('accountOwnerId'); + expect(companies).toHaveProperty('tagline'); + expect(companies).toHaveProperty('workPolicy'); + expect(companies).toHaveProperty('visaSponsorship'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/company.e2e-spec.ts b/packages/twenty-server/test/company.e2e-spec.ts deleted file mode 100644 index f3c29e694..000000000 --- a/packages/twenty-server/test/company.e2e-spec.ts +++ /dev/null @@ -1,310 +0,0 @@ -import { INestApplication } from '@nestjs/common'; - -import request from 'supertest'; - -import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; - -import { createApp } from './utils/create-app'; - -describe('CompanyResolver (e2e)', () => { - let app: INestApplication; - let companyId: string | undefined; - - const authGuardMock = { canActivate: (): any => true }; - - beforeEach(async () => { - [app] = await createApp({ - moduleBuilderHook: (moduleBuilder) => - moduleBuilder.overrideGuard(WorkspaceAuthGuard).useValue(authGuardMock), - }); - }); - - afterEach(async () => { - await app.close(); - }); - - it('should create a company', () => { - const queryData = { - query: ` - mutation CreateOneCompany($data: CompanyCreateInput!) { - createOneCompany(data: $data) { - id - name - domainName - address { - addressCity - } - } - } - `, - variables: { - data: { - name: 'New Company', - domainName: 'new-company.com', - address: { addressCity: 'Paris' }, - }, - }, - }; - - return request(app.getHttpServer()) - .post('/graphql') - .send(queryData) - .expect(200) - .expect((res) => { - const data = res.body.data.createOneCompany; - - companyId = data.id; - - expect(data).toBeDefined(); - expect(data).toHaveProperty('id'); - expect(data).toHaveProperty('name', 'New Company'); - expect(data).toHaveProperty('domainName', 'new-company.com'); - expect(data).toHaveProperty('address', { addressCity: 'Paris' }); - }); - }); - - it('should find many companies', () => { - const queryData = { - query: ` - query FindManyCompany { - findManyCompany { - id - name - domainName - address { - addressCity - } - } - } - `, - }; - - return request(app.getHttpServer()) - .post('/graphql') - .send(queryData) - .expect(200) - .expect((res) => { - const data = res.body.data.findManyCompany; - - expect(data).toBeDefined(); - expect(Array.isArray(data)).toBe(true); - expect(data.length).toBeGreaterThan(0); - - const company = data.find((c) => c.id === companyId); - - expect(company).toBeDefined(); - expect(company).toHaveProperty('id'); - expect(company).toHaveProperty('name', 'New Company'); - expect(company).toHaveProperty('domainName', 'new-company.com'); - expect(company).toHaveProperty('address', { addressCity: 'Paris' }); - - // Check if we have access to ressources outside of our workspace - const instagramCompany = data.find((c) => c.name === 'Instagram'); - - expect(instagramCompany).toBeUndefined(); - }); - }); - - it('should find unique company', () => { - const queryData = { - query: ` - query FindUniqueCompany($where: CompanyWhereUniqueInput!) { - findUniqueCompany(where: $where) { - id - name - domainName - address { - addressCity - } - } - } - `, - variables: { - where: { - id: companyId, - }, - }, - }; - - return request(app.getHttpServer()) - .post('/graphql') - .send(queryData) - .expect(200) - .expect((res) => { - const data = res.body.data.findUniqueCompany; - - expect(data).toBeDefined(); - expect(data).toHaveProperty('id'); - expect(data).toHaveProperty('name', 'New Company'); - expect(data).toHaveProperty('domainName', 'new-company.com'); - expect(data).toHaveProperty('address', { addressCity: 'Paris' }); - }); - }); - - it('should not find unique company (forbidden because outside workspace)', () => { - const queryData = { - query: ` - query FindUniqueCompany($where: CompanyWhereUniqueInput!) { - findUniqueCompany(where: $where) { - id - name - domainName - address { - addressCity - } - } - } - `, - variables: { - where: { - id: 'twenty-dev-a674fa6c-1455-4c57-afaf-dd5dc086361e', - }, - }, - }; - - return request(app.getHttpServer()) - .post('/graphql') - .send(queryData) - .expect(200) - .expect((res) => { - const errors = res.body.errors; - const error = errors?.[0]; - - expect(error).toBeDefined(); - expect(error.message).toBe('Forbidden resource'); - }); - }); - - it('should update a company', () => { - const queryData = { - query: ` - mutation UpdateOneCompany($where: CompanyWhereUniqueInput!, $data: CompanyUpdateInput!) { - updateOneCompany(data: $data, where: $where) { - id - name - domainName - address { - addressCity - } - } - } - `, - variables: { - where: { - id: companyId, - }, - data: { - name: 'Updated Company', - domainName: 'updated-company.com', - address: { addressCity: 'Updated City' }, - }, - }, - }; - - return request(app.getHttpServer()) - .post('/graphql') - .send(queryData) - .expect(200) - .expect((res) => { - const data = res.body.data.updateOneCompany; - - expect(data).toBeDefined(); - expect(data).toHaveProperty('id'); - expect(data).toHaveProperty('name', 'Updated Company'); - expect(data).toHaveProperty('domainName', 'updated-company.com'); - expect(data).toHaveProperty('address', { addressCity: 'Updated City' }); - }); - }); - - it('should not update a company (forbidden because outside workspace)', () => { - const queryData = { - query: ` - mutation UpdateOneCompany($where: CompanyWhereUniqueInput!, $data: CompanyUpdateInput!) { - updateOneCompany(data: $data, where: $where) { - id - name - domainName - address { - addressCity - } - } - } - `, - variables: { - where: { - id: 'twenty-dev-a674fa6c-1455-4c57-afaf-dd5dc086361e', - }, - data: { - name: 'Updated Instagram', - }, - }, - }; - - return request(app.getHttpServer()) - .post('/graphql') - .send(queryData) - .expect(200) - .expect((res) => { - const errors = res.body.errors; - const error = errors?.[0]; - - expect(error).toBeDefined(); - expect(error.message).toBe('Forbidden resource'); - }); - }); - - it('should delete a company', () => { - const queryData = { - query: ` - mutation DeleteManyCompany($ids: [String!]) { - deleteManyCompany(where: {id: {in: $ids}}) { - count - } - } - `, - variables: { - ids: [companyId], - }, - }; - - return request(app.getHttpServer()) - .post('/graphql') - .send(queryData) - .expect(200) - .expect((res) => { - const data = res.body.data.deleteManyCompany; - - companyId = undefined; - - expect(data).toBeDefined(); - expect(data).toHaveProperty('count', 1); - }); - }); - - it('should not delete a company (forbidden because outside workspace)', () => { - const queryData = { - query: ` - mutation DeleteManyCompany($ids: [String!]) { - deleteManyCompany(where: {id: {in: $ids}}) { - count - } - } - `, - variables: { - ids: ['twenty-dev-a674fa6c-1455-4c57-afaf-dd5dc086361e'], - }, - }; - - return request(app.getHttpServer()) - .post('/graphql') - .send(queryData) - .expect(200) - .expect((res) => { - const errors = res.body.errors; - const error = errors?.[0]; - - expect(error).toBeDefined(); - expect(error.message).toBe('Forbidden resource'); - }); - }); -}); diff --git a/packages/twenty-server/test/company.integration-spec.ts b/packages/twenty-server/test/company.integration-spec.ts new file mode 100644 index 000000000..bd25d66eb --- /dev/null +++ b/packages/twenty-server/test/company.integration-spec.ts @@ -0,0 +1,48 @@ +import request from 'supertest'; + +const graphqlClient = request(`http://localhost:${APP_PORT}`); + +describe('CompanyResolver (integration)', () => { + it('should find many companies', () => { + const queryData = { + query: ` + query Companies { + companies { + edges { + node { + id + name + } + } + } + } + `, + }; + + return graphqlClient + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.companies; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const company = edges[0].node; + + expect(company).toBeDefined(); + expect(company).toHaveProperty('id'); + expect(company).toHaveProperty('name'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/connected-accounts.integration-spec.ts b/packages/twenty-server/test/connected-accounts.integration-spec.ts new file mode 100644 index 000000000..e17fd5b28 --- /dev/null +++ b/packages/twenty-server/test/connected-accounts.integration-spec.ts @@ -0,0 +1,67 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('connectedAccountsResolver (integration)', () => { + it('should find many connectedAccounts', () => { + const queryData = { + query: ` + query connectedAccounts { + connectedAccounts { + edges { + node { + handle + provider + accessToken + refreshToken + lastSyncHistoryId + authFailedAt + handleAliases + id + createdAt + updatedAt + deletedAt + accountOwnerId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.connectedAccounts; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const connectedAccounts = edges[0].node; + + expect(connectedAccounts).toHaveProperty('handle'); + expect(connectedAccounts).toHaveProperty('provider'); + expect(connectedAccounts).toHaveProperty('accessToken'); + expect(connectedAccounts).toHaveProperty('refreshToken'); + expect(connectedAccounts).toHaveProperty('lastSyncHistoryId'); + expect(connectedAccounts).toHaveProperty('authFailedAt'); + expect(connectedAccounts).toHaveProperty('handleAliases'); + expect(connectedAccounts).toHaveProperty('id'); + expect(connectedAccounts).toHaveProperty('createdAt'); + expect(connectedAccounts).toHaveProperty('updatedAt'); + expect(connectedAccounts).toHaveProperty('deletedAt'); + expect(connectedAccounts).toHaveProperty('accountOwnerId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/favorites.integration-spec.ts b/packages/twenty-server/test/favorites.integration-spec.ts new file mode 100644 index 000000000..f58e702e5 --- /dev/null +++ b/packages/twenty-server/test/favorites.integration-spec.ts @@ -0,0 +1,67 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('favoritesResolver (integration)', () => { + it('should find many favorites', () => { + const queryData = { + query: ` + query favorites { + favorites { + edges { + node { + position + id + createdAt + updatedAt + deletedAt + workspaceMemberId + personId + companyId + opportunityId + taskId + noteId + viewId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.favorites; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const favorites = edges[0].node; + + expect(favorites).toHaveProperty('position'); + expect(favorites).toHaveProperty('id'); + expect(favorites).toHaveProperty('createdAt'); + expect(favorites).toHaveProperty('updatedAt'); + expect(favorites).toHaveProperty('deletedAt'); + expect(favorites).toHaveProperty('workspaceMemberId'); + expect(favorites).toHaveProperty('personId'); + expect(favorites).toHaveProperty('companyId'); + expect(favorites).toHaveProperty('opportunityId'); + expect(favorites).toHaveProperty('taskId'); + expect(favorites).toHaveProperty('noteId'); + expect(favorites).toHaveProperty('viewId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/jest-e2e.json b/packages/twenty-server/test/jest-e2e.json deleted file mode 100644 index 0456e6d75..000000000 --- a/packages/twenty-server/test/jest-e2e.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "moduleFileExtensions": ["js", "json", "ts"], - "rootDir": ".", - "testEnvironment": "node", - "testRegex": ".e2e-spec.ts$", - "setupFilesAfterEnv": ["/utils/setup-tests.ts"], - "moduleNameMapper": { - "^src/(.*)": "/../src/$1", - "^test/(.*)": "/$1" - }, - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - } -} diff --git a/packages/twenty-server/test/message-channel-message-associations.integration-spec.ts b/packages/twenty-server/test/message-channel-message-associations.integration-spec.ts new file mode 100644 index 000000000..a250550f4 --- /dev/null +++ b/packages/twenty-server/test/message-channel-message-associations.integration-spec.ts @@ -0,0 +1,67 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('messageChannelMessageAssociationsResolver (integration)', () => { + it('should find many messageChannelMessageAssociations', () => { + const queryData = { + query: ` + query messageChannelMessageAssociations { + messageChannelMessageAssociations { + edges { + node { + createdAt + messageExternalId + messageThreadExternalId + direction + id + updatedAt + deletedAt + messageChannelId + messageId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.messageChannelMessageAssociations; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const messageChannelMessageAssociations = edges[0].node; + + expect(messageChannelMessageAssociations).toHaveProperty('createdAt'); + expect(messageChannelMessageAssociations).toHaveProperty( + 'messageExternalId', + ); + expect(messageChannelMessageAssociations).toHaveProperty( + 'messageThreadExternalId', + ); + expect(messageChannelMessageAssociations).toHaveProperty('direction'); + expect(messageChannelMessageAssociations).toHaveProperty('id'); + expect(messageChannelMessageAssociations).toHaveProperty('updatedAt'); + expect(messageChannelMessageAssociations).toHaveProperty('deletedAt'); + expect(messageChannelMessageAssociations).toHaveProperty( + 'messageChannelId', + ); + expect(messageChannelMessageAssociations).toHaveProperty('messageId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/message-channels.integration-spec.ts b/packages/twenty-server/test/message-channels.integration-spec.ts new file mode 100644 index 000000000..8100a885d --- /dev/null +++ b/packages/twenty-server/test/message-channels.integration-spec.ts @@ -0,0 +1,85 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('messageChannelsResolver (integration)', () => { + it('should find many messageChannels', () => { + const queryData = { + query: ` + query messageChannels { + messageChannels { + edges { + node { + visibility + handle + type + isContactAutoCreationEnabled + contactAutoCreationPolicy + excludeNonProfessionalEmails + excludeGroupEmails + isSyncEnabled + syncCursor + syncedAt + syncStatus + syncStage + syncStageStartedAt + throttleFailureCount + id + createdAt + updatedAt + deletedAt + connectedAccountId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.messageChannels; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const messageChannels = edges[0].node; + + expect(messageChannels).toHaveProperty('visibility'); + expect(messageChannels).toHaveProperty('handle'); + expect(messageChannels).toHaveProperty('type'); + expect(messageChannels).toHaveProperty( + 'isContactAutoCreationEnabled', + ); + expect(messageChannels).toHaveProperty('contactAutoCreationPolicy'); + expect(messageChannels).toHaveProperty( + 'excludeNonProfessionalEmails', + ); + expect(messageChannels).toHaveProperty('excludeGroupEmails'); + expect(messageChannels).toHaveProperty('isSyncEnabled'); + expect(messageChannels).toHaveProperty('syncCursor'); + expect(messageChannels).toHaveProperty('syncedAt'); + expect(messageChannels).toHaveProperty('syncStatus'); + expect(messageChannels).toHaveProperty('syncStage'); + expect(messageChannels).toHaveProperty('syncStageStartedAt'); + expect(messageChannels).toHaveProperty('throttleFailureCount'); + expect(messageChannels).toHaveProperty('id'); + expect(messageChannels).toHaveProperty('createdAt'); + expect(messageChannels).toHaveProperty('updatedAt'); + expect(messageChannels).toHaveProperty('deletedAt'); + expect(messageChannels).toHaveProperty('connectedAccountId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/message-participants.integration-spec.ts b/packages/twenty-server/test/message-participants.integration-spec.ts new file mode 100644 index 000000000..45c190c53 --- /dev/null +++ b/packages/twenty-server/test/message-participants.integration-spec.ts @@ -0,0 +1,63 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('messageParticipantsResolver (integration)', () => { + it('should find many messageParticipants', () => { + const queryData = { + query: ` + query messageParticipants { + messageParticipants { + edges { + node { + role + handle + displayName + id + createdAt + updatedAt + deletedAt + messageId + personId + workspaceMemberId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.messageParticipants; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const messageParticipants = edges[0].node; + + expect(messageParticipants).toHaveProperty('role'); + expect(messageParticipants).toHaveProperty('handle'); + expect(messageParticipants).toHaveProperty('displayName'); + expect(messageParticipants).toHaveProperty('id'); + expect(messageParticipants).toHaveProperty('createdAt'); + expect(messageParticipants).toHaveProperty('updatedAt'); + expect(messageParticipants).toHaveProperty('deletedAt'); + expect(messageParticipants).toHaveProperty('messageId'); + expect(messageParticipants).toHaveProperty('personId'); + expect(messageParticipants).toHaveProperty('workspaceMemberId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/message-threads.integration-spec.ts b/packages/twenty-server/test/message-threads.integration-spec.ts new file mode 100644 index 000000000..714bf06bb --- /dev/null +++ b/packages/twenty-server/test/message-threads.integration-spec.ts @@ -0,0 +1,51 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('messageThreadsResolver (integration)', () => { + it('should find many messageThreads', () => { + const queryData = { + query: ` + query messageThreads { + messageThreads { + edges { + node { + id + createdAt + updatedAt + deletedAt + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.messageThreads; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const messageThreads = edges[0].node; + + expect(messageThreads).toHaveProperty('id'); + expect(messageThreads).toHaveProperty('createdAt'); + expect(messageThreads).toHaveProperty('updatedAt'); + expect(messageThreads).toHaveProperty('deletedAt'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/mock-data/user.json b/packages/twenty-server/test/mock-data/user.json deleted file mode 100644 index f6fbc0d03..000000000 --- a/packages/twenty-server/test/mock-data/user.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "id": "20202020-a838-4fa9-b59b-96409b9a1c30", - "firstName": "Tim", - "lastName": "Apple", - "email": "tim@apple.dev", - "locale": "en", - "passwordHash": "$2b$10$66d.6DuQExxnrfI9rMqOg.U1XIYpagr6Lv05uoWLYbYmtK0HDIvS6", - "avatarUrl": null -} diff --git a/packages/twenty-server/test/mock-data/workspace.json b/packages/twenty-server/test/mock-data/workspace.json deleted file mode 100644 index 37d7f52a3..000000000 --- a/packages/twenty-server/test/mock-data/workspace.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "20202020-1c25-4d02-bf25-6aeccf7ea419", - "displayName": "Apple", - "domainName": "apple.dev", - "inviteHash": "apple.dev-invite-hash", - "logo": "data:image/png;base64,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" -} diff --git a/packages/twenty-server/test/note-targets.integration-spec.ts b/packages/twenty-server/test/note-targets.integration-spec.ts new file mode 100644 index 000000000..30d309dc3 --- /dev/null +++ b/packages/twenty-server/test/note-targets.integration-spec.ts @@ -0,0 +1,59 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('noteTargetsResolver (integration)', () => { + it('should find many noteTargets', () => { + const queryData = { + query: ` + query noteTargets { + noteTargets { + edges { + node { + id + createdAt + updatedAt + deletedAt + noteId + personId + companyId + opportunityId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.noteTargets; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const noteTargets = edges[0].node; + + expect(noteTargets).toHaveProperty('id'); + expect(noteTargets).toHaveProperty('createdAt'); + expect(noteTargets).toHaveProperty('updatedAt'); + expect(noteTargets).toHaveProperty('deletedAt'); + expect(noteTargets).toHaveProperty('noteId'); + expect(noteTargets).toHaveProperty('personId'); + expect(noteTargets).toHaveProperty('companyId'); + expect(noteTargets).toHaveProperty('opportunityId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/notes.integration-spec.ts b/packages/twenty-server/test/notes.integration-spec.ts new file mode 100644 index 000000000..eb13fedba --- /dev/null +++ b/packages/twenty-server/test/notes.integration-spec.ts @@ -0,0 +1,57 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('notesResolver (integration)', () => { + it('should find many notes', () => { + const queryData = { + query: ` + query notes { + notes { + edges { + node { + position + title + body + id + createdAt + updatedAt + deletedAt + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.notes; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const notes = edges[0].node; + + expect(notes).toHaveProperty('position'); + expect(notes).toHaveProperty('title'); + expect(notes).toHaveProperty('body'); + expect(notes).toHaveProperty('id'); + expect(notes).toHaveProperty('createdAt'); + expect(notes).toHaveProperty('updatedAt'); + expect(notes).toHaveProperty('deletedAt'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/objects.integration-spec.ts b/packages/twenty-server/test/objects.integration-spec.ts new file mode 100644 index 000000000..80d1458ab --- /dev/null +++ b/packages/twenty-server/test/objects.integration-spec.ts @@ -0,0 +1,75 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('objectsResolver (integration)', () => { + it('should find many objects', () => { + const queryData = { + query: ` + query objects { + objects { + edges { + node { + id + dataSourceId + nameSingular + namePlural + labelSingular + labelPlural + description + icon + isCustom + isRemote + isActive + isSystem + createdAt + updatedAt + labelIdentifierFieldMetadataId + imageIdentifierFieldMetadataId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.objects; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const objects = edges[0].node; + + expect(objects).toHaveProperty('id'); + expect(objects).toHaveProperty('dataSourceId'); + expect(objects).toHaveProperty('nameSingular'); + expect(objects).toHaveProperty('namePlural'); + expect(objects).toHaveProperty('labelSingular'); + expect(objects).toHaveProperty('labelPlural'); + expect(objects).toHaveProperty('description'); + expect(objects).toHaveProperty('icon'); + expect(objects).toHaveProperty('isCustom'); + expect(objects).toHaveProperty('isRemote'); + expect(objects).toHaveProperty('isActive'); + expect(objects).toHaveProperty('isSystem'); + expect(objects).toHaveProperty('createdAt'); + expect(objects).toHaveProperty('updatedAt'); + expect(objects).toHaveProperty('labelIdentifierFieldMetadataId'); + expect(objects).toHaveProperty('imageIdentifierFieldMetadataId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/opportunities.integration-spec.ts b/packages/twenty-server/test/opportunities.integration-spec.ts new file mode 100644 index 000000000..9eebe96a0 --- /dev/null +++ b/packages/twenty-server/test/opportunities.integration-spec.ts @@ -0,0 +1,63 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('opportunitiesResolver (integration)', () => { + it('should find many opportunities', () => { + const queryData = { + query: ` + query opportunities { + opportunities { + edges { + node { + name + closeDate + stage + position + id + createdAt + updatedAt + deletedAt + pointOfContactId + companyId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.opportunities; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const opportunities = edges[0].node; + + expect(opportunities).toHaveProperty('name'); + expect(opportunities).toHaveProperty('closeDate'); + expect(opportunities).toHaveProperty('stage'); + expect(opportunities).toHaveProperty('position'); + expect(opportunities).toHaveProperty('id'); + expect(opportunities).toHaveProperty('createdAt'); + expect(opportunities).toHaveProperty('updatedAt'); + expect(opportunities).toHaveProperty('deletedAt'); + expect(opportunities).toHaveProperty('pointOfContactId'); + expect(opportunities).toHaveProperty('companyId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/people.integration-spec.ts b/packages/twenty-server/test/people.integration-spec.ts new file mode 100644 index 000000000..b96bb6700 --- /dev/null +++ b/packages/twenty-server/test/people.integration-spec.ts @@ -0,0 +1,71 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('peopleResolver (integration)', () => { + it('should find many people', () => { + const queryData = { + query: ` + query people { + people { + edges { + node { + jobTitle + phone + city + avatarUrl + position + id + createdAt + updatedAt + deletedAt + companyId + intro + whatsapp + workPrefereance + performanceRating + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.people; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const people = edges[0].node; + + expect(people).toHaveProperty('jobTitle'); + expect(people).toHaveProperty('phone'); + expect(people).toHaveProperty('city'); + expect(people).toHaveProperty('avatarUrl'); + expect(people).toHaveProperty('position'); + expect(people).toHaveProperty('id'); + expect(people).toHaveProperty('createdAt'); + expect(people).toHaveProperty('updatedAt'); + expect(people).toHaveProperty('deletedAt'); + expect(people).toHaveProperty('companyId'); + expect(people).toHaveProperty('intro'); + expect(people).toHaveProperty('whatsapp'); + expect(people).toHaveProperty('workPrefereance'); + expect(people).toHaveProperty('performanceRating'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/serverless-functions.integration-spec.ts b/packages/twenty-server/test/serverless-functions.integration-spec.ts new file mode 100644 index 000000000..b4b87ff7c --- /dev/null +++ b/packages/twenty-server/test/serverless-functions.integration-spec.ts @@ -0,0 +1,61 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('serverlessFunctionsResolver (integration)', () => { + it('should find many serverlessFunctions', () => { + const queryData = { + query: ` + query serverlessFunctions { + serverlessFunctions { + edges { + node { + id + name + description + sourceCodeHash + runtime + latestVersion + syncStatus + createdAt + updatedAt + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.serverlessFunctions; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const serverlessFunctions = edges[0].node; + + expect(serverlessFunctions).toHaveProperty('id'); + expect(serverlessFunctions).toHaveProperty('name'); + expect(serverlessFunctions).toHaveProperty('description'); + expect(serverlessFunctions).toHaveProperty('sourceCodeHash'); + expect(serverlessFunctions).toHaveProperty('runtime'); + expect(serverlessFunctions).toHaveProperty('latestVersion'); + expect(serverlessFunctions).toHaveProperty('syncStatus'); + expect(serverlessFunctions).toHaveProperty('createdAt'); + expect(serverlessFunctions).toHaveProperty('updatedAt'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/task-targets.integration-spec.ts b/packages/twenty-server/test/task-targets.integration-spec.ts new file mode 100644 index 000000000..e54e855d3 --- /dev/null +++ b/packages/twenty-server/test/task-targets.integration-spec.ts @@ -0,0 +1,59 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('taskTargetsResolver (integration)', () => { + it('should find many taskTargets', () => { + const queryData = { + query: ` + query taskTargets { + taskTargets { + edges { + node { + id + createdAt + updatedAt + deletedAt + taskId + personId + companyId + opportunityId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.taskTargets; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const taskTargets = edges[0].node; + + expect(taskTargets).toHaveProperty('id'); + expect(taskTargets).toHaveProperty('createdAt'); + expect(taskTargets).toHaveProperty('updatedAt'); + expect(taskTargets).toHaveProperty('deletedAt'); + expect(taskTargets).toHaveProperty('taskId'); + expect(taskTargets).toHaveProperty('personId'); + expect(taskTargets).toHaveProperty('companyId'); + expect(taskTargets).toHaveProperty('opportunityId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/tasks.integration-spec.ts b/packages/twenty-server/test/tasks.integration-spec.ts new file mode 100644 index 000000000..900fd3de5 --- /dev/null +++ b/packages/twenty-server/test/tasks.integration-spec.ts @@ -0,0 +1,63 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('tasksResolver (integration)', () => { + it('should find many tasks', () => { + const queryData = { + query: ` + query tasks { + tasks { + edges { + node { + position + title + body + dueAt + status + id + createdAt + updatedAt + deletedAt + assigneeId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.tasks; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const tasks = edges[0].node; + + expect(tasks).toHaveProperty('position'); + expect(tasks).toHaveProperty('title'); + expect(tasks).toHaveProperty('body'); + expect(tasks).toHaveProperty('dueAt'); + expect(tasks).toHaveProperty('status'); + expect(tasks).toHaveProperty('id'); + expect(tasks).toHaveProperty('createdAt'); + expect(tasks).toHaveProperty('updatedAt'); + expect(tasks).toHaveProperty('deletedAt'); + expect(tasks).toHaveProperty('assigneeId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/timeline-activities.integration-spec.ts b/packages/twenty-server/test/timeline-activities.integration-spec.ts new file mode 100644 index 000000000..a5ef6a5f9 --- /dev/null +++ b/packages/twenty-server/test/timeline-activities.integration-spec.ts @@ -0,0 +1,75 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('timelineActivitiesResolver (integration)', () => { + it('should find many timelineActivities', () => { + const queryData = { + query: ` + query timelineActivities { + timelineActivities { + edges { + node { + happensAt + name + properties + linkedRecordCachedName + linkedRecordId + linkedObjectMetadataId + id + createdAt + updatedAt + deletedAt + workspaceMemberId + personId + companyId + opportunityId + noteId + taskId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.timelineActivities; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const timelineActivities = edges[0].node; + + expect(timelineActivities).toHaveProperty('happensAt'); + expect(timelineActivities).toHaveProperty('name'); + expect(timelineActivities).toHaveProperty('properties'); + expect(timelineActivities).toHaveProperty('linkedRecordCachedName'); + expect(timelineActivities).toHaveProperty('linkedRecordId'); + expect(timelineActivities).toHaveProperty('linkedObjectMetadataId'); + expect(timelineActivities).toHaveProperty('id'); + expect(timelineActivities).toHaveProperty('createdAt'); + expect(timelineActivities).toHaveProperty('updatedAt'); + expect(timelineActivities).toHaveProperty('deletedAt'); + expect(timelineActivities).toHaveProperty('workspaceMemberId'); + expect(timelineActivities).toHaveProperty('personId'); + expect(timelineActivities).toHaveProperty('companyId'); + expect(timelineActivities).toHaveProperty('opportunityId'); + expect(timelineActivities).toHaveProperty('noteId'); + expect(timelineActivities).toHaveProperty('taskId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/utils/create-app.ts b/packages/twenty-server/test/utils/create-app.ts index 9011599ea..dfa136e4d 100644 --- a/packages/twenty-server/test/utils/create-app.ts +++ b/packages/twenty-server/test/utils/create-app.ts @@ -1,10 +1,6 @@ import { NestExpressApplication } from '@nestjs/platform-express'; import { Test, TestingModule, TestingModuleBuilder } from '@nestjs/testing'; -import mockUser from 'test/mock-data/user.json'; -import mockWorkspace from 'test/mock-data/workspace.json'; -import { RequestHandler } from 'express'; - import { AppModule } from 'src/app.module'; interface TestingModuleCreatePreHook { @@ -19,14 +15,14 @@ export type TestingAppCreatePreHook = ( ) => Promise; /** - * Sets basic e2e testing module of app + * Sets basic integration testing module of app */ export const createApp = async ( config: { moduleBuilderHook?: TestingModuleCreatePreHook; appInitHook?: TestingAppCreatePreHook; } = {}, -): Promise<[NestExpressApplication, TestingModule]> => { +): Promise => { let moduleBuilder: TestingModuleBuilder = Test.createTestingModule({ imports: [AppModule], }); @@ -36,21 +32,14 @@ export const createApp = async ( } const moduleFixture: TestingModule = await moduleBuilder.compile(); + const app = moduleFixture.createNestApplication(); if (config.appInitHook) { await config.appInitHook(app); } - const mockAuthHandler: RequestHandler = (req, _res, next) => { - req.user = { - user: mockUser, - workspace: mockWorkspace, - }; - next(); - }; + await app.init(); - app.use(mockAuthHandler); - - return [await app.init(), moduleFixture]; + return app; }; diff --git a/packages/twenty-server/test/utils/setup-test.ts b/packages/twenty-server/test/utils/setup-test.ts new file mode 100644 index 000000000..aac860fc7 --- /dev/null +++ b/packages/twenty-server/test/utils/setup-test.ts @@ -0,0 +1,16 @@ +import 'tsconfig-paths/register'; +import { JestConfigWithTsJest } from 'ts-jest'; + +import { createApp } from './create-app'; + +export default async (_, projectConfig: JestConfigWithTsJest) => { + const app = await createApp({}); + + if (!projectConfig.globals) { + throw new Error('No globals found in project config'); + } + + await app.listen(projectConfig.globals.APP_PORT); + + global.app = app; +}; diff --git a/packages/twenty-server/test/utils/setup-tests.ts b/packages/twenty-server/test/utils/setup-tests.ts deleted file mode 100644 index 9e48ba513..000000000 --- a/packages/twenty-server/test/utils/setup-tests.ts +++ /dev/null @@ -1,3 +0,0 @@ -global.beforeEach(() => { - // resetDb(); -}); diff --git a/packages/twenty-server/test/utils/teardown-test.ts b/packages/twenty-server/test/utils/teardown-test.ts new file mode 100644 index 000000000..8cc1946d5 --- /dev/null +++ b/packages/twenty-server/test/utils/teardown-test.ts @@ -0,0 +1,5 @@ +import 'tsconfig-paths/register'; + +export default async () => { + global.app.close(); +}; diff --git a/packages/twenty-server/test/view-fields.integration-spec.ts b/packages/twenty-server/test/view-fields.integration-spec.ts new file mode 100644 index 000000000..058568763 --- /dev/null +++ b/packages/twenty-server/test/view-fields.integration-spec.ts @@ -0,0 +1,61 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('viewFieldsResolver (integration)', () => { + it('should find many viewFields', () => { + const queryData = { + query: ` + query viewFields { + viewFields { + edges { + node { + fieldMetadataId + isVisible + size + position + id + createdAt + updatedAt + deletedAt + viewId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.viewFields; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const viewFields = edges[0].node; + + expect(viewFields).toHaveProperty('fieldMetadataId'); + expect(viewFields).toHaveProperty('isVisible'); + expect(viewFields).toHaveProperty('size'); + expect(viewFields).toHaveProperty('position'); + expect(viewFields).toHaveProperty('id'); + expect(viewFields).toHaveProperty('createdAt'); + expect(viewFields).toHaveProperty('updatedAt'); + expect(viewFields).toHaveProperty('deletedAt'); + expect(viewFields).toHaveProperty('viewId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/view-filters.integration-spec.ts b/packages/twenty-server/test/view-filters.integration-spec.ts new file mode 100644 index 000000000..8caa942b2 --- /dev/null +++ b/packages/twenty-server/test/view-filters.integration-spec.ts @@ -0,0 +1,61 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('viewFiltersResolver (integration)', () => { + it('should find many viewFilters', () => { + const queryData = { + query: ` + query viewFilters { + viewFilters { + edges { + node { + fieldMetadataId + operand + value + displayValue + id + createdAt + updatedAt + deletedAt + viewId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.viewFilters; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const viewFilters = edges[0].node; + + expect(viewFilters).toHaveProperty('fieldMetadataId'); + expect(viewFilters).toHaveProperty('operand'); + expect(viewFilters).toHaveProperty('value'); + expect(viewFilters).toHaveProperty('displayValue'); + expect(viewFilters).toHaveProperty('id'); + expect(viewFilters).toHaveProperty('createdAt'); + expect(viewFilters).toHaveProperty('updatedAt'); + expect(viewFilters).toHaveProperty('deletedAt'); + expect(viewFilters).toHaveProperty('viewId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/view-sorts.integration-spec.ts b/packages/twenty-server/test/view-sorts.integration-spec.ts new file mode 100644 index 000000000..fc29b1d4c --- /dev/null +++ b/packages/twenty-server/test/view-sorts.integration-spec.ts @@ -0,0 +1,57 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('viewSortsResolver (integration)', () => { + it('should find many viewSorts', () => { + const queryData = { + query: ` + query viewSorts { + viewSorts { + edges { + node { + fieldMetadataId + direction + id + createdAt + updatedAt + deletedAt + viewId + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.viewSorts; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const viewSorts = edges[0].node; + + expect(viewSorts).toHaveProperty('fieldMetadataId'); + expect(viewSorts).toHaveProperty('direction'); + expect(viewSorts).toHaveProperty('id'); + expect(viewSorts).toHaveProperty('createdAt'); + expect(viewSorts).toHaveProperty('updatedAt'); + expect(viewSorts).toHaveProperty('deletedAt'); + expect(viewSorts).toHaveProperty('viewId'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/views.integration-spec.ts b/packages/twenty-server/test/views.integration-spec.ts new file mode 100644 index 000000000..122a8c398 --- /dev/null +++ b/packages/twenty-server/test/views.integration-spec.ts @@ -0,0 +1,67 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('viewsResolver (integration)', () => { + it('should find many views', () => { + const queryData = { + query: ` + query views { + views { + edges { + node { + position + name + objectMetadataId + type + key + icon + kanbanFieldMetadataId + isCompact + id + createdAt + updatedAt + deletedAt + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.views; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const views = edges[0].node; + + expect(views).toHaveProperty('position'); + expect(views).toHaveProperty('name'); + expect(views).toHaveProperty('objectMetadataId'); + expect(views).toHaveProperty('type'); + expect(views).toHaveProperty('key'); + expect(views).toHaveProperty('icon'); + expect(views).toHaveProperty('kanbanFieldMetadataId'); + expect(views).toHaveProperty('isCompact'); + expect(views).toHaveProperty('id'); + expect(views).toHaveProperty('createdAt'); + expect(views).toHaveProperty('updatedAt'); + expect(views).toHaveProperty('deletedAt'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/webhooks.integration-spec.ts b/packages/twenty-server/test/webhooks.integration-spec.ts new file mode 100644 index 000000000..7c4224b69 --- /dev/null +++ b/packages/twenty-server/test/webhooks.integration-spec.ts @@ -0,0 +1,57 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('webhooksResolver (integration)', () => { + it('should find many webhooks', () => { + const queryData = { + query: ` + query webhooks { + webhooks { + edges { + node { + targetUrl + operation + description + id + createdAt + updatedAt + deletedAt + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.webhooks; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const webhooks = edges[0].node; + + expect(webhooks).toHaveProperty('targetUrl'); + expect(webhooks).toHaveProperty('operation'); + expect(webhooks).toHaveProperty('description'); + expect(webhooks).toHaveProperty('id'); + expect(webhooks).toHaveProperty('createdAt'); + expect(webhooks).toHaveProperty('updatedAt'); + expect(webhooks).toHaveProperty('deletedAt'); + } + }); + }); +}); diff --git a/packages/twenty-server/test/workspace-members.integration-spec.ts b/packages/twenty-server/test/workspace-members.integration-spec.ts new file mode 100644 index 000000000..5ef7a415d --- /dev/null +++ b/packages/twenty-server/test/workspace-members.integration-spec.ts @@ -0,0 +1,67 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('workspaceMembersResolver (integration)', () => { + it('should find many workspaceMembers', () => { + const queryData = { + query: ` + query workspaceMembers { + workspaceMembers { + edges { + node { + id + colorScheme + avatarUrl + locale + timeZone + dateFormat + timeFormat + userEmail + userId + createdAt + updatedAt + deletedAt + } + } + } + } + `, + }; + + return client + .post('/graphql') + .set('Authorization', `Bearer ${ACCESS_TOKEN}`) + .send(queryData) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.errors).toBeUndefined(); + }) + .expect((res) => { + const data = res.body.data.workspaceMembers; + + expect(data).toBeDefined(); + expect(Array.isArray(data.edges)).toBe(true); + + const edges = data.edges; + + if (edges.length > 0) { + const workspaceMembers = edges[0].node; + + expect(workspaceMembers).toHaveProperty('id'); + expect(workspaceMembers).toHaveProperty('colorScheme'); + expect(workspaceMembers).toHaveProperty('avatarUrl'); + expect(workspaceMembers).toHaveProperty('locale'); + expect(workspaceMembers).toHaveProperty('timeZone'); + expect(workspaceMembers).toHaveProperty('dateFormat'); + expect(workspaceMembers).toHaveProperty('timeFormat'); + expect(workspaceMembers).toHaveProperty('userEmail'); + expect(workspaceMembers).toHaveProperty('userId'); + expect(workspaceMembers).toHaveProperty('createdAt'); + expect(workspaceMembers).toHaveProperty('updatedAt'); + expect(workspaceMembers).toHaveProperty('deletedAt'); + } + }); + }); +}); diff --git a/packages/twenty-server/tsconfig.json b/packages/twenty-server/tsconfig.json index 7b2af2c49..272cbdde6 100644 --- a/packages/twenty-server/tsconfig.json +++ b/packages/twenty-server/tsconfig.json @@ -27,7 +27,7 @@ "paths": { "src/*": ["packages/twenty-server/src/*"], "test/*": ["packages/twenty-server/test/*"], - "twenty-emails": ["packages/twenty-emails/src/index.ts"] + "twenty-emails": ["packages/twenty-emails/dist"] } }, "ts-node": { diff --git a/packages/twenty-server/tsconfig.scripts.json b/packages/twenty-server/tsconfig.scripts.json new file mode 100644 index 000000000..ae5b155f1 --- /dev/null +++ b/packages/twenty-server/tsconfig.scripts.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["scripts/**/*.ts"], + "exclude": ["node_modules", "dist"] +}