Compare commits

..

1 Commits

Author SHA1 Message Date
Jakub P.
d31309a92d test: add delete flow tests 2024-10-16 23:33:43 +02:00
246 changed files with 18584 additions and 24060 deletions

View File

@@ -5,11 +5,8 @@ BACKEND_PORT=3000
WEB_PORT=3001 WEB_PORT=3001
echo "Configuring backend environment variables..." echo "Configuring backend environment variables..."
cd packages/backend cd packages/backend
rm -rf .env rm -rf .env
echo " echo "
PORT=$BACKEND_PORT PORT=$BACKEND_PORT
WEB_APP_URL=http://localhost:$WEB_PORT WEB_APP_URL=http://localhost:$WEB_PORT
@@ -24,34 +21,23 @@ WEBHOOK_SECRET_KEY=sample_webhook_secret_key
APP_SECRET_KEY=sample_app_secret_key APP_SECRET_KEY=sample_app_secret_key
REDIS_HOST=redis REDIS_HOST=redis
SERVE_WEB_APP_SEPARATELY=true" >> .env SERVE_WEB_APP_SEPARATELY=true" >> .env
echo "Installing backend dependencies..."
yarn
cd $CURRENT_DIR cd $CURRENT_DIR
echo "Configuring web environment variables..." echo "Configuring web environment variables..."
cd packages/web cd packages/web
rm -rf .env rm -rf .env
echo " echo "
PORT=$WEB_PORT PORT=$WEB_PORT
REACT_APP_BACKEND_URL=http://localhost:$BACKEND_PORT REACT_APP_BACKEND_URL=http://localhost:$BACKEND_PORT
" >> .env " >> .env
echo "Installing web dependencies..."
yarn
cd $CURRENT_DIR cd $CURRENT_DIR
echo "Installing and linking dependencies..."
yarn
yarn lerna bootstrap
echo "Migrating database..." echo "Migrating database..."
cd packages/backend cd packages/backend
yarn db:migrate yarn db:migrate
yarn db:seed:user yarn db:seed:user

View File

@@ -41,11 +41,8 @@ jobs:
with: with:
node-version: 18 node-version: 18
- name: Install dependencies - name: Install dependencies
run: yarn run: cd packages/backend && yarn
working-directory: packages/backend
- name: Copy .env-example.test file to .env.test - name: Copy .env-example.test file to .env.test
run: cp .env-example.test .env.test run: cd packages/backend && cp .env-example.test .env.test
working-directory: packages/backend
- name: Run tests - name: Run tests
run: yarn test:coverage run: cd packages/backend && yarn test
working-directory: packages/backend

View File

@@ -18,13 +18,11 @@ jobs:
with: with:
node-version: '18' node-version: '18'
cache: 'yarn' cache: 'yarn'
cache-dependency-path: packages/backend/yarn.lock cache-dependency-path: yarn.lock
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner." - run: echo "🖥️ The workflow is now ready to test your code on the runner."
- run: yarn --frozen-lockfile - run: yarn --frozen-lockfile
working-directory: packages/backend - run: cd packages/backend && yarn lint
- run: yarn lint
working-directory: packages/backend
- run: echo "🍏 This job's status is ${{ job.status }}." - run: echo "🍏 This job's status is ${{ job.status }}."
start-backend-server: start-backend-server:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -37,13 +35,11 @@ jobs:
with: with:
node-version: '18' node-version: '18'
cache: 'yarn' cache: 'yarn'
cache-dependency-path: packages/backend/yarn.lock cache-dependency-path: yarn.lock
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner." - run: echo "🖥️ The workflow is now ready to test your code on the runner."
- run: yarn --frozen-lockfile - run: yarn --frozen-lockfile && yarn lerna bootstrap
working-directory: packages/backend - run: cd packages/backend && yarn start
- run: yarn start
working-directory: packages/backend
env: env:
ENCRYPTION_KEY: sample_encryption_key ENCRYPTION_KEY: sample_encryption_key
WEBHOOK_SECRET_KEY: sample_webhook_secret_key WEBHOOK_SECRET_KEY: sample_webhook_secret_key
@@ -59,13 +55,11 @@ jobs:
with: with:
node-version: '18' node-version: '18'
cache: 'yarn' cache: 'yarn'
cache-dependency-path: packages/backend/yarn.lock cache-dependency-path: yarn.lock
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner." - run: echo "🖥️ The workflow is now ready to test your code on the runner."
- run: yarn --frozen-lockfile - run: yarn --frozen-lockfile && yarn lerna bootstrap
working-directory: packages/backend - run: cd packages/backend && yarn start:worker
- run: yarn start:worker
working-directory: packages/backend
env: env:
ENCRYPTION_KEY: sample_encryption_key ENCRYPTION_KEY: sample_encryption_key
WEBHOOK_SECRET_KEY: sample_webhook_secret_key WEBHOOK_SECRET_KEY: sample_webhook_secret_key
@@ -81,13 +75,11 @@ jobs:
with: with:
node-version: '18' node-version: '18'
cache: 'yarn' cache: 'yarn'
cache-dependency-path: packages/web/yarn.lock cache-dependency-path: yarn.lock
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner." - run: echo "🖥️ The workflow is now ready to test your code on the runner."
- run: yarn --frozen-lockfile - run: yarn --frozen-lockfile && yarn lerna bootstrap
working-directory: packages/web - run: cd packages/web && yarn build
- run: yarn build
working-directory: packages/web
env: env:
CI: false CI: false
- run: echo "🍏 This job's status is ${{ job.status }}." - run: echo "🍏 This job's status is ${{ job.status }}."

View File

@@ -3,7 +3,6 @@ on:
push: push:
branches: branches:
- main - main
# TODO: Add pull request after optimizing the total excecution time of the test suite.
pull_request: pull_request:
paths: paths:
- 'packages/backend/**' - 'packages/backend/**'
@@ -13,6 +12,9 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
BULLMQ_DASHBOARD_USERNAME: root
BULLMQ_DASHBOARD_PASSWORD: sample
ENABLE_BULLMQ_DASHBOARD: true
ENCRYPTION_KEY: sample_encryption_key ENCRYPTION_KEY: sample_encryption_key
WEBHOOK_SECRET_KEY: sample_webhook_secret_key WEBHOOK_SECRET_KEY: sample_webhook_secret_key
APP_SECRET_KEY: sample_app_secret_key APP_SECRET_KEY: sample_app_secret_key
@@ -23,6 +25,7 @@ env:
POSTGRES_PASSWORD: automatisch_password POSTGRES_PASSWORD: automatisch_password
REDIS_HOST: localhost REDIS_HOST: localhost
APP_ENV: production APP_ENV: production
PORT: 3000
LICENSE_KEY: dummy_license_key LICENSE_KEY: dummy_license_key
jobs: jobs:
@@ -59,21 +62,13 @@ jobs:
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 18
- name: Install web dependencies - name: Install dependencies
run: yarn run: yarn && yarn lerna bootstrap
working-directory: ./packages/web
- name: Install backend dependencies
run: yarn
working-directory: ./packages/backend
- name: Install e2e-tests dependencies
run: yarn
working-directory: ./packages/e2e-tests
- name: Install Playwright Browsers - name: Install Playwright Browsers
run: yarn playwright install --with-deps run: yarn playwright install --with-deps
working-directory: ./packages/e2e-tests
- name: Build Automatisch web - name: Build Automatisch web
run: yarn build
working-directory: ./packages/web working-directory: ./packages/web
run: yarn build
env: env:
# Keep this until we clean up warnings in build processes # Keep this until we clean up warnings in build processes
CI: false CI: false

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@ logs
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
lerna-debug.log*
.pnpm-debug.log* .pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html) # Diagnostic reports (https://nodejs.org/api/report.html)

View File

@@ -11,12 +11,10 @@ WORKDIR /automatisch
# copy the app, note .dockerignore # copy the app, note .dockerignore
COPY . /automatisch COPY . /automatisch
RUN cd packages/web && yarn RUN yarn
RUN cd packages/web && yarn build RUN cd packages/web && yarn build
RUN cd packages/backend && yarn --production
RUN \ RUN \
rm -rf /usr/local/share/.cache/ && \ rm -rf /usr/local/share/.cache/ && \
apk del build-dependencies apk del build-dependencies

13
lerna.json Normal file
View File

@@ -0,0 +1,13 @@
{
"packages": [
"packages/*"
],
"version": "0.10.0",
"npmClient": "yarn",
"useWorkspaces": true,
"command": {
"add": {
"exact": true
}
}
}

32
package.json Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "@automatisch/root",
"license": "See LICENSE file",
"private": true,
"scripts": {
"start": "lerna run --stream --parallel --scope=@*/{web,backend} dev",
"start:web": "lerna run --stream --scope=@*/web dev",
"start:backend": "lerna run --stream --scope=@*/backend dev",
"build:docs": "cd ./packages/docs && yarn install && yarn build"
},
"workspaces": {
"packages": [
"packages/*"
],
"nohoist": [
"**/babel-loader",
"**/webpack",
"**/@automatisch/web",
"**/ajv"
]
},
"devDependencies": {
"eslint": "^8.13.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"lerna": "^4.0.0",
"prettier": "^2.5.1"
},
"publishConfig": {
"access": "public"
}
}

