Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bab87e9e67 | ||
![]() |
7d793ce2a2 | ||
![]() |
f83deac469 |
2
.github/workflows/backend.yml
vendored
2
.github/workflows/backend.yml
vendored
@@ -47,5 +47,5 @@ jobs:
|
||||
run: cp .env-example.test .env.test
|
||||
working-directory: packages/backend
|
||||
- name: Run tests
|
||||
run: yarn test:coverage
|
||||
run: yarn test
|
||||
working-directory: packages/backend
|
||||
|
35
.github/workflows/playwright.yml
vendored
35
.github/workflows/playwright.yml
vendored
@@ -3,7 +3,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
# TODO: Add pull request after optimizing the total excecution time of the test suite.
|
||||
pull_request:
|
||||
paths:
|
||||
- 'packages/backend/**'
|
||||
@@ -56,27 +55,44 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Install web dependencies
|
||||
run: yarn
|
||||
working-directory: ./packages/web
|
||||
node-version: '18'
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: |
|
||||
packages/backend/yarn.lock
|
||||
packages/web/yarn.lock
|
||||
packages/e2e-tests/yarn.lock
|
||||
- name: Install backend dependencies
|
||||
run: yarn
|
||||
run: yarn --frozen-lockfile
|
||||
working-directory: ./packages/backend
|
||||
- name: Install web dependencies
|
||||
run: yarn --frozen-lockfile
|
||||
working-directory: ./packages/web
|
||||
- name: Install e2e-tests dependencies
|
||||
run: yarn
|
||||
run: yarn --frozen-lockfile
|
||||
working-directory: ./packages/e2e-tests
|
||||
- name: Get installed Playwright version
|
||||
id: playwright-version
|
||||
run: echo "PLAYWRIGHT_VERSION=$(node -e "console.log(require('./package.json').devDependencies['@playwright/test'])")" >> $GITHUB_ENV
|
||||
working-directory: ./packages/e2e-tests
|
||||
- name: Cache playwright binaries
|
||||
uses: actions/cache@v3
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: |
|
||||
~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }}
|
||||
- name: Install Playwright Browsers
|
||||
run: yarn playwright install --with-deps
|
||||
working-directory: ./packages/e2e-tests
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
- name: Build Automatisch web
|
||||
run: yarn build
|
||||
working-directory: ./packages/web
|
||||
env:
|
||||
# Keep this until we clean up warnings in build processes
|
||||
CI: false
|
||||
working-directory: ./packages/web
|
||||
- name: Migrate database
|
||||
working-directory: ./packages/backend
|
||||
run: yarn db:migrate
|
||||
@@ -116,6 +132,7 @@ jobs:
|
||||
env:
|
||||
LOGIN_EMAIL: user@automatisch.io
|
||||
LOGIN_PASSWORD: sample
|
||||
BACKEND_APP_URL: http://localhost:3000
|
||||
BASE_URL: http://localhost:3000
|
||||
GITHUB_CLIENT_ID: 1c0417daf898adfbd99a
|
||||
GITHUB_CLIENT_SECRET: 3328fa814dd582ccd03dbe785cfd683fb8da92b3
|
||||
|
@@ -12,7 +12,6 @@
|
||||
"pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js",
|
||||
"test": "APP_ENV=test vitest run",
|
||||
"test:watch": "APP_ENV=test vitest watch",
|
||||
"test:coverage": "yarn test --coverage",
|
||||
"lint": "eslint .",
|
||||
"db:create": "node ./bin/database/create.js",
|
||||
"db:seed:user": "node ./bin/database/seed-user.js",
|
||||
@@ -98,11 +97,10 @@
|
||||
"url": "https://github.com/automatisch/automatisch/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitest/coverage-v8": "^2.1.5",
|
||||
"node-gyp": "^10.1.0",
|
||||
"nodemon": "^2.0.13",
|
||||
"supertest": "^6.3.3",
|
||||
"vitest": "^2.1.5"
|
||||
"vitest": "^1.1.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
@@ -10,11 +10,12 @@ export default async (request, response) => {
|
||||
};
|
||||
|
||||
const appConfigParams = (request) => {
|
||||
const { useOnlyPredefinedAuthClients, disabled } = request.body;
|
||||
const { customConnectionAllowed, shared, disabled } = request.body;
|
||||
|
||||
return {
|
||||
key: request.params.appKey,
|
||||
useOnlyPredefinedAuthClients,
|
||||
customConnectionAllowed,
|
||||
shared,
|
||||
disabled,
|
||||
};
|
||||
};
|
||||
|
@@ -23,7 +23,8 @@ describe('POST /api/v1/admin/apps/:appKey/config', () => {
|
||||
|
||||
it('should return created app config', async () => {
|
||||
const appConfig = {
|
||||
useOnlyPredefinedAuthClients: false,
|
||||
customConnectionAllowed: true,
|
||||
shared: true,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
@@ -37,14 +38,14 @@ describe('POST /api/v1/admin/apps/:appKey/config', () => {
|
||||
...appConfig,
|
||||
key: 'gitlab',
|
||||
});
|
||||
|
||||
expect(response.body).toMatchObject(expectedPayload);
|
||||
});
|
||||
|
||||
it('should return HTTP 422 for already existing app config', async () => {
|
||||
const appConfig = {
|
||||
key: 'gitlab',
|
||||
useOnlyPredefinedAuthClients: false,
|
||||
customConnectionAllowed: true,
|
||||
shared: true,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
|
@@ -17,10 +17,11 @@ export default async (request, response) => {
|
||||
};
|
||||
|
||||
const appConfigParams = (request) => {
|
||||
const { useOnlyPredefinedAuthClients, disabled } = request.body;
|
||||
const { customConnectionAllowed, shared, disabled } = request.body;
|
||||
|
||||
return {
|
||||
useOnlyPredefinedAuthClients,
|
||||
customConnectionAllowed,
|
||||
shared,
|
||||
disabled,
|
||||
};
|
||||
};
|
||||
|
@@ -24,15 +24,17 @@ describe('PATCH /api/v1/admin/apps/:appKey/config', () => {
|
||||
it('should return updated app config', async () => {
|
||||
const appConfig = {
|
||||
key: 'gitlab',
|
||||
useOnlyPredefinedAuthClients: true,
|
||||
customConnectionAllowed: true,
|
||||
shared: true,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
await createAppConfig(appConfig);
|
||||
|
||||
const newAppConfigValues = {
|
||||
shared: false,
|
||||
disabled: true,
|
||||
useOnlyPredefinedAuthClients: false,
|
||||
customConnectionAllowed: false,
|
||||
};
|
||||
|
||||
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 () => {
|
||||
const appConfig = {
|
||||
shared: false,
|
||||
disabled: true,
|
||||
useOnlyPredefinedAuthClients: false,
|
||||
customConnectionAllowed: false,
|
||||
};
|
||||
|
||||
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 () => {
|
||||
const appConfig = {
|
||||
key: 'gitlab',
|
||||
useOnlyPredefinedAuthClients: true,
|
||||
customConnectionAllowed: true,
|
||||
shared: true,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
|
@@ -7,7 +7,7 @@ export default async (request, response) => {
|
||||
.throwIfNotFound();
|
||||
|
||||
const roleMappings = await samlAuthProvider
|
||||
.$relatedQuery('roleMappings')
|
||||
.$relatedQuery('samlAuthProvidersRoleMappings')
|
||||
.orderBy('remote_role_name', 'asc');
|
||||
|
||||
renderObject(response, roleMappings);
|
||||
|
@@ -8,14 +8,15 @@ export default async (request, response) => {
|
||||
.findById(samlAuthProviderId)
|
||||
.throwIfNotFound();
|
||||
|
||||
const roleMappings = await samlAuthProvider.updateRoleMappings(
|
||||
roleMappingsParams(request)
|
||||
const samlAuthProvidersRoleMappings =
|
||||
await samlAuthProvider.updateRoleMappings(
|
||||
samlAuthProvidersRoleMappingsParams(request)
|
||||
);
|
||||
|
||||
renderObject(response, roleMappings);
|
||||
renderObject(response, samlAuthProvidersRoleMappings);
|
||||
};
|
||||
|
||||
const roleMappingsParams = (request) => {
|
||||
const samlAuthProvidersRoleMappingsParams = (request) => {
|
||||
const roleMappings = request.body;
|
||||
|
||||
return roleMappings.map(({ roleId, remoteRoleName }) => ({
|
||||
|
@@ -6,7 +6,7 @@ import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by
|
||||
import { createRole } from '../../../../../../test/factories/role.js';
|
||||
import { createUser } from '../../../../../../test/factories/user.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 * 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();
|
||||
|
||||
await createRoleMapping({
|
||||
await createSamlAuthProvidersRoleMapping({
|
||||
samlAuthProviderId: samlAuthProvider.id,
|
||||
remoteRoleName: 'Viewer',
|
||||
});
|
||||
|
||||
await createRoleMapping({
|
||||
await createSamlAuthProvidersRoleMapping({
|
||||
samlAuthProviderId: samlAuthProvider.id,
|
||||
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 () => {
|
||||
const existingRoleMappings = await samlAuthProvider.$relatedQuery(
|
||||
'roleMappings'
|
||||
'samlAuthProvidersRoleMappings'
|
||||
);
|
||||
|
||||
expect(existingRoleMappings.length).toBe(2);
|
||||
@@ -149,4 +149,34 @@ describe('PATCH /api/v1/admin/saml-auth-providers/:samlAuthProviderId/role-mappi
|
||||
.send(roleMappings)
|
||||
.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);
|
||||
});
|
||||
});
|
||||
|
@@ -155,7 +155,7 @@ describe('POST /api/v1/apps/:appKey/connections', () => {
|
||||
await createAppConfig({
|
||||
key: 'gitlab',
|
||||
disabled: false,
|
||||
useOnlyPredefinedAuthClients: false,
|
||||
customConnectionAllowed: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -218,7 +218,7 @@ describe('POST /api/v1/apps/:appKey/connections', () => {
|
||||
await createAppConfig({
|
||||
key: 'gitlab',
|
||||
disabled: false,
|
||||
useOnlyPredefinedAuthClients: true,
|
||||
customConnectionAllowed: 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;
|
||||
|
||||
beforeEach(async () => {
|
||||
await createAppConfig({
|
||||
key: 'gitlab',
|
||||
disabled: false,
|
||||
useOnlyPredefinedAuthClients: false,
|
||||
shared: true,
|
||||
});
|
||||
|
||||
appAuthClient = await createAppAuthClient({
|
||||
@@ -310,6 +310,19 @@ describe('POST /api/v1/apps/:appKey/connections', () => {
|
||||
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 () => {
|
||||
await request(app)
|
||||
.post('/api/v1/apps/invalid-app-key/connections')
|
||||
@@ -336,20 +349,18 @@ describe('POST /api/v1/apps/:appKey/connections', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with auth client disabled', async () => {
|
||||
describe('with auth clients disabled', async () => {
|
||||
let appAuthClient;
|
||||
|
||||
beforeEach(async () => {
|
||||
await createAppConfig({
|
||||
key: 'gitlab',
|
||||
disabled: false,
|
||||
useOnlyPredefinedAuthClients: false,
|
||||
shared: false,
|
||||
});
|
||||
|
||||
appAuthClient = await createAppAuthClient({
|
||||
appKey: 'gitlab',
|
||||
active: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -362,7 +373,7 @@ describe('POST /api/v1/apps/:appKey/connections', () => {
|
||||
.post('/api/v1/apps/gitlab/connections')
|
||||
.set('Authorization', token)
|
||||
.send(connectionData)
|
||||
.expect(404);
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
it('should return not found response for invalid app key', async () => {
|
||||
|
@@ -17,7 +17,8 @@ describe('GET /api/v1/apps/:appKey/config', () => {
|
||||
|
||||
appConfig = await createAppConfig({
|
||||
key: 'deepl',
|
||||
useOnlyPredefinedAuthClients: false,
|
||||
customConnectionAllowed: true,
|
||||
shared: true,
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
|
@@ -87,14 +87,14 @@ describe('GET /api/v1/apps/:appKey/connections', () => {
|
||||
|
||||
it('should return not found response for invalid connection UUID', async () => {
|
||||
await createPermission({
|
||||
action: 'read',
|
||||
action: 'update',
|
||||
subject: 'Connection',
|
||||
roleId: currentUserRole.id,
|
||||
conditions: ['isCreator'],
|
||||
});
|
||||
|
||||
await request(app)
|
||||
.get('/api/v1/apps/invalid-connection-id/connections')
|
||||
.get('/api/v1/connections/invalid-connection-id/connections')
|
||||
.set('Authorization', token)
|
||||
.expect(404);
|
||||
});
|
||||
|
@@ -47,6 +47,7 @@ describe('POST /api/v1/connections/:connectionId/reset', () => {
|
||||
|
||||
const expectedPayload = resetConnectionMock({
|
||||
...refetchedCurrentUserConnection,
|
||||
reconnectable: refetchedCurrentUserConnection.reconnectable,
|
||||
formattedData: {
|
||||
screenName: 'Connection name',
|
||||
},
|
||||
|
@@ -55,9 +55,10 @@ describe('PATCH /api/v1/connections/:connectionId', () => {
|
||||
|
||||
const refetchedCurrentUserConnection = await currentUserConnection.$query();
|
||||
|
||||
const expectedPayload = updateConnectionMock(
|
||||
refetchedCurrentUserConnection
|
||||
);
|
||||
const expectedPayload = updateConnectionMock({
|
||||
...refetchedCurrentUserConnection,
|
||||
reconnectable: refetchedCurrentUserConnection.reconnectable,
|
||||
});
|
||||
|
||||
expect(response.body).toStrictEqual(expectedPayload);
|
||||
});
|
||||
|
@@ -193,7 +193,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-data', () => {
|
||||
const notExistingStepUUID = Crypto.randomUUID();
|
||||
|
||||
await request(app)
|
||||
.post(`/api/v1/steps/${notExistingStepUUID}/dynamic-data`)
|
||||
.get(`/api/v1/steps/${notExistingStepUUID}/dynamic-data`)
|
||||
.set('Authorization', token)
|
||||
.expect(404);
|
||||
});
|
||||
@@ -216,7 +216,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-data', () => {
|
||||
const step = await createStep({ appKey: null });
|
||||
|
||||
await request(app)
|
||||
.post(`/api/v1/steps/${step.id}/dynamic-data`)
|
||||
.get(`/api/v1/steps/${step.id}/dynamic-data`)
|
||||
.set('Authorization', token)
|
||||
.expect(404);
|
||||
});
|
||||
|
@@ -118,7 +118,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-fields', () => {
|
||||
const notExistingStepUUID = Crypto.randomUUID();
|
||||
|
||||
await request(app)
|
||||
.post(`/api/v1/steps/${notExistingStepUUID}/dynamic-fields`)
|
||||
.get(`/api/v1/steps/${notExistingStepUUID}/dynamic-fields`)
|
||||
.set('Authorization', token)
|
||||
.expect(404);
|
||||
});
|
||||
@@ -138,11 +138,10 @@ describe('POST /api/v1/steps/:stepId/dynamic-fields', () => {
|
||||
conditions: [],
|
||||
});
|
||||
|
||||
const step = await createStep();
|
||||
await step.$query().patch({ appKey: null });
|
||||
const step = await createStep({ appKey: null });
|
||||
|
||||
await request(app)
|
||||
.post(`/api/v1/steps/${step.id}/dynamic-fields`)
|
||||
.get(`/api/v1/steps/${step.id}/dynamic-fields`)
|
||||
.set('Authorization', token)
|
||||
.expect(404);
|
||||
});
|
||||
|
@@ -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');
|
||||
}
|
@@ -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');
|
||||
});
|
||||
}
|
@@ -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);
|
||||
});
|
||||
}
|
@@ -30,7 +30,7 @@ const findOrCreateUserBySamlIdentity = async (
|
||||
: [mappedUser.role];
|
||||
|
||||
const samlAuthProviderRoleMapping = await samlAuthProvider
|
||||
.$relatedQuery('roleMappings')
|
||||
.$relatedQuery('samlAuthProvidersRoleMappings')
|
||||
.whereIn('remote_role_name', mappedRoles)
|
||||
.limit(1)
|
||||
.first();
|
||||
|
@@ -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([]);
|
||||
});
|
||||
});
|
@@ -3,9 +3,17 @@
|
||||
exports[`AppConfig model > jsonSchema should have correct validations 1`] = `
|
||||
{
|
||||
"properties": {
|
||||
"connectionAllowed": {
|
||||
"default": false,
|
||||
"type": "boolean",
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
},
|
||||
"customConnectionAllowed": {
|
||||
"default": false,
|
||||
"type": "boolean",
|
||||
},
|
||||
"disabled": {
|
||||
"default": false,
|
||||
"type": "boolean",
|
||||
@@ -17,13 +25,13 @@ exports[`AppConfig model > jsonSchema should have correct validations 1`] = `
|
||||
"key": {
|
||||
"type": "string",
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
},
|
||||
"useOnlyPredefinedAuthClients": {
|
||||
"shared": {
|
||||
"default": false,
|
||||
"type": "boolean",
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"key",
|
||||
|
@@ -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",
|
||||
}
|
||||
`;
|
@@ -1,6 +1,6 @@
|
||||
// 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": {
|
||||
"id": {
|
||||
|
@@ -60,26 +60,39 @@ class AppAuthClient extends Base {
|
||||
return this.authDefaults ? true : false;
|
||||
}
|
||||
|
||||
async triggerAppConfigUpdate() {
|
||||
const appConfig = await this.$relatedQuery('appConfig');
|
||||
|
||||
// This is a workaround to update connection allowed column for AppConfig
|
||||
await appConfig?.$query().patch({
|
||||
key: appConfig.key,
|
||||
shared: appConfig.shared,
|
||||
disabled: appConfig.disabled,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Make another abstraction like beforeSave instead of using
|
||||
// beforeInsert and beforeUpdate separately for the same operation.
|
||||
async $beforeInsert(queryContext) {
|
||||
await super.$beforeInsert(queryContext);
|
||||
|
||||
this.encryptData();
|
||||
}
|
||||
|
||||
async $afterInsert(queryContext) {
|
||||
await super.$afterInsert(queryContext);
|
||||
|
||||
await this.triggerAppConfigUpdate();
|
||||
}
|
||||
|
||||
async $beforeUpdate(opt, queryContext) {
|
||||
await super.$beforeUpdate(opt, queryContext);
|
||||
|
||||
this.encryptData();
|
||||
}
|
||||
|
||||
async $afterUpdate(opt, queryContext) {
|
||||
await super.$afterUpdate(opt, queryContext);
|
||||
|
||||
await this.triggerAppConfigUpdate();
|
||||
}
|
||||
|
||||
async $afterFind() {
|
||||
|
@@ -7,6 +7,7 @@ import AppAuthClient from './app-auth-client.js';
|
||||
import Base from './base.js';
|
||||
import appConfig from '../config/app.js';
|
||||
import { createAppAuthClient } from '../../test/factories/app-auth-client.js';
|
||||
import { createAppConfig } from '../../test/factories/app-config.js';
|
||||
|
||||
describe('AppAuthClient model', () => {
|
||||
it('tableName should return correct name', () => {
|
||||
@@ -163,6 +164,63 @@ describe('AppAuthClient model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('triggerAppConfigUpdate', () => {
|
||||
it('should trigger an update in related app config', async () => {
|
||||
await createAppConfig({ key: 'gitlab' });
|
||||
|
||||
const appAuthClient = await createAppAuthClient({
|
||||
appKey: 'gitlab',
|
||||
});
|
||||
|
||||
const appConfigBeforeUpdateSpy = vi.spyOn(
|
||||
AppConfig.prototype,
|
||||
'$beforeUpdate'
|
||||
);
|
||||
|
||||
await appAuthClient.triggerAppConfigUpdate();
|
||||
|
||||
expect(appConfigBeforeUpdateSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should update related AppConfig after creating an instance', async () => {
|
||||
const appConfig = await createAppConfig({
|
||||
key: 'gitlab',
|
||||
disabled: false,
|
||||
shared: true,
|
||||
});
|
||||
|
||||
await createAppAuthClient({
|
||||
appKey: 'gitlab',
|
||||
active: true,
|
||||
});
|
||||
|
||||
const refetchedAppConfig = await appConfig.$query();
|
||||
|
||||
expect(refetchedAppConfig.connectionAllowed).toBe(true);
|
||||
});
|
||||
|
||||
it('should update related AppConfig after updating an instance', async () => {
|
||||
const appConfig = await createAppConfig({
|
||||
key: 'gitlab',
|
||||
disabled: false,
|
||||
shared: true,
|
||||
});
|
||||
|
||||
const appAuthClient = await createAppAuthClient({
|
||||
appKey: 'gitlab',
|
||||
active: false,
|
||||
});
|
||||
|
||||
let refetchedAppConfig = await appConfig.$query();
|
||||
expect(refetchedAppConfig.connectionAllowed).toBe(false);
|
||||
|
||||
await appAuthClient.$query().patchAndFetch({ active: true });
|
||||
|
||||
refetchedAppConfig = await appConfig.$query();
|
||||
expect(refetchedAppConfig.connectionAllowed).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('$beforeInsert should call AppAuthClient.encryptData', async () => {
|
||||
const appAuthClientBeforeInsertSpy = vi.spyOn(
|
||||
AppAuthClient.prototype,
|
||||
@@ -174,6 +232,17 @@ describe('AppAuthClient model', () => {
|
||||
expect(appAuthClientBeforeInsertSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('$afterInsert should call AppAuthClient.triggerAppConfigUpdate', async () => {
|
||||
const appAuthClientAfterInsertSpy = vi.spyOn(
|
||||
AppAuthClient.prototype,
|
||||
'triggerAppConfigUpdate'
|
||||
);
|
||||
|
||||
await createAppAuthClient();
|
||||
|
||||
expect(appAuthClientAfterInsertSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('$beforeUpdate should call AppAuthClient.encryptData', async () => {
|
||||
const appAuthClient = await createAppAuthClient();
|
||||
|
||||
@@ -187,6 +256,19 @@ describe('AppAuthClient model', () => {
|
||||
expect(appAuthClientBeforeUpdateSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('$afterUpdate should call AppAuthClient.triggerAppConfigUpdate', async () => {
|
||||
const appAuthClient = await createAppAuthClient();
|
||||
|
||||
const appAuthClientAfterUpdateSpy = vi.spyOn(
|
||||
AppAuthClient.prototype,
|
||||
'triggerAppConfigUpdate'
|
||||
);
|
||||
|
||||
await appAuthClient.$query().patchAndFetch({ name: 'sample' });
|
||||
|
||||
expect(appAuthClientAfterUpdateSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('$afterFind should call AppAuthClient.decryptData', async () => {
|
||||
const appAuthClient = await createAppAuthClient();
|
||||
|
||||
|
@@ -16,7 +16,9 @@ class AppConfig extends Base {
|
||||
properties: {
|
||||
id: { type: 'string', format: 'uuid' },
|
||||
key: { type: 'string' },
|
||||
useOnlyPredefinedAuthClients: { type: 'boolean', default: false },
|
||||
connectionAllowed: { type: 'boolean', default: false },
|
||||
customConnectionAllowed: { type: 'boolean', default: false },
|
||||
shared: { type: 'boolean', default: false },
|
||||
disabled: { type: 'boolean', default: false },
|
||||
createdAt: { type: 'string' },
|
||||
updatedAt: { type: 'string' },
|
||||
@@ -39,6 +41,39 @@ class AppConfig extends Base {
|
||||
|
||||
return await App.findOneByKey(this.key);
|
||||
}
|
||||
|
||||
async computeAndAssignConnectionAllowedProperty() {
|
||||
this.connectionAllowed = await this.computeConnectionAllowedProperty();
|
||||
}
|
||||
|
||||
async computeConnectionAllowedProperty() {
|
||||
const appAuthClients = await this.$relatedQuery('appAuthClients');
|
||||
|
||||
const hasSomeActiveAppAuthClients =
|
||||
appAuthClients?.some((appAuthClient) => appAuthClient.active) || false;
|
||||
|
||||
const conditions = [
|
||||
hasSomeActiveAppAuthClients,
|
||||
this.shared,
|
||||
!this.disabled,
|
||||
];
|
||||
|
||||
const connectionAllowed = conditions.every(Boolean);
|
||||
|
||||
return connectionAllowed;
|
||||
}
|
||||
|
||||
async $beforeInsert(queryContext) {
|
||||
await super.$beforeInsert(queryContext);
|
||||
|
||||
await this.computeAndAssignConnectionAllowedProperty();
|
||||
}
|
||||
|
||||
async $beforeUpdate(opt, queryContext) {
|
||||
await super.$beforeUpdate(opt, queryContext);
|
||||
|
||||
await this.computeAndAssignConnectionAllowedProperty();
|
||||
}
|
||||
}
|
||||
|
||||
export default AppConfig;
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { vi, describe, it, expect } from 'vitest';
|
||||
|
||||
import Base from './base.js';
|
||||
import AppConfig from './app-config.js';
|
||||
import App from './app.js';
|
||||
import AppAuthClient from './app-auth-client.js';
|
||||
import { createAppConfig } from '../../test/factories/app-config.js';
|
||||
import { createAppAuthClient } from '../../test/factories/app-auth-client.js';
|
||||
|
||||
describe('AppConfig model', () => {
|
||||
it('tableName should return correct name', () => {
|
||||
@@ -53,4 +55,126 @@ describe('AppConfig model', () => {
|
||||
expect(app).toStrictEqual(expectedApp);
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeAndAssignConnectionAllowedProperty', () => {
|
||||
it('should call computeConnectionAllowedProperty and assign the result', async () => {
|
||||
const appConfig = await createAppConfig();
|
||||
|
||||
const computeConnectionAllowedPropertySpy = vi
|
||||
.spyOn(appConfig, 'computeConnectionAllowedProperty')
|
||||
.mockResolvedValue(true);
|
||||
|
||||
await appConfig.computeAndAssignConnectionAllowedProperty();
|
||||
|
||||
expect(computeConnectionAllowedPropertySpy).toHaveBeenCalled();
|
||||
expect(appConfig.connectionAllowed).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeConnectionAllowedProperty', () => {
|
||||
it('should return true when app is enabled, shared and allows custom connection with an active app auth client', async () => {
|
||||
await createAppAuthClient({
|
||||
appKey: 'deepl',
|
||||
active: true,
|
||||
});
|
||||
|
||||
await createAppAuthClient({
|
||||
appKey: 'deepl',
|
||||
active: false,
|
||||
});
|
||||
|
||||
const appConfig = await createAppConfig({
|
||||
disabled: false,
|
||||
customConnectionAllowed: true,
|
||||
shared: true,
|
||||
key: 'deepl',
|
||||
});
|
||||
|
||||
const connectionAllowed =
|
||||
await appConfig.computeConnectionAllowedProperty();
|
||||
|
||||
expect(connectionAllowed).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if there is no active app auth client', async () => {
|
||||
await createAppAuthClient({
|
||||
appKey: 'deepl',
|
||||
active: false,
|
||||
});
|
||||
|
||||
const appConfig = await createAppConfig({
|
||||
disabled: false,
|
||||
customConnectionAllowed: true,
|
||||
shared: true,
|
||||
key: 'deepl',
|
||||
});
|
||||
|
||||
const connectionAllowed =
|
||||
await appConfig.computeConnectionAllowedProperty();
|
||||
|
||||
expect(connectionAllowed).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if there is no app auth clients', async () => {
|
||||
const appConfig = await createAppConfig({
|
||||
disabled: false,
|
||||
customConnectionAllowed: true,
|
||||
shared: true,
|
||||
key: 'deepl',
|
||||
});
|
||||
|
||||
const connectionAllowed =
|
||||
await appConfig.computeConnectionAllowedProperty();
|
||||
|
||||
expect(connectionAllowed).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when app is disabled', async () => {
|
||||
const appConfig = await createAppConfig({
|
||||
disabled: true,
|
||||
customConnectionAllowed: true,
|
||||
});
|
||||
|
||||
const connectionAllowed =
|
||||
await appConfig.computeConnectionAllowedProperty();
|
||||
|
||||
expect(connectionAllowed).toBe(false);
|
||||
});
|
||||
|
||||
it(`should return false when app doesn't allow custom connection`, async () => {
|
||||
const appConfig = await createAppConfig({
|
||||
disabled: false,
|
||||
customConnectionAllowed: false,
|
||||
});
|
||||
|
||||
const connectionAllowed =
|
||||
await appConfig.computeConnectionAllowedProperty();
|
||||
|
||||
expect(connectionAllowed).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('$beforeInsert should call computeAndAssignConnectionAllowedProperty', async () => {
|
||||
const computeAndAssignConnectionAllowedPropertySpy = vi
|
||||
.spyOn(AppConfig.prototype, 'computeAndAssignConnectionAllowedProperty')
|
||||
.mockResolvedValue(true);
|
||||
|
||||
await createAppConfig();
|
||||
|
||||
expect(computeAndAssignConnectionAllowedPropertySpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('$beforeUpdate should call computeAndAssignConnectionAllowedProperty', async () => {
|
||||
const appConfig = await createAppConfig();
|
||||
|
||||
const computeAndAssignConnectionAllowedPropertySpy = vi
|
||||
.spyOn(AppConfig.prototype, 'computeAndAssignConnectionAllowedProperty')
|
||||
.mockResolvedValue(true);
|
||||
|
||||
await appConfig.$query().patch({
|
||||
key: 'deepl',
|
||||
});
|
||||
|
||||
expect(computeAndAssignConnectionAllowedPropertySpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
@@ -33,6 +33,10 @@ class Connection extends Base {
|
||||
},
|
||||
};
|
||||
|
||||
static get virtualAttributes() {
|
||||
return ['reconnectable'];
|
||||
}
|
||||
|
||||
static relationMappings = () => ({
|
||||
user: {
|
||||
relation: Base.BelongsToOneRelation,
|
||||
@@ -79,6 +83,18 @@ class Connection extends Base {
|
||||
},
|
||||
});
|
||||
|
||||
get reconnectable() {
|
||||
if (this.appAuthClientId) {
|
||||
return this.appAuthClient.active;
|
||||
}
|
||||
|
||||
if (this.appConfig) {
|
||||
return !this.appConfig.disabled && this.appConfig.customConnectionAllowed;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
encryptData() {
|
||||
if (!this.eligibleForEncryption()) return;
|
||||
|
||||
@@ -128,13 +144,19 @@ class Connection extends Base {
|
||||
);
|
||||
}
|
||||
|
||||
if (appConfig.useOnlyPredefinedAuthClients && this.formattedData) {
|
||||
if (!appConfig.customConnectionAllowed && this.formattedData) {
|
||||
throw new NotAuthorizedError(
|
||||
`New custom connections have been disabled for ${app.name}!`
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.formattedData) {
|
||||
if (!appConfig.shared && this.appAuthClientId) {
|
||||
throw new NotAuthorizedError(
|
||||
'The connection with the given app auth client is not allowed!'
|
||||
);
|
||||
}
|
||||
|
||||
if (appConfig.shared && !this.formattedData) {
|
||||
const authClient = await appConfig
|
||||
.$relatedQuery('appAuthClients')
|
||||
.findById(this.appAuthClientId)
|
||||
|
@@ -23,6 +23,14 @@ describe('Connection model', () => {
|
||||
expect(Connection.jsonSchema).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('virtualAttributes should return correct attributes', () => {
|
||||
const virtualAttributes = Connection.virtualAttributes;
|
||||
|
||||
const expectedAttributes = ['reconnectable'];
|
||||
|
||||
expect(virtualAttributes).toStrictEqual(expectedAttributes);
|
||||
});
|
||||
|
||||
describe('relationMappings', () => {
|
||||
it('should return correct associations', () => {
|
||||
const relationMappings = Connection.relationMappings();
|
||||
@@ -84,6 +92,78 @@ describe('Connection model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('reconnectable', () => {
|
||||
it('should return active status of app auth client when created via app auth client', async () => {
|
||||
const appAuthClient = await createAppAuthClient({
|
||||
active: true,
|
||||
formattedAuthDefaults: {
|
||||
clientId: 'sample-id',
|
||||
},
|
||||
});
|
||||
|
||||
const connection = await createConnection({
|
||||
appAuthClientId: appAuthClient.id,
|
||||
formattedData: {
|
||||
token: 'sample-token',
|
||||
},
|
||||
});
|
||||
|
||||
const connectionWithAppAuthClient = await connection
|
||||
.$query()
|
||||
.withGraphFetched({
|
||||
appAuthClient: true,
|
||||
});
|
||||
|
||||
expect(connectionWithAppAuthClient.reconnectable).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when app config is not disabled and allows custom connection', async () => {
|
||||
const appConfig = await createAppConfig({
|
||||
key: 'gitlab',
|
||||
disabled: false,
|
||||
customConnectionAllowed: true,
|
||||
});
|
||||
|
||||
const connection = await createConnection({
|
||||
key: appConfig.key,
|
||||
formattedData: {
|
||||
token: 'sample-token',
|
||||
},
|
||||
});
|
||||
|
||||
const connectionWithAppAuthClient = await connection
|
||||
.$query()
|
||||
.withGraphFetched({
|
||||
appConfig: true,
|
||||
});
|
||||
|
||||
expect(connectionWithAppAuthClient.reconnectable).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when app config is disabled or does not allow custom connection', async () => {
|
||||
const connection = await createConnection({
|
||||
key: 'gitlab',
|
||||
formattedData: {
|
||||
token: 'sample-token',
|
||||
},
|
||||
});
|
||||
|
||||
await createAppConfig({
|
||||
key: 'gitlab',
|
||||
disabled: true,
|
||||
customConnectionAllowed: false,
|
||||
});
|
||||
|
||||
const connectionWithAppAuthClient = await connection
|
||||
.$query()
|
||||
.withGraphFetched({
|
||||
appConfig: true,
|
||||
});
|
||||
|
||||
expect(connectionWithAppAuthClient.reconnectable).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('encryptData', () => {
|
||||
it('should return undefined if eligibleForEncryption is not true', async () => {
|
||||
vi.spyOn(Connection.prototype, 'eligibleForEncryption').mockReturnValue(
|
||||
@@ -286,7 +366,6 @@ describe('Connection model', () => {
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: update test case name
|
||||
it('should throw an error when app config does not allow custom connection with formatted data', async () => {
|
||||
vi.spyOn(Connection.prototype, 'getApp').mockResolvedValue({
|
||||
name: 'gitlab',
|
||||
@@ -294,7 +373,7 @@ describe('Connection model', () => {
|
||||
|
||||
vi.spyOn(Connection.prototype, 'getAppConfig').mockResolvedValue({
|
||||
disabled: false,
|
||||
useOnlyPredefinedAuthClients: true,
|
||||
customConnectionAllowed: false,
|
||||
});
|
||||
|
||||
const connection = new Connection();
|
||||
@@ -307,10 +386,32 @@ describe('Connection model', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error when app config is not shared with app auth client', async () => {
|
||||
vi.spyOn(Connection.prototype, 'getApp').mockResolvedValue({
|
||||
name: 'gitlab',
|
||||
});
|
||||
|
||||
vi.spyOn(Connection.prototype, 'getAppConfig').mockResolvedValue({
|
||||
disabled: false,
|
||||
shared: false,
|
||||
});
|
||||
|
||||
const connection = new Connection();
|
||||
connection.appAuthClientId = 'sample-id';
|
||||
|
||||
await expect(() =>
|
||||
connection.checkEligibilityForCreation()
|
||||
).rejects.toThrow(
|
||||
'The connection with the given app auth client is not allowed!'
|
||||
);
|
||||
});
|
||||
|
||||
it('should apply app auth client auth defaults when creating with shared app auth client', async () => {
|
||||
await createAppConfig({
|
||||
key: 'gitlab',
|
||||
disabled: false,
|
||||
customConnectionAllowed: true,
|
||||
shared: true,
|
||||
});
|
||||
|
||||
const appAuthClient = await createAppAuthClient({
|
||||
|
@@ -5,7 +5,7 @@ import appConfig from '../config/app.js';
|
||||
import axios from '../helpers/axios-with-proxy.js';
|
||||
import Base from './base.js';
|
||||
import Identity from './identity.ee.js';
|
||||
import RoleMapping from './role-mapping.ee.js';
|
||||
import SamlAuthProvidersRoleMapping from './saml-auth-providers-role-mapping.ee.js';
|
||||
|
||||
class SamlAuthProvider extends Base {
|
||||
static tableName = 'saml_auth_providers';
|
||||
@@ -53,12 +53,12 @@ class SamlAuthProvider extends Base {
|
||||
to: 'saml_auth_providers.id',
|
||||
},
|
||||
},
|
||||
roleMappings: {
|
||||
samlAuthProvidersRoleMappings: {
|
||||
relation: Base.HasManyRelation,
|
||||
modelClass: RoleMapping,
|
||||
modelClass: SamlAuthProvidersRoleMapping,
|
||||
join: {
|
||||
from: 'saml_auth_providers.id',
|
||||
to: 'role_mappings.saml_auth_provider_id',
|
||||
to: 'saml_auth_providers_role_mappings.saml_auth_provider_id',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -133,22 +133,27 @@ class SamlAuthProvider extends Base {
|
||||
}
|
||||
|
||||
async updateRoleMappings(roleMappings) {
|
||||
await this.$relatedQuery('roleMappings').delete();
|
||||
return await SamlAuthProvider.transaction(async (trx) => {
|
||||
await this.$relatedQuery('samlAuthProvidersRoleMappings', trx).delete();
|
||||
|
||||
if (isEmpty(roleMappings)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const roleMappingsData = roleMappings.map((roleMapping) => ({
|
||||
...roleMapping,
|
||||
const samlAuthProvidersRoleMappingsData = roleMappings.map(
|
||||
(samlAuthProvidersRoleMapping) => ({
|
||||
...samlAuthProvidersRoleMapping,
|
||||
samlAuthProviderId: this.id,
|
||||
}));
|
||||
|
||||
const newRoleMappings = await RoleMapping.query().insertAndFetch(
|
||||
roleMappingsData
|
||||
})
|
||||
);
|
||||
|
||||
return newRoleMappings;
|
||||
const samlAuthProvidersRoleMappings =
|
||||
await SamlAuthProvidersRoleMapping.query(trx).insertAndFetch(
|
||||
samlAuthProvidersRoleMappingsData
|
||||
);
|
||||
|
||||
return samlAuthProvidersRoleMappings;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,14 +1,9 @@
|
||||
import { vi, beforeEach, describe, it, expect } from 'vitest';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { vi, describe, it, expect } from 'vitest';
|
||||
import SamlAuthProvider from '../models/saml-auth-provider.ee';
|
||||
import RoleMapping from '../models/role-mapping.ee';
|
||||
import axios from '../helpers/axios-with-proxy.js';
|
||||
import SamlAuthProvidersRoleMapping from '../models/saml-auth-providers-role-mapping.ee';
|
||||
import Identity from './identity.ee';
|
||||
import Base from './base';
|
||||
import appConfig from '../config/app';
|
||||
import { createSamlAuthProvider } from '../../test/factories/saml-auth-provider.ee.js';
|
||||
import { createRoleMapping } from '../../test/factories/role-mapping.js';
|
||||
import { createRole } from '../../test/factories/role.js';
|
||||
|
||||
describe('SamlAuthProvider model', () => {
|
||||
it('tableName should return correct name', () => {
|
||||
@@ -31,12 +26,12 @@ describe('SamlAuthProvider model', () => {
|
||||
to: 'saml_auth_providers.id',
|
||||
},
|
||||
},
|
||||
roleMappings: {
|
||||
samlAuthProvidersRoleMappings: {
|
||||
relation: Base.HasManyRelation,
|
||||
modelClass: RoleMapping,
|
||||
modelClass: SamlAuthProvidersRoleMapping,
|
||||
join: {
|
||||
from: 'saml_auth_providers.id',
|
||||
to: 'role_mappings.saml_auth_provider_id',
|
||||
to: 'saml_auth_providers_role_mappings.saml_auth_provider_id',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -86,146 +81,4 @@ describe('SamlAuthProvider model', () => {
|
||||
'https://example.com/saml/logout'
|
||||
);
|
||||
});
|
||||
|
||||
it('config should return the correct configuration object', () => {
|
||||
const samlAuthProvider = new SamlAuthProvider();
|
||||
|
||||
samlAuthProvider.certificate = 'sample-certificate';
|
||||
samlAuthProvider.signatureAlgorithm = 'sha256';
|
||||
samlAuthProvider.entryPoint = 'https://example.com/saml';
|
||||
samlAuthProvider.issuer = 'sample-issuer';
|
||||
|
||||
vi.spyOn(appConfig, 'baseUrl', 'get').mockReturnValue(
|
||||
'https://automatisch.io'
|
||||
);
|
||||
|
||||
const expectedConfig = {
|
||||
callbackUrl: 'https://automatisch.io/login/saml/sample-issuer/callback',
|
||||
cert: 'sample-certificate',
|
||||
entryPoint: 'https://example.com/saml',
|
||||
issuer: 'sample-issuer',
|
||||
signatureAlgorithm: 'sha256',
|
||||
logoutUrl: 'https://example.com/saml',
|
||||
};
|
||||
|
||||
expect(samlAuthProvider.config).toStrictEqual(expectedConfig);
|
||||
});
|
||||
|
||||
it('generateLogoutRequestBody should return a correctly encoded SAML logout request', () => {
|
||||
vi.mock('uuid', () => ({
|
||||
v4: vi.fn(),
|
||||
}));
|
||||
|
||||
const samlAuthProvider = new SamlAuthProvider();
|
||||
|
||||
samlAuthProvider.entryPoint = 'https://example.com/saml';
|
||||
samlAuthProvider.issuer = 'sample-issuer';
|
||||
|
||||
const mockUuid = '123e4567-e89b-12d3-a456-426614174000';
|
||||
uuidv4.mockReturnValue(mockUuid);
|
||||
|
||||
const sessionId = 'test-session-id';
|
||||
|
||||
const logoutRequest = samlAuthProvider.generateLogoutRequestBody(sessionId);
|
||||
|
||||
const expectedLogoutRequest = `
|
||||
<samlp:LogoutRequest
|
||||
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
|
||||
ID="${mockUuid}"
|
||||
Version="2.0"
|
||||
IssueInstant="${new Date().toISOString()}"
|
||||
Destination="https://example.com/saml">
|
||||
|
||||
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">sample-issuer</saml:Issuer>
|
||||
<samlp:SessionIndex>test-session-id</samlp:SessionIndex>
|
||||
</samlp:LogoutRequest>
|
||||
`;
|
||||
|
||||
const expectedEncodedRequest = Buffer.from(expectedLogoutRequest).toString(
|
||||
'base64'
|
||||
);
|
||||
|
||||
expect(logoutRequest).toBe(expectedEncodedRequest);
|
||||
});
|
||||
|
||||
it('terminateRemoteSession should send the correct POST request and return the response', async () => {
|
||||
vi.mock('../helpers/axios-with-proxy.js', () => ({
|
||||
default: {
|
||||
post: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const samlAuthProvider = new SamlAuthProvider();
|
||||
|
||||
samlAuthProvider.entryPoint = 'https://example.com/saml';
|
||||
samlAuthProvider.generateLogoutRequestBody = vi
|
||||
.fn()
|
||||
.mockReturnValue('mockEncodedLogoutRequest');
|
||||
|
||||
const sessionId = 'test-session-id';
|
||||
|
||||
const mockResponse = { data: 'Logout Successful' };
|
||||
axios.post.mockResolvedValue(mockResponse);
|
||||
|
||||
const response = await samlAuthProvider.terminateRemoteSession(sessionId);
|
||||
|
||||
expect(samlAuthProvider.generateLogoutRequestBody).toHaveBeenCalledWith(
|
||||
sessionId
|
||||
);
|
||||
|
||||
expect(axios.post).toHaveBeenCalledWith(
|
||||
'https://example.com/saml',
|
||||
'SAMLRequest=mockEncodedLogoutRequest',
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(response).toBe(mockResponse);
|
||||
});
|
||||
|
||||
describe('updateRoleMappings', () => {
|
||||
let samlAuthProvider;
|
||||
|
||||
beforeEach(async () => {
|
||||
samlAuthProvider = await createSamlAuthProvider();
|
||||
});
|
||||
|
||||
it('should remove all existing role mappings', async () => {
|
||||
await createRoleMapping({
|
||||
samlAuthProviderId: samlAuthProvider.id,
|
||||
remoteRoleName: 'Admin',
|
||||
});
|
||||
|
||||
await createRoleMapping({
|
||||
samlAuthProviderId: samlAuthProvider.id,
|
||||
remoteRoleName: 'User',
|
||||
});
|
||||
|
||||
await samlAuthProvider.updateRoleMappings([]);
|
||||
|
||||
const roleMappings = await samlAuthProvider.$relatedQuery('roleMappings');
|
||||
expect(roleMappings).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('should return the updated role mappings when new ones are provided', async () => {
|
||||
const adminRole = await createRole({ name: 'Admin' });
|
||||
const userRole = await createRole({ name: 'User' });
|
||||
|
||||
const newRoleMappings = [
|
||||
{ remoteRoleName: 'Admin', roleId: adminRole.id },
|
||||
{ remoteRoleName: 'User', roleId: userRole.id },
|
||||
];
|
||||
|
||||
const result = await samlAuthProvider.updateRoleMappings(newRoleMappings);
|
||||
|
||||
const refetchedRoleMappings = await samlAuthProvider.$relatedQuery(
|
||||
'roleMappings'
|
||||
);
|
||||
|
||||
expect(result).toStrictEqual(refetchedRoleMappings);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import Base from './base.js';
|
||||
import SamlAuthProvider from './saml-auth-provider.ee.js';
|
||||
|
||||
class RoleMapping extends Base {
|
||||
static tableName = 'role_mappings';
|
||||
class SamlAuthProvidersRoleMapping extends Base {
|
||||
static tableName = 'saml_auth_providers_role_mappings';
|
||||
|
||||
static jsonSchema = {
|
||||
type: 'object',
|
||||
@@ -21,11 +21,11 @@ class RoleMapping extends Base {
|
||||
relation: Base.BelongsToOneRelation,
|
||||
modelClass: SamlAuthProvider,
|
||||
join: {
|
||||
from: 'role_mappings.saml_auth_provider_id',
|
||||
from: 'saml_auth_providers_role_mappings.saml_auth_provider_id',
|
||||
to: 'saml_auth_providers.id',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default RoleMapping;
|
||||
export default SamlAuthProvidersRoleMapping;
|
@@ -1,26 +1,28 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import RoleMapping from './role-mapping.ee';
|
||||
import SamlAuthProvidersRoleMapping from '../models/saml-auth-providers-role-mapping.ee';
|
||||
import SamlAuthProvider from './saml-auth-provider.ee';
|
||||
import Base from './base';
|
||||
|
||||
describe('RoleMapping model', () => {
|
||||
describe('SamlAuthProvidersRoleMapping model', () => {
|
||||
it('tableName should return correct name', () => {
|
||||
expect(RoleMapping.tableName).toBe('role_mappings');
|
||||
expect(SamlAuthProvidersRoleMapping.tableName).toBe(
|
||||
'saml_auth_providers_role_mappings'
|
||||
);
|
||||
});
|
||||
|
||||
it('jsonSchema should have the correct schema', () => {
|
||||
expect(RoleMapping.jsonSchema).toMatchSnapshot();
|
||||
expect(SamlAuthProvidersRoleMapping.jsonSchema).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('relationMappings should return correct associations', () => {
|
||||
const relationMappings = RoleMapping.relationMappings();
|
||||
const relationMappings = SamlAuthProvidersRoleMapping.relationMappings();
|
||||
|
||||
const expectedRelations = {
|
||||
samlAuthProvider: {
|
||||
relation: Base.BelongsToOneRelation,
|
||||
modelClass: SamlAuthProvider,
|
||||
join: {
|
||||
from: 'role_mappings.saml_auth_provider_id',
|
||||
from: 'saml_auth_providers_role_mappings.saml_auth_provider_id',
|
||||
to: 'saml_auth_providers.id',
|
||||
},
|
||||
},
|
@@ -212,10 +212,6 @@ class User extends Base {
|
||||
return `${appConfig.webAppUrl}/accept-invitation?token=${this.invitationToken}`;
|
||||
}
|
||||
|
||||
get ability() {
|
||||
return userAbility(this);
|
||||
}
|
||||
|
||||
static async authenticate(email, password) {
|
||||
const user = await User.query().findOne({
|
||||
email: email?.toLowerCase() || null,
|
||||
@@ -587,6 +583,62 @@ class User extends Base {
|
||||
return user;
|
||||
}
|
||||
|
||||
async $beforeInsert(queryContext) {
|
||||
await super.$beforeInsert(queryContext);
|
||||
|
||||
this.email = this.email.toLowerCase();
|
||||
await this.generateHash();
|
||||
|
||||
if (appConfig.isCloud) {
|
||||
this.startTrialPeriod();
|
||||
}
|
||||
}
|
||||
|
||||
async $beforeUpdate(opt, queryContext) {
|
||||
await super.$beforeUpdate(opt, queryContext);
|
||||
|
||||
if (this.email) {
|
||||
this.email = this.email.toLowerCase();
|
||||
}
|
||||
|
||||
await this.generateHash();
|
||||
}
|
||||
|
||||
async $afterInsert(queryContext) {
|
||||
await super.$afterInsert(queryContext);
|
||||
|
||||
if (appConfig.isCloud) {
|
||||
await this.$relatedQuery('usageData').insert({
|
||||
userId: this.id,
|
||||
consumedTaskCount: 0,
|
||||
nextResetAt: DateTime.now().plus({ days: 30 }).toISODate(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async $afterFind() {
|
||||
if (await hasValidLicense()) return this;
|
||||
|
||||
if (Array.isArray(this.permissions)) {
|
||||
this.permissions = this.permissions.filter((permission) => {
|
||||
const restrictedSubjects = [
|
||||
'App',
|
||||
'Role',
|
||||
'SamlAuthProvider',
|
||||
'Config',
|
||||
];
|
||||
|
||||
return !restrictedSubjects.includes(permission.subject);
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
get ability() {
|
||||
return userAbility(this);
|
||||
}
|
||||
|
||||
can(action, subject) {
|
||||
const can = this.ability.can(action, subject);
|
||||
|
||||
@@ -602,68 +654,12 @@ class User extends Base {
|
||||
return conditionMap;
|
||||
}
|
||||
|
||||
lowercaseEmail() {
|
||||
if (this.email) {
|
||||
this.email = this.email.toLowerCase();
|
||||
}
|
||||
}
|
||||
cannot(action, subject) {
|
||||
const cannot = this.ability.cannot(action, subject);
|
||||
|
||||
async createUsageData() {
|
||||
if (appConfig.isCloud) {
|
||||
return await this.$relatedQuery('usageData').insertAndFetch({
|
||||
userId: this.id,
|
||||
consumedTaskCount: 0,
|
||||
nextResetAt: DateTime.now().plus({ days: 30 }).toISODate(),
|
||||
});
|
||||
}
|
||||
}
|
||||
if (cannot) throw new NotAuthorizedError();
|
||||
|
||||
async omitEnterprisePermissionsWithoutValidLicense() {
|
||||
if (await hasValidLicense()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (Array.isArray(this.permissions)) {
|
||||
this.permissions = this.permissions.filter((permission) => {
|
||||
const restrictedSubjects = [
|
||||
'App',
|
||||
'Role',
|
||||
'SamlAuthProvider',
|
||||
'Config',
|
||||
];
|
||||
|
||||
return !restrictedSubjects.includes(permission.subject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async $beforeInsert(queryContext) {
|
||||
await super.$beforeInsert(queryContext);
|
||||
|
||||
this.lowercaseEmail();
|
||||
await this.generateHash();
|
||||
|
||||
if (appConfig.isCloud) {
|
||||
this.startTrialPeriod();
|
||||
}
|
||||
}
|
||||
|
||||
async $beforeUpdate(opt, queryContext) {
|
||||
await super.$beforeUpdate(opt, queryContext);
|
||||
|
||||
this.lowercaseEmail();
|
||||
|
||||
await this.generateHash();
|
||||
}
|
||||
|
||||
async $afterInsert(queryContext) {
|
||||
await super.$afterInsert(queryContext);
|
||||
|
||||
await this.createUsageData();
|
||||
}
|
||||
|
||||
async $afterFind() {
|
||||
await this.omitEnterprisePermissionsWithoutValidLicense();
|
||||
return cannot;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { DateTime, Duration } from 'luxon';
|
||||
import appConfig from '../config/app.js';
|
||||
import * as licenseModule from '../helpers/license.ee.js';
|
||||
import Base from './base.js';
|
||||
import AccessToken from './access-token.js';
|
||||
import Config from './config.js';
|
||||
@@ -21,7 +20,6 @@ import {
|
||||
REMOVE_AFTER_30_DAYS_OR_150_JOBS,
|
||||
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
|
||||
} from '../helpers/remove-job-configuration.js';
|
||||
import * as userAbilityModule from '../helpers/user-ability.js';
|
||||
import { createUser } from '../../test/factories/user.js';
|
||||
import { createConnection } from '../../test/factories/connection.js';
|
||||
import { createRole } from '../../test/factories/role.js';
|
||||
@@ -207,6 +205,64 @@ describe('User model', () => {
|
||||
expect(virtualAttributes).toStrictEqual(expectedAttributes);
|
||||
});
|
||||
|
||||
it('acceptInvitationUrl should return accept invitation page URL with invitation token', async () => {
|
||||
const user = new User();
|
||||
user.invitationToken = 'invitation-token';
|
||||
|
||||
vi.spyOn(appConfig, 'webAppUrl', 'get').mockReturnValue(
|
||||
'https://automatisch.io'
|
||||
);
|
||||
|
||||
expect(user.acceptInvitationUrl).toBe(
|
||||
'https://automatisch.io/accept-invitation?token=invitation-token'
|
||||
);
|
||||
});
|
||||
|
||||
describe('authenticate', () => {
|
||||
it('should create and return the token for correct email and password', async () => {
|
||||
const user = await createUser({
|
||||
email: 'test-user@automatisch.io',
|
||||
password: 'sample-password',
|
||||
});
|
||||
|
||||
const token = await User.authenticate(
|
||||
'test-user@automatisch.io',
|
||||
'sample-password'
|
||||
);
|
||||
|
||||
const persistedToken = await AccessToken.query().findOne({
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
expect(token).toBe(persistedToken.token);
|
||||
});
|
||||
|
||||
it('should return undefined for existing email and incorrect password', async () => {
|
||||
await createUser({
|
||||
email: 'test-user@automatisch.io',
|
||||
password: 'sample-password',
|
||||
});
|
||||
|
||||
const token = await User.authenticate(
|
||||
'test-user@automatisch.io',
|
||||
'wrong-password'
|
||||
);
|
||||
|
||||
expect(token).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should return undefined for non-existing email', async () => {
|
||||
await createUser({
|
||||
email: 'test-user@automatisch.io',
|
||||
password: 'sample-password',
|
||||
});
|
||||
|
||||
const token = await User.authenticate('non-existing-user@automatisch.io');
|
||||
|
||||
expect(token).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('authorizedFlows', () => {
|
||||
it('should return user flows with isCreator condition', async () => {
|
||||
const userRole = await createRole({ name: 'User' });
|
||||
@@ -376,10 +432,7 @@ describe('User model', () => {
|
||||
const anotherUserConnection = await createConnection();
|
||||
|
||||
expect(
|
||||
await userWithRoleAndPermissions.authorizedConnections.orderBy(
|
||||
'created_at',
|
||||
'asc'
|
||||
)
|
||||
await userWithRoleAndPermissions.authorizedConnections
|
||||
).toStrictEqual([userConnection, anotherUserConnection]);
|
||||
});
|
||||
|
||||
@@ -452,76 +505,6 @@ describe('User model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('acceptInvitationUrl should return accept invitation page URL with invitation token', async () => {
|
||||
const user = new User();
|
||||
user.invitationToken = 'invitation-token';
|
||||
|
||||
vi.spyOn(appConfig, 'webAppUrl', 'get').mockReturnValue(
|
||||
'https://automatisch.io'
|
||||
);
|
||||
|
||||
expect(user.acceptInvitationUrl).toBe(
|
||||
'https://automatisch.io/accept-invitation?token=invitation-token'
|
||||
);
|
||||
});
|
||||
|
||||
it('ability should return userAbility for the user', () => {
|
||||
const user = new User();
|
||||
user.fullName = 'Sample user';
|
||||
|
||||
const userAbilitySpy = vi
|
||||
.spyOn(userAbilityModule, 'default')
|
||||
.mockReturnValue('user-ability');
|
||||
|
||||
expect(user.ability).toStrictEqual('user-ability');
|
||||
expect(userAbilitySpy).toHaveBeenNthCalledWith(1, user);
|
||||
});
|
||||
|
||||
describe('authenticate', () => {
|
||||
it('should create and return the token for correct email and password', async () => {
|
||||
const user = await createUser({
|
||||
email: 'test-user@automatisch.io',
|
||||
password: 'sample-password',
|
||||
});
|
||||
|
||||
const token = await User.authenticate(
|
||||
'test-user@automatisch.io',
|
||||
'sample-password'
|
||||
);
|
||||
|
||||
const persistedToken = await AccessToken.query().findOne({
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
expect(token).toBe(persistedToken.token);
|
||||
});
|
||||
|
||||
it('should return undefined for existing email and incorrect password', async () => {
|
||||
await createUser({
|
||||
email: 'test-user@automatisch.io',
|
||||
password: 'sample-password',
|
||||
});
|
||||
|
||||
const token = await User.authenticate(
|
||||
'test-user@automatisch.io',
|
||||
'wrong-password'
|
||||
);
|
||||
|
||||
expect(token).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should return undefined for non-existing email', async () => {
|
||||
await createUser({
|
||||
email: 'test-user@automatisch.io',
|
||||
password: 'sample-password',
|
||||
});
|
||||
|
||||
const token = await User.authenticate('non-existing-user@automatisch.io');
|
||||
|
||||
expect(token).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('login', () => {
|
||||
it('should return true when the given password matches with the user password', async () => {
|
||||
const user = await createUser({ password: 'sample-password' });
|
||||
@@ -999,9 +982,21 @@ describe('User model', () => {
|
||||
|
||||
const user = await createUser();
|
||||
|
||||
const presentDate = DateTime.fromObject(
|
||||
{ year: 2024, month: 11, day: 17, hour: 11, minute: 30 },
|
||||
{ zone: 'UTC+0' }
|
||||
);
|
||||
|
||||
vi.setSystemTime(presentDate);
|
||||
|
||||
await user.startTrialPeriod();
|
||||
|
||||
vi.setSystemTime(DateTime.now().plus({ month: 1 }));
|
||||
const futureDate = DateTime.fromObject(
|
||||
{ year: 2025, month: 1, day: 1 },
|
||||
{ zone: 'UTC+0' }
|
||||
);
|
||||
|
||||
vi.setSystemTime(futureDate);
|
||||
|
||||
const refetchedUser = await user.$query();
|
||||
|
||||
@@ -1109,9 +1104,7 @@ describe('User model', () => {
|
||||
|
||||
const user = await createUser();
|
||||
|
||||
await expect(() => user.getPlanAndUsage()).rejects.toThrow(
|
||||
'NotFoundError'
|
||||
);
|
||||
expect(() => user.getPlanAndUsage()).rejects.toThrow('NotFoundError');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1182,7 +1175,7 @@ describe('User model', () => {
|
||||
});
|
||||
|
||||
it('should throw not found error when user role does not exist', async () => {
|
||||
await expect(() =>
|
||||
expect(() =>
|
||||
User.registerUser({
|
||||
fullName: 'Sample user',
|
||||
email: 'user@automatisch.io',
|
||||
@@ -1191,342 +1184,4 @@ describe('User model', () => {
|
||||
).rejects.toThrowError('NotFoundError');
|
||||
});
|
||||
});
|
||||
|
||||
describe('can', () => {
|
||||
it('should return conditions for the given action and subject of the user', async () => {
|
||||
const userRole = await createRole({ name: 'User' });
|
||||
|
||||
await createPermission({
|
||||
roleId: userRole.id,
|
||||
subject: 'Flow',
|
||||
action: 'read',
|
||||
conditions: ['isCreator'],
|
||||
});
|
||||
|
||||
await createPermission({
|
||||
roleId: userRole.id,
|
||||
subject: 'Connection',
|
||||
action: 'read',
|
||||
conditions: [],
|
||||
});
|
||||
|
||||
const user = await createUser({ roleId: userRole.id });
|
||||
|
||||
const userWithRoleAndPermissions = await user
|
||||
.$query()
|
||||
.withGraphFetched({ role: true, permissions: true });
|
||||
|
||||
expect(userWithRoleAndPermissions.can('read', 'Flow')).toStrictEqual({
|
||||
isCreator: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
userWithRoleAndPermissions.can('read', 'Connection')
|
||||
).toStrictEqual({});
|
||||
});
|
||||
|
||||
it('should return not authorized error when the user is not permitted for the given action and subject', async () => {
|
||||
const userRole = await createRole({ name: 'User' });
|
||||
const user = await createUser({ roleId: userRole.id });
|
||||
|
||||
const userWithRoleAndPermissions = await user
|
||||
.$query()
|
||||
.withGraphFetched({ role: true, permissions: true });
|
||||
|
||||
expect(() => userWithRoleAndPermissions.can('read', 'Flow')).toThrowError(
|
||||
'The user is not authorized!'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('lowercaseEmail should lowercase the user email', () => {
|
||||
const user = new User();
|
||||
user.email = 'USER@AUTOMATISCH.IO';
|
||||
|
||||
user.lowercaseEmail();
|
||||
|
||||
expect(user.email).toBe('user@automatisch.io');
|
||||
});
|
||||
|
||||
describe('createUsageData', () => {
|
||||
it('should create usage data if Automatisch is a cloud installation', async () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
|
||||
|
||||
const user = await createUser({
|
||||
fullName: 'Sample user',
|
||||
email: 'user@automatisch.io',
|
||||
});
|
||||
|
||||
vi.setSystemTime(DateTime.now().plus({ month: 1 }));
|
||||
|
||||
const usageData = await user.createUsageData();
|
||||
const currentUsageData = await user.$relatedQuery('currentUsageData');
|
||||
|
||||
expect(usageData).toStrictEqual(currentUsageData);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should not create usage data if Automatisch is not a cloud installation', async () => {
|
||||
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
|
||||
|
||||
const user = await createUser({
|
||||
fullName: 'Sample user',
|
||||
email: 'user@automatisch.io',
|
||||
});
|
||||
|
||||
const usageData = await user.createUsageData();
|
||||
|
||||
expect(usageData).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('omitEnterprisePermissionsWithoutValidLicense', () => {
|
||||
it('should return user as-is with valid license', async () => {
|
||||
const userRole = await createRole({ name: 'User' });
|
||||
const user = await createUser({
|
||||
fullName: 'Sample user',
|
||||
email: 'user@automatisch.io',
|
||||
roleId: userRole.id,
|
||||
});
|
||||
|
||||
const readFlowPermission = await createPermission({
|
||||
roleId: userRole.id,
|
||||
subject: 'Flow',
|
||||
action: 'read',
|
||||
conditions: [],
|
||||
});
|
||||
|
||||
await createPermission({
|
||||
roleId: userRole.id,
|
||||
subject: 'App',
|
||||
action: 'read',
|
||||
conditions: [],
|
||||
});
|
||||
|
||||
await createPermission({
|
||||
roleId: userRole.id,
|
||||
subject: 'Role',
|
||||
action: 'read',
|
||||
conditions: [],
|
||||
});
|
||||
|
||||
await createPermission({
|
||||
roleId: userRole.id,
|
||||
subject: 'Config',
|
||||
action: 'read',
|
||||
conditions: [],
|
||||
});
|
||||
|
||||
await createPermission({
|
||||
roleId: userRole.id,
|
||||
subject: 'SamlAuthProvider',
|
||||
action: 'read',
|
||||
conditions: [],
|
||||
});
|
||||
|
||||
const userWithRoleAndPermissions = await user
|
||||
.$query()
|
||||
.withGraphFetched({ role: true, permissions: true });
|
||||
|
||||
expect(userWithRoleAndPermissions.permissions).toStrictEqual([
|
||||
readFlowPermission,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should omit enterprise permissions without valid license', async () => {
|
||||
vi.spyOn(licenseModule, 'hasValidLicense').mockResolvedValue(false);
|
||||
|
||||
const userRole = await createRole({ name: 'User' });
|
||||
const user = await createUser({
|
||||
fullName: 'Sample user',
|
||||
email: 'user@automatisch.io',
|
||||
roleId: userRole.id,
|
||||
});
|
||||
|
||||
const readFlowPermission = await createPermission({
|
||||
roleId: userRole.id,
|
||||
subject: 'Flow',
|
||||
action: 'read',
|
||||
conditions: [],
|
||||
});
|
||||
|
||||
await createPermission({
|
||||
roleId: userRole.id,
|
||||
subject: 'App',
|
||||
action: 'read',
|
||||
conditions: [],
|
||||
});
|
||||
|
||||
await createPermission({
|
||||
roleId: userRole.id,
|
||||
subject: 'Role',
|
||||
action: 'read',
|
||||
conditions: [],
|
||||
});
|
||||
|
||||
await createPermission({
|
||||
roleId: userRole.id,
|
||||
subject: 'Config',
|
||||
action: 'read',
|
||||
conditions: [],
|
||||
});
|
||||
|
||||
await createPermission({
|
||||
roleId: userRole.id,
|
||||
subject: 'SamlAuthProvider',
|
||||
action: 'read',
|
||||
conditions: [],
|
||||
});
|
||||
|
||||
const userWithRoleAndPermissions = await user
|
||||
.$query()
|
||||
.withGraphFetched({ role: true, permissions: true });
|
||||
|
||||
expect(userWithRoleAndPermissions.permissions).toStrictEqual([
|
||||
readFlowPermission,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('$beforeInsert', () => {
|
||||
it('should call super.$beforeInsert', async () => {
|
||||
const superBeforeInsertSpy = vi
|
||||
.spyOn(User.prototype, '$beforeInsert')
|
||||
.mockResolvedValue();
|
||||
|
||||
await createUser();
|
||||
|
||||
expect(superBeforeInsertSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should lowercase the user email', async () => {
|
||||
const user = await createUser({
|
||||
fullName: 'Sample user',
|
||||
email: 'USER@AUTOMATISCH.IO',
|
||||
});
|
||||
|
||||
expect(user.email).toBe('user@automatisch.io');
|
||||
});
|
||||
|
||||
it('should generate password hash', async () => {
|
||||
const user = await createUser({
|
||||
fullName: 'Sample user',
|
||||
email: 'user@automatisch.io',
|
||||
password: 'sample-password',
|
||||
});
|
||||
|
||||
expect(user.password).not.toBe('sample-password');
|
||||
expect(await user.login('sample-password')).toBe(true);
|
||||
});
|
||||
|
||||
it('should start trial period if Automatisch is a cloud installation', async () => {
|
||||
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
|
||||
|
||||
const startTrialPeriodSpy = vi.spyOn(User.prototype, 'startTrialPeriod');
|
||||
|
||||
await createUser({
|
||||
fullName: 'Sample user',
|
||||
email: 'user@automatisch.io',
|
||||
});
|
||||
|
||||
expect(startTrialPeriodSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should not start trial period if Automatisch is not a cloud installation', async () => {
|
||||
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
|
||||
|
||||
const startTrialPeriodSpy = vi.spyOn(User.prototype, 'startTrialPeriod');
|
||||
|
||||
await createUser({
|
||||
fullName: 'Sample user',
|
||||
email: 'user@automatisch.io',
|
||||
});
|
||||
|
||||
expect(startTrialPeriodSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('$beforeUpdate', () => {
|
||||
it('should call super.$beforeUpdate', async () => {
|
||||
const superBeforeUpdateSpy = vi
|
||||
.spyOn(User.prototype, '$beforeUpdate')
|
||||
.mockResolvedValue();
|
||||
|
||||
const user = await createUser({
|
||||
fullName: 'Sample user',
|
||||
email: 'user@automatisch.io',
|
||||
});
|
||||
|
||||
await user.$query().patch({ fullName: 'Updated user name' });
|
||||
|
||||
expect(superBeforeUpdateSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should lowercase the user email if given', async () => {
|
||||
const user = await createUser({
|
||||
fullName: 'Sample user',
|
||||
email: 'user@automatisch.io',
|
||||
});
|
||||
|
||||
await user.$query().patchAndFetch({ email: 'NEW_EMAIL@AUTOMATISCH.IO' });
|
||||
|
||||
expect(user.email).toBe('new_email@automatisch.io');
|
||||
});
|
||||
|
||||
it('should generate password hash', async () => {
|
||||
const user = await createUser({
|
||||
fullName: 'Sample user',
|
||||
email: 'user@automatisch.io',
|
||||
password: 'sample-password',
|
||||
});
|
||||
|
||||
await user.$query().patchAndFetch({ password: 'new-password' });
|
||||
|
||||
expect(user.password).not.toBe('new-password');
|
||||
expect(await user.login('new-password')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('$afterInsert', () => {
|
||||
it('should call super.$afterInsert', async () => {
|
||||
const superAfterInsertSpy = vi.spyOn(User.prototype, '$afterInsert');
|
||||
|
||||
await createUser({
|
||||
fullName: 'Sample user',
|
||||
email: 'user@automatisch.io',
|
||||
});
|
||||
|
||||
expect(superAfterInsertSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should call createUsageData', async () => {
|
||||
const createUsageDataSpy = vi.spyOn(User.prototype, 'createUsageData');
|
||||
|
||||
await createUser({
|
||||
fullName: 'Sample user',
|
||||
email: 'user@automatisch.io',
|
||||
});
|
||||
|
||||
expect(createUsageDataSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
it('$afterFind should invoke omitEnterprisePermissionsWithoutValidLicense method', async () => {
|
||||
const omitEnterprisePermissionsWithoutValidLicenseSpy = vi.spyOn(
|
||||
User.prototype,
|
||||
'omitEnterprisePermissionsWithoutValidLicense'
|
||||
);
|
||||
|
||||
await createUser({
|
||||
fullName: 'Sample user',
|
||||
email: 'user@automatisch.io',
|
||||
});
|
||||
|
||||
expect(
|
||||
omitEnterprisePermissionsWithoutValidLicenseSpy
|
||||
).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
@@ -11,6 +11,10 @@ const redisConnection = {
|
||||
|
||||
const actionQueue = new Queue('action', redisConnection);
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
await actionQueue.close();
|
||||
});
|
||||
|
||||
actionQueue.on('error', (error) => {
|
||||
if (error.code === CONNECTION_REFUSED) {
|
||||
logger.error(
|
||||
|
@@ -11,6 +11,10 @@ const redisConnection = {
|
||||
|
||||
const deleteUserQueue = new Queue('delete-user', redisConnection);
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
await deleteUserQueue.close();
|
||||
});
|
||||
|
||||
deleteUserQueue.on('error', (error) => {
|
||||
if (error.code === CONNECTION_REFUSED) {
|
||||
logger.error(
|
||||
|
@@ -11,6 +11,10 @@ const redisConnection = {
|
||||
|
||||
const emailQueue = new Queue('email', redisConnection);
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
await emailQueue.close();
|
||||
});
|
||||
|
||||
emailQueue.on('error', (error) => {
|
||||
if (error.code === CONNECTION_REFUSED) {
|
||||
logger.error(
|
||||
|
@@ -11,6 +11,10 @@ const redisConnection = {
|
||||
|
||||
const flowQueue = new Queue('flow', redisConnection);
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
await flowQueue.close();
|
||||
});
|
||||
|
||||
flowQueue.on('error', (error) => {
|
||||
if (error.code === CONNECTION_REFUSED) {
|
||||
logger.error(
|
||||
|
@@ -1,21 +0,0 @@
|
||||
import appConfig from '../config/app.js';
|
||||
import actionQueue from './action.js';
|
||||
import emailQueue from './email.js';
|
||||
import flowQueue from './flow.js';
|
||||
import triggerQueue from './trigger.js';
|
||||
import deleteUserQueue from './delete-user.ee.js';
|
||||
import removeCancelledSubscriptionsQueue from './remove-cancelled-subscriptions.ee.js';
|
||||
|
||||
const queues = [
|
||||
actionQueue,
|
||||
emailQueue,
|
||||
flowQueue,
|
||||
triggerQueue,
|
||||
deleteUserQueue,
|
||||
];
|
||||
|
||||
if (appConfig.isCloud) {
|
||||
queues.push(removeCancelledSubscriptionsQueue);
|
||||
}
|
||||
|
||||
export default queues;
|
@@ -14,6 +14,10 @@ const removeCancelledSubscriptionsQueue = new Queue(
|
||||
redisConnection
|
||||
);
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
await removeCancelledSubscriptionsQueue.close();
|
||||
});
|
||||
|
||||
removeCancelledSubscriptionsQueue.on('error', (error) => {
|
||||
if (error.code === CONNECTION_REFUSED) {
|
||||
logger.error(
|
||||
|
@@ -11,6 +11,10 @@ const redisConnection = {
|
||||
|
||||
const triggerQueue = new Queue('trigger', redisConnection);
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
await triggerQueue.close();
|
||||
});
|
||||
|
||||
triggerQueue.on('error', (error) => {
|
||||
if (error.code === CONNECTION_REFUSED) {
|
||||
logger.error(
|
||||
|
@@ -1,8 +1,10 @@
|
||||
const appConfigSerializer = (appConfig) => {
|
||||
return {
|
||||
key: appConfig.key,
|
||||
useOnlyPredefinedAuthClients: appConfig.useOnlyPredefinedAuthClients,
|
||||
customConnectionAllowed: appConfig.customConnectionAllowed,
|
||||
shared: appConfig.shared,
|
||||
disabled: appConfig.disabled,
|
||||
connectionAllowed: appConfig.connectionAllowed,
|
||||
createdAt: appConfig.createdAt.getTime(),
|
||||
updatedAt: appConfig.updatedAt.getTime(),
|
||||
};
|
||||
|
@@ -12,8 +12,10 @@ describe('appConfig serializer', () => {
|
||||
it('should return app config data', async () => {
|
||||
const expectedPayload = {
|
||||
key: appConfig.key,
|
||||
useOnlyPredefinedAuthClients: appConfig.useOnlyPredefinedAuthClients,
|
||||
customConnectionAllowed: appConfig.customConnectionAllowed,
|
||||
shared: appConfig.shared,
|
||||
disabled: appConfig.disabled,
|
||||
connectionAllowed: appConfig.connectionAllowed,
|
||||
createdAt: appConfig.createdAt.getTime(),
|
||||
updatedAt: appConfig.updatedAt.getTime(),
|
||||
};
|
||||
|
@@ -2,9 +2,7 @@ const authSerializer = (auth) => {
|
||||
return {
|
||||
fields: auth.fields,
|
||||
authenticationSteps: auth.authenticationSteps,
|
||||
sharedAuthenticationSteps: auth.sharedAuthenticationSteps,
|
||||
reconnectionSteps: auth.reconnectionSteps,
|
||||
sharedReconnectionSteps: auth.sharedReconnectionSteps,
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -10,8 +10,6 @@ describe('authSerializer', () => {
|
||||
fields: auth.fields,
|
||||
authenticationSteps: auth.authenticationSteps,
|
||||
reconnectionSteps: auth.reconnectionSteps,
|
||||
sharedAuthenticationSteps: auth.sharedAuthenticationSteps,
|
||||
sharedReconnectionSteps: auth.sharedReconnectionSteps,
|
||||
};
|
||||
|
||||
expect(authSerializer(auth)).toStrictEqual(expectedPayload);
|
||||
|
@@ -2,6 +2,7 @@ const connectionSerializer = (connection) => {
|
||||
return {
|
||||
id: connection.id,
|
||||
key: connection.key,
|
||||
reconnectable: connection.reconnectable,
|
||||
appAuthClientId: connection.appAuthClientId,
|
||||
formattedData: {
|
||||
screenName: connection.formattedData.screenName,
|
||||
|
@@ -13,6 +13,7 @@ describe('connectionSerializer', () => {
|
||||
const expectedPayload = {
|
||||
id: connection.id,
|
||||
key: connection.key,
|
||||
reconnectable: connection.reconnectable,
|
||||
appAuthClientId: connection.appAuthClientId,
|
||||
formattedData: {
|
||||
screenName: connection.formattedData.screenName,
|
||||
|
@@ -26,7 +26,7 @@ const serializers = {
|
||||
Permission: permissionSerializer,
|
||||
AdminSamlAuthProvider: adminSamlAuthProviderSerializer,
|
||||
SamlAuthProvider: samlAuthProviderSerializer,
|
||||
RoleMapping: samlAuthProviderRoleMappingSerializer,
|
||||
SamlAuthProvidersRoleMapping: samlAuthProviderRoleMappingSerializer,
|
||||
AppAuthClient: appAuthClientSerializer,
|
||||
AppConfig: appConfigSerializer,
|
||||
Flow: flowSerializer,
|
||||
|
@@ -1,22 +1,20 @@
|
||||
import * as Sentry from './helpers/sentry.ee.js';
|
||||
import process from 'node:process';
|
||||
import appConfig from './config/app.js';
|
||||
|
||||
Sentry.init();
|
||||
|
||||
import './config/orm.js';
|
||||
import './helpers/check-worker-readiness.js';
|
||||
import queues from './queues/index.js';
|
||||
import workers from './workers/index.js';
|
||||
import './workers/flow.js';
|
||||
import './workers/trigger.js';
|
||||
import './workers/action.js';
|
||||
import './workers/email.js';
|
||||
import './workers/delete-user.ee.js';
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
for (const queue of queues) {
|
||||
await queue.close();
|
||||
}
|
||||
|
||||
for (const worker of workers) {
|
||||
await worker.close();
|
||||
}
|
||||
});
|
||||
if (appConfig.isCloud) {
|
||||
import('./workers/remove-cancelled-subscriptions.ee.js');
|
||||
import('./queues/remove-cancelled-subscriptions.ee.js');
|
||||
}
|
||||
|
||||
import telemetry from './helpers/telemetry/index.js';
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Worker } from 'bullmq';
|
||||
import process from 'node:process';
|
||||
|
||||
import * as Sentry from '../helpers/sentry.ee.js';
|
||||
import redisConfig from '../config/redis.js';
|
||||
@@ -14,7 +15,7 @@ import delayAsMilliseconds from '../helpers/delay-as-milliseconds.js';
|
||||
|
||||
const DEFAULT_DELAY_DURATION = 0;
|
||||
|
||||
const actionWorker = new Worker(
|
||||
export const worker = new Worker(
|
||||
'action',
|
||||
async (job) => {
|
||||
const { stepId, flowId, executionId, computedParameters, executionStep } =
|
||||
@@ -54,11 +55,11 @@ const actionWorker = new Worker(
|
||||
{ connection: redisConfig }
|
||||
);
|
||||
|
||||
actionWorker.on('completed', (job) => {
|
||||
worker.on('completed', (job) => {
|
||||
logger.info(`JOB ID: ${job.id} - FLOW ID: ${job.data.flowId} has started!`);
|
||||
});
|
||||
|
||||
actionWorker.on('failed', (job, err) => {
|
||||
worker.on('failed', (job, err) => {
|
||||
const errorMessage = `
|
||||
JOB ID: ${job.id} - FLOW ID: ${job.data.flowId} has failed to start with ${err.message}
|
||||
\n ${err.stack}
|
||||
@@ -73,4 +74,6 @@ actionWorker.on('failed', (job, err) => {
|
||||
});
|
||||
});
|
||||
|
||||
export default actionWorker;
|
||||
process.on('SIGTERM', async () => {
|
||||
await worker.close();
|
||||
});
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Worker } from 'bullmq';
|
||||
import process from 'node:process';
|
||||
|
||||
import * as Sentry from '../helpers/sentry.ee.js';
|
||||
import redisConfig from '../config/redis.js';
|
||||
@@ -7,7 +8,7 @@ import appConfig from '../config/app.js';
|
||||
import User from '../models/user.js';
|
||||
import ExecutionStep from '../models/execution-step.js';
|
||||
|
||||
const deleteUserWorker = new Worker(
|
||||
export const worker = new Worker(
|
||||
'delete-user',
|
||||
async (job) => {
|
||||
const { id } = job.data;
|
||||
@@ -45,13 +46,13 @@ const deleteUserWorker = new Worker(
|
||||
{ connection: redisConfig }
|
||||
);
|
||||
|
||||
deleteUserWorker.on('completed', (job) => {
|
||||
worker.on('completed', (job) => {
|
||||
logger.info(
|
||||
`JOB ID: ${job.id} - The user with the ID of '${job.data.id}' has been deleted!`
|
||||
);
|
||||
});
|
||||
|
||||
deleteUserWorker.on('failed', (job, err) => {
|
||||
worker.on('failed', (job, err) => {
|
||||
const errorMessage = `
|
||||
JOB ID: ${job.id} - The user with the ID of '${job.data.id}' has failed to be deleted! ${err.message}
|
||||
\n ${err.stack}
|
||||
@@ -66,4 +67,6 @@ deleteUserWorker.on('failed', (job, err) => {
|
||||
});
|
||||
});
|
||||
|
||||
export default deleteUserWorker;
|
||||
process.on('SIGTERM', async () => {
|
||||
await worker.close();
|
||||
});
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Worker } from 'bullmq';
|
||||
import process from 'node:process';
|
||||
|
||||
import * as Sentry from '../helpers/sentry.ee.js';
|
||||
import redisConfig from '../config/redis.js';
|
||||
@@ -15,7 +16,7 @@ const isAutomatischEmail = (email) => {
|
||||
return email.endsWith('@automatisch.io');
|
||||
};
|
||||
|
||||
const emailWorker = new Worker(
|
||||
export const worker = new Worker(
|
||||
'email',
|
||||
async (job) => {
|
||||
const { email, subject, template, params } = job.data;
|
||||
@@ -38,13 +39,13 @@ const emailWorker = new Worker(
|
||||
{ connection: redisConfig }
|
||||
);
|
||||
|
||||
emailWorker.on('completed', (job) => {
|
||||
worker.on('completed', (job) => {
|
||||
logger.info(
|
||||
`JOB ID: ${job.id} - ${job.data.subject} email sent to ${job.data.email}!`
|
||||
);
|
||||
});
|
||||
|
||||
emailWorker.on('failed', (job, err) => {
|
||||
worker.on('failed', (job, err) => {
|
||||
const errorMessage = `
|
||||
JOB ID: ${job.id} - ${job.data.subject} email to ${job.data.email} has failed to send with ${err.message}
|
||||
\n ${err.stack}
|
||||
@@ -59,4 +60,6 @@ emailWorker.on('failed', (job, err) => {
|
||||
});
|
||||
});
|
||||
|
||||
export default emailWorker;
|
||||
process.on('SIGTERM', async () => {
|
||||
await worker.close();
|
||||
});
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Worker } from 'bullmq';
|
||||
import process from 'node:process';
|
||||
|
||||
import * as Sentry from '../helpers/sentry.ee.js';
|
||||
import redisConfig from '../config/redis.js';
|
||||
@@ -12,7 +13,7 @@ import {
|
||||
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
|
||||
} from '../helpers/remove-job-configuration.js';
|
||||
|
||||
const flowWorker = new Worker(
|
||||
export const worker = new Worker(
|
||||
'flow',
|
||||
async (job) => {
|
||||
const { flowId } = job.data;
|
||||
@@ -63,11 +64,11 @@ const flowWorker = new Worker(
|
||||
{ connection: redisConfig }
|
||||
);
|
||||
|
||||
flowWorker.on('completed', (job) => {
|
||||
worker.on('completed', (job) => {
|
||||
logger.info(`JOB ID: ${job.id} - FLOW ID: ${job.data.flowId} has started!`);
|
||||
});
|
||||
|
||||
flowWorker.on('failed', async (job, err) => {
|
||||
worker.on('failed', async (job, err) => {
|
||||
const errorMessage = `
|
||||
JOB ID: ${job.id} - FLOW ID: ${job.data.flowId} has failed to start with ${err.message}
|
||||
\n ${err.stack}
|
||||
@@ -94,4 +95,6 @@ flowWorker.on('failed', async (job, err) => {
|
||||
});
|
||||
});
|
||||
|
||||
export default flowWorker;
|
||||
process.on('SIGTERM', async () => {
|
||||
await worker.close();
|
||||
});
|
||||
|
@@ -1,21 +0,0 @@
|
||||
import appConfig from '../config/app.js';
|
||||
import actionWorker from './action.js';
|
||||
import emailWorker from './email.js';
|
||||
import flowWorker from './flow.js';
|
||||
import triggerWorker from './trigger.js';
|
||||
import deleteUserWorker from './delete-user.ee.js';
|
||||
import removeCancelledSubscriptionsWorker from './remove-cancelled-subscriptions.ee.js';
|
||||
|
||||
const workers = [
|
||||
actionWorker,
|
||||
emailWorker,
|
||||
flowWorker,
|
||||
triggerWorker,
|
||||
deleteUserWorker,
|
||||
];
|
||||
|
||||
if (appConfig.isCloud) {
|
||||
workers.push(removeCancelledSubscriptionsWorker);
|
||||
}
|
||||
|
||||
export default workers;
|
@@ -1,11 +1,12 @@
|
||||
import { Worker } from 'bullmq';
|
||||
import process from 'node:process';
|
||||
import { DateTime } from 'luxon';
|
||||
import * as Sentry from '../helpers/sentry.ee.js';
|
||||
import redisConfig from '../config/redis.js';
|
||||
import logger from '../helpers/logger.js';
|
||||
import Subscription from '../models/subscription.ee.js';
|
||||
|
||||
const removeCancelledSubscriptionsWorker = new Worker(
|
||||
export const worker = new Worker(
|
||||
'remove-cancelled-subscriptions',
|
||||
async () => {
|
||||
await Subscription.query()
|
||||
@@ -22,13 +23,13 @@ const removeCancelledSubscriptionsWorker = new Worker(
|
||||
{ connection: redisConfig }
|
||||
);
|
||||
|
||||
removeCancelledSubscriptionsWorker.on('completed', (job) => {
|
||||
worker.on('completed', (job) => {
|
||||
logger.info(
|
||||
`JOB ID: ${job.id} - The cancelled subscriptions have been removed!`
|
||||
);
|
||||
});
|
||||
|
||||
removeCancelledSubscriptionsWorker.on('failed', (job, err) => {
|
||||
worker.on('failed', (job, err) => {
|
||||
const errorMessage = `
|
||||
JOB ID: ${job.id} - ERROR: The cancelled subscriptions can not be removed! ${err.message}
|
||||
\n ${err.stack}
|
||||
@@ -41,4 +42,6 @@ removeCancelledSubscriptionsWorker.on('failed', (job, err) => {
|
||||
});
|
||||
});
|
||||
|
||||
export default removeCancelledSubscriptionsWorker;
|
||||
process.on('SIGTERM', async () => {
|
||||
await worker.close();
|
||||
});
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Worker } from 'bullmq';
|
||||
import process from 'node:process';
|
||||
|
||||
import * as Sentry from '../helpers/sentry.ee.js';
|
||||
import redisConfig from '../config/redis.js';
|
||||
@@ -11,7 +12,7 @@ import {
|
||||
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
|
||||
} from '../helpers/remove-job-configuration.js';
|
||||
|
||||
const triggerWorker = new Worker(
|
||||
export const worker = new Worker(
|
||||
'trigger',
|
||||
async (job) => {
|
||||
const { flowId, executionId, stepId, executionStep } = await processTrigger(
|
||||
@@ -40,11 +41,11 @@ const triggerWorker = new Worker(
|
||||
{ connection: redisConfig }
|
||||
);
|
||||
|
||||
triggerWorker.on('completed', (job) => {
|
||||
worker.on('completed', (job) => {
|
||||
logger.info(`JOB ID: ${job.id} - FLOW ID: ${job.data.flowId} has started!`);
|
||||
});
|
||||
|
||||
triggerWorker.on('failed', (job, err) => {
|
||||
worker.on('failed', (job, err) => {
|
||||
const errorMessage = `
|
||||
JOB ID: ${job.id} - FLOW ID: ${job.data.flowId} has failed to start with ${err.message}
|
||||
\n ${err.stack}
|
||||
@@ -59,4 +60,6 @@ triggerWorker.on('failed', (job, err) => {
|
||||
});
|
||||
});
|
||||
|
||||
export default triggerWorker;
|
||||
process.on('SIGTERM', async () => {
|
||||
await worker.close();
|
||||
});
|
||||
|
@@ -1,15 +1,16 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { createRole } from './role.js';
|
||||
import RoleMapping from '../../src/models/role-mapping.ee.js';
|
||||
import { createSamlAuthProvider } from './saml-auth-provider.ee.js';
|
||||
import SamlAuthProviderRoleMapping from '../../src/models/saml-auth-providers-role-mapping.ee.js';
|
||||
|
||||
export const createRoleMapping = async (params = {}) => {
|
||||
params.roleId = params.roleId || (await createRole()).id;
|
||||
params.roleId = params?.roleId || (await createRole()).id;
|
||||
params.samlAuthProviderId =
|
||||
params.samlAuthProviderId || (await createSamlAuthProvider()).id;
|
||||
params.remoteRoleName = params.remoteRoleName || faker.person.jobType();
|
||||
params?.samlAuthProviderId || (await createSamlAuthProvider()).id;
|
||||
|
||||
const roleMapping = await RoleMapping.query().insertAndFetch(params);
|
||||
params.remoteRoleName = params?.remoteRoleName || 'User';
|
||||
|
||||
return roleMapping;
|
||||
const samlAuthProviderRoleMapping =
|
||||
await SamlAuthProviderRoleMapping.query().insertAndFetch(params);
|
||||
|
||||
return samlAuthProviderRoleMapping;
|
||||
};
|
||||
|
@@ -0,0 +1,16 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { createRole } from './role.js';
|
||||
import SamlAuthProvidersRoleMapping from '../../src/models/saml-auth-providers-role-mapping.ee.js';
|
||||
import { createSamlAuthProvider } from './saml-auth-provider.ee.js';
|
||||
|
||||
export const createSamlAuthProvidersRoleMapping = async (params = {}) => {
|
||||
params.roleId = params.roleId || (await createRole()).id;
|
||||
params.samlAuthProviderId =
|
||||
params.samlAuthProviderId || (await createSamlAuthProvider()).id;
|
||||
params.remoteRoleName = params.remoteRoleName || faker.person.jobType();
|
||||
|
||||
const samlAuthProvider =
|
||||
await SamlAuthProvidersRoleMapping.query().insertAndFetch(params);
|
||||
|
||||
return samlAuthProvider;
|
||||
};
|
@@ -2,7 +2,8 @@ const createAppConfigMock = (appConfig) => {
|
||||
return {
|
||||
data: {
|
||||
key: appConfig.key,
|
||||
useOnlyPredefinedAuthClients: appConfig.useOnlyPredefinedAuthClients,
|
||||
customConnectionAllowed: appConfig.customConnectionAllowed,
|
||||
shared: appConfig.shared,
|
||||
disabled: appConfig.disabled,
|
||||
},
|
||||
meta: {
|
||||
|
@@ -15,7 +15,7 @@ const getRoleMappingsMock = async (roleMappings) => {
|
||||
currentPage: null,
|
||||
isArray: true,
|
||||
totalPages: null,
|
||||
type: 'RoleMapping',
|
||||
type: 'SamlAuthProvidersRoleMapping',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@@ -15,7 +15,7 @@ const createRoleMappingsMock = async (roleMappings) => {
|
||||
currentPage: null,
|
||||
isArray: true,
|
||||
totalPages: null,
|
||||
type: 'RoleMapping',
|
||||
type: 'SamlAuthProvidersRoleMapping',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@@ -2,6 +2,7 @@ const createConnection = (connection) => {
|
||||
const connectionData = {
|
||||
id: connection.id,
|
||||
key: connection.key,
|
||||
reconnectable: connection.reconnectable || true,
|
||||
appAuthClientId: connection.appAuthClientId,
|
||||
formattedData: connection.formattedData,
|
||||
verified: connection.verified || false,
|
||||
|
@@ -4,8 +4,6 @@ const getAuthMock = (auth) => {
|
||||
fields: auth.fields,
|
||||
authenticationSteps: auth.authenticationSteps,
|
||||
reconnectionSteps: auth.reconnectionSteps,
|
||||
sharedReconnectionSteps: auth.sharedReconnectionSteps,
|
||||
sharedAuthenticationSteps: auth.sharedAuthenticationSteps,
|
||||
},
|
||||
meta: {
|
||||
count: 1,
|
||||
|
@@ -2,8 +2,10 @@ const getAppConfigMock = (appConfig) => {
|
||||
return {
|
||||
data: {
|
||||
key: appConfig.key,
|
||||
useOnlyPredefinedAuthClients: appConfig.useOnlyPredefinedAuthClients,
|
||||
customConnectionAllowed: appConfig.customConnectionAllowed,
|
||||
shared: appConfig.shared,
|
||||
disabled: appConfig.disabled,
|
||||
connectionAllowed: appConfig.connectionAllowed,
|
||||
createdAt: appConfig.createdAt.getTime(),
|
||||
updatedAt: appConfig.updatedAt.getTime(),
|
||||
},
|
||||
|
@@ -3,6 +3,7 @@ const getConnectionsMock = (connections) => {
|
||||
data: connections.map((connection) => ({
|
||||
id: connection.id,
|
||||
key: connection.key,
|
||||
reconnectable: connection.reconnectable,
|
||||
verified: connection.verified,
|
||||
appAuthClientId: connection.appAuthClientId,
|
||||
formattedData: {
|
||||
|
@@ -3,6 +3,7 @@ const resetConnectionMock = (connection) => {
|
||||
id: connection.id,
|
||||
key: connection.key,
|
||||
verified: connection.verified,
|
||||
reconnectable: connection.reconnectable,
|
||||
appAuthClientId: connection.appAuthClientId,
|
||||
formattedData: {
|
||||
screenName: connection.formattedData.screenName,
|
||||
|
@@ -3,6 +3,7 @@ const updateConnectionMock = (connection) => {
|
||||
id: connection.id,
|
||||
key: connection.key,
|
||||
verified: connection.verified,
|
||||
reconnectable: connection.reconnectable,
|
||||
appAuthClientId: connection.appAuthClientId,
|
||||
formattedData: {
|
||||
screenName: connection.formattedData.screenName,
|
||||
|
@@ -3,6 +3,7 @@ const getConnectionMock = async (connection) => {
|
||||
id: connection.id,
|
||||
key: connection.key,
|
||||
verified: connection.verified,
|
||||
reconnectable: connection.reconnectable,
|
||||
appAuthClientId: connection.appAuthClientId,
|
||||
formattedData: {
|
||||
screenName: connection.formattedData.screenName,
|
||||
|
@@ -2,25 +2,8 @@ import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
root: './',
|
||||
environment: 'node',
|
||||
setupFiles: ['./test/setup/global-hooks.js'],
|
||||
globals: true,
|
||||
reporters: process.env.GITHUB_ACTIONS ? ['dot', 'github-actions'] : ['dot'],
|
||||
coverage: {
|
||||
reportOnFailure: true,
|
||||
provider: 'v8',
|
||||
reportsDirectory: './coverage',
|
||||
reporter: ['text', 'lcov'],
|
||||
all: true,
|
||||
include: ['**/src/models/**', '**/src/controllers/**'],
|
||||
thresholds: {
|
||||
autoUpdate: true,
|
||||
statements: 95.16,
|
||||
branches: 94.66,
|
||||
functions: 97.65,
|
||||
lines: 95.16,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@@ -2,31 +2,6 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@ampproject/remapping@^2.3.0":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4"
|
||||
integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==
|
||||
dependencies:
|
||||
"@jridgewell/gen-mapping" "^0.3.5"
|
||||
"@jridgewell/trace-mapping" "^0.3.24"
|
||||
|
||||
"@babel/helper-string-parser@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c"
|
||||
integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==
|
||||
|
||||
"@babel/helper-validator-identifier@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7"
|
||||
integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
|
||||
|
||||
"@babel/parser@^7.25.4":
|
||||
version "7.26.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.2.tgz#fd7b6f487cfea09889557ef5d4eeb9ff9a5abd11"
|
||||
integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==
|
||||
dependencies:
|
||||
"@babel/types" "^7.26.0"
|
||||
|
||||
"@babel/runtime@^7.15.4":
|
||||
version "7.26.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1"
|
||||
@@ -34,19 +9,6 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/types@^7.25.4", "@babel/types@^7.26.0":
|
||||
version "7.26.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.0.tgz#deabd08d6b753bc8e0f198f8709fb575e31774ff"
|
||||
integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==
|
||||
dependencies:
|
||||
"@babel/helper-string-parser" "^7.25.9"
|
||||
"@babel/helper-validator-identifier" "^7.25.9"
|
||||
|
||||
"@bcoe/v8-coverage@^0.2.3":
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||
|
||||
"@bull-board/api@3.11.1":
|
||||
version "3.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.11.1.tgz#98b2c9556f643718bb5bde4a1306e6706af8192e"
|
||||
@@ -280,43 +242,18 @@
|
||||
wrap-ansi "^8.1.0"
|
||||
wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
|
||||
|
||||
"@istanbuljs/schema@^0.1.2":
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
|
||||
integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
|
||||
|
||||
"@jridgewell/gen-mapping@^0.3.5":
|
||||
version "0.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36"
|
||||
integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==
|
||||
"@jest/schemas@^29.6.3":
|
||||
version "29.6.3"
|
||||
resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03"
|
||||
integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==
|
||||
dependencies:
|
||||
"@jridgewell/set-array" "^1.2.1"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
"@jridgewell/trace-mapping" "^0.3.24"
|
||||
"@sinclair/typebox" "^0.27.8"
|
||||
|
||||
"@jridgewell/resolve-uri@^3.1.0":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
|
||||
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
|
||||
|
||||
"@jridgewell/set-array@^1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280"
|
||||
integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0":
|
||||
"@jridgewell/sourcemap-codec@^1.5.0":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
|
||||
integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
|
||||
|
||||
"@jridgewell/trace-mapping@^0.3.23", "@jridgewell/trace-mapping@^0.3.24":
|
||||
version "0.3.25"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0"
|
||||
integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
|
||||
dependencies:
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@mapbox/node-pre-gyp@^1.0.11":
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa"
|
||||
@@ -637,6 +574,11 @@
|
||||
dependencies:
|
||||
"@sentry/types" "7.120.0"
|
||||
|
||||
"@sinclair/typebox@^0.27.8":
|
||||
version "0.27.8"
|
||||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
|
||||
integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
|
||||
|
||||
"@types/body-parser@*":
|
||||
version "1.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4"
|
||||
@@ -828,82 +770,49 @@
|
||||
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
|
||||
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
|
||||
|
||||
"@vitest/coverage-v8@^2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-2.1.5.tgz#74ef3bf6737f9897a54af22f820d90e85883ff83"
|
||||
integrity sha512-/RoopB7XGW7UEkUndRXF87A9CwkoZAJW01pj8/3pgmDVsjMH2IKy6H1A38po9tmUlwhSyYs0az82rbKd9Yaynw==
|
||||
"@vitest/expect@1.6.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.6.0.tgz#0b3ba0914f738508464983f4d811bc122b51fb30"
|
||||
integrity sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==
|
||||
dependencies:
|
||||
"@ampproject/remapping" "^2.3.0"
|
||||
"@bcoe/v8-coverage" "^0.2.3"
|
||||
debug "^4.3.7"
|
||||
istanbul-lib-coverage "^3.2.2"
|
||||
istanbul-lib-report "^3.0.1"
|
||||
istanbul-lib-source-maps "^5.0.6"
|
||||
istanbul-reports "^3.1.7"
|
||||
magic-string "^0.30.12"
|
||||
magicast "^0.3.5"
|
||||
std-env "^3.8.0"
|
||||
test-exclude "^7.0.1"
|
||||
tinyrainbow "^1.2.0"
|
||||
"@vitest/spy" "1.6.0"
|
||||
"@vitest/utils" "1.6.0"
|
||||
chai "^4.3.10"
|
||||
|
||||
"@vitest/expect@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.5.tgz#5a6afa6314cae7a61847927bb5bc038212ca7381"
|
||||
integrity sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==
|
||||
"@vitest/runner@1.6.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.6.0.tgz#a6de49a96cb33b0e3ba0d9064a3e8d6ce2f08825"
|
||||
integrity sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==
|
||||
dependencies:
|
||||
"@vitest/spy" "2.1.5"
|
||||
"@vitest/utils" "2.1.5"
|
||||
chai "^5.1.2"
|
||||
tinyrainbow "^1.2.0"
|
||||
"@vitest/utils" "1.6.0"
|
||||
p-limit "^5.0.0"
|
||||
pathe "^1.1.1"
|
||||
|
||||
"@vitest/mocker@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.5.tgz#54ee50648bc0bb606dfc58e13edfacb8b9208324"
|
||||
integrity sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==
|
||||
"@vitest/snapshot@1.6.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-1.6.0.tgz#deb7e4498a5299c1198136f56e6e0f692e6af470"
|
||||
integrity sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==
|
||||
dependencies:
|
||||
"@vitest/spy" "2.1.5"
|
||||
magic-string "^0.30.5"
|
||||
pathe "^1.1.1"
|
||||
pretty-format "^29.7.0"
|
||||
|
||||
"@vitest/spy@1.6.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-1.6.0.tgz#362cbd42ccdb03f1613798fde99799649516906d"
|
||||
integrity sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==
|
||||
dependencies:
|
||||
tinyspy "^2.2.0"
|
||||
|
||||
"@vitest/utils@1.6.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.6.0.tgz#5c5675ca7d6f546a7b4337de9ae882e6c57896a1"
|
||||
integrity sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==
|
||||
dependencies:
|
||||
diff-sequences "^29.6.3"
|
||||
estree-walker "^3.0.3"
|
||||
magic-string "^0.30.12"
|
||||
|
||||
"@vitest/pretty-format@2.1.5", "@vitest/pretty-format@^2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.5.tgz#bc79b8826d4a63dc04f2a75d2944694039fa50aa"
|
||||
integrity sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==
|
||||
dependencies:
|
||||
tinyrainbow "^1.2.0"
|
||||
|
||||
"@vitest/runner@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.5.tgz#4d5e2ba2dfc0af74e4b0f9f3f8be020559b26ea9"
|
||||
integrity sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==
|
||||
dependencies:
|
||||
"@vitest/utils" "2.1.5"
|
||||
pathe "^1.1.2"
|
||||
|
||||
"@vitest/snapshot@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.5.tgz#a09a8712547452a84e08b3ec97b270d9cc156b4f"
|
||||
integrity sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==
|
||||
dependencies:
|
||||
"@vitest/pretty-format" "2.1.5"
|
||||
magic-string "^0.30.12"
|
||||
pathe "^1.1.2"
|
||||
|
||||
"@vitest/spy@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.5.tgz#f790d1394a5030644217ce73562e92465e83147e"
|
||||
integrity sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==
|
||||
dependencies:
|
||||
tinyspy "^3.0.2"
|
||||
|
||||
"@vitest/utils@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.5.tgz#0e19ce677c870830a1573d33ee86b0d6109e9546"
|
||||
integrity sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==
|
||||
dependencies:
|
||||
"@vitest/pretty-format" "2.1.5"
|
||||
loupe "^3.1.2"
|
||||
tinyrainbow "^1.2.0"
|
||||
loupe "^2.3.7"
|
||||
pretty-format "^29.7.0"
|
||||
|
||||
"@xmldom/xmldom@^0.8.5", "@xmldom/xmldom@^0.8.6", "@xmldom/xmldom@^0.8.8":
|
||||
version "0.8.10"
|
||||
@@ -938,7 +847,14 @@ acorn-jsx@^5.3.2:
|
||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||
|
||||
acorn@^8.9.0:
|
||||
acorn-walk@^8.3.2:
|
||||
version "8.3.4"
|
||||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7"
|
||||
integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==
|
||||
dependencies:
|
||||
acorn "^8.11.0"
|
||||
|
||||
acorn@^8.11.0, acorn@^8.14.0, acorn@^8.9.0:
|
||||
version "8.14.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0"
|
||||
integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
|
||||
@@ -1009,6 +925,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
|
||||
dependencies:
|
||||
color-convert "^2.0.1"
|
||||
|
||||
ansi-styles@^5.0.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
|
||||
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
|
||||
|
||||
ansi-styles@^6.1.0:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
|
||||
@@ -1055,10 +976,10 @@ asap@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
|
||||
integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==
|
||||
|
||||
assertion-error@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7"
|
||||
integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==
|
||||
assertion-error@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
|
||||
integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
|
||||
|
||||
async@^3.2.3:
|
||||
version "3.2.6"
|
||||
@@ -1290,16 +1211,18 @@ callsites@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
||||
|
||||
chai@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.2.tgz#3afbc340b994ae3610ca519a6c70ace77ad4378d"
|
||||
integrity sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==
|
||||
chai@^4.3.10:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/chai/-/chai-4.5.0.tgz#707e49923afdd9b13a8b0b47d33d732d13812fd8"
|
||||
integrity sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==
|
||||
dependencies:
|
||||
assertion-error "^2.0.1"
|
||||
check-error "^2.1.1"
|
||||
deep-eql "^5.0.1"
|
||||
loupe "^3.1.0"
|
||||
pathval "^2.0.0"
|
||||
assertion-error "^1.1.0"
|
||||
check-error "^1.0.3"
|
||||
deep-eql "^4.1.3"
|
||||
get-func-name "^2.0.2"
|
||||
loupe "^2.3.6"
|
||||
pathval "^1.1.1"
|
||||
type-detect "^4.1.0"
|
||||
|
||||
chalk@^4.0.0, chalk@^4.0.2:
|
||||
version "4.1.2"
|
||||
@@ -1314,10 +1237,12 @@ charenc@0.0.2:
|
||||
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
|
||||
integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==
|
||||
|
||||
check-error@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc"
|
||||
integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==
|
||||
check-error@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694"
|
||||
integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==
|
||||
dependencies:
|
||||
get-func-name "^2.0.2"
|
||||
|
||||
chokidar@^3.5.2:
|
||||
version "3.6.0"
|
||||
@@ -1454,6 +1379,11 @@ concat-stream@^1.5.2:
|
||||
readable-stream "^2.2.2"
|
||||
typedarray "^0.0.6"
|
||||
|
||||
confbox@^0.1.8:
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06"
|
||||
integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==
|
||||
|
||||
console-control-strings@^1.0.0, console-control-strings@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
|
||||
@@ -1511,7 +1441,7 @@ cron-parser@^4.2.1, cron-parser@^4.6.0:
|
||||
dependencies:
|
||||
luxon "^3.2.1"
|
||||
|
||||
cross-spawn@^7.0.0, cross-spawn@^7.0.2:
|
||||
cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||
version "7.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
|
||||
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
|
||||
@@ -1558,7 +1488,7 @@ debug@2.6.9, debug@~2.6.9:
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7:
|
||||
debug@4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
|
||||
version "4.3.7"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
|
||||
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
|
||||
@@ -1586,10 +1516,12 @@ decompress-response@^6.0.0:
|
||||
dependencies:
|
||||
mimic-response "^3.1.0"
|
||||
|
||||
deep-eql@^5.0.1:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341"
|
||||
integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==
|
||||
deep-eql@^4.1.3:
|
||||
version "4.1.4"
|
||||
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.4.tgz#d0d3912865911bb8fac5afb4e3acfa6a28dc72b7"
|
||||
integrity sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==
|
||||
dependencies:
|
||||
type-detect "^4.0.0"
|
||||
|
||||
deep-extend@^0.6.0:
|
||||
version "0.6.0"
|
||||
@@ -1658,6 +1590,11 @@ dezalgo@^1.0.4:
|
||||
asap "^2.0.0"
|
||||
wrappy "1"
|
||||
|
||||
diff-sequences@^29.6.3:
|
||||
version "29.6.3"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
|
||||
integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==
|
||||
|
||||
doctrine@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
|
||||
@@ -1785,11 +1722,6 @@ es-errors@^1.3.0:
|
||||
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
|
||||
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
|
||||
|
||||
es-module-lexer@^1.5.4:
|
||||
version "1.5.4"
|
||||
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78"
|
||||
integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==
|
||||
|
||||
esbuild@^0.21.3:
|
||||
version "0.21.5"
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
|
||||
@@ -1953,16 +1885,26 @@ etag@~1.8.1:
|
||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
|
||||
|
||||
execa@^8.0.1:
|
||||
version "8.0.1"
|
||||
resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c"
|
||||
integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==
|
||||
dependencies:
|
||||
cross-spawn "^7.0.3"
|
||||
get-stream "^8.0.1"
|
||||
human-signals "^5.0.0"
|
||||
is-stream "^3.0.0"
|
||||
merge-stream "^2.0.0"
|
||||
npm-run-path "^5.1.0"
|
||||
onetime "^6.0.0"
|
||||
signal-exit "^4.1.0"
|
||||
strip-final-newline "^3.0.0"
|
||||
|
||||
expand-template@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
|
||||
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
|
||||
|
||||
expect-type@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.1.0.tgz#a146e414250d13dfc49eafcfd1344a4060fa4c75"
|
||||
integrity sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==
|
||||
|
||||
exponential-backoff@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6"
|
||||
@@ -2267,6 +2209,11 @@ gauge@^3.0.0:
|
||||
strip-ansi "^6.0.1"
|
||||
wide-align "^1.1.2"
|
||||
|
||||
get-func-name@^2.0.1, get-func-name@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41"
|
||||
integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==
|
||||
|
||||
get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
|
||||
@@ -2288,6 +2235,11 @@ get-port@^5.1.1:
|
||||
resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
|
||||
integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==
|
||||
|
||||
get-stream@^8.0.1:
|
||||
version "8.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2"
|
||||
integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==
|
||||
|
||||
getopts@2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4"
|
||||
@@ -2312,7 +2264,7 @@ glob-parent@~5.1.2:
|
||||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
glob@^10.2.2, glob@^10.3.10, glob@^10.4.1:
|
||||
glob@^10.2.2, glob@^10.3.10:
|
||||
version "10.4.5"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956"
|
||||
integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==
|
||||
@@ -2432,11 +2384,6 @@ hexoid@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18"
|
||||
integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==
|
||||
|
||||
html-escaper@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
|
||||
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
|
||||
|
||||
http-cache-semantics@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
|
||||
@@ -2498,6 +2445,11 @@ https-proxy-agent@^7.0.1:
|
||||
agent-base "^7.0.2"
|
||||
debug "4"
|
||||
|
||||
human-signals@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28"
|
||||
integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
@@ -2672,6 +2624,11 @@ is-stream@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
|
||||
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
|
||||
|
||||
is-stream@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac"
|
||||
integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==
|
||||
|
||||
isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
@@ -2694,37 +2651,6 @@ isolated-vm@^5.0.1:
|
||||
dependencies:
|
||||
prebuild-install "^7.1.1"
|
||||
|
||||
istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756"
|
||||
integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==
|
||||
|
||||
istanbul-lib-report@^3.0.0, istanbul-lib-report@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d"
|
||||
integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==
|
||||
dependencies:
|
||||
istanbul-lib-coverage "^3.0.0"
|
||||
make-dir "^4.0.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
istanbul-lib-source-maps@^5.0.6:
|
||||
version "5.0.6"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz#acaef948df7747c8eb5fbf1265cb980f6353a441"
|
||||
integrity sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==
|
||||
dependencies:
|
||||
"@jridgewell/trace-mapping" "^0.3.23"
|
||||
debug "^4.1.1"
|
||||
istanbul-lib-coverage "^3.0.0"
|
||||
|
||||
istanbul-reports@^3.1.7:
|
||||
version "3.1.7"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b"
|
||||
integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==
|
||||
dependencies:
|
||||
html-escaper "^2.0.0"
|
||||
istanbul-lib-report "^3.0.0"
|
||||
|
||||
jackspeak@^3.1.2:
|
||||
version "3.4.3"
|
||||
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a"
|
||||
@@ -2749,6 +2675,11 @@ join-component@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/join-component/-/join-component-1.1.0.tgz#b8417b750661a392bee2c2537c68b2a9d4977cd5"
|
||||
integrity sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==
|
||||
|
||||
js-tokens@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-9.0.0.tgz#0f893996d6f3ed46df7f0a3b12a03f5fd84223c1"
|
||||
integrity sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==
|
||||
|
||||
js-yaml@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
|
||||
@@ -2866,6 +2797,14 @@ lie@3.1.1:
|
||||
dependencies:
|
||||
immediate "~3.0.5"
|
||||
|
||||
local-pkg@^0.5.0:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.5.1.tgz#69658638d2a95287534d4c2fff757980100dbb6d"
|
||||
integrity sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==
|
||||
dependencies:
|
||||
mlly "^1.7.3"
|
||||
pkg-types "^1.2.1"
|
||||
|
||||
localforage@^1.8.1:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4"
|
||||
@@ -2957,10 +2896,12 @@ logform@^2.7.0:
|
||||
safe-stable-stringify "^2.3.1"
|
||||
triple-beam "^1.3.0"
|
||||
|
||||
loupe@^3.1.0, loupe@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.2.tgz#c86e0696804a02218f2206124c45d8b15291a240"
|
||||
integrity sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==
|
||||
loupe@^2.3.6, loupe@^2.3.7:
|
||||
version "2.3.7"
|
||||
resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697"
|
||||
integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==
|
||||
dependencies:
|
||||
get-func-name "^2.0.1"
|
||||
|
||||
lru-cache@^10.0.1, lru-cache@^10.2.0:
|
||||
version "10.4.3"
|
||||
@@ -2977,22 +2918,13 @@ luxon@^3.2.1:
|
||||
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.5.0.tgz#6b6f65c5cd1d61d1fd19dbf07ee87a50bf4b8e20"
|
||||
integrity sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==
|
||||
|
||||
magic-string@^0.30.12:
|
||||
magic-string@^0.30.5:
|
||||
version "0.30.13"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.13.tgz#92438e3ff4946cf54f18247c981e5c161c46683c"
|
||||
integrity sha512-8rYBO+MsWkgjDSOvLomYnzhdwEG51olQ4zL5KXnNJWV5MNmrb4rTZdrtkhxjnD/QyZUqR/Z/XDsUs/4ej2nx0g==
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec" "^1.5.0"
|
||||
|
||||
magicast@^0.3.5:
|
||||
version "0.3.5"
|
||||
resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.5.tgz#8301c3c7d66704a0771eb1bad74274f0ec036739"
|
||||
integrity sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.25.4"
|
||||
"@babel/types" "^7.25.4"
|
||||
source-map-js "^1.2.0"
|
||||
|
||||
make-dir@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
|
||||
@@ -3000,13 +2932,6 @@ make-dir@^3.1.0:
|
||||
dependencies:
|
||||
semver "^6.0.0"
|
||||
|
||||
make-dir@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e"
|
||||
integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==
|
||||
dependencies:
|
||||
semver "^7.5.3"
|
||||
|
||||
make-fetch-happen@^13.0.0:
|
||||
version "13.0.1"
|
||||
resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz#273ba2f78f45e1f3a6dca91cede87d9fa4821e36"
|
||||
@@ -3049,6 +2974,11 @@ merge-descriptors@1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
|
||||
|
||||
merge-stream@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
|
||||
|
||||
methods@^1.1.2, methods@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||
@@ -3076,6 +3006,11 @@ mime@2.6.0:
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
|
||||
integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
|
||||
|
||||
mimic-fn@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc"
|
||||
integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==
|
||||
|
||||
mimic-response@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
|
||||
@@ -3188,6 +3123,16 @@ mkdirp@^1.0.3:
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
mlly@^1.7.2, mlly@^1.7.3:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.7.3.tgz#d86c0fcd8ad8e16395eb764a5f4b831590cee48c"
|
||||
integrity sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==
|
||||
dependencies:
|
||||
acorn "^8.14.0"
|
||||
pathe "^1.1.2"
|
||||
pkg-types "^1.2.1"
|
||||
ufo "^1.5.4"
|
||||
|
||||
morgan@^1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7"
|
||||
@@ -3375,6 +3320,13 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
npm-run-path@^5.1.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f"
|
||||
integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==
|
||||
dependencies:
|
||||
path-key "^4.0.0"
|
||||
|
||||
npmlog@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0"
|
||||
@@ -3449,6 +3401,13 @@ one-time@^1.0.0:
|
||||
dependencies:
|
||||
fn.name "1.x.x"
|
||||
|
||||
onetime@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4"
|
||||
integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==
|
||||
dependencies:
|
||||
mimic-fn "^4.0.0"
|
||||
|
||||
optionator@^0.9.3:
|
||||
version "0.9.4"
|
||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
|
||||
@@ -3468,6 +3427,13 @@ p-limit@^3.0.2:
|
||||
dependencies:
|
||||
yocto-queue "^0.1.0"
|
||||
|
||||
p-limit@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-5.0.0.tgz#6946d5b7140b649b7a33a027d89b4c625b3a5985"
|
||||
integrity sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==
|
||||
dependencies:
|
||||
yocto-queue "^1.0.0"
|
||||
|
||||
p-locate@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
|
||||
@@ -3528,6 +3494,11 @@ path-key@^3.1.0:
|
||||
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
|
||||
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
|
||||
|
||||
path-key@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18"
|
||||
integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==
|
||||
|
||||
path-parse@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||
@@ -3546,15 +3517,15 @@ path-to-regexp@0.1.7:
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
|
||||
|
||||
pathe@^1.1.2:
|
||||
pathe@^1.1.1, pathe@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec"
|
||||
integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==
|
||||
|
||||
pathval@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25"
|
||||
integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==
|
||||
pathval@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d"
|
||||
integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==
|
||||
|
||||
pause@0.0.1:
|
||||
version "0.0.1"
|
||||
@@ -3627,7 +3598,7 @@ php-serialize@^4.0.2:
|
||||
resolved "https://registry.yarnpkg.com/php-serialize/-/php-serialize-4.1.1.tgz#1a614fde3da42361af05afffbaf967fb6556591e"
|
||||
integrity sha512-7drCrSZdJ05UdG3hyYEIRW0XyKyUFkxa5A3dpIp3NTjUHpI080pkdBAvqaBtkA+kBkMeXX3XnaSnaLGJRz071A==
|
||||
|
||||
picocolors@^1.1.1:
|
||||
picocolors@^1.0.0, picocolors@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
|
||||
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
|
||||
@@ -3637,6 +3608,15 @@ picomatch@^2.0.4, picomatch@^2.2.1:
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
pkg-types@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.2.1.tgz#6ac4e455a5bb4b9a6185c1c79abd544c901db2e5"
|
||||
integrity sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==
|
||||
dependencies:
|
||||
confbox "^0.1.8"
|
||||
mlly "^1.7.2"
|
||||
pathe "^1.1.2"
|
||||
|
||||
pluralize@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
|
||||
@@ -3708,6 +3688,15 @@ prettier@^2.5.1:
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
|
||||
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
|
||||
|
||||
pretty-format@^29.7.0:
|
||||
version "29.7.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812"
|
||||
integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==
|
||||
dependencies:
|
||||
"@jest/schemas" "^29.6.3"
|
||||
ansi-styles "^5.0.0"
|
||||
react-is "^18.0.0"
|
||||
|
||||
proc-log@^4.1.0, proc-log@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-4.2.0.tgz#b6f461e4026e75fdfe228b265e9f7a00779d7034"
|
||||
@@ -3823,6 +3812,11 @@ rc@^1.2.7:
|
||||
minimist "^1.2.0"
|
||||
strip-json-comments "~2.0.1"
|
||||
|
||||
react-is@^18.0.0:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
|
||||
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
|
||||
|
||||
readable-stream@^2.2.2:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
|
||||
@@ -4003,7 +3997,7 @@ semver@^6.0.0:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||
|
||||
semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4:
|
||||
semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.4:
|
||||
version "7.6.3"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
|
||||
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
|
||||
@@ -4144,7 +4138,7 @@ signal-exit@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
||||
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
|
||||
|
||||
signal-exit@^4.0.1:
|
||||
signal-exit@^4.0.1, signal-exit@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
|
||||
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
|
||||
@@ -4199,7 +4193,7 @@ socks@^2.8.3:
|
||||
ip-address "^9.0.5"
|
||||
smart-buffer "^4.2.0"
|
||||
|
||||
source-map-js@^1.2.0, source-map-js@^1.2.1:
|
||||
source-map-js@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
||||
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
||||
@@ -4251,7 +4245,7 @@ statuses@2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
||||
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
|
||||
|
||||
std-env@^3.8.0:
|
||||
std-env@^3.5.0:
|
||||
version "3.8.0"
|
||||
resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.0.tgz#b56ffc1baf1a29dcc80a3bdf11d7fca7c315e7d5"
|
||||
integrity sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==
|
||||
@@ -4261,16 +4255,7 @@ streamsearch@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
|
||||
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -4302,14 +4287,7 @@ string_decoder@~1.1.1:
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@@ -4323,6 +4301,11 @@ strip-ansi@^7.0.1:
|
||||
dependencies:
|
||||
ansi-regex "^6.0.1"
|
||||
|
||||
strip-final-newline@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd"
|
||||
integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==
|
||||
|
||||
strip-json-comments@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
||||
@@ -4333,6 +4316,13 @@ strip-json-comments@~2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
|
||||
|
||||
strip-literal@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-2.1.0.tgz#6d82ade5e2e74f5c7e8739b6c84692bd65f0bd2a"
|
||||
integrity sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==
|
||||
dependencies:
|
||||
js-tokens "^9.0.0"
|
||||
|
||||
strnum@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db"
|
||||
@@ -4419,15 +4409,6 @@ tarn@^3.0.2:
|
||||
resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693"
|
||||
integrity sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==
|
||||
|
||||
test-exclude@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-7.0.1.tgz#20b3ba4906ac20994e275bbcafd68d510264c2a2"
|
||||
integrity sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==
|
||||
dependencies:
|
||||
"@istanbuljs/schema" "^0.1.2"
|
||||
glob "^10.4.1"
|
||||
minimatch "^9.0.4"
|
||||
|
||||
text-hex@1.0.x:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"
|
||||
@@ -4443,30 +4424,20 @@ tildify@2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a"
|
||||
integrity sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==
|
||||
|
||||
tinybench@^2.9.0:
|
||||
tinybench@^2.5.1:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b"
|
||||
integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==
|
||||
|
||||
tinyexec@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.1.tgz#0ab0daf93b43e2c211212396bdb836b468c97c98"
|
||||
integrity sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==
|
||||
tinypool@^0.8.3:
|
||||
version "0.8.4"
|
||||
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.4.tgz#e217fe1270d941b39e98c625dcecebb1408c9aa8"
|
||||
integrity sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==
|
||||
|
||||
tinypool@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.2.tgz#706193cc532f4c100f66aa00b01c42173d9051b2"
|
||||
integrity sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==
|
||||
|
||||
tinyrainbow@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5"
|
||||
integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==
|
||||
|
||||
tinyspy@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a"
|
||||
integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==
|
||||
tinyspy@^2.2.0:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.2.1.tgz#117b2342f1f38a0dbdcc73a50a454883adf861d1"
|
||||
integrity sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==
|
||||
|
||||
to-regex-range@^5.0.1:
|
||||
version "5.0.1"
|
||||
@@ -4514,6 +4485,11 @@ type-check@^0.4.0, type-check@~0.4.0:
|
||||
dependencies:
|
||||
prelude-ls "^1.2.1"
|
||||
|
||||
type-detect@^4.0.0, type-detect@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c"
|
||||
integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==
|
||||
|
||||
type-fest@^0.20.2:
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
|
||||
@@ -4532,6 +4508,11 @@ typedarray@^0.0.6:
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
|
||||
|
||||
ufo@^1.5.4:
|
||||
version "1.5.4"
|
||||
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.4.tgz#16d6949674ca0c9e0fbbae1fa20a71d7b1ded754"
|
||||
integrity sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.19.3"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f"
|
||||
@@ -4598,15 +4579,15 @@ vary@^1, vary@~1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
|
||||
|
||||
vite-node@2.1.5:
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.5.tgz#cf28c637b2ebe65921f3118a165b7cf00a1cdf19"
|
||||
integrity sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==
|
||||
vite-node@1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.6.0.tgz#2c7e61129bfecc759478fa592754fd9704aaba7f"
|
||||
integrity sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==
|
||||
dependencies:
|
||||
cac "^6.7.14"
|
||||
debug "^4.3.7"
|
||||
es-module-lexer "^1.5.4"
|
||||
pathe "^1.1.2"
|
||||
debug "^4.3.4"
|
||||
pathe "^1.1.1"
|
||||
picocolors "^1.0.0"
|
||||
vite "^5.0.0"
|
||||
|
||||
vite@^5.0.0:
|
||||
@@ -4620,31 +4601,31 @@ vite@^5.0.0:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.3"
|
||||
|
||||
vitest@^2.1.5:
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.5.tgz#a93b7b84a84650130727baae441354e6df118148"
|
||||
integrity sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==
|
||||
vitest@^1.1.3:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/vitest/-/vitest-1.6.0.tgz#9d5ad4752a3c451be919e412c597126cffb9892f"
|
||||
integrity sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==
|
||||
dependencies:
|
||||
"@vitest/expect" "2.1.5"
|
||||
"@vitest/mocker" "2.1.5"
|
||||
"@vitest/pretty-format" "^2.1.5"
|
||||
"@vitest/runner" "2.1.5"
|
||||
"@vitest/snapshot" "2.1.5"
|
||||
"@vitest/spy" "2.1.5"
|
||||
"@vitest/utils" "2.1.5"
|
||||
chai "^5.1.2"
|
||||
debug "^4.3.7"
|
||||
expect-type "^1.1.0"
|
||||
magic-string "^0.30.12"
|
||||
pathe "^1.1.2"
|
||||
std-env "^3.8.0"
|
||||
tinybench "^2.9.0"
|
||||
tinyexec "^0.3.1"
|
||||
tinypool "^1.0.1"
|
||||
tinyrainbow "^1.2.0"
|
||||
"@vitest/expect" "1.6.0"
|
||||
"@vitest/runner" "1.6.0"
|
||||
"@vitest/snapshot" "1.6.0"
|
||||
"@vitest/spy" "1.6.0"
|
||||
"@vitest/utils" "1.6.0"
|
||||
acorn-walk "^8.3.2"
|
||||
chai "^4.3.10"
|
||||
debug "^4.3.4"
|
||||
execa "^8.0.1"
|
||||
local-pkg "^0.5.0"
|
||||
magic-string "^0.30.5"
|
||||
pathe "^1.1.1"
|
||||
picocolors "^1.0.0"
|
||||
std-env "^3.5.0"
|
||||
strip-literal "^2.0.0"
|
||||
tinybench "^2.5.1"
|
||||
tinypool "^0.8.3"
|
||||
vite "^5.0.0"
|
||||
vite-node "2.1.5"
|
||||
why-is-node-running "^2.3.0"
|
||||
vite-node "1.6.0"
|
||||
why-is-node-running "^2.2.2"
|
||||
|
||||
webidl-conversions@^3.0.0:
|
||||
version "3.0.1"
|
||||
@@ -4673,7 +4654,7 @@ which@^4.0.0:
|
||||
dependencies:
|
||||
isexe "^3.1.1"
|
||||
|
||||
why-is-node-running@^2.3.0:
|
||||
why-is-node-running@^2.2.2:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04"
|
||||
integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==
|
||||
@@ -4819,3 +4800,8 @@ yocto-queue@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
|
||||
yocto-queue@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110"
|
||||
integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==
|
||||
|
@@ -3,3 +3,4 @@ POSTGRES_USER=automatisch_user
|
||||
POSTGRES_PASSWORD=automatisch_password
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_HOST=localhost
|
||||
BACKEND_APP_URL=http://localhost:3000
|
@@ -20,44 +20,54 @@ export class AdminApplicationSettingsPage extends AuthenticatedPage {
|
||||
}
|
||||
|
||||
async allowCustomConnections() {
|
||||
await expect(this.disableConnectionsSwitch).not.toBeChecked();
|
||||
await expect(this.allowCustomConnectionsSwitch).not.toBeChecked();
|
||||
await this.allowCustomConnectionsSwitch.check();
|
||||
await expect(this.allowCustomConnectionsSwitch).toBeChecked();
|
||||
await this.saveButton.click();
|
||||
}
|
||||
|
||||
async allowSharedConnections() {
|
||||
await expect(this.disableConnectionsSwitch).not.toBeChecked();
|
||||
await expect(this.allowSharedConnectionsSwitch).not.toBeChecked();
|
||||
await this.allowSharedConnectionsSwitch.check();
|
||||
await expect(this.allowSharedConnectionsSwitch).toBeChecked();
|
||||
await this.saveButton.click();
|
||||
}
|
||||
|
||||
async disallowConnections() {
|
||||
await expect(this.disableConnectionsSwitch).not.toBeChecked();
|
||||
await this.disableConnectionsSwitch.check();
|
||||
await expect(this.disableConnectionsSwitch).toBeChecked();
|
||||
await this.saveButton.click();
|
||||
}
|
||||
|
||||
async disallowCustomConnections() {
|
||||
await expect(this.disableConnectionsSwitch).toBeChecked();
|
||||
await expect(this.allowCustomConnectionsSwitch).toBeChecked();
|
||||
await this.allowCustomConnectionsSwitch.uncheck();
|
||||
await expect(this.allowCustomConnectionsSwitch).not.toBeChecked();
|
||||
await this.saveButton.click();
|
||||
}
|
||||
|
||||
async disallowSharedConnections() {
|
||||
await expect(this.disableConnectionsSwitch).toBeChecked();
|
||||
await expect(this.allowSharedConnectionsSwitch).toBeChecked();
|
||||
await this.allowSharedConnectionsSwitch.uncheck();
|
||||
await expect(this.allowSharedConnectionsSwitch).not.toBeChecked();
|
||||
await this.saveButton.click();
|
||||
}
|
||||
|
||||
async allowConnections() {
|
||||
await expect(this.disableConnectionsSwitch).toBeChecked();
|
||||
await this.disableConnectionsSwitch.uncheck();
|
||||
await expect(this.disableConnectionsSwitch).not.toBeChecked();
|
||||
await this.saveButton.click();
|
||||
}
|
||||
|
||||
async expectSuccessSnackbarToBeVisible() {
|
||||
await expect(this.successSnackbar).toHaveCount(1);
|
||||
await this.successSnackbar.click();
|
||||
await expect(this.successSnackbar).toHaveCount(0);
|
||||
const snackbars = await this.successSnackbar.all();
|
||||
for (const snackbar of snackbars) {
|
||||
await expect(await snackbar.getAttribute('data-snackbar-variant')).toBe(
|
||||
'success'
|
||||
);
|
||||
// await snackbar.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
const { AuthenticatedPage } = require('../authenticated-page');
|
||||
const { RoleConditionsModal } = require('./role-conditions-modal');
|
||||
|
||||
@@ -16,6 +18,7 @@ export class AdminCreateRolePage extends AuthenticatedPage {
|
||||
this.executionRow = page.getByTestId('Execution-permission-row');
|
||||
this.flowRow = page.getByTestId('Flow-permission-row');
|
||||
this.pageTitle = page.getByTestId('create-role-title');
|
||||
this.permissionsCatalog = page.getByTestId('permissions-catalog');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,4 +107,8 @@ export class AdminCreateRolePage extends AuthenticatedPage {
|
||||
throw new Error(`${subject} does not have action ${action}`);
|
||||
}
|
||||
}
|
||||
|
||||
async waitForPermissionsCatalogToVisible() {
|
||||
await expect(this.permissionsCatalog).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
@@ -14,8 +14,12 @@ export class AdminCreateUserPage extends AuthenticatedPage {
|
||||
this.roleInput = page.getByTestId('role.id-autocomplete');
|
||||
this.createButton = page.getByTestId('create-button');
|
||||
this.pageTitle = page.getByTestId('create-user-title');
|
||||
this.invitationEmailInfoAlert = page.getByTestId('invitation-email-info-alert');
|
||||
this.acceptInvitationLink = page.getByTestId('invitation-email-info-alert').getByRole('link');
|
||||
this.invitationEmailInfoAlert = page.getByTestId(
|
||||
'invitation-email-info-alert'
|
||||
);
|
||||
this.acceptInvitationLink = page
|
||||
.getByTestId('invitation-email-info-alert')
|
||||
.getByRole('link');
|
||||
}
|
||||
|
||||
seed(seed) {
|
||||
|
@@ -95,7 +95,6 @@ export class AdminUsersPage extends AuthenticatedPage {
|
||||
});
|
||||
}
|
||||
const rowLocator = await this.getUserRowByEmail(email);
|
||||
console.log('rowLocator.count', email, await rowLocator.count());
|
||||
if ((await rowLocator.count()) === 1) {
|
||||
return rowLocator;
|
||||
}
|
||||
|
@@ -51,10 +51,20 @@ export class BasePage {
|
||||
};
|
||||
}
|
||||
|
||||
async closeSnackbar() {
|
||||
await this.snackbar.click();
|
||||
}
|
||||
|
||||
async closeSnackbarAndWaitUntilDetached() {
|
||||
const snackbar = await this.snackbar;
|
||||
await snackbar.click();
|
||||
await snackbar.waitFor({ state: 'detached' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all snackbars, should be replaced later
|
||||
*/
|
||||
async closeSnackbar() {
|
||||
async closeAllSnackbars() {
|
||||
const snackbars = await this.snackbar.all();
|
||||
for (const snackbar of snackbars) {
|
||||
await snackbar.click();
|
||||
|
16
packages/e2e-tests/helpers/auth-api-helper.js
Normal file
16
packages/e2e-tests/helpers/auth-api-helper.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const { expect } = require('../fixtures/index');
|
||||
|
||||
export const getToken = async (apiRequest) => {
|
||||
const tokenResponse = await apiRequest.post(
|
||||
`${process.env.BACKEND_APP_URL}/api/v1/access-tokens`,
|
||||
{
|
||||
data: {
|
||||
email: process.env.LOGIN_EMAIL,
|
||||
password: process.env.LOGIN_PASSWORD,
|
||||
},
|
||||
}
|
||||
);
|
||||
await expect(tokenResponse.status()).toBe(200);
|
||||
|
||||
return await tokenResponse.json();
|
||||
};
|
69
packages/e2e-tests/helpers/flow-api-helper.js
Normal file
69
packages/e2e-tests/helpers/flow-api-helper.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const { expect } = require('../fixtures/index');
|
||||
|
||||
export const createFlow = async (request, token) => {
|
||||
const response = await request.post(
|
||||
`${process.env.BACKEND_APP_URL}/api/v1/flows`,
|
||||
{ headers: { Authorization: token } }
|
||||
);
|
||||
await expect(response.status()).toBe(201);
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
export const getFlow = async (request, token, flowId) => {
|
||||
const response = await request.get(
|
||||
`${process.env.BACKEND_APP_URL}/api/v1/flows/${flowId}`,
|
||||
{ headers: { Authorization: token } }
|
||||
);
|
||||
await expect(response.status()).toBe(200);
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
export const updateFlowName = async (request, token, flowId) => {
|
||||
const updateFlowNameResponse = await request.patch(
|
||||
`${process.env.BACKEND_APP_URL}/api/v1/flows/${flowId}`,
|
||||
{
|
||||
headers: { Authorization: token },
|
||||
data: { name: flowId },
|
||||
}
|
||||
);
|
||||
await expect(updateFlowNameResponse.status()).toBe(200);
|
||||
};
|
||||
|
||||
export const updateFlowStep = async (request, token, stepId, requestBody) => {
|
||||
const updateTriggerStepResponse = await request.patch(
|
||||
`${process.env.BACKEND_APP_URL}/api/v1/steps/${stepId}`,
|
||||
{
|
||||
headers: { Authorization: token },
|
||||
data: requestBody,
|
||||
}
|
||||
);
|
||||
await expect(updateTriggerStepResponse.status()).toBe(200);
|
||||
return await updateTriggerStepResponse.json();
|
||||
};
|
||||
|
||||
export const testStep = async (request, token, stepId) => {
|
||||
const testTriggerStepResponse = await request.post(
|
||||
`${process.env.BACKEND_APP_URL}/api/v1/steps/${stepId}/test`,
|
||||
{
|
||||
headers: { Authorization: token },
|
||||
}
|
||||
);
|
||||
await expect(testTriggerStepResponse.status()).toBe(200);
|
||||
};
|
||||
|
||||
export const publishFlow = async (request, token, flowId) => {
|
||||
const publishFlowResponse = await request.patch(
|
||||
`${process.env.BACKEND_APP_URL}/api/v1/flows/${flowId}/status`,
|
||||
{
|
||||
headers: { Authorization: token },
|
||||
data: { active: true },
|
||||
}
|
||||
);
|
||||
await expect(publishFlowResponse.status()).toBe(200);
|
||||
return publishFlowResponse.json();
|
||||
};
|
||||
|
||||
export const triggerFlow = async (request, url) => {
|
||||
const triggerFlowResponse = await request.get(url);
|
||||
await expect(triggerFlowResponse.status()).toBe(204);
|
||||
};
|
24
packages/e2e-tests/helpers/user-api-helper.js
Normal file
24
packages/e2e-tests/helpers/user-api-helper.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const { expect } = require('../fixtures/index');
|
||||
|
||||
export const addUser = async (apiRequest, token, request) => {
|
||||
const addUserResponse = await apiRequest.post(
|
||||
`${process.env.BACKEND_APP_URL}/api/v1/admin/users`,
|
||||
{
|
||||
headers: { Authorization: token },
|
||||
data: request,
|
||||
}
|
||||
);
|
||||
await expect(addUserResponse.status()).toBe(201);
|
||||
|
||||
return await addUserResponse.json();
|
||||
};
|
||||
|
||||
export const acceptInvitation = async (apiRequest, request) => {
|
||||
const acceptInvitationResponse = await apiRequest.post(
|
||||
`${process.env.BACKEND_APP_URL}/api/v1/users/invitation`,
|
||||
{
|
||||
data: request,
|
||||
}
|
||||
);
|
||||
await expect(acceptInvitationResponse.status()).toBe(204);
|
||||
};
|
@@ -1,3 +1,5 @@
|
||||
import { knexSnakeCaseMappers } from 'objection';
|
||||
|
||||
const fileExtension = 'js';
|
||||
|
||||
const knexConfig = {
|
||||
@@ -7,7 +9,7 @@ const knexConfig = {
|
||||
user: process.env.POSTGRES_USERNAME,
|
||||
port: process.env.POSTGRES_PORT,
|
||||
password: process.env.POSTGRES_PASSWORD,
|
||||
database: process.env.POSTGRES_DATABASE
|
||||
database: process.env.POSTGRES_DATABASE,
|
||||
},
|
||||
searchPath: ['public'],
|
||||
pool: { min: 0, max: 20 },
|
||||
|
@@ -26,7 +26,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.2.0",
|
||||
"@playwright/test": "^1.45.1"
|
||||
"@playwright/test": "1.49.0",
|
||||
"objection": "^3.1.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
|
@@ -15,9 +15,9 @@ module.exports = defineConfig({
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: 0,
|
||||
retries: process.env.CI ? 1 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
workers: undefined,
|
||||
/* Timeout threshold for each test */
|
||||
timeout: 30000,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
@@ -30,7 +30,7 @@ module.exports = defineConfig({
|
||||
baseURL: process.env.BASE_URL || 'http://localhost:3001',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'retain-on-failure',
|
||||
trace: 'on-first-retry',
|
||||
testIdAttribute: 'data-test',
|
||||
viewport: { width: 1280, height: 720 },
|
||||
},
|
||||
|
@@ -5,16 +5,18 @@ test.describe('Admin Applications', () => {
|
||||
test.beforeAll(async () => {
|
||||
const deleteAppAuthClients = {
|
||||
text: 'DELETE FROM app_auth_clients WHERE app_key in ($1, $2, $3, $4, $5)',
|
||||
values: ['carbone', 'spotify', 'deepl', 'mailchimp', 'reddit']
|
||||
values: ['carbone', 'spotify', 'deepl', 'mailchimp', 'reddit'],
|
||||
};
|
||||
|
||||
const deleteAppConfigs = {
|
||||
text: 'DELETE FROM app_configs WHERE key in ($1, $2, $3, $4, $5)',
|
||||
values: ['carbone', 'spotify', 'deepl', 'mailchimp', 'reddit']
|
||||
values: ['carbone', 'spotify', 'deepl', 'mailchimp', 'reddit'],
|
||||
};
|
||||
|
||||
try {
|
||||
const deleteAppAuthClientsResult = await pgPool.query(deleteAppAuthClients);
|
||||
const deleteAppAuthClientsResult = await pgPool.query(
|
||||
deleteAppAuthClients
|
||||
);
|
||||
expect(deleteAppAuthClientsResult.command).toBe('DELETE');
|
||||
const deleteAppConfigsResult = await pgPool.query(deleteAppConfigs);
|
||||
expect(deleteAppConfigsResult.command).toBe('DELETE');
|
||||
@@ -28,10 +30,11 @@ test.describe('Admin Applications', () => {
|
||||
await adminApplicationsPage.navigateTo();
|
||||
});
|
||||
|
||||
test('Admin should be able to toggle Application settings', async ({
|
||||
// TODO skip until https://github.com/automatisch/automatisch/pull/2244
|
||||
test.skip('Admin should be able to toggle Application settings', async ({
|
||||
adminApplicationsPage,
|
||||
adminApplicationSettingsPage,
|
||||
page
|
||||
page,
|
||||
}) => {
|
||||
await adminApplicationsPage.openApplication('Carbone');
|
||||
await expect(page.url()).toContain('/admin-settings/apps/carbone/settings');
|
||||
@@ -57,7 +60,7 @@ test.describe('Admin Applications', () => {
|
||||
adminApplicationsPage,
|
||||
adminApplicationSettingsPage,
|
||||
flowEditorPage,
|
||||
page
|
||||
page,
|
||||
}) => {
|
||||
await adminApplicationsPage.openApplication('Spotify');
|
||||
await expect(page.url()).toContain('/admin-settings/apps/spotify/settings');
|
||||
@@ -75,11 +78,15 @@ test.describe('Admin Applications', () => {
|
||||
const triggerStep = flowEditorPage.flowStep.last();
|
||||
await triggerStep.click();
|
||||
|
||||
await flowEditorPage.chooseAppAndEvent("Spotify", "Create Playlist");
|
||||
await flowEditorPage.chooseAppAndEvent('Spotify', 'Create Playlist');
|
||||
await flowEditorPage.connectionAutocomplete.click();
|
||||
|
||||
const newConnectionOption = page.getByRole('option').filter({ hasText: 'Add new connection' });
|
||||
const newSharedConnectionOption = page.getByRole('option').filter({ hasText: 'Add new shared connection' });
|
||||
const newConnectionOption = page
|
||||
.getByRole('option')
|
||||
.filter({ hasText: 'Add new connection' });
|
||||
const newSharedConnectionOption = page
|
||||
.getByRole('option')
|
||||
.filter({ hasText: 'Add new shared connection' });
|
||||
|
||||
await expect(newConnectionOption).toBeEnabled();
|
||||
await expect(newConnectionOption).toHaveCount(1);
|
||||
@@ -91,7 +98,7 @@ test.describe('Admin Applications', () => {
|
||||
adminApplicationSettingsPage,
|
||||
adminApplicationAuthClientsPage,
|
||||
flowEditorPage,
|
||||
page
|
||||
page,
|
||||
}) => {
|
||||
await adminApplicationsPage.openApplication('Reddit');
|
||||
await expect(page.url()).toContain('/admin-settings/apps/reddit/settings');
|
||||
@@ -101,13 +108,21 @@ test.describe('Admin Applications', () => {
|
||||
|
||||
await adminApplicationAuthClientsPage.openAuthClientsTab();
|
||||
await adminApplicationAuthClientsPage.openFirstAuthClientCreateForm();
|
||||
const authClientForm = page.getByTestId("auth-client-form");
|
||||
const authClientForm = page.getByTestId('auth-client-form');
|
||||
await authClientForm.locator(page.getByTestId('switch')).check();
|
||||
await authClientForm.locator(page.locator('[name="name"]')).fill('redditAuthClient');
|
||||
await authClientForm.locator(page.locator('[name="clientId"]')).fill('redditClientId');
|
||||
await authClientForm.locator(page.locator('[name="clientSecret"]')).fill('redditClientSecret');
|
||||
await authClientForm
|
||||
.locator(page.locator('[name="name"]'))
|
||||
.fill('redditAuthClient');
|
||||
await authClientForm
|
||||
.locator(page.locator('[name="clientId"]'))
|
||||
.fill('redditClientId');
|
||||
await authClientForm
|
||||
.locator(page.locator('[name="clientSecret"]'))
|
||||
.fill('redditClientSecret');
|
||||
await adminApplicationAuthClientsPage.submitAuthClientForm();
|
||||
await adminApplicationAuthClientsPage.authClientShouldBeVisible('redditAuthClient');
|
||||
await adminApplicationAuthClientsPage.authClientShouldBeVisible(
|
||||
'redditAuthClient'
|
||||
);
|
||||
|
||||
await page.goto('/');
|
||||
await page.getByTestId('create-flow-button').click();
|
||||
@@ -119,11 +134,15 @@ test.describe('Admin Applications', () => {
|
||||
const triggerStep = flowEditorPage.flowStep.last();
|
||||
await triggerStep.click();
|
||||
|
||||
await flowEditorPage.chooseAppAndEvent("Reddit", "Create link post");
|
||||
await flowEditorPage.chooseAppAndEvent('Reddit', 'Create link post');
|
||||
await flowEditorPage.connectionAutocomplete.click();
|
||||
|
||||
const newConnectionOption = page.getByRole('option').filter({ hasText: 'Add new connection' });
|
||||
const newSharedConnectionOption = page.getByRole('option').filter({ hasText: 'Add new shared connection' });
|
||||
const newConnectionOption = page
|
||||
.getByRole('option')
|
||||
.filter({ hasText: 'Add new connection' });
|
||||
const newSharedConnectionOption = page
|
||||
.getByRole('option')
|
||||
.filter({ hasText: 'Add new shared connection' });
|
||||
|
||||
await expect(newConnectionOption).toHaveCount(0);
|
||||
await expect(newSharedConnectionOption).toBeEnabled();
|
||||
@@ -134,7 +153,7 @@ test.describe('Admin Applications', () => {
|
||||
adminApplicationsPage,
|
||||
adminApplicationSettingsPage,
|
||||
flowEditorPage,
|
||||
page
|
||||
page,
|
||||
}) => {
|
||||
await adminApplicationsPage.openApplication('DeepL');
|
||||
await expect(page.url()).toContain('/admin-settings/apps/deepl/settings');
|
||||
@@ -152,12 +171,18 @@ test.describe('Admin Applications', () => {
|
||||
const triggerStep = flowEditorPage.flowStep.last();
|
||||
await triggerStep.click();
|
||||
|
||||
await flowEditorPage.chooseAppAndEvent("DeepL", "Translate text");
|
||||
await flowEditorPage.chooseAppAndEvent('DeepL', 'Translate text');
|
||||
await flowEditorPage.connectionAutocomplete.click();
|
||||
|
||||
const newConnectionOption = page.getByRole('option').filter({ hasText: 'Add new connection' });
|
||||
const newSharedConnectionOption = page.getByRole('option').filter({ hasText: 'Add new shared connection' });
|
||||
const noConnectionsOption = page.locator('.MuiAutocomplete-noOptions').filter({ hasText: 'No options' });
|
||||
const newConnectionOption = page
|
||||
.getByRole('option')
|
||||
.filter({ hasText: 'Add new connection' });
|
||||
const newSharedConnectionOption = page
|
||||
.getByRole('option')
|
||||
.filter({ hasText: 'Add new shared connection' });
|
||||
const noConnectionsOption = page
|
||||
.locator('.MuiAutocomplete-noOptions')
|
||||
.filter({ hasText: 'No options' });
|
||||
|
||||
await expect(noConnectionsOption).toHaveCount(1);
|
||||
await expect(newConnectionOption).toHaveCount(0);
|
||||
@@ -168,11 +193,11 @@ test.describe('Admin Applications', () => {
|
||||
adminApplicationsPage,
|
||||
adminApplicationSettingsPage,
|
||||
flowEditorPage,
|
||||
page
|
||||
page,
|
||||
}) => {
|
||||
const queryUser = {
|
||||
text: 'SELECT * FROM users WHERE email = $1',
|
||||
values: [process.env.LOGIN_EMAIL]
|
||||
values: [process.env.LOGIN_EMAIL],
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -183,14 +208,16 @@ test.describe('Admin Applications', () => {
|
||||
text: 'INSERT INTO connections (key, data, user_id, verified, draft) VALUES ($1, $2, $3, $4, $5)',
|
||||
values: [
|
||||
'mailchimp',
|
||||
"U2FsdGVkX1+cAtdHwLiuRL4DaK/T1aljeeKyPMmtWK0AmAIsKhYwQiuyQCYJO3mdZ31z73hqF2Y+yj2Kn2/IIpLRqCxB2sC0rCDCZyolzOZ290YcBXSzYRzRUxhoOcZEtwYDKsy8AHygKK/tkj9uv9k6wOe1LjipNik4VmRhKjEYizzjLrJpbeU1oY+qW0GBpPYomFTeNf+MejSSmsUYyYJ8+E/4GeEfaonvsTSwMT7AId98Lck6Vy4wrfgpm7sZZ8xU15/HqXZNc8UCo2iTdw45xj/Oov9+brX4WUASFPG8aYrK8dl/EdaOvr89P8uIofbSNZ25GjJvVF5ymarrPkTZ7djjJXchzpwBY+7GTJfs3funR/vIk0Hq95jgOFFP1liZyqTXSa49ojG3hzojRQ==",
|
||||
'U2FsdGVkX1+cAtdHwLiuRL4DaK/T1aljeeKyPMmtWK0AmAIsKhYwQiuyQCYJO3mdZ31z73hqF2Y+yj2Kn2/IIpLRqCxB2sC0rCDCZyolzOZ290YcBXSzYRzRUxhoOcZEtwYDKsy8AHygKK/tkj9uv9k6wOe1LjipNik4VmRhKjEYizzjLrJpbeU1oY+qW0GBpPYomFTeNf+MejSSmsUYyYJ8+E/4GeEfaonvsTSwMT7AId98Lck6Vy4wrfgpm7sZZ8xU15/HqXZNc8UCo2iTdw45xj/Oov9+brX4WUASFPG8aYrK8dl/EdaOvr89P8uIofbSNZ25GjJvVF5ymarrPkTZ7djjJXchzpwBY+7GTJfs3funR/vIk0Hq95jgOFFP1liZyqTXSa49ojG3hzojRQ==',
|
||||
queryUserResult.rows[0].id,
|
||||
'true',
|
||||
'false'
|
||||
'false',
|
||||
],
|
||||
};
|
||||
|
||||
const createMailchimpConnectionResult = await pgPool.query(createMailchimpConnection);
|
||||
const createMailchimpConnectionResult = await pgPool.query(
|
||||
createMailchimpConnection
|
||||
);
|
||||
expect(createMailchimpConnectionResult.rowCount).toBe(1);
|
||||
expect(createMailchimpConnectionResult.command).toBe('INSERT');
|
||||
} catch (err) {
|
||||
@@ -199,7 +226,9 @@ test.describe('Admin Applications', () => {
|
||||
}
|
||||
|
||||
await adminApplicationsPage.openApplication('Mailchimp');
|
||||
await expect(page.url()).toContain('/admin-settings/apps/mailchimp/settings');
|
||||
await expect(page.url()).toContain(
|
||||
'/admin-settings/apps/mailchimp/settings'
|
||||
);
|
||||
|
||||
await adminApplicationSettingsPage.disallowConnections();
|
||||
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
|
||||
@@ -214,14 +243,22 @@ test.describe('Admin Applications', () => {
|
||||
const triggerStep = flowEditorPage.flowStep.last();
|
||||
await triggerStep.click();
|
||||
|
||||
await flowEditorPage.chooseAppAndEvent("Mailchimp", "Create campaign");
|
||||
await flowEditorPage.chooseAppAndEvent('Mailchimp', 'Create campaign');
|
||||
await flowEditorPage.connectionAutocomplete.click();
|
||||
await expect(page.getByRole('option').first()).toHaveText('Unnamed');
|
||||
|
||||
const existingConnection = page.getByRole('option').filter({ hasText: 'Unnamed' });
|
||||
const newConnectionOption = page.getByRole('option').filter({ hasText: 'Add new connection' });
|
||||
const newSharedConnectionOption = page.getByRole('option').filter({ hasText: 'Add new shared connection' });
|
||||
const noConnectionsOption = page.locator('.MuiAutocomplete-noOptions').filter({ hasText: 'No options' });
|
||||
const existingConnection = page
|
||||
.getByRole('option')
|
||||
.filter({ hasText: 'Unnamed' });
|
||||
const newConnectionOption = page
|
||||
.getByRole('option')
|
||||
.filter({ hasText: 'Add new connection' });
|
||||
const newSharedConnectionOption = page
|
||||
.getByRole('option')
|
||||
.filter({ hasText: 'Add new shared connection' });
|
||||
const noConnectionsOption = page
|
||||
.locator('.MuiAutocomplete-noOptions')
|
||||
.filter({ hasText: 'No options' });
|
||||
|
||||
await expect(await existingConnection.count()).toBeGreaterThan(0);
|
||||
await expect(noConnectionsOption).toHaveCount(0);
|
||||
|
@@ -22,22 +22,18 @@ test.describe('Role management page', () => {
|
||||
await adminRolesPage.navigateTo();
|
||||
await adminRolesPage.createRoleButton.click();
|
||||
await adminCreateRolePage.isMounted();
|
||||
await adminCreateRolePage.waitForPermissionsCatalogToVisible();
|
||||
await adminCreateRolePage.nameInput.fill('Create Edit Test');
|
||||
await adminCreateRolePage.descriptionInput.fill('Test description');
|
||||
await adminCreateRolePage.createButton.click();
|
||||
await adminCreateRolePage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
const snackbar = await adminCreateRolePage.getSnackbarData(
|
||||
'snackbar-create-role-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminCreateRolePage.closeSnackbar();
|
||||
});
|
||||
|
||||
let roleRow = await test.step(
|
||||
'Make sure role data is correct',
|
||||
async () => {
|
||||
let roleRow =
|
||||
await test.step('Make sure role data is correct', async () => {
|
||||
const roleRow = await adminRolesPage.getRoleRowByName(
|
||||
'Create Edit Test'
|
||||
);
|
||||
@@ -48,8 +44,7 @@ test.describe('Role management page', () => {
|
||||
await expect(roleData.canEdit).toBe(true);
|
||||
await expect(roleData.canDelete).toBe(true);
|
||||
return roleRow;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
await test.step('Edit the role', async () => {
|
||||
await adminRolesPage.clickEditRole(roleRow);
|
||||
@@ -57,19 +52,14 @@ test.describe('Role management page', () => {
|
||||
await adminEditRolePage.nameInput.fill('Create Update Test');
|
||||
await adminEditRolePage.descriptionInput.fill('Update test description');
|
||||
await adminEditRolePage.updateButton.click();
|
||||
await adminEditRolePage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
const snackbar = await adminEditRolePage.getSnackbarData(
|
||||
'snackbar-edit-role-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminEditRolePage.closeSnackbar();
|
||||
});
|
||||
|
||||
roleRow = await test.step(
|
||||
'Make sure changes reflected on roles page',
|
||||
async () => {
|
||||
roleRow =
|
||||
await test.step('Make sure changes reflected on roles page', async () => {
|
||||
await adminRolesPage.isMounted();
|
||||
const roleRow = await adminRolesPage.getRoleRowByName(
|
||||
'Create Update Test'
|
||||
@@ -81,8 +71,7 @@ test.describe('Role management page', () => {
|
||||
await expect(roleData.canEdit).toBe(true);
|
||||
await expect(roleData.canDelete).toBe(true);
|
||||
return roleRow;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
await test.step('Delete the role', async () => {
|
||||
await adminRolesPage.clickDeleteRole(roleRow);
|
||||
@@ -91,14 +80,10 @@ test.describe('Role management page', () => {
|
||||
state: 'attached',
|
||||
});
|
||||
await deleteModal.deleteButton.click();
|
||||
await adminRolesPage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
const snackbar = await adminRolesPage.getSnackbarData(
|
||||
'snackbar-delete-role-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminRolesPage.closeSnackbar();
|
||||
await deleteModal.modal.waitFor({
|
||||
state: 'detached',
|
||||
});
|
||||
@@ -173,20 +158,15 @@ test.describe('Role management page', () => {
|
||||
await test.step('Create a new role', async () => {
|
||||
await adminRolesPage.createRoleButton.click();
|
||||
await adminCreateRolePage.isMounted();
|
||||
await adminCreateRolePage.waitForPermissionsCatalogToVisible();
|
||||
await adminCreateRolePage.nameInput.fill('Delete Role');
|
||||
await adminCreateRolePage.createButton.click();
|
||||
await adminCreateRolePage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
const snackbar = await adminCreateRolePage.getSnackbarData(
|
||||
'snackbar-create-role-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminCreateRolePage.closeSnackbar();
|
||||
});
|
||||
await test.step(
|
||||
'Create a new user with the "Delete Role" role',
|
||||
async () => {
|
||||
await test.step('Create a new user with the "Delete Role" role', async () => {
|
||||
await adminUsersPage.navigateTo();
|
||||
await adminUsersPage.createUserButton.click();
|
||||
await adminCreateUserPage.fullNameInput.fill('User Role Test');
|
||||
@@ -198,9 +178,6 @@ test.describe('Role management page', () => {
|
||||
.getByRole('option', { name: 'Delete Role', exact: true })
|
||||
.click();
|
||||
await adminCreateUserPage.createButton.click();
|
||||
await adminCreateUserPage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
await adminCreateUserPage.invitationEmailInfoAlert.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
@@ -208,25 +185,18 @@ test.describe('Role management page', () => {
|
||||
'snackbar-create-user-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
}
|
||||
);
|
||||
await test.step(
|
||||
'Try to delete "Delete Role" role when new user has it',
|
||||
async () => {
|
||||
});
|
||||
await test.step('Try to delete "Delete Role" role when new user has it', async () => {
|
||||
await adminRolesPage.navigateTo();
|
||||
const row = await adminRolesPage.getRoleRowByName('Delete Role');
|
||||
const modal = await adminRolesPage.clickDeleteRole(row);
|
||||
await modal.deleteButton.click();
|
||||
await adminRolesPage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
const snackbar = await adminRolesPage.getSnackbarData('snackbar-delete-role-error');
|
||||
await expect(snackbar.variant).toBe('error');
|
||||
await adminRolesPage.closeSnackbar();
|
||||
await modal.close();
|
||||
}
|
||||
const snackbar = await adminRolesPage.getSnackbarData(
|
||||
'snackbar-delete-role-error'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('error');
|
||||
await modal.close();
|
||||
});
|
||||
await test.step('Change the role the user has', async () => {
|
||||
await adminUsersPage.navigateTo();
|
||||
await adminUsersPage.usersLoader.waitFor({
|
||||
@@ -241,14 +211,10 @@ test.describe('Role management page', () => {
|
||||
.getByRole('option', { name: 'Admin' })
|
||||
.click();
|
||||
await adminEditUserPage.updateButton.click();
|
||||
await adminEditUserPage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
const snackbar = await adminEditUserPage.getSnackbarData(
|
||||
'snackbar-edit-user-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminEditUserPage.closeSnackbar();
|
||||
});
|
||||
await test.step('Delete the original role', async () => {
|
||||
await adminRolesPage.navigateTo();
|
||||
@@ -256,14 +222,10 @@ test.describe('Role management page', () => {
|
||||
const modal = await adminRolesPage.clickDeleteRole(row);
|
||||
await expect(modal.modal).toBeVisible();
|
||||
await modal.deleteButton.click();
|
||||
await adminRolesPage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
const snackbar = await adminRolesPage.getSnackbarData(
|
||||
'snackbar-delete-role-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminRolesPage.closeSnackbar();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -277,16 +239,13 @@ test.describe('Role management page', () => {
|
||||
await test.step('Create a new role', async () => {
|
||||
await adminRolesPage.createRoleButton.click();
|
||||
await adminCreateRolePage.isMounted();
|
||||
await adminCreateRolePage.waitForPermissionsCatalogToVisible();
|
||||
await adminCreateRolePage.nameInput.fill('Cannot Delete Role');
|
||||
await adminCreateRolePage.createButton.click();
|
||||
await adminCreateRolePage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
const snackbar = await adminCreateRolePage.getSnackbarData(
|
||||
'snackbar-create-role-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminCreateRolePage.closeSnackbar();
|
||||
});
|
||||
await test.step('Create a new user with this role', async () => {
|
||||
await adminUsersPage.navigateTo();
|
||||
@@ -301,9 +260,6 @@ test.describe('Role management page', () => {
|
||||
.getByRole('option', { name: 'Cannot Delete Role' })
|
||||
.click();
|
||||
await adminCreateUserPage.createButton.click();
|
||||
await adminCreateUserPage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
await adminCreateUserPage.invitationEmailInfoAlert.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
@@ -311,40 +267,34 @@ test.describe('Role management page', () => {
|
||||
'snackbar-create-user-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminCreateUserPage.closeSnackbar();
|
||||
});
|
||||
await test.step('Delete this user', async () => {
|
||||
await adminUsersPage.navigateTo();
|
||||
const row = await adminUsersPage.findUserPageWithEmail(
|
||||
'user-delete-role-test@automatisch.io'
|
||||
);
|
||||
// await test.waitForTimeout(10000);
|
||||
const modal = await adminUsersPage.clickDeleteUser(row);
|
||||
await modal.deleteButton.click();
|
||||
await adminUsersPage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-delete-user-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
});
|
||||
await test.step('Try deleting this role', async () => {
|
||||
await adminRolesPage.navigateTo();
|
||||
const row = await adminRolesPage.getRoleRowByName('Cannot Delete Role');
|
||||
const modal = await adminRolesPage.clickDeleteRole(row);
|
||||
await modal.deleteButton.click();
|
||||
await adminRolesPage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
const snackbar = await adminRolesPage.getSnackbarData(
|
||||
'snackbar-delete-role-error'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('error');
|
||||
/*
|
||||
* TODO: await snackbar - make assertions based on product
|
||||
* decisions
|
||||
const snackbar = await adminRolesPage.getSnackbarData();
|
||||
await expect(snackbar.variant).toBe('...');
|
||||
*/
|
||||
await adminRolesPage.closeSnackbar();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -362,16 +312,13 @@ test('Accessibility of role management page', async ({
|
||||
await adminRolesPage.navigateTo();
|
||||
await adminRolesPage.createRoleButton.click();
|
||||
await adminCreateRolePage.isMounted();
|
||||
await adminCreateRolePage.waitForPermissionsCatalogToVisible();
|
||||
await adminCreateRolePage.nameInput.fill('Basic Test');
|
||||
await adminCreateRolePage.createButton.click();
|
||||
await adminCreateRolePage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
const snackbar = await adminCreateRolePage.getSnackbarData(
|
||||
'snackbar-create-role-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminCreateRolePage.closeSnackbar();
|
||||
});
|
||||
|
||||
await test.step('Create a new user with the basic role', async () => {
|
||||
@@ -385,9 +332,6 @@ test('Accessibility of role management page', async ({
|
||||
.getByRole('option', { name: 'Basic Test' })
|
||||
.click();
|
||||
await adminCreateUserPage.createButton.click();
|
||||
await adminCreateUserPage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
await adminCreateUserPage.invitationEmailInfoAlert.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
@@ -395,36 +339,27 @@ test('Accessibility of role management page', async ({
|
||||
'snackbar-create-user-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminCreateUserPage.closeSnackbar();
|
||||
});
|
||||
|
||||
await test.step('Logout and login to the basic role user', async () => {
|
||||
const acceptInvitationLink = await adminCreateUserPage.acceptInvitationLink;
|
||||
console.log(acceptInvitationLink);
|
||||
const acceptInvitationUrl = await acceptInvitationLink.textContent();
|
||||
console.log(acceptInvitationUrl);
|
||||
const acceptInvitatonToken = acceptInvitationUrl.split('?token=')[1];
|
||||
|
||||
await page.getByTestId('profile-menu-button').click();
|
||||
await page.getByTestId('logout-item').click();
|
||||
|
||||
const acceptInvitationPage = new AcceptInvitation(page);
|
||||
|
||||
await acceptInvitationPage.open(acceptInvitatonToken);
|
||||
|
||||
await acceptInvitationPage.acceptInvitation('sample');
|
||||
|
||||
const loginPage = new LoginPage(page);
|
||||
|
||||
// await loginPage.isMounted();
|
||||
await loginPage.login('basic-role-test@automatisch.io', 'sample');
|
||||
await expect(loginPage.loginButton).not.toBeVisible();
|
||||
await expect(page).toHaveURL('/flows');
|
||||
});
|
||||
|
||||
await test.step(
|
||||
'Navigate to the admin settings page and make sure it is blank',
|
||||
async () => {
|
||||
await test.step('Navigate to the admin settings page and make sure it is blank', async () => {
|
||||
const pageUrl = new URL(page.url());
|
||||
const url = `${pageUrl.origin}/admin-settings/users`;
|
||||
await page.goto(url);
|
||||
@@ -443,8 +378,7 @@ test('Accessibility of role management page', async ({
|
||||
return false;
|
||||
});
|
||||
await expect(isUnmounted).toBe(true);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
await test.step('Log back into the admin account', async () => {
|
||||
await page.goto('/');
|
||||
@@ -465,10 +399,10 @@ test('Accessibility of role management page', async ({
|
||||
await adminEditUserPage.roleInput.click();
|
||||
await adminEditUserPage.page.getByRole('option', { name: 'Admin' }).click();
|
||||
await adminEditUserPage.updateButton.click();
|
||||
await adminEditUserPage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
await adminEditUserPage.closeSnackbar();
|
||||
const snackbar = await adminEditUserPage.getSnackbarData(
|
||||
'snackbar-edit-user-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
});
|
||||
|
||||
await test.step('Delete the role', async () => {
|
||||
@@ -480,14 +414,10 @@ test('Accessibility of role management page', async ({
|
||||
state: 'attached',
|
||||
});
|
||||
await deleteModal.deleteButton.click();
|
||||
await adminRolesPage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
const snackbar = await adminRolesPage.getSnackbarData(
|
||||
'snackbar-delete-role-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminRolesPage.closeSnackbar();
|
||||
await deleteModal.modal.waitFor({
|
||||
state: 'detached',
|
||||
});
|
||||
|
@@ -5,15 +5,16 @@ const { test, expect } = require('../../fixtures/index');
|
||||
* otherwise tests will fail since users are only *soft*-deleted
|
||||
*/
|
||||
test.describe('User management page', () => {
|
||||
|
||||
test.beforeEach(async ({ adminUsersPage }) => {
|
||||
await adminUsersPage.navigateTo();
|
||||
await adminUsersPage.closeSnackbar();
|
||||
await adminUsersPage.closeAllSnackbars();
|
||||
});
|
||||
|
||||
test(
|
||||
'User creation and deletion process',
|
||||
async ({ adminCreateUserPage, adminEditUserPage, adminUsersPage }) => {
|
||||
test('User creation and deletion process', async ({
|
||||
adminCreateUserPage,
|
||||
adminEditUserPage,
|
||||
adminUsersPage,
|
||||
}) => {
|
||||
adminCreateUserPage.seed(9000);
|
||||
const user = adminCreateUserPage.generateUser();
|
||||
await adminUsersPage.usersLoader.waitFor({
|
||||
@@ -21,21 +22,19 @@ test.describe('User management page', () => {
|
||||
because visibility: hidden is used as part of the state transition in
|
||||
notistack, see
|
||||
https://github.com/iamhosseindhv/notistack/blob/122f47057eb7ce5a1abfe923316cf8475303e99a/src/transitions/Collapse/Collapse.tsx#L110
|
||||
*/
|
||||
*/,
|
||||
});
|
||||
await test.step(
|
||||
'Create a user',
|
||||
async () => {
|
||||
await test.step('Create a user', async () => {
|
||||
await adminUsersPage.createUserButton.click();
|
||||
await adminCreateUserPage.fullNameInput.fill(user.fullName);
|
||||
await adminCreateUserPage.emailInput.fill(user.email);
|
||||
await adminCreateUserPage.roleInput.click();
|
||||
await adminCreateUserPage.page.getByRole(
|
||||
'option', { name: 'Admin' }
|
||||
).click();
|
||||
await adminCreateUserPage.page
|
||||
.getByRole('option', { name: 'Admin' })
|
||||
.click();
|
||||
await adminCreateUserPage.createButton.click();
|
||||
await adminCreateUserPage.invitationEmailInfoAlert.waitFor({
|
||||
state: 'attached'
|
||||
state: 'attached',
|
||||
});
|
||||
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
@@ -43,23 +42,16 @@ test.describe('User management page', () => {
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminUsersPage.navigateTo();
|
||||
await adminUsersPage.closeSnackbar();
|
||||
}
|
||||
);
|
||||
await test.step(
|
||||
'Check the user exists with the expected properties',
|
||||
async () => {
|
||||
});
|
||||
await test.step('Check the user exists with the expected properties', async () => {
|
||||
await adminUsersPage.findUserPageWithEmail(user.email);
|
||||
const userRow = await adminUsersPage.getUserRowByEmail(user.email);
|
||||
const data = await adminUsersPage.getRowData(userRow);
|
||||
await expect(data.email).toBe(user.email);
|
||||
await expect(data.fullName).toBe(user.fullName);
|
||||
await expect(data.role).toBe('Admin');
|
||||
}
|
||||
);
|
||||
await test.step(
|
||||
'Edit user info and make sure the edit works correctly',
|
||||
async () => {
|
||||
});
|
||||
await test.step('Edit user info and make sure the edit works correctly', async () => {
|
||||
await adminUsersPage.findUserPageWithEmail(user.email);
|
||||
|
||||
let userRow = await adminUsersPage.getUserRowByEmail(user.email);
|
||||
@@ -73,17 +65,13 @@ test.describe('User management page', () => {
|
||||
'snackbar-edit-user-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
|
||||
await adminUsersPage.findUserPageWithEmail(user.email);
|
||||
userRow = await adminUsersPage.getUserRowByEmail(user.email);
|
||||
const rowData = await adminUsersPage.getRowData(userRow);
|
||||
await expect(rowData.fullName).toBe(newUserInfo.fullName);
|
||||
}
|
||||
);
|
||||
await test.step(
|
||||
'Delete user and check the page confirms this deletion',
|
||||
async () => {
|
||||
});
|
||||
await test.step('Delete user and check the page confirms this deletion', async () => {
|
||||
await adminUsersPage.findUserPageWithEmail(user.email);
|
||||
const userRow = await adminUsersPage.getUserRowByEmail(user.email);
|
||||
await adminUsersPage.clickDeleteUser(userRow);
|
||||
@@ -94,41 +82,34 @@ test.describe('User management page', () => {
|
||||
'snackbar-delete-user-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
await expect(userRow).not.toBeVisible(false);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test(
|
||||
'Creating a user which has been deleted',
|
||||
async ({ adminCreateUserPage, adminUsersPage }) => {
|
||||
test('Creating a user which has been deleted', async ({
|
||||
adminCreateUserPage,
|
||||
adminUsersPage,
|
||||
}) => {
|
||||
adminCreateUserPage.seed(9100);
|
||||
const testUser = adminCreateUserPage.generateUser();
|
||||
|
||||
await test.step(
|
||||
'Create the test user',
|
||||
async () => {
|
||||
await test.step('Create the test user', async () => {
|
||||
await adminUsersPage.navigateTo();
|
||||
await adminUsersPage.createUserButton.click();
|
||||
await adminCreateUserPage.fullNameInput.fill(testUser.fullName);
|
||||
await adminCreateUserPage.emailInput.fill(testUser.email);
|
||||
await adminCreateUserPage.roleInput.click();
|
||||
await adminCreateUserPage.page.getByRole(
|
||||
'option', { name: 'Admin' }
|
||||
).click();
|
||||
await adminCreateUserPage.page
|
||||
.getByRole('option', { name: 'Admin' })
|
||||
.click();
|
||||
await adminCreateUserPage.createButton.click();
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-create-user-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
await test.step(
|
||||
'Delete the created user',
|
||||
async () => {
|
||||
await test.step('Delete the created user', async () => {
|
||||
await adminUsersPage.navigateTo();
|
||||
await adminUsersPage.findUserPageWithEmail(testUser.email);
|
||||
const userRow = await adminUsersPage.getUserRowByEmail(testUser.email);
|
||||
@@ -140,129 +121,107 @@ test.describe('User management page', () => {
|
||||
);
|
||||
await expect(snackbar).not.toBeNull();
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
await expect(userRow).not.toBeVisible(false);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
await test.step(
|
||||
'Create the user again',
|
||||
async () => {
|
||||
await test.step('Create the user again', async () => {
|
||||
await adminUsersPage.createUserButton.click();
|
||||
await adminCreateUserPage.fullNameInput.fill(testUser.fullName);
|
||||
await adminCreateUserPage.emailInput.fill(testUser.email);
|
||||
await adminCreateUserPage.roleInput.click();
|
||||
await adminCreateUserPage.page.getByRole(
|
||||
'option', { name: 'Admin' }
|
||||
).click();
|
||||
await adminCreateUserPage.page
|
||||
.getByRole('option', { name: 'Admin' })
|
||||
.click();
|
||||
await adminCreateUserPage.createButton.click();
|
||||
const snackbar = await adminUsersPage.getSnackbarData('snackbar-error');
|
||||
await expect(snackbar.variant).toBe('error');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test(
|
||||
'Creating a user which already exists',
|
||||
async ({ adminCreateUserPage, adminUsersPage, page }) => {
|
||||
test('Creating a user which already exists', async ({
|
||||
adminCreateUserPage,
|
||||
adminUsersPage,
|
||||
page,
|
||||
}) => {
|
||||
adminCreateUserPage.seed(9200);
|
||||
const testUser = adminCreateUserPage.generateUser();
|
||||
|
||||
await test.step(
|
||||
'Create the test user',
|
||||
async () => {
|
||||
await test.step('Create the test user', async () => {
|
||||
await adminUsersPage.createUserButton.click();
|
||||
await adminCreateUserPage.fullNameInput.fill(testUser.fullName);
|
||||
await adminCreateUserPage.emailInput.fill(testUser.email);
|
||||
await adminCreateUserPage.roleInput.click();
|
||||
await adminCreateUserPage.page.getByRole(
|
||||
'option', { name: 'Admin' }
|
||||
).click();
|
||||
await adminCreateUserPage.page
|
||||
.getByRole('option', { name: 'Admin' })
|
||||
.click();
|
||||
await adminCreateUserPage.createButton.click();
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-create-user-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
await test.step(
|
||||
'Create the user again',
|
||||
async () => {
|
||||
await test.step('Create the user again', async () => {
|
||||
await adminUsersPage.navigateTo();
|
||||
await adminUsersPage.createUserButton.click();
|
||||
await adminCreateUserPage.fullNameInput.fill(testUser.fullName);
|
||||
await adminCreateUserPage.emailInput.fill(testUser.email);
|
||||
const createUserPageUrl = page.url();
|
||||
await adminCreateUserPage.roleInput.click();
|
||||
await adminCreateUserPage.page.getByRole(
|
||||
'option', { name: 'Admin' }
|
||||
).click();
|
||||
await adminCreateUserPage.page
|
||||
.getByRole('option', { name: 'Admin' })
|
||||
.click();
|
||||
await adminCreateUserPage.createButton.click();
|
||||
|
||||
await expect(page.url()).toBe(createUserPageUrl);
|
||||
const snackbar = await adminUsersPage.getSnackbarData('snackbar-error');
|
||||
await expect(snackbar.variant).toBe('error');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test(
|
||||
'Editing a user to have the same email as another user should not be allowed',
|
||||
async ({
|
||||
adminCreateUserPage, adminEditUserPage, adminUsersPage, page
|
||||
test('Editing a user to have the same email as another user should not be allowed', async ({
|
||||
adminCreateUserPage,
|
||||
adminEditUserPage,
|
||||
adminUsersPage,
|
||||
page,
|
||||
}) => {
|
||||
adminCreateUserPage.seed(9300);
|
||||
const user1 = adminCreateUserPage.generateUser();
|
||||
const user2 = adminCreateUserPage.generateUser();
|
||||
await test.step(
|
||||
'Create the first user',
|
||||
async () => {
|
||||
await test.step('Create the first user', async () => {
|
||||
await adminUsersPage.navigateTo();
|
||||
await adminUsersPage.createUserButton.click();
|
||||
await adminCreateUserPage.fullNameInput.fill(user1.fullName);
|
||||
await adminCreateUserPage.emailInput.fill(user1.email);
|
||||
await adminCreateUserPage.roleInput.click();
|
||||
await adminCreateUserPage.page.getByRole(
|
||||
'option', { name: 'Admin' }
|
||||
).click();
|
||||
await adminCreateUserPage.page
|
||||
.getByRole('option', { name: 'Admin' })
|
||||
.click();
|
||||
await adminCreateUserPage.createButton.click();
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-create-user-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
}
|
||||
);
|
||||
await adminUsersPage.closeAllSnackbars();
|
||||
});
|
||||
|
||||
await test.step(
|
||||
'Create the second user',
|
||||
async () => {
|
||||
await test.step('Create the second user', async () => {
|
||||
await adminUsersPage.navigateTo();
|
||||
await adminUsersPage.createUserButton.click();
|
||||
await adminCreateUserPage.fullNameInput.fill(user2.fullName);
|
||||
await adminCreateUserPage.emailInput.fill(user2.email);
|
||||
await adminCreateUserPage.roleInput.click();
|
||||
await adminCreateUserPage.page.getByRole(
|
||||
'option', { name: 'Admin' }
|
||||
).click();
|
||||
await adminCreateUserPage.page
|
||||
.getByRole('option', { name: 'Admin' })
|
||||
.click();
|
||||
await adminCreateUserPage.createButton.click();
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-create-user-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
await test.step(
|
||||
'Try editing the second user to have the email of the first user',
|
||||
async () => {
|
||||
await test.step('Try editing the second user to have the email of the first user', async () => {
|
||||
await adminUsersPage.navigateTo();
|
||||
await adminUsersPage.findUserPageWithEmail(user2.email);
|
||||
let userRow = await adminUsersPage.getUserRowByEmail(user2.email);
|
||||
@@ -272,14 +231,9 @@ test.describe('User management page', () => {
|
||||
const editPageUrl = page.url();
|
||||
await adminEditUserPage.updateButton.click();
|
||||
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-error'
|
||||
);
|
||||
const snackbar = await adminUsersPage.getSnackbarData('snackbar-error');
|
||||
await expect(snackbar.variant).toBe('error');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
await expect(page.url()).toBe(editPageUrl);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,24 +1,55 @@
|
||||
const { request } = require('@playwright/test');
|
||||
const { test, expect } = require('../../fixtures/index');
|
||||
const {AddMattermostConnectionModal} = require('../../fixtures/apps/mattermost/add-mattermost-connection-modal');
|
||||
const {
|
||||
AddMattermostConnectionModal,
|
||||
} = require('../../fixtures/apps/mattermost/add-mattermost-connection-modal');
|
||||
const { createFlow, updateFlowName, getFlow, updateFlowStep, testStep } = require('../../helpers/flow-api-helper');
|
||||
const { getToken } = require('../../helpers/auth-api-helper');
|
||||
|
||||
test.describe('Pop-up message on connections', () => {
|
||||
test.beforeEach(async ({ flowEditorPage, page }) => {
|
||||
await page.getByTestId('create-flow-button').click();
|
||||
await page.waitForURL(
|
||||
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
|
||||
);
|
||||
await expect(page.getByTestId('flow-step')).toHaveCount(2);
|
||||
const apiRequest = await request.newContext();
|
||||
const tokenJsonResponse = await getToken(apiRequest);
|
||||
const token = tokenJsonResponse.data.token;
|
||||
|
||||
await flowEditorPage.flowName.click();
|
||||
await flowEditorPage.flowNameInput.fill('PopupFlow');
|
||||
await flowEditorPage.createWebhookTrigger(true);
|
||||
let flow = await createFlow(apiRequest, token);
|
||||
const flowId = flow.data.id;
|
||||
await updateFlowName(apiRequest, token, flowId);
|
||||
flow = await getFlow(apiRequest, token, flowId);
|
||||
const flowSteps = flow.data.steps;
|
||||
|
||||
await flowEditorPage.chooseAppAndEvent('Mattermost', 'Send a message to channel');
|
||||
await expect(flowEditorPage.continueButton).toHaveCount(1);
|
||||
await expect(flowEditorPage.continueButton).not.toBeEnabled();
|
||||
const triggerStepId = flowSteps.find((step) => step.type === 'trigger').id;
|
||||
const actionStepId = flowSteps.find((step) => step.type === 'action').id;
|
||||
|
||||
const triggerStep = await updateFlowStep(apiRequest, token, triggerStepId, {
|
||||
appKey: 'webhook',
|
||||
key: 'catchRawWebhook',
|
||||
parameters: {
|
||||
workSynchronously: false,
|
||||
},
|
||||
});
|
||||
await apiRequest.get(triggerStep.data.webhookUrl);
|
||||
await testStep(apiRequest, token, triggerStepId);
|
||||
|
||||
await updateFlowStep(apiRequest, token, actionStepId, {
|
||||
appKey: 'mattermost',
|
||||
key: 'sendMessageToChannel',
|
||||
});
|
||||
await testStep(apiRequest, token, actionStepId);
|
||||
|
||||
await page.reload();
|
||||
|
||||
const flowRow = await page.getByTestId('flow-row').filter({
|
||||
hasText: flowId,
|
||||
});
|
||||
await flowRow.click();
|
||||
const flowTriggerStep = await page.getByTestId('flow-step').nth(1);
|
||||
await flowTriggerStep.click();
|
||||
await page.getByText('Choose connection').click();
|
||||
|
||||
await flowEditorPage.connectionAutocomplete.click();
|
||||
await flowEditorPage.addNewConnectionItem.click(); });
|
||||
await flowEditorPage.addNewConnectionItem.click();
|
||||
});
|
||||
|
||||
test('should show error to remind to enable pop-up on connection create', async ({
|
||||
page,
|
||||
@@ -28,7 +59,7 @@ test.describe('Pop-up message on connections', () => {
|
||||
// Inject script to override window.open
|
||||
await page.evaluate(() => {
|
||||
// eslint-disable-next-line no-undef
|
||||
window.open = function() {
|
||||
window.open = function () {
|
||||
console.log('Popup blocked!');
|
||||
return null;
|
||||
};
|
||||
@@ -37,8 +68,10 @@ test.describe('Pop-up message on connections', () => {
|
||||
await addMattermostConnectionModal.fillConnectionForm();
|
||||
await addMattermostConnectionModal.submitConnectionForm();
|
||||
|
||||
await expect(page.getByTestId("add-connection-error")).toHaveCount(1);
|
||||
await expect(page.getByTestId("add-connection-error")).toHaveText('Make sure pop-ups are enabled in your browser.');
|
||||
await expect(page.getByTestId('add-connection-error')).toHaveCount(1);
|
||||
await expect(page.getByTestId('add-connection-error')).toHaveText(
|
||||
'Make sure pop-ups are enabled in your browser.'
|
||||
);
|
||||
});
|
||||
|
||||
test('should not show pop-up error if pop-ups are enabled on connection create', async ({
|
||||
@@ -51,13 +84,15 @@ test.describe('Pop-up message on connections', () => {
|
||||
await addMattermostConnectionModal.submitConnectionForm();
|
||||
|
||||
const popup = await popupPromise;
|
||||
await expect(popup.url()).toContain("mattermost");
|
||||
await expect(page.getByTestId("add-connection-error")).toHaveCount(0);
|
||||
await expect(popup.url()).toContain('mattermost');
|
||||
await expect(page.getByTestId('add-connection-error')).toHaveCount(0);
|
||||
|
||||
await test.step('Should show error on failed credentials verification', async () => {
|
||||
await popup.close();
|
||||
await expect(page.getByTestId("add-connection-error")).toHaveCount(1);
|
||||
await expect(page.getByTestId("add-connection-error")).toHaveText('Error occured while verifying credentials!');
|
||||
await expect(page.getByTestId('add-connection-error')).toHaveCount(1);
|
||||
await expect(page.getByTestId('add-connection-error')).toHaveText(
|
||||
'Error occured while verifying credentials!'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,57 +1,38 @@
|
||||
const { request } = require('@playwright/test');
|
||||
const { publicTest, expect } = require('../../fixtures/index');
|
||||
const { AdminUsersPage } = require('../../fixtures/admin/users-page');
|
||||
const { MyProfilePage } = require('../../fixtures/my-profile-page');
|
||||
const { LoginPage } = require('../../fixtures/login-page');
|
||||
const { addUser, acceptInvitation } = require('../../helpers/user-api-helper');
|
||||
const { getToken } = require('../../helpers/auth-api-helper');
|
||||
|
||||
publicTest.describe('My Profile', () => {
|
||||
let testUser;
|
||||
|
||||
publicTest.beforeEach(
|
||||
async ({ acceptInvitationPage, adminCreateUserPage, loginPage, page }) => {
|
||||
let acceptInvitationLink;
|
||||
async ({ adminCreateUserPage, loginPage, page }) => {
|
||||
let addUserResponse;
|
||||
const apiRequest = await request.newContext();
|
||||
|
||||
adminCreateUserPage.seed(
|
||||
Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)
|
||||
);
|
||||
testUser = adminCreateUserPage.generateUser();
|
||||
|
||||
const adminUsersPage = new AdminUsersPage(page);
|
||||
const myProfilePage = new MyProfilePage(page);
|
||||
|
||||
await publicTest.step('login as Admin', async () => {
|
||||
await loginPage.login();
|
||||
await expect(loginPage.page).toHaveURL('/flows');
|
||||
});
|
||||
|
||||
await publicTest.step('create new user', async () => {
|
||||
await adminUsersPage.navigateTo();
|
||||
await adminUsersPage.createUserButton.click();
|
||||
await adminCreateUserPage.fullNameInput.fill(testUser.fullName);
|
||||
await adminCreateUserPage.emailInput.fill(testUser.email);
|
||||
await adminCreateUserPage.roleInput.click();
|
||||
await adminCreateUserPage.page
|
||||
.getByRole('option', { name: 'Admin' })
|
||||
.click();
|
||||
await adminCreateUserPage.createButton.click();
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-create-user-success'
|
||||
const tokenJsonResponse = await getToken(apiRequest);
|
||||
addUserResponse = await addUser(
|
||||
apiRequest,
|
||||
tokenJsonResponse.data.token,
|
||||
{
|
||||
fullName: testUser.fullName,
|
||||
email: testUser.email,
|
||||
}
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
});
|
||||
|
||||
await publicTest.step('copy invitation link', async () => {
|
||||
const invitationMessage =
|
||||
await adminCreateUserPage.acceptInvitationLink;
|
||||
acceptInvitationLink = await invitationMessage.getAttribute('href');
|
||||
});
|
||||
|
||||
await publicTest.step('logout', async () => {
|
||||
await myProfilePage.logout();
|
||||
});
|
||||
|
||||
await publicTest.step('accept invitation', async () => {
|
||||
await page.goto(acceptInvitationLink);
|
||||
await acceptInvitationPage.acceptInvitation(LoginPage.defaultPassword);
|
||||
let acceptToken = addUserResponse.data.acceptInvitationUrl.split('=')[1];
|
||||
await acceptInvitation(apiRequest, {token:acceptToken, password:LoginPage.defaultPassword});
|
||||
});
|
||||
|
||||
await publicTest.step('login as new Admin', async () => {
|
||||
|
@@ -79,7 +79,7 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@playwright/test@^1.45.1":
|
||||
"@playwright/test@1.49.0":
|
||||
version "1.49.0"
|
||||
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.49.0.tgz#74227385b58317ee076b86b56d0e1e1b25cff01e"
|
||||
integrity sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw==
|
||||
@@ -101,6 +101,13 @@ acorn@^8.9.0:
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0"
|
||||
integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
|
||||
|
||||
ajv-formats@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520"
|
||||
integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==
|
||||
dependencies:
|
||||
ajv "^8.0.0"
|
||||
|
||||
ajv@^6.12.4:
|
||||
version "6.12.6"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||
@@ -111,6 +118,16 @@ ajv@^6.12.4:
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ajv@^8.0.0, ajv@^8.17.1:
|
||||
version "8.17.1"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6"
|
||||
integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.3"
|
||||
fast-uri "^3.0.1"
|
||||
json-schema-traverse "^1.0.0"
|
||||
require-from-string "^2.0.2"
|
||||
|
||||
ansi-regex@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||
@@ -226,6 +243,11 @@ cross-spawn@^7.0.2:
|
||||
shebang-command "^2.0.0"
|
||||
which "^2.0.1"
|
||||
|
||||
db-errors@^0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/db-errors/-/db-errors-0.2.3.tgz#a6a38952e00b20e790f2695a6446b3c65497ffa2"
|
||||
integrity sha512-OOgqgDuCavHXjYSJoV2yGhv6SeG8nk42aoCSoyXLZUH7VwFG27rxbavU1z+VrZbZjphw5UkDQwUlD21MwZpUng==
|
||||
|
||||
debug@4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
@@ -404,6 +426,11 @@ fast-levenshtein@^2.0.6:
|
||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
|
||||
|
||||
fast-uri@^3.0.1:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.3.tgz#892a1c91802d5d7860de728f18608a0573142241"
|
||||
integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==
|
||||
|
||||
fastq@^1.6.0:
|
||||
version "1.17.1"
|
||||
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47"
|
||||
@@ -622,6 +649,11 @@ json-schema-traverse@^0.4.1:
|
||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
||||
|
||||
json-schema-traverse@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
|
||||
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
|
||||
|
||||
json-stable-stringify-without-jsonify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
||||
@@ -727,6 +759,15 @@ natural-compare@^1.4.0:
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
||||
|
||||
objection@^3.1.5:
|
||||
version "3.1.5"
|
||||
resolved "https://registry.yarnpkg.com/objection/-/objection-3.1.5.tgz#53c32f6b6cba2958bc28cf723de96c2676da8286"
|
||||
integrity sha512-Hx/ipAwXSuRBbOMWFKtRsAN0yITafqXtWB4OT4Z9wED7ty1h7bOnBdhLtcNus23GwLJqcMsRWdodL2p5GwlnfQ==
|
||||
dependencies:
|
||||
ajv "^8.17.1"
|
||||
ajv-formats "^2.1.1"
|
||||
db-errors "^0.2.3"
|
||||
|
||||
once@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
@@ -933,6 +974,11 @@ rechoir@^0.8.0:
|
||||
dependencies:
|
||||
resolve "^1.20.0"
|
||||
|
||||
require-from-string@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
|
||||
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
|
||||
|
||||
resolve-from@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
||||
|
@@ -83,7 +83,6 @@
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@simbathesailor/use-what-changed": "^2.0.0",
|
||||
"@tanstack/eslint-plugin-query": "^5.20.1",
|
||||
"@tanstack/react-query-devtools": "^5.24.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
|
@@ -18,7 +18,6 @@ import { generateExternalLink } from 'helpers/translationValues';
|
||||
import { Form } from './style';
|
||||
import useAppAuth from 'hooks/useAppAuth';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useWhatChanged } from '@simbathesailor/use-what-changed';
|
||||
|
||||
function AddAppConnection(props) {
|
||||
const { application, connectionId, onClose } = props;
|
||||
@@ -65,7 +64,7 @@ function AddAppConnection(props) {
|
||||
|
||||
asyncAuthenticate();
|
||||
},
|
||||
[appAuthClientId, authenticate, key, navigate],
|
||||
[appAuthClientId, authenticate],
|
||||
);
|
||||
|
||||
const handleClientClick = (appAuthClientId) =>
|
||||
|
@@ -34,10 +34,10 @@ function AdminApplicationCreateAuthClient(props) {
|
||||
|
||||
if (!appConfigKey) {
|
||||
const { data: appConfigData } = await createAppConfig({
|
||||
useOnlyPredefinedAuthClients: false,
|
||||
customConnectionAllowed: true,
|
||||
shared: false,
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
appConfigKey = appConfigData.key;
|
||||
}
|
||||
|
||||
|
@@ -46,8 +46,9 @@ function AdminApplicationSettings(props) {
|
||||
|
||||
const defaultValues = useMemo(
|
||||
() => ({
|
||||
useOnlyPredefinedAuthClients:
|
||||
appConfig?.data?.useOnlyPredefinedAuthClients || false,
|
||||
customConnectionAllowed:
|
||||
appConfig?.data?.customConnectionAllowed || false,
|
||||
shared: appConfig?.data?.shared || false,
|
||||
disabled: appConfig?.data?.disabled || false,
|
||||
}),
|
||||
[appConfig?.data],
|
||||
@@ -61,17 +62,21 @@ function AdminApplicationSettings(props) {
|
||||
<Paper sx={{ p: 2, mt: 4 }}>
|
||||
<Stack spacing={2} direction="column">
|
||||
<Switch
|
||||
name="useOnlyPredefinedAuthClients"
|
||||
label={formatMessage(
|
||||
'adminAppsSettings.useOnlyPredefinedAuthClients',
|
||||
)}
|
||||
name="customConnectionAllowed"
|
||||
label={formatMessage('adminAppsSettings.customConnectionAllowed')}
|
||||
FormControlLabelProps={{
|
||||
labelPlacement: 'start',
|
||||
}}
|
||||
/>
|
||||
<Divider />
|
||||
<Switch
|
||||
name="shared"
|
||||
label={formatMessage('adminAppsSettings.shared')}
|
||||
FormControlLabelProps={{
|
||||
labelPlacement: 'start',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Switch
|
||||
name="disabled"
|
||||
label={formatMessage('adminAppsSettings.disabled')}
|
||||
@@ -81,7 +86,6 @@ function AdminApplicationSettings(props) {
|
||||
/>
|
||||
<Divider />
|
||||
</Stack>
|
||||
|
||||
<Stack>
|
||||
<LoadingButton
|
||||
data-test="submit-button"
|
||||
|
@@ -15,7 +15,17 @@ function AppAuthClientsDialog(props) {
|
||||
|
||||
const formatMessage = useFormatMessage();
|
||||
|
||||
if (!appAuthClients?.data.length) return <React.Fragment />;
|
||||
React.useEffect(
|
||||
function autoAuthenticateSingleClient() {
|
||||
if (appAuthClients?.data.length === 1) {
|
||||
onClientClick(appAuthClients.data[0].id);
|
||||
}
|
||||
},
|
||||
[appAuthClients?.data],
|
||||
);
|
||||
|
||||
if (!appAuthClients?.data.length || appAuthClients?.data.length === 1)
|
||||
return <React.Fragment />;
|
||||
|
||||
return (
|
||||
<Dialog onClose={onClose} open={true}>
|
||||
|
@@ -11,7 +11,14 @@ import { useQueryClient } from '@tanstack/react-query';
|
||||
import Can from 'components/Can';
|
||||
|
||||
function ContextMenu(props) {
|
||||
const { appKey, connection, onClose, onMenuItemClick, anchorEl } = props;
|
||||
const {
|
||||
appKey,
|
||||
connection,
|
||||
onClose,
|
||||
onMenuItemClick,
|
||||
anchorEl,
|
||||
disableReconnection,
|
||||
} = props;
|
||||
const formatMessage = useFormatMessage();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@@ -66,7 +73,7 @@ function ContextMenu(props) {
|
||||
{(allowed) => (
|
||||
<MenuItem
|
||||
component={Link}
|
||||
disabled={!allowed}
|
||||
disabled={!allowed || disableReconnection}
|
||||
to={URLS.APP_RECONNECT_CONNECTION(
|
||||
appKey,
|
||||
connection.id,
|
||||
@@ -102,6 +109,7 @@ ContextMenu.propTypes = {
|
||||
PropTypes.func,
|
||||
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
|
||||
]),
|
||||
disableReconnection: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default ContextMenu;
|
||||
|
@@ -30,7 +30,8 @@ const countTranslation = (value) => (
|
||||
function AppConnectionRow(props) {
|
||||
const formatMessage = useFormatMessage();
|
||||
const enqueueSnackbar = useEnqueueSnackbar();
|
||||
const { id, key, formattedData, verified, createdAt } = props.connection;
|
||||
const { id, key, formattedData, verified, createdAt, reconnectable } =
|
||||
props.connection;
|
||||
const [verificationVisible, setVerificationVisible] = React.useState(false);
|
||||
const contextButtonRef = React.useRef(null);
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
@@ -173,6 +174,7 @@ function AppConnectionRow(props) {
|
||||
<ConnectionContextMenu
|
||||
appKey={key}
|
||||
connection={props.connection}
|
||||
disableReconnection={!reconnectable}
|
||||
onClose={handleClose}
|
||||
onMenuItemClick={onContextMenuAction}
|
||||
anchorEl={anchorEl}
|
||||
|
@@ -95,8 +95,7 @@ function ChooseConnectionSubstep(props) {
|
||||
|
||||
if (
|
||||
!appConfig?.data ||
|
||||
(!appConfig.data?.disabled === false &&
|
||||
appConfig.data?.useOnlyPredefinedAuthClients === false)
|
||||
(!appConfig.data?.disabled && appConfig.data?.customConnectionAllowed)
|
||||
) {
|
||||
options.push({
|
||||
label: formatMessage('chooseConnectionSubstep.addNewConnection'),
|
||||
@@ -104,10 +103,12 @@ function ChooseConnectionSubstep(props) {
|
||||
});
|
||||
}
|
||||
|
||||
if (appConfig?.data?.connectionAllowed) {
|
||||
options.push({
|
||||
label: formatMessage('chooseConnectionSubstep.addNewSharedConnection'),
|
||||
value: ADD_SHARED_CONNECTION_VALUE,
|
||||
});
|
||||
}
|
||||
|
||||
return options;
|
||||
}, [data, formatMessage, appConfig?.data]);
|
||||
|
@@ -30,7 +30,7 @@ const PermissionCatalogField = ({
|
||||
if (isPermissionCatalogLoading) return <PermissionCatalogFieldLoader />;
|
||||
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<TableContainer data-test="permissions-catalog" component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user