View File

@@ -12,7 +12,6 @@
"pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js", "pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js",
"test": "APP_ENV=test vitest run", "test": "APP_ENV=test vitest run",
"test:watch": "APP_ENV=test vitest watch", "test:watch": "APP_ENV=test vitest watch",
"test:coverage": "yarn test --coverage",
"lint": "eslint .", "lint": "eslint .",
"db:create": "node ./bin/database/create.js", "db:create": "node ./bin/database/create.js",
"db:seed:user": "node ./bin/database/seed-user.js", "db:seed:user": "node ./bin/database/seed-user.js",
@@ -24,7 +23,6 @@
"dependencies": { "dependencies": {
"@bull-board/express": "^3.10.1", "@bull-board/express": "^3.10.1",
"@casl/ability": "^6.5.0", "@casl/ability": "^6.5.0",
"@faker-js/faker": "^9.2.0",
"@node-saml/passport-saml": "^4.0.4", "@node-saml/passport-saml": "^4.0.4",
"@rudderstack/rudder-sdk-node": "^1.1.2", "@rudderstack/rudder-sdk-node": "^1.1.2",
"@sentry/node": "^7.42.0", "@sentry/node": "^7.42.0",
@@ -38,9 +36,6 @@
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"debug": "~2.6.9", "debug": "~2.6.9",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"eslint": "^8.13.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"express": "~4.18.2", "express": "~4.18.2",
"express-async-errors": "^3.1.1", "express-async-errors": "^3.1.1",
"express-basic-auth": "^1.2.1", "express-basic-auth": "^1.2.1",
@@ -66,7 +61,6 @@
"pg": "^8.7.1", "pg": "^8.7.1",
"php-serialize": "^4.0.2", "php-serialize": "^4.0.2",
"pluralize": "^8.0.0", "pluralize": "^8.0.0",
"prettier": "^2.5.1",
"raw-body": "^2.5.2", "raw-body": "^2.5.2",
"showdown": "^2.1.0", "showdown": "^2.1.0",
"uuid": "^9.0.1", "uuid": "^9.0.1",
@@ -98,11 +92,10 @@
"url": "https://github.com/automatisch/automatisch/issues" "url": "https://github.com/automatisch/automatisch/issues"
}, },
"devDependencies": { "devDependencies": {
"@vitest/coverage-v8": "^2.1.5",
"node-gyp": "^10.1.0", "node-gyp": "^10.1.0",
"nodemon": "^2.0.13", "nodemon": "^2.0.13",
"supertest": "^6.3.3", "supertest": "^6.3.3",
"vitest": "^2.1.5" "vitest": "^1.1.3"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

View File

@@ -8,7 +8,7 @@ export default {
key: 'instanceUrl', key: 'instanceUrl',
label: 'WordPress instance URL', label: 'WordPress instance URL',
type: 'string', type: 'string',
required: true, required: false,
readOnly: false, readOnly: false,
value: null, value: null,
placeholder: null, placeholder: null,

View File

@@ -52,7 +52,7 @@ const appConfig = {
isDev: appEnv === 'development', isDev: appEnv === 'development',
isTest: appEnv === 'test', isTest: appEnv === 'test',
isProd: appEnv === 'production', isProd: appEnv === 'production',
version: '0.14.0', version: '0.13.1',
postgresDatabase: process.env.POSTGRES_DATABASE || 'automatisch_development', postgresDatabase: process.env.POSTGRES_DATABASE || 'automatisch_development',
postgresSchema: process.env.POSTGRES_SCHEMA || 'public', postgresSchema: process.env.POSTGRES_SCHEMA || 'public',
postgresPort: parseInt(process.env.POSTGRES_PORT || '5432'), postgresPort: parseInt(process.env.POSTGRES_PORT || '5432'),

View File

@@ -32,7 +32,7 @@ describe('POST /api/v1/access-tokens', () => {
}) })
.expect(422); .expect(422);
expect(response.body.errors.general).toStrictEqual([ expect(response.body.errors.general).toEqual([
'Incorrect email or password.', 'Incorrect email or password.',
]); ]);
}); });

View File

@@ -83,7 +83,7 @@ describe('POST /api/v1/admin/apps/:appKey/auth-clients', () => {
.send(appAuthClient) .send(appAuthClient)
.expect(422); .expect(422);
expect(response.body.meta.type).toStrictEqual('ModelValidation'); expect(response.body.meta.type).toEqual('ModelValidation');
expect(response.body.errors).toMatchObject({ expect(response.body.errors).toMatchObject({
name: ["must have required property 'name'"], name: ["must have required property 'name'"],
formattedAuthDefaults: [ formattedAuthDefaults: [

View File

@@ -10,11 +10,12 @@ export default async (request, response) => {
}; };
const appConfigParams = (request) => { const appConfigParams = (request) => {
const { useOnlyPredefinedAuthClients, disabled } = request.body; const { allowCustomConnection, shared, disabled } = request.body;
return { return {
key: request.params.appKey, key: request.params.appKey,
useOnlyPredefinedAuthClients, allowCustomConnection,
shared,
disabled, disabled,
}; };
}; };

View File

@@ -23,7 +23,8 @@ describe('POST /api/v1/admin/apps/:appKey/config', () => {
it('should return created app config', async () => { it('should return created app config', async () => {
const appConfig = { const appConfig = {
useOnlyPredefinedAuthClients: false, allowCustomConnection: true,
shared: true,
disabled: false, disabled: false,
}; };
@@ -37,14 +38,14 @@ describe('POST /api/v1/admin/apps/:appKey/config', () => {
...appConfig, ...appConfig,
key: 'gitlab', key: 'gitlab',
}); });
expect(response.body).toMatchObject(expectedPayload); expect(response.body).toMatchObject(expectedPayload);
}); });
it('should return HTTP 422 for already existing app config', async () => { it('should return HTTP 422 for already existing app config', async () => {
const appConfig = { const appConfig = {
key: 'gitlab', key: 'gitlab',
useOnlyPredefinedAuthClients: false, allowCustomConnection: true,
shared: true,
disabled: false, disabled: false,
}; };
@@ -58,7 +59,7 @@ describe('POST /api/v1/admin/apps/:appKey/config', () => {
}) })
.expect(422); .expect(422);
expect(response.body.meta.type).toStrictEqual('UniqueViolationError'); expect(response.body.meta.type).toEqual('UniqueViolationError');
expect(response.body.errors).toMatchObject({ expect(response.body.errors).toMatchObject({
key: ["'key' must be unique."], key: ["'key' must be unique."],
}); });

View File

@@ -32,7 +32,7 @@ describe('GET /api/v1/admin/apps/:appKey/auth-clients/:appAuthClientId', () => {
.expect(200); .expect(200);
const expectedPayload = getAppAuthClientMock(currentAppAuthClient); const expectedPayload = getAppAuthClientMock(currentAppAuthClient);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for not existing app auth client ID', async () => { it('should return not found response for not existing app auth client ID', async () => {

View File

@@ -39,6 +39,6 @@ describe('GET /api/v1/admin/apps/:appKey/auth-clients', () => {
appAuthClientOne, appAuthClientOne,
]); ]);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
}); });

View File

@@ -8,19 +8,17 @@ export default async (request, response) => {
}) })
.throwIfNotFound(); .throwIfNotFound();
await appConfig.$query().patchAndFetch({ await appConfig.$query().patchAndFetch(appConfigParams(request));
...appConfigParams(request),
key: request.params.appKey,
});
renderObject(response, appConfig); renderObject(response, appConfig);
}; };
const appConfigParams = (request) => { const appConfigParams = (request) => {
const { useOnlyPredefinedAuthClients, disabled } = request.body; const { allowCustomConnection, shared, disabled } = request.body;
return { return {
useOnlyPredefinedAuthClients, allowCustomConnection,
shared,
disabled, disabled,
}; };
}; };

View File

@@ -24,15 +24,17 @@ describe('PATCH /api/v1/admin/apps/:appKey/config', () => {
it('should return updated app config', async () => { it('should return updated app config', async () => {
const appConfig = { const appConfig = {
key: 'gitlab', key: 'gitlab',
useOnlyPredefinedAuthClients: true, allowCustomConnection: true,
shared: true,
disabled: false, disabled: false,
}; };
await createAppConfig(appConfig); await createAppConfig(appConfig);
const newAppConfigValues = { const newAppConfigValues = {
shared: false,
disabled: true, disabled: true,
useOnlyPredefinedAuthClients: false, allowCustomConnection: false,
}; };
const response = await request(app) const response = await request(app)
@@ -51,8 +53,9 @@ describe('PATCH /api/v1/admin/apps/:appKey/config', () => {
it('should return not found response for unexisting app config', async () => { it('should return not found response for unexisting app config', async () => {
const appConfig = { const appConfig = {
shared: false,
disabled: true, disabled: true,
useOnlyPredefinedAuthClients: false, allowCustomConnection: false,
}; };
await request(app) await request(app)
@@ -65,7 +68,8 @@ describe('PATCH /api/v1/admin/apps/:appKey/config', () => {
it('should return HTTP 422 for invalid app config data', async () => { it('should return HTTP 422 for invalid app config data', async () => {
const appConfig = { const appConfig = {
key: 'gitlab', key: 'gitlab',
useOnlyPredefinedAuthClients: true, allowCustomConnection: true,
shared: true,
disabled: false, disabled: false,
}; };
@@ -79,7 +83,7 @@ describe('PATCH /api/v1/admin/apps/:appKey/config', () => {
}) })
.expect(422); .expect(422);
expect(response.body.meta.type).toStrictEqual('ModelValidation'); expect(response.body.meta.type).toEqual('ModelValidation');
expect(response.body.errors).toMatchObject({ expect(response.body.errors).toMatchObject({
disabled: ['must be boolean'], disabled: ['must be boolean'],
}); });

View File

@@ -50,8 +50,8 @@ describe('PATCH /api/v1/admin/config', () => {
.send(newConfigValues) .send(newConfigValues)
.expect(200); .expect(200);
expect(response.body.data.title).toStrictEqual(newTitle); expect(response.body.data.title).toEqual(newTitle);
expect(response.body.meta.type).toStrictEqual('Config'); expect(response.body.meta.type).toEqual('Config');
}); });
it('should return created config for unexisting config', async () => { it('should return created config for unexisting config', async () => {
@@ -67,8 +67,8 @@ describe('PATCH /api/v1/admin/config', () => {
.send(newConfigValues) .send(newConfigValues)
.expect(200); .expect(200);
expect(response.body.data.title).toStrictEqual(newTitle); expect(response.body.data.title).toEqual(newTitle);
expect(response.body.meta.type).toStrictEqual('Config'); expect(response.body.meta.type).toEqual('Config');
}); });
it('should return null for deleted config entry', async () => { it('should return null for deleted config entry', async () => {
@@ -83,6 +83,6 @@ describe('PATCH /api/v1/admin/config', () => {
.expect(200); .expect(200);
expect(response.body.data.title).toBeNull(); expect(response.body.data.title).toBeNull();
expect(response.body.meta.type).toStrictEqual('Config'); expect(response.body.meta.type).toEqual('Config');
}); });
}); });

View File

@@ -27,6 +27,6 @@ describe('GET /api/v1/admin/permissions/catalog', () => {
const expectedPayload = await getPermissionsCatalogMock(); const expectedPayload = await getPermissionsCatalogMock();
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
}); });

View File

@@ -58,7 +58,7 @@ describe('POST /api/v1/admin/roles', () => {
] ]
); );
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return unprocessable entity response for invalid role data', async () => { it('should return unprocessable entity response for invalid role data', async () => {

View File

@@ -92,4 +92,21 @@ describe('DELETE /api/v1/admin/roles/:roleId', () => {
}, },
}); });
}); });
it('should not delete role and permissions on unsuccessful response', async () => {
const role = await createRole();
const permission = await createPermission({ roleId: role.id });
await createUser({ roleId: role.id });
await request(app)
.delete(`/api/v1/admin/roles/${role.id}`)
.set('Authorization', token)
.expect(422);
const refetchedRole = await role.$query();
const refetchedPermission = await permission.$query();
expect(refetchedRole).toStrictEqual(role);
expect(refetchedPermission).toStrictEqual(permission);
});
}); });

View File

@@ -34,7 +34,7 @@ describe('GET /api/v1/admin/roles/:roleId', () => {
permissionTwo, permissionTwo,
]); ]);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for not existing role UUID', async () => { it('should return not found response for not existing role UUID', async () => {

View File

@@ -28,6 +28,6 @@ describe('GET /api/v1/admin/roles', () => {
const expectedPayload = await getRolesMock([roleOne, roleTwo]); const expectedPayload = await getRolesMock([roleOne, roleTwo]);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
}); });

View File

@@ -7,7 +7,7 @@ export default async (request, response) => {
.throwIfNotFound(); .throwIfNotFound();
const roleMappings = await samlAuthProvider const roleMappings = await samlAuthProvider
.$relatedQuery('roleMappings') .$relatedQuery('samlAuthProvidersRoleMappings')
.orderBy('remote_role_name', 'asc'); .orderBy('remote_role_name', 'asc');
renderObject(response, roleMappings); renderObject(response, roleMappings);

View File

@@ -46,6 +46,6 @@ describe('GET /api/v1/admin/saml-auth-providers/:samlAuthProviderId/role-mapping
roleMappingTwo, roleMappingTwo,
]); ]);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
}); });

View File

@@ -30,7 +30,7 @@ describe('GET /api/v1/admin/saml-auth-provider/:samlAuthProviderId', () => {
const expectedPayload = await getSamlAuthProviderMock(samlAuthProvider); const expectedPayload = await getSamlAuthProviderMock(samlAuthProvider);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for not existing saml auth provider UUID', async () => { it('should return not found response for not existing saml auth provider UUID', async () => {

View File

@@ -34,6 +34,6 @@ describe('GET /api/v1/admin/saml-auth-providers', () => {
samlAuthProviderOne, samlAuthProviderOne,
]); ]);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
}); });

View File

@@ -8,14 +8,15 @@ export default async (request, response) => {
.findById(samlAuthProviderId) .findById(samlAuthProviderId)
.throwIfNotFound(); .throwIfNotFound();
const roleMappings = await samlAuthProvider.updateRoleMappings( const samlAuthProvidersRoleMappings =
roleMappingsParams(request) await samlAuthProvider.updateRoleMappings(
); samlAuthProvidersRoleMappingsParams(request)
);
renderObject(response, roleMappings); renderObject(response, samlAuthProvidersRoleMappings);
}; };
const roleMappingsParams = (request) => { const samlAuthProvidersRoleMappingsParams = (request) => {
const roleMappings = request.body; const roleMappings = request.body;
return roleMappings.map(({ roleId, remoteRoleName }) => ({ return roleMappings.map(({ roleId, remoteRoleName }) => ({

View File

@@ -6,7 +6,7 @@ import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by
import { createRole } from '../../../../../../test/factories/role.js'; import { createRole } from '../../../../../../test/factories/role.js';
import { createUser } from '../../../../../../test/factories/user.js'; import { createUser } from '../../../../../../test/factories/user.js';
import { createSamlAuthProvider } from '../../../../../../test/factories/saml-auth-provider.ee.js'; import { createSamlAuthProvider } from '../../../../../../test/factories/saml-auth-provider.ee.js';
import { createRoleMapping } from '../../../../../../test/factories/role-mapping.js'; import { createSamlAuthProvidersRoleMapping } from '../../../../../../test/factories/saml-auth-providers-role-mapping.js';
import createRoleMappingsMock from '../../../../../../test/mocks/rest/api/v1/admin/saml-auth-providers/update-role-mappings.ee.js'; import createRoleMappingsMock from '../../../../../../test/mocks/rest/api/v1/admin/saml-auth-providers/update-role-mappings.ee.js';
import * as license from '../../../../../helpers/license.ee.js'; import * as license from '../../../../../helpers/license.ee.js';
@@ -21,12 +21,12 @@ describe('PATCH /api/v1/admin/saml-auth-providers/:samlAuthProviderId/role-mappi
samlAuthProvider = await createSamlAuthProvider(); samlAuthProvider = await createSamlAuthProvider();
await createRoleMapping({ await createSamlAuthProvidersRoleMapping({
samlAuthProviderId: samlAuthProvider.id, samlAuthProviderId: samlAuthProvider.id,
remoteRoleName: 'Viewer', remoteRoleName: 'Viewer',
}); });
await createRoleMapping({ await createSamlAuthProvidersRoleMapping({
samlAuthProviderId: samlAuthProvider.id, samlAuthProviderId: samlAuthProvider.id,
remoteRoleName: 'Editor', remoteRoleName: 'Editor',
}); });
@@ -64,7 +64,7 @@ describe('PATCH /api/v1/admin/saml-auth-providers/:samlAuthProviderId/role-mappi
it('should delete role mappings when given empty role mappings', async () => { it('should delete role mappings when given empty role mappings', async () => {
const existingRoleMappings = await samlAuthProvider.$relatedQuery( const existingRoleMappings = await samlAuthProvider.$relatedQuery(
'roleMappings' 'samlAuthProvidersRoleMappings'
); );
expect(existingRoleMappings.length).toBe(2); expect(existingRoleMappings.length).toBe(2);
@@ -149,4 +149,34 @@ describe('PATCH /api/v1/admin/saml-auth-providers/:samlAuthProviderId/role-mappi
.send(roleMappings) .send(roleMappings)
.expect(404); .expect(404);
}); });
it('should not delete existing role mapping when error thrown', async () => {
const roleMappings = [
{
roleId: userRole.id,
remoteRoleName: {
invalid: 'data',
},
},
];
const roleMappingsBeforeRequest = await samlAuthProvider.$relatedQuery(
'samlAuthProvidersRoleMappings'
);
await request(app)
.patch(
`/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}/role-mappings`
)
.set('Authorization', token)
.send(roleMappings)
.expect(422);
const roleMappingsAfterRequest = await samlAuthProvider.$relatedQuery(
'samlAuthProvidersRoleMappings'
);
expect(roleMappingsBeforeRequest).toStrictEqual(roleMappingsAfterRequest);
expect(roleMappingsAfterRequest.length).toBe(2);
});
}); });

View File

@@ -30,7 +30,7 @@ describe('GET /api/v1/admin/users/:userId', () => {
.expect(200); .expect(200);
const expectedPayload = getUserMock(anotherUser, anotherUserRole); const expectedPayload = getUserMock(anotherUser, anotherUserRole);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for not existing user UUID', async () => { it('should return not found response for not existing user UUID', async () => {

View File

@@ -40,6 +40,6 @@ describe('GET /api/v1/admin/users', () => {
[anotherUserRole, currentUserRole] [anotherUserRole, currentUserRole]
); );
expect(response.body).toStrictEqual(expectedResponsePayload); expect(response.body).toEqual(expectedResponsePayload);
}); });
}); });

View File

@@ -61,8 +61,7 @@ describe('PATCH /api/v1/admin/users/:userId', () => {
.send(anotherUserUpdatedData) .send(anotherUserUpdatedData)
.expect(422); .expect(422);
expect(response.body.meta.type).toStrictEqual('ModelValidation'); expect(response.body.meta.type).toEqual('ModelValidation');
expect(response.body.errors).toMatchObject({ expect(response.body.errors).toMatchObject({
email: ['must be string'], email: ['must be string'],
fullName: ['must be string'], fullName: ['must be string'],

View File

@@ -155,7 +155,7 @@ describe('POST /api/v1/apps/:appKey/connections', () => {
await createAppConfig({ await createAppConfig({
key: 'gitlab', key: 'gitlab',
disabled: false, disabled: false,
useOnlyPredefinedAuthClients: false, allowCustomConnection: true,
}); });
}); });
@@ -218,7 +218,7 @@ describe('POST /api/v1/apps/:appKey/connections', () => {
await createAppConfig({ await createAppConfig({
key: 'gitlab', key: 'gitlab',
disabled: false, disabled: false,
useOnlyPredefinedAuthClients: true, allowCustomConnection: false,
}); });
}); });
@@ -266,14 +266,14 @@ describe('POST /api/v1/apps/:appKey/connections', () => {
}); });
}); });
describe('with auth client enabled', async () => { describe('with auth clients enabled', async () => {
let appAuthClient; let appAuthClient;
beforeEach(async () => { beforeEach(async () => {
await createAppConfig({ await createAppConfig({
key: 'gitlab', key: 'gitlab',
disabled: false, disabled: false,
useOnlyPredefinedAuthClients: false, shared: true,
}); });
appAuthClient = await createAppAuthClient({ appAuthClient = await createAppAuthClient({
@@ -310,6 +310,19 @@ describe('POST /api/v1/apps/:appKey/connections', () => {
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toStrictEqual(expectedPayload);
}); });
it('should return not authorized response for appAuthClientId and formattedData together', async () => {
const connectionData = {
appAuthClientId: appAuthClient.id,
formattedData: {},
};
await request(app)
.post('/api/v1/apps/gitlab/connections')
.set('Authorization', token)
.send(connectionData)
.expect(403);
});
it('should return not found response for invalid app key', async () => { it('should return not found response for invalid app key', async () => {
await request(app) await request(app)
.post('/api/v1/apps/invalid-app-key/connections') .post('/api/v1/apps/invalid-app-key/connections')
@@ -336,20 +349,18 @@ describe('POST /api/v1/apps/:appKey/connections', () => {
}); });
}); });
}); });
describe('with auth clients disabled', async () => {
describe('with auth client disabled', async () => {
let appAuthClient; let appAuthClient;
beforeEach(async () => { beforeEach(async () => {
await createAppConfig({ await createAppConfig({
key: 'gitlab', key: 'gitlab',
disabled: false, disabled: false,
useOnlyPredefinedAuthClients: false, shared: false,
}); });
appAuthClient = await createAppAuthClient({ appAuthClient = await createAppAuthClient({
appKey: 'gitlab', appKey: 'gitlab',
active: false,
}); });
}); });
@@ -362,7 +373,7 @@ describe('POST /api/v1/apps/:appKey/connections', () => {
.post('/api/v1/apps/gitlab/connections') .post('/api/v1/apps/gitlab/connections')
.set('Authorization', token) .set('Authorization', token)
.send(connectionData) .send(connectionData)
.expect(404); .expect(403);
}); });
it('should return not found response for invalid app key', async () => { it('should return not found response for invalid app key', async () => {

View File

@@ -29,7 +29,7 @@ describe('GET /api/v1/apps/:appKey/actions/:actionKey/substeps', () => {
.expect(200); .expect(200);
const expectedPayload = getActionSubstepsMock(exampleAction.substeps); const expectedPayload = getActionSubstepsMock(exampleAction.substeps);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for invalid app key', async () => { it('should return not found response for invalid app key', async () => {
@@ -47,6 +47,6 @@ describe('GET /api/v1/apps/:appKey/actions/:actionKey/substeps', () => {
.set('Authorization', token) .set('Authorization', token)
.expect(200); .expect(200);
expect(response.body.data).toStrictEqual([]); expect(response.body.data).toEqual([]);
}); });
}); });

View File

@@ -23,7 +23,7 @@ describe('GET /api/v1/apps/:appKey/actions', () => {
.expect(200); .expect(200);
const expectedPayload = getActionsMock(exampleApp.actions); const expectedPayload = getActionsMock(exampleApp.actions);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for invalid app key', async () => { it('should return not found response for invalid app key', async () => {

View File

@@ -23,7 +23,7 @@ describe('GET /api/v1/apps/:appKey', () => {
.expect(200); .expect(200);
const expectedPayload = getAppMock(exampleApp); const expectedPayload = getAppMock(exampleApp);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for invalid app key', async () => { it('should return not found response for invalid app key', async () => {

View File

@@ -22,7 +22,7 @@ describe('GET /api/v1/apps', () => {
.expect(200); .expect(200);
const expectedPayload = getAppsMock(apps); const expectedPayload = getAppsMock(apps);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return all apps filtered by name', async () => { it('should return all apps filtered by name', async () => {
@@ -34,7 +34,7 @@ describe('GET /api/v1/apps', () => {
.expect(200); .expect(200);
const expectedPayload = getAppsMock(appsWithNameGit); const expectedPayload = getAppsMock(appsWithNameGit);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return only the apps with triggers', async () => { it('should return only the apps with triggers', async () => {
@@ -46,7 +46,7 @@ describe('GET /api/v1/apps', () => {
.expect(200); .expect(200);
const expectedPayload = getAppsMock(appsWithTriggers); const expectedPayload = getAppsMock(appsWithTriggers);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return only the apps with actions', async () => { it('should return only the apps with actions', async () => {
@@ -58,6 +58,6 @@ describe('GET /api/v1/apps', () => {
.expect(200); .expect(200);
const expectedPayload = getAppsMock(appsWithActions); const expectedPayload = getAppsMock(appsWithActions);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
}); });

View File

@@ -29,7 +29,7 @@ describe('GET /api/v1/apps/:appKey/auth-clients/:appAuthClientId', () => {
.expect(200); .expect(200);
const expectedPayload = getAppAuthClientMock(currentAppAuthClient); const expectedPayload = getAppAuthClientMock(currentAppAuthClient);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for not existing app auth client ID', async () => { it('should return not found response for not existing app auth client ID', async () => {

View File

@@ -37,6 +37,6 @@ describe('GET /api/v1/apps/:appKey/auth-clients', () => {
appAuthClientOne, appAuthClientOne,
]); ]);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
}); });

View File

@@ -23,7 +23,7 @@ describe('GET /api/v1/apps/:appKey/auth', () => {
.expect(200); .expect(200);
const expectedPayload = getAuthMock(exampleApp.auth); const expectedPayload = getAuthMock(exampleApp.auth);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for invalid app key', async () => { it('should return not found response for invalid app key', async () => {

View File

@@ -17,7 +17,8 @@ describe('GET /api/v1/apps/:appKey/config', () => {
appConfig = await createAppConfig({ appConfig = await createAppConfig({
key: 'deepl', key: 'deepl',
useOnlyPredefinedAuthClients: false, allowCustomConnection: true,
shared: true,
disabled: false, disabled: false,
}); });
@@ -31,7 +32,7 @@ describe('GET /api/v1/apps/:appKey/config', () => {
.expect(200); .expect(200);
const expectedPayload = getAppConfigMock(appConfig); const expectedPayload = getAppConfigMock(appConfig);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for not existing app key', async () => { it('should return not found response for not existing app key', async () => {

View File

@@ -47,7 +47,7 @@ describe('GET /api/v1/apps/:appKey/connections', () => {
currentUserConnectionOne, currentUserConnectionOne,
]); ]);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return the connections data of specified app for another user', async () => { it('should return the connections data of specified app for another user', async () => {
@@ -82,19 +82,19 @@ describe('GET /api/v1/apps/:appKey/connections', () => {
anotherUserConnectionOne, anotherUserConnectionOne,
]); ]);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for invalid connection UUID', async () => { it('should return not found response for invalid connection UUID', async () => {
await createPermission({ await createPermission({
action: 'read', action: 'update',
subject: 'Connection', subject: 'Connection',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
}); });
await request(app) await request(app)
.get('/api/v1/apps/invalid-connection-id/connections') .get('/api/v1/connections/invalid-connection-id/connections')
.set('Authorization', token) .set('Authorization', token)
.expect(404); .expect(404);
}); });

View File

@@ -62,7 +62,7 @@ describe('GET /api/v1/apps/:appKey/flows', () => {
[triggerStepFlowOne, actionStepFlowOne] [triggerStepFlowOne, actionStepFlowOne]
); );
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return the flows data of specified app for another user', async () => { it('should return the flows data of specified app for another user', async () => {
@@ -110,7 +110,7 @@ describe('GET /api/v1/apps/:appKey/flows', () => {
[triggerStepFlowOne, actionStepFlowOne] [triggerStepFlowOne, actionStepFlowOne]
); );
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for invalid app key', async () => { it('should return not found response for invalid app key', async () => {

View File

@@ -29,7 +29,7 @@ describe('GET /api/v1/apps/:appKey/triggers/:triggerKey/substeps', () => {
.expect(200); .expect(200);
const expectedPayload = getTriggerSubstepsMock(exampleTrigger.substeps); const expectedPayload = getTriggerSubstepsMock(exampleTrigger.substeps);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for invalid app key', async () => { it('should return not found response for invalid app key', async () => {
@@ -47,6 +47,6 @@ describe('GET /api/v1/apps/:appKey/triggers/:triggerKey/substeps', () => {
.set('Authorization', token) .set('Authorization', token)
.expect(200); .expect(200);
expect(response.body.data).toStrictEqual([]); expect(response.body.data).toEqual([]);
}); });
}); });

View File

@@ -23,7 +23,7 @@ describe('GET /api/v1/apps/:appKey/triggers', () => {
.expect(200); .expect(200);
const expectedPayload = getTriggersMock(exampleApp.triggers); const expectedPayload = getTriggersMock(exampleApp.triggers);
expect(expectedPayload).toMatchObject(response.body); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for invalid app key', async () => { it('should return not found response for invalid app key', async () => {

View File

@@ -20,6 +20,6 @@ describe('GET /api/v1/automatisch/info', () => {
const expectedPayload = infoMock(); const expectedPayload = infoMock();
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
}); });

View File

@@ -18,6 +18,6 @@ describe('GET /api/v1/automatisch/license', () => {
const expectedPayload = licenseMock(); const expectedPayload = licenseMock();
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
}); });

View File

@@ -10,7 +10,7 @@ describe('GET /api/v1/automatisch/version', () => {
const expectedPayload = { const expectedPayload = {
data: { data: {
version: '0.14.0', version: '0.13.1',
}, },
meta: { meta: {
count: 1, count: 1,
@@ -21,6 +21,6 @@ describe('GET /api/v1/automatisch/version', () => {
}, },
}; };
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
}); });

View File

@@ -69,7 +69,7 @@ describe('GET /api/v1/connections/:connectionId/flows', () => {
[triggerStepFlowOne, actionStepFlowOne] [triggerStepFlowOne, actionStepFlowOne]
); );
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return the flows data of specified connection for another user', async () => { it('should return the flows data of specified connection for another user', async () => {
@@ -123,6 +123,6 @@ describe('GET /api/v1/connections/:connectionId/flows', () => {
[triggerStepFlowOne, actionStepFlowOne] [triggerStepFlowOne, actionStepFlowOne]
); );
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
}); });

View File

@@ -47,6 +47,7 @@ describe('POST /api/v1/connections/:connectionId/reset', () => {
const expectedPayload = resetConnectionMock({ const expectedPayload = resetConnectionMock({
...refetchedCurrentUserConnection, ...refetchedCurrentUserConnection,
reconnectable: refetchedCurrentUserConnection.reconnectable,
formattedData: { formattedData: {
screenName: 'Connection name', screenName: 'Connection name',
}, },

View File

@@ -43,7 +43,7 @@ describe('POST /api/v1/connections/:connectionId/test', () => {
.set('Authorization', token) .set('Authorization', token)
.expect(200); .expect(200);
expect(response.body.data.verified).toStrictEqual(false); expect(response.body.data.verified).toEqual(false);
}); });
it('should update the connection as not verified for another user', async () => { it('should update the connection as not verified for another user', async () => {
@@ -74,7 +74,7 @@ describe('POST /api/v1/connections/:connectionId/test', () => {
.set('Authorization', token) .set('Authorization', token)
.expect(200); .expect(200);
expect(response.body.data.verified).toStrictEqual(false); expect(response.body.data.verified).toEqual(false);
}); });
it('should return not found response for not existing connection UUID', async () => { it('should return not found response for not existing connection UUID', async () => {

View File

@@ -8,7 +8,7 @@ export default async (request, response) => {
}) })
.throwIfNotFound(); .throwIfNotFound();
connection = await connection.updateFormattedData(connectionParams(request)); connection = await connection.update(connectionParams(request));
renderObject(response, connection); renderObject(response, connection);
}; };

View File

@@ -55,9 +55,10 @@ describe('PATCH /api/v1/connections/:connectionId', () => {
const refetchedCurrentUserConnection = await currentUserConnection.$query(); const refetchedCurrentUserConnection = await currentUserConnection.$query();
const expectedPayload = updateConnectionMock( const expectedPayload = updateConnectionMock({
refetchedCurrentUserConnection ...refetchedCurrentUserConnection,
); reconnectable: refetchedCurrentUserConnection.reconnectable,
});
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toStrictEqual(expectedPayload);
}); });

View File

@@ -47,7 +47,7 @@ describe('POST /api/v1/connections/:connectionId/verify', () => {
.set('Authorization', token) .set('Authorization', token)
.expect(200); .expect(200);
expect(response.body.data.verified).toStrictEqual(true); expect(response.body.data.verified).toEqual(true);
}); });
it('should return not found response for not existing connection UUID', async () => { it('should return not found response for not existing connection UUID', async () => {

View File

@@ -69,7 +69,7 @@ describe('GET /api/v1/executions/:executionId/execution-steps', () => {
[stepOne, stepTwo] [stepOne, stepTwo]
); );
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return the execution steps of another user execution', async () => { it('should return the execution steps of another user execution', async () => {
@@ -118,7 +118,7 @@ describe('GET /api/v1/executions/:executionId/execution-steps', () => {
[stepOne, stepTwo] [stepOne, stepTwo]
); );
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for not existing execution step UUID', async () => { it('should return not found response for not existing execution step UUID', async () => {

View File

@@ -57,7 +57,7 @@ describe('GET /api/v1/executions/:executionId', () => {
[stepOne, stepTwo] [stepOne, stepTwo]
); );
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return the execution data of another user', async () => { it('should return the execution data of another user', async () => {
@@ -99,7 +99,7 @@ describe('GET /api/v1/executions/:executionId', () => {
[stepOne, stepTwo] [stepOne, stepTwo]
); );
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for not existing execution UUID', async () => { it('should return not found response for not existing execution UUID', async () => {

View File

@@ -66,7 +66,7 @@ describe('GET /api/v1/executions', () => {
[stepOne, stepTwo] [stepOne, stepTwo]
); );
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return the executions of another user', async () => { it('should return the executions of another user', async () => {
@@ -114,6 +114,6 @@ describe('GET /api/v1/executions', () => {
[stepOne, stepTwo] [stepOne, stepTwo]
); );
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
}); });

View File

@@ -1,11 +1,11 @@
import { renderObject } from '../../../../helpers/renderer.js'; import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => { export default async (request, response) => {
const flow = await request.currentUser.$relatedQuery('flows').insertAndFetch({ let flow = await request.currentUser.$relatedQuery('flows').insert({
name: 'Name your flow', name: 'Name your flow',
}); });
await flow.createInitialSteps(); flow = await flow.createInitialSteps();
renderObject(response, flow, { status: 201 }); renderObject(response, flow, { status: 201 });
}; };

View File

@@ -6,7 +6,7 @@ export default async (request, response) => {
.findById(request.params.flowId) .findById(request.params.flowId)
.throwIfNotFound(); .throwIfNotFound();
const createdActionStep = await flow.createStepAfter( const createdActionStep = await flow.createActionStep(
request.body.previousStepId request.body.previousStepId
); );

View File

@@ -41,7 +41,7 @@ describe('GET /api/v1/flows/:flowId', () => {
actionStep, actionStep,
]); ]);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return the flow data of another user', async () => { it('should return the flow data of another user', async () => {
@@ -67,7 +67,7 @@ describe('GET /api/v1/flows/:flowId', () => {
actionStep, actionStep,
]); ]);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for not existing flow UUID', async () => { it('should return not found response for not existing flow UUID', async () => {

View File

@@ -63,7 +63,7 @@ describe('GET /api/v1/flows', () => {
] ]
); );
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return the flows data of another user', async () => { it('should return the flows data of another user', async () => {
@@ -113,6 +113,6 @@ describe('GET /api/v1/flows', () => {
] ]
); );
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
}); });

View File

@@ -53,7 +53,7 @@ describe('POST /api/v1/installation/users', () => {
const usersCountAfter = await User.query().resultSize(); const usersCountAfter = await User.query().resultSize();
expect(usersCountBefore).toStrictEqual(usersCountAfter); expect(usersCountBefore).toEqual(usersCountAfter);
}); });
}); });

View File

@@ -28,6 +28,6 @@ describe('GET /api/v1/payment/paddle-info', () => {
const expectedResponsePayload = await getPaddleInfoMock(); const expectedResponsePayload = await getPaddleInfoMock();
expect(response.body).toStrictEqual(expectedResponsePayload); expect(response.body).toEqual(expectedResponsePayload);
}); });
}); });

View File

@@ -24,6 +24,6 @@ describe('GET /api/v1/payment/plans', () => {
const expectedResponsePayload = await getPaymentPlansMock(); const expectedResponsePayload = await getPaymentPlansMock();
expect(response.body).toStrictEqual(expectedResponsePayload); expect(response.body).toEqual(expectedResponsePayload);
}); });
}); });

View File

@@ -25,6 +25,6 @@ describe('GET /api/v1/saml-auth-providers', () => {
samlAuthProviderOne, samlAuthProviderOne,
]); ]);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
}); });

View File

@@ -78,7 +78,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-data', () => {
}) })
.expect(200); .expect(200);
expect(response.body.data).toStrictEqual(repositories); expect(response.body.data).toEqual(repositories);
}); });
it('of the another users step', async () => { it('of the another users step', async () => {
@@ -117,7 +117,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-data', () => {
}) })
.expect(200); .expect(200);
expect(response.body.data).toStrictEqual(repositories); expect(response.body.data).toEqual(repositories);
}); });
}); });
@@ -171,7 +171,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-data', () => {
}) })
.expect(422); .expect(422);
expect(response.body.errors).toStrictEqual(errors); expect(response.body.errors).toEqual(errors);
}); });
}); });
@@ -193,7 +193,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-data', () => {
const notExistingStepUUID = Crypto.randomUUID(); const notExistingStepUUID = Crypto.randomUUID();
await request(app) await request(app)
.post(`/api/v1/steps/${notExistingStepUUID}/dynamic-data`) .get(`/api/v1/steps/${notExistingStepUUID}/dynamic-data`)
.set('Authorization', token) .set('Authorization', token)
.expect(404); .expect(404);
}); });
@@ -216,7 +216,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-data', () => {
const step = await createStep({ appKey: null }); const step = await createStep({ appKey: null });
await request(app) await request(app)
.post(`/api/v1/steps/${step.id}/dynamic-data`) .get(`/api/v1/steps/${step.id}/dynamic-data`)
.set('Authorization', token) .set('Authorization', token)
.expect(404); .expect(404);
}); });

View File

@@ -56,7 +56,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-fields', () => {
const expectedPayload = await createDynamicFieldsMock(); const expectedPayload = await createDynamicFieldsMock();
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return dynamically created fields of the another users step', async () => { it('should return dynamically created fields of the another users step', async () => {
@@ -97,7 +97,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-fields', () => {
const expectedPayload = await createDynamicFieldsMock(); const expectedPayload = await createDynamicFieldsMock();
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for not existing step UUID', async () => { it('should return not found response for not existing step UUID', async () => {
@@ -118,7 +118,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-fields', () => {
const notExistingStepUUID = Crypto.randomUUID(); const notExistingStepUUID = Crypto.randomUUID();
await request(app) await request(app)
.post(`/api/v1/steps/${notExistingStepUUID}/dynamic-fields`) .get(`/api/v1/steps/${notExistingStepUUID}/dynamic-fields`)
.set('Authorization', token) .set('Authorization', token)
.expect(404); .expect(404);
}); });
@@ -138,11 +138,10 @@ describe('POST /api/v1/steps/:stepId/dynamic-fields', () => {
conditions: [], conditions: [],
}); });
const step = await createStep(); const step = await createStep({ appKey: null });
await step.$query().patch({ appKey: null });
await request(app) await request(app)
.post(`/api/v1/steps/${step.id}/dynamic-fields`) .get(`/api/v1/steps/${step.id}/dynamic-fields`)
.set('Authorization', token) .set('Authorization', token)
.expect(404); .expect(404);
}); });

View File

@@ -43,7 +43,7 @@ describe('GET /api/v1/steps/:stepId/connection', () => {
const expectedPayload = await getConnectionMock(currentUserConnection); const expectedPayload = await getConnectionMock(currentUserConnection);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return the current user connection data of specified step', async () => { it('should return the current user connection data of specified step', async () => {
@@ -70,7 +70,7 @@ describe('GET /api/v1/steps/:stepId/connection', () => {
const expectedPayload = await getConnectionMock(anotherUserConnection); const expectedPayload = await getConnectionMock(anotherUserConnection);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for not existing step without connection', async () => { it('should return not found response for not existing step without connection', async () => {

View File

@@ -70,7 +70,7 @@ describe('GET /api/v1/steps/:stepId/previous-steps', () => {
[executionStepOne, executionStepTwo] [executionStepOne, executionStepTwo]
); );
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return the previous steps of the specified step of another user', async () => { it('should return the previous steps of the specified step of another user', async () => {
@@ -124,7 +124,7 @@ describe('GET /api/v1/steps/:stepId/previous-steps', () => {
[executionStepOne, executionStepTwo] [executionStepOne, executionStepTwo]
); );
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for not existing step UUID', async () => { it('should return not found response for not existing step UUID', async () => {

View File

@@ -79,7 +79,7 @@ describe('GET /api/v1/users/:userId/apps', () => {
.expect(200); .expect(200);
const expectedPayload = getAppsMock(); const expectedPayload = getAppsMock();
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return all apps of the another user', async () => { it('should return all apps of the another user', async () => {
@@ -143,7 +143,7 @@ describe('GET /api/v1/users/:userId/apps', () => {
.expect(200); .expect(200);
const expectedPayload = getAppsMock(); const expectedPayload = getAppsMock();
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return specified app of the current user', async () => { it('should return specified app of the current user', async () => {
@@ -204,7 +204,7 @@ describe('GET /api/v1/users/:userId/apps', () => {
.set('Authorization', token) .set('Authorization', token)
.expect(200); .expect(200);
expect(response.body.data.length).toStrictEqual(1); expect(response.body.data.length).toEqual(1);
expect(response.body.data[0].key).toStrictEqual('deepl'); expect(response.body.data[0].key).toEqual('deepl');
}); });
}); });

View File

@@ -39,6 +39,6 @@ describe('GET /api/v1/users/me', () => {
permissionTwo, permissionTwo,
]); ]);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
}); });

View File

@@ -29,6 +29,6 @@ describe('GET /api/v1/user/invoices', () => {
const expectedPayload = await getInvoicesMock(invoices); const expectedPayload = await getInvoicesMock(invoices);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
}); });

View File

@@ -36,7 +36,7 @@ describe('GET /api/v1/users/:userId/plan-and-usage', () => {
}, },
}; };
expect(response.body.data).toStrictEqual(expectedResponseData); expect(response.body.data).toEqual(expectedResponseData);
}); });
it('should return current plan and usage data', async () => { it('should return current plan and usage data', async () => {
@@ -63,6 +63,6 @@ describe('GET /api/v1/users/:userId/plan-and-usage', () => {
}, },
}; };
expect(response.body.data).toStrictEqual(expectedResponseData); expect(response.body.data).toEqual(expectedResponseData);
}); });
}); });

View File

@@ -33,7 +33,7 @@ describe('GET /api/v1/users/:userId/subscription', () => {
const expectedPayload = getSubscriptionMock(subscription); const expectedPayload = getSubscriptionMock(subscription);
expect(response.body).toStrictEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response if there is no current subscription', async () => { it('should return not found response if there is no current subscription', async () => {

View File

@@ -32,7 +32,7 @@ describe('GET /api/v1/users/:userId/trial', () => {
.expect(200); .expect(200);
const expectedResponsePayload = await getUserTrialMock(user); const expectedResponsePayload = await getUserTrialMock(user);
expect(response.body).toStrictEqual(expectedResponsePayload); expect(response.body).toEqual(expectedResponsePayload);
}); });
}); });
}); });

View File

@@ -43,7 +43,7 @@ describe('PATCH /api/v1/users/:userId/password', () => {
.send(userData) .send(userData)
.expect(422); .expect(422);
expect(response.body.meta.type).toStrictEqual('ValidationError'); expect(response.body.meta.type).toEqual('ValidationError');
expect(response.body.errors).toMatchObject({ expect(response.body.errors).toMatchObject({
currentPassword: ['is incorrect.'], currentPassword: ['is incorrect.'],
}); });

View File

@@ -47,8 +47,7 @@ describe('PATCH /api/v1/users/:userId', () => {
.send(userData) .send(userData)
.expect(422); .expect(422);
expect(response.body.meta.type).toStrictEqual('ModelValidation'); expect(response.body.meta.type).toEqual('ModelValidation');
expect(response.body.errors).toMatchObject({ expect(response.body.errors).toMatchObject({
email: ['must be string'], email: ['must be string'],
fullName: ['must be string'], fullName: ['must be string'],

View File

@@ -1,37 +0,0 @@
export async function up(knex) {
await knex.schema.alterTable('app_configs', (table) => {
table.boolean('connection_allowed').defaultTo(false);
});
const appConfigs = await knex('app_configs').select('*');
for (const appConfig of appConfigs) {
const appAuthClients = await knex('app_auth_clients').where(
'app_key',
appConfig.key
);
const hasSomeActiveAppAuthClients = !!appAuthClients?.some(
(appAuthClient) => appAuthClient.active
);
const shared = appConfig.shared;
const active = appConfig.disabled === false;
const connectionAllowedConditions = [
hasSomeActiveAppAuthClients,
shared,
active,
];
const connectionAllowed = connectionAllowedConditions.every(Boolean);
await knex('app_configs')
.where('id', appConfig.id)
.update({ connection_allowed: connectionAllowed });
}
}
export async function down(knex) {
await knex.schema.alterTable('app_configs', (table) => {
table.dropColumn('connection_allowed');
});
}

View File

@@ -1,11 +0,0 @@
export async function up(knex) {
return knex.schema.alterTable('app_configs', (table) => {
table.renameColumn('allow_custom_connection', 'custom_connection_allowed');
});
}
export async function down(knex) {
return knex.schema.alterTable('app_configs', (table) => {
table.renameColumn('custom_connection_allowed', 'allow_custom_connection');
});
}

View File

@@ -1,13 +0,0 @@
export async function up(knex) {
return knex.schema.alterTable('app_configs', function (table) {
table.dropPrimary();
table.primary('key');
});
}
export async function down(knex) {
return knex.schema.alterTable('app_configs', function (table) {
table.dropPrimary();
table.primary('id');
});
}

View File

@@ -1,11 +0,0 @@
export async function up(knex) {
return knex.schema.alterTable('app_configs', function (table) {
table.dropColumn('id');
});
}
export async function down(knex) {
return knex.schema.alterTable('app_configs', function (table) {
table.uuid('id').defaultTo(knex.raw('gen_random_uuid()'));
});
}

View File

@@ -1,52 +0,0 @@
export async function up(knex) {
await knex.schema.createTable('role_mappings', (table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table
.uuid('saml_auth_provider_id')
.references('id')
.inTable('saml_auth_providers');
table.uuid('role_id').references('id').inTable('roles');
table.string('remote_role_name').notNullable();
table.unique(['saml_auth_provider_id', 'remote_role_name']);
table.timestamps(true, true);
});
const existingRoleMappings = await knex('saml_auth_providers_role_mappings');
if (existingRoleMappings.length) {
await knex('role_mappings').insert(existingRoleMappings);
}
return await knex.schema.dropTable('saml_auth_providers_role_mappings');
}
export async function down(knex) {
await knex.schema.createTable(
'saml_auth_providers_role_mappings',
(table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table
.uuid('saml_auth_provider_id')
.references('id')
.inTable('saml_auth_providers');
table.uuid('role_id').references('id').inTable('roles');
table.string('remote_role_name').notNullable();
table.unique(['saml_auth_provider_id', 'remote_role_name']);
table.timestamps(true, true);
}
);
const existingRoleMappings = await knex('role_mappings');
if (existingRoleMappings.length) {
await knex('saml_auth_providers_role_mappings').insert(
existingRoleMappings
);
}
return await knex.schema.dropTable('role_mappings');
}

View File

@@ -1,11 +0,0 @@
export async function up(knex) {
return await knex.schema.alterTable('app_configs', (table) => {
table.boolean('use_only_predefined_auth_clients').defaultTo(false);
});
}
export async function down(knex) {
return await knex.schema.alterTable('app_configs', (table) => {
table.dropColumn('use_only_predefined_auth_clients');
});
}

View File

@@ -1,15 +0,0 @@
export async function up(knex) {
return await knex.schema.alterTable('app_configs', (table) => {
table.dropColumn('shared');
table.dropColumn('connection_allowed');
table.dropColumn('custom_connection_allowed');
});
}
export async function down(knex) {
return await knex.schema.alterTable('app_configs', (table) => {
table.boolean('shared').defaultTo(false);
table.boolean('connection_allowed').defaultTo(false);
table.boolean('custom_connection_allowed').defaultTo(false);
});
}

View File

@@ -30,7 +30,7 @@ const findOrCreateUserBySamlIdentity = async (
: [mappedUser.role]; : [mappedUser.role];
const samlAuthProviderRoleMapping = await samlAuthProvider const samlAuthProviderRoleMapping = await samlAuthProvider
.$relatedQuery('roleMappings') .$relatedQuery('samlAuthProvidersRoleMappings')
.whereIn('remote_role_name', mappedRoles) .whereIn('remote_role_name', mappedRoles)
.limit(1) .limit(1)
.first(); .first();

View File

@@ -1,46 +0,0 @@
import { describe, expect, it } from 'vitest';
import userAbility from './user-ability.js';
describe('userAbility', () => {
it('should return PureAbility instantiated with user permissions', () => {
const user = {
permissions: [
{
subject: 'Flow',
action: 'read',
conditions: ['isCreator'],
},
],
role: {
name: 'User',
},
};
const ability = userAbility(user);
expect(ability.rules).toStrictEqual(user.permissions);
});
it('should return permission-less PureAbility for user with no role', () => {
const user = {
permissions: [
{
subject: 'Flow',
action: 'read',
conditions: ['isCreator'],
},
],
role: null,
};
const ability = userAbility(user);
expect(ability.rules).toStrictEqual([]);
});
it('should return permission-less PureAbility for user with no permissions', () => {
const user = { permissions: null, role: { name: 'User' } };
const ability = userAbility(user);
expect(ability.rules).toStrictEqual([]);
});
});

View File

@@ -1,33 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`AppConfig model > jsonSchema should have correct validations 1`] = `
{
"properties": {
"createdAt": {
"type": "string",
},
"disabled": {
"default": false,
"type": "boolean",
},
"id": {
"format": "uuid",
"type": "string",
},
"key": {
"type": "string",
},
"updatedAt": {
"type": "string",
},
"useOnlyPredefinedAuthClients": {
"default": false,
"type": "boolean",
},
},
"required": [
"key",
],
"type": "object",
}
`;

View File

@@ -1,42 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Flow model > jsonSchema should have correct validations 1`] = `
{
"properties": {
"active": {
"type": "boolean",
},
"createdAt": {
"type": "string",
},
"deletedAt": {
"type": "string",
},
"id": {
"format": "uuid",
"type": "string",
},
"name": {
"minLength": 1,
"type": "string",
},
"publishedAt": {
"type": "string",
},
"remoteWebhookId": {
"type": "string",
},
"updatedAt": {
"type": "string",
},
"userId": {
"format": "uuid",
"type": "string",
},
},
"required": [
"name",
],
"type": "object",
}
`;

View File

@@ -1,42 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Permission model > jsonSchema should have correct validations 1`] = `
{
"properties": {
"action": {
"minLength": 1,
"type": "string",
},
"conditions": {
"items": {
"type": "string",
},
"type": "array",
},
"createdAt": {
"type": "string",
},
"id": {
"format": "uuid",
"type": "string",
},
"roleId": {
"format": "uuid",
"type": "string",
},
"subject": {
"minLength": 1,
"type": "string",
},
"updatedAt": {
"type": "string",
},
},
"required": [
"roleId",
"action",
"subject",
],
"type": "object",
}
`;

View File

@@ -1,30 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`RoleMapping model > jsonSchema should have the correct schema 1`] = `
{
"properties": {
"id": {
"format": "uuid",
"type": "string",
},
"remoteRoleName": {
"minLength": 1,
"type": "string",
},
"roleId": {
"format": "uuid",
"type": "string",
},
"samlAuthProviderId": {
"format": "uuid",
"type": "string",
},
},
"required": [
"samlAuthProviderId",
"roleId",
"remoteRoleName",
],
"type": "object",
}
`;

View File

@@ -1,33 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Role model > jsonSchema should have correct validations 1`] = `
{
"properties": {
"createdAt": {
"type": "string",
},
"description": {
"maxLength": 255,
"type": [
"string",
"null",
],
},
"id": {
"format": "uuid",
"type": "string",
},
"name": {
"minLength": 1,
"type": "string",
},
"updatedAt": {
"type": "string",
},
},
"required": [
"name",
],
"type": "object",
}
`;

View File

@@ -1,72 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`SamlAuthProvider model > jsonSchema should have the correct schema 1`] = `
{
"properties": {
"active": {
"type": "boolean",
},
"certificate": {
"minLength": 1,
"type": "string",
},
"defaultRoleId": {
"format": "uuid",
"type": "string",
},
"emailAttributeName": {
"minLength": 1,
"type": "string",
},
"entryPoint": {
"minLength": 1,
"type": "string",
},
"firstnameAttributeName": {
"minLength": 1,
"type": "string",
},
"id": {
"format": "uuid",
"type": "string",
},
"issuer": {
"minLength": 1,
"type": "string",
},
"name": {
"minLength": 1,
"type": "string",
},
"roleAttributeName": {
"minLength": 1,
"type": "string",
},
"signatureAlgorithm": {
"enum": [
"sha1",
"sha256",
"sha512",
],
"type": "string",
},
"surnameAttributeName": {
"minLength": 1,
"type": "string",
},
},
"required": [
"name",
"certificate",
"signatureAlgorithm",
"entryPoint",
"issuer",
"firstnameAttributeName",
"surnameAttributeName",
"emailAttributeName",
"roleAttributeName",
"defaultRoleId",
],
"type": "object",
}
`;

View File

@@ -1,6 +1,6 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`RoleMapping model > jsonSchema should have the correct schema 1`] = ` exports[`SamlAuthProvidersRoleMapping model > jsonSchema should have the correct schema 1`] = `
{ {
"properties": { "properties": {
"id": { "id": {
@@ -28,3 +28,14 @@ exports[`RoleMapping model > jsonSchema should have the correct schema 1`] = `
"type": "object", "type": "object",
} }
`; `;
exports[`SamlAuthProvidersRoleMapping model > relationMappings should have samlAuthProvider relation 1`] = `
{
"join": {
"from": "saml_auth_providers_role_mappings.saml_auth_provider_id",
"to": "saml_auth_providers.id",
},
"modelClass": [Function],
"relation": [Function],
}
`;

View File

@@ -1,77 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Step model > jsonSchema should have correct validations 1`] = `
{
"properties": {
"appKey": {
"maxLength": 255,
"minLength": 1,
"type": [
"string",
"null",
],
},
"connectionId": {
"format": "uuid",
"type": [
"string",
"null",
],
},
"createdAt": {
"type": "string",
},
"deletedAt": {
"type": "string",
},
"flowId": {
"format": "uuid",
"type": "string",
},
"id": {
"format": "uuid",
"type": "string",
},
"key": {
"type": [
"string",
"null",
],
},
"parameters": {
"type": "object",
},
"position": {
"type": "integer",
},
"status": {
"default": "incomplete",
"enum": [
"incomplete",
"completed",
],
"type": "string",
},
"type": {
"enum": [
"action",
"trigger",
],
"type": "string",
},
"updatedAt": {
"type": "string",
},
"webhookPath": {
"type": [
"string",
"null",
],
},
},
"required": [
"type",
],
"type": "object",
}
`;

View File

@@ -1,81 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`User model > jsonSchema should have correct validations 1`] = `
{
"properties": {
"createdAt": {
"type": "string",
},
"deletedAt": {
"type": "string",
},
"email": {
"format": "email",
"maxLength": 255,
"minLength": 1,
"type": "string",
},
"fullName": {
"minLength": 1,
"type": "string",
},
"id": {
"format": "uuid",
"type": "string",
},
"invitationToken": {
"type": [
"string",
"null",
],
},
"invitationTokenSentAt": {
"format": "date-time",
"type": [
"string",
"null",
],
},
"password": {
"minLength": 6,
"type": "string",
},
"resetPasswordToken": {
"type": [
"string",
"null",
],
},
"resetPasswordTokenSentAt": {
"format": "date-time",
"type": [
"string",
"null",
],
},
"roleId": {
"format": "uuid",
"type": "string",
},
"status": {
"default": "active",
"enum": [
"active",
"invited",
],
"type": "string",
},
"trialExpiryDate": {
"type": "string",
},
"updatedAt": {
"type": "string",
},
},
"required": [
"fullName",
"email",
],
"type": "object",
}
`;

View File

@@ -2,7 +2,6 @@ import AES from 'crypto-js/aes.js';
import enc from 'crypto-js/enc-utf8.js'; import enc from 'crypto-js/enc-utf8.js';
import appConfig from '../config/app.js'; import appConfig from '../config/app.js';
import Base from './base.js'; import Base from './base.js';
import AppConfig from './app-config.js';
class AppAuthClient extends Base { class AppAuthClient extends Base {
static tableName = 'app_auth_clients'; static tableName = 'app_auth_clients';
@@ -22,17 +21,6 @@ class AppAuthClient extends Base {
}, },
}; };
static relationMappings = () => ({
appConfig: {
relation: Base.BelongsToOneRelation,
modelClass: AppConfig,
join: {
from: 'app_auth_clients.app_key',
to: 'app_configs.key',
},
},
});
encryptData() { encryptData() {
if (!this.eligibleForEncryption()) return; if (!this.eligibleForEncryption()) return;
@@ -64,24 +52,14 @@ class AppAuthClient extends Base {
// beforeInsert and beforeUpdate separately for the same operation. // beforeInsert and beforeUpdate separately for the same operation.
async $beforeInsert(queryContext) { async $beforeInsert(queryContext) {
await super.$beforeInsert(queryContext); await super.$beforeInsert(queryContext);
this.encryptData(); this.encryptData();
} }
async $afterInsert(queryContext) {
await super.$afterInsert(queryContext);
}
async $beforeUpdate(opt, queryContext) { async $beforeUpdate(opt, queryContext) {
await super.$beforeUpdate(opt, queryContext); await super.$beforeUpdate(opt, queryContext);
this.encryptData(); this.encryptData();
} }
async $afterUpdate(opt, queryContext) {
await super.$afterUpdate(opt, queryContext);
}
async $afterFind() { async $afterFind() {
this.decryptData(); this.decryptData();
} }

View File

@@ -2,9 +2,7 @@ import { describe, it, expect, vi } from 'vitest';
import AES from 'crypto-js/aes.js'; import AES from 'crypto-js/aes.js';
import enc from 'crypto-js/enc-utf8.js'; import enc from 'crypto-js/enc-utf8.js';
import AppConfig from './app-config.js';
import AppAuthClient from './app-auth-client.js'; import AppAuthClient from './app-auth-client.js';
import Base from './base.js';
import appConfig from '../config/app.js'; import appConfig from '../config/app.js';
import { createAppAuthClient } from '../../test/factories/app-auth-client.js'; import { createAppAuthClient } from '../../test/factories/app-auth-client.js';
@@ -17,23 +15,6 @@ describe('AppAuthClient model', () => {
expect(AppAuthClient.jsonSchema).toMatchSnapshot(); expect(AppAuthClient.jsonSchema).toMatchSnapshot();
}); });
it('relationMappings should return correct associations', () => {
const relationMappings = AppAuthClient.relationMappings();
const expectedRelations = {
appConfig: {
relation: Base.BelongsToOneRelation,
modelClass: AppConfig,
join: {
from: 'app_auth_clients.app_key',
to: 'app_configs.key',
},
},
};
expect(relationMappings).toStrictEqual(expectedRelations);
});
describe('encryptData', () => { describe('encryptData', () => {
it('should return undefined if eligibleForEncryption is not true', async () => { it('should return undefined if eligibleForEncryption is not true', async () => {
vi.spyOn( vi.spyOn(
@@ -68,9 +49,7 @@ describe('AppAuthClient model', () => {
); );
expect(formattedAuthDefaults).toStrictEqual(expectedDecryptedValue); expect(formattedAuthDefaults).toStrictEqual(expectedDecryptedValue);
expect(appAuthClient.authDefaults).not.toStrictEqual( expect(appAuthClient.authDefaults).not.toEqual(formattedAuthDefaults);
formattedAuthDefaults
);
}); });
it('should encrypt formattedAuthDefaults and remove formattedAuthDefaults', async () => { it('should encrypt formattedAuthDefaults and remove formattedAuthDefaults', async () => {
@@ -125,9 +104,7 @@ describe('AppAuthClient model', () => {
expect(appAuthClient.formattedAuthDefaults).toStrictEqual( expect(appAuthClient.formattedAuthDefaults).toStrictEqual(
formattedAuthDefaults formattedAuthDefaults
); );
expect(appAuthClient.authDefaults).not.toStrictEqual( expect(appAuthClient.authDefaults).not.toEqual(formattedAuthDefaults);
formattedAuthDefaults
);
}); });
}); });

View File

@@ -5,10 +5,6 @@ import Base from './base.js';
class AppConfig extends Base { class AppConfig extends Base {
static tableName = 'app_configs'; static tableName = 'app_configs';
static get idColumn() {
return 'key';
}
static jsonSchema = { static jsonSchema = {
type: 'object', type: 'object',
required: ['key'], required: ['key'],
@@ -16,7 +12,8 @@ class AppConfig extends Base {
properties: { properties: {
id: { type: 'string', format: 'uuid' }, id: { type: 'string', format: 'uuid' },
key: { type: 'string' }, key: { type: 'string' },
useOnlyPredefinedAuthClients: { type: 'boolean', default: false }, allowCustomConnection: { type: 'boolean', default: false },
shared: { type: 'boolean', default: false },
disabled: { type: 'boolean', default: false }, disabled: { type: 'boolean', default: false },
createdAt: { type: 'string' }, createdAt: { type: 'string' },
updatedAt: { type: 'string' }, updatedAt: { type: 'string' },
@@ -34,6 +31,26 @@ class AppConfig extends Base {
}, },
}); });
static get virtualAttributes() {
return ['canConnect', 'canCustomConnect'];
}
get canCustomConnect() {
return !this.disabled && this.allowCustomConnection;
}
get canConnect() {
const hasSomeActiveAppAuthClients = !!this.appAuthClients?.some(
(appAuthClient) => appAuthClient.active
);
const shared = this.shared;
const active = this.disabled === false;
const conditions = [hasSomeActiveAppAuthClients, shared, active];
return conditions.every(Boolean);
}
async getApp() { async getApp() {
if (!this.key) return null; if (!this.key) return null;

Some files were not shown because too many files have changed in this diff Show More