Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0012c9fb59 | ||
![]() |
feba2a32f9 | ||
![]() |
5090ece9b6 | ||
![]() |
221b19586e | ||
![]() |
3346c14255 | ||
![]() |
6e97e023c9 | ||
![]() |
b26e2ecf2e | ||
![]() |
d896238f23 | ||
![]() |
d2c8f5a75c | ||
![]() |
ce430d238c | ||
![]() |
ee397441ed | ||
![]() |
ba82d986c1 | ||
![]() |
2361cb521e | ||
![]() |
05f8d95281 | ||
![]() |
6c60b1c263 | ||
![]() |
0c32a0693c | ||
![]() |
807faa3c93 | ||
![]() |
fb53e37f7a | ||
![]() |
4ffdf98e16 | ||
![]() |
09335fcd79 |
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
|
||||
run: yarn test:coverage
|
||||
working-directory: packages/backend
|
||||
|
48
.github/workflows/playwright.yml
vendored
48
.github/workflows/playwright.yml
vendored
@@ -3,12 +3,13 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
paths:
|
||||
- 'packages/backend/**'
|
||||
- 'packages/e2e-tests/**'
|
||||
- 'packages/web/**'
|
||||
- '!packages/backend/src/apps/**'
|
||||
# TODO: Add pull request after optimizing the total excecution time of the test suite.
|
||||
# pull_request:
|
||||
# paths:
|
||||
# - 'packages/backend/**'
|
||||
# - 'packages/e2e-tests/**'
|
||||
# - 'packages/web/**'
|
||||
# - '!packages/backend/src/apps/**'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
@@ -55,44 +56,27 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
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 --frozen-lockfile
|
||||
working-directory: ./packages/backend
|
||||
node-version: 18
|
||||
- name: Install web dependencies
|
||||
run: yarn --frozen-lockfile
|
||||
run: yarn
|
||||
working-directory: ./packages/web
|
||||
- name: Install backend dependencies
|
||||
run: yarn
|
||||
working-directory: ./packages/backend
|
||||
- name: Install e2e-tests dependencies
|
||||
run: yarn --frozen-lockfile
|
||||
run: yarn
|
||||
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
|
||||
@@ -130,9 +114,9 @@ jobs:
|
||||
- name: Run Playwright tests
|
||||
working-directory: ./packages/e2e-tests
|
||||
env:
|
||||
PORT: 3000
|
||||
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,6 +12,7 @@
|
||||
"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",
|
||||
@@ -97,10 +98,11 @@
|
||||
"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": "^1.1.3"
|
||||
"vitest": "^2.1.5"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
@@ -7,7 +7,7 @@ export default async (request, response) => {
|
||||
.throwIfNotFound();
|
||||
|
||||
const roleMappings = await samlAuthProvider
|
||||
.$relatedQuery('samlAuthProvidersRoleMappings')
|
||||
.$relatedQuery('roleMappings')
|
||||
.orderBy('remote_role_name', 'asc');
|
||||
|
||||
renderObject(response, roleMappings);
|
||||
|
@@ -8,15 +8,14 @@ export default async (request, response) => {
|
||||
.findById(samlAuthProviderId)
|
||||
.throwIfNotFound();
|
||||
|
||||
const samlAuthProvidersRoleMappings =
|
||||
await samlAuthProvider.updateRoleMappings(
|
||||
samlAuthProvidersRoleMappingsParams(request)
|
||||
);
|
||||
const roleMappings = await samlAuthProvider.updateRoleMappings(
|
||||
roleMappingsParams(request)
|
||||
);
|
||||
|
||||
renderObject(response, samlAuthProvidersRoleMappings);
|
||||
renderObject(response, roleMappings);
|
||||
};
|
||||
|
||||
const samlAuthProvidersRoleMappingsParams = (request) => {
|
||||
const roleMappingsParams = (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 { createSamlAuthProvidersRoleMapping } from '../../../../../../test/factories/saml-auth-providers-role-mapping.js';
|
||||
import { createRoleMapping } from '../../../../../../test/factories/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 createSamlAuthProvidersRoleMapping({
|
||||
await createRoleMapping({
|
||||
samlAuthProviderId: samlAuthProvider.id,
|
||||
remoteRoleName: 'Viewer',
|
||||
});
|
||||
|
||||
await createSamlAuthProvidersRoleMapping({
|
||||
await createRoleMapping({
|
||||
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(
|
||||
'samlAuthProvidersRoleMappings'
|
||||
'roleMappings'
|
||||
);
|
||||
|
||||
expect(existingRoleMappings.length).toBe(2);
|
||||
@@ -149,34 +149,4 @@ 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);
|
||||
});
|
||||
});
|
||||
|
@@ -0,0 +1,52 @@
|
||||
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');
|
||||
}
|
@@ -30,7 +30,7 @@ const findOrCreateUserBySamlIdentity = async (
|
||||
: [mappedUser.role];
|
||||
|
||||
const samlAuthProviderRoleMapping = await samlAuthProvider
|
||||
.$relatedQuery('samlAuthProvidersRoleMappings')
|
||||
.$relatedQuery('roleMappings')
|
||||
.whereIn('remote_role_name', mappedRoles)
|
||||
.limit(1)
|
||||
.first();
|
||||
|
46
packages/backend/src/helpers/user-ability.test.js
Normal file
46
packages/backend/src/helpers/user-ability.test.js
Normal file
@@ -0,0 +1,46 @@
|
||||
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([]);
|
||||
});
|
||||
});
|
@@ -0,0 +1,30 @@
|
||||
// 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[`SamlAuthProvidersRoleMapping model > jsonSchema should have the correct schema 1`] = `
|
||||
exports[`RoleMapping model > jsonSchema should have the correct schema 1`] = `
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import Base from './base.js';
|
||||
import SamlAuthProvider from './saml-auth-provider.ee.js';
|
||||
|
||||
class SamlAuthProvidersRoleMapping extends Base {
|
||||
static tableName = 'saml_auth_providers_role_mappings';
|
||||
class RoleMapping extends Base {
|
||||
static tableName = 'role_mappings';
|
||||
|
||||
static jsonSchema = {
|
||||
type: 'object',
|
||||
@@ -21,11 +21,11 @@ class SamlAuthProvidersRoleMapping extends Base {
|
||||
relation: Base.BelongsToOneRelation,
|
||||
modelClass: SamlAuthProvider,
|
||||
join: {
|
||||
from: 'saml_auth_providers_role_mappings.saml_auth_provider_id',
|
||||
from: 'role_mappings.saml_auth_provider_id',
|
||||
to: 'saml_auth_providers.id',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default SamlAuthProvidersRoleMapping;
|
||||
export default RoleMapping;
|
@@ -1,28 +1,26 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import SamlAuthProvidersRoleMapping from '../models/saml-auth-providers-role-mapping.ee';
|
||||
import RoleMapping from './role-mapping.ee';
|
||||
import SamlAuthProvider from './saml-auth-provider.ee';
|
||||
import Base from './base';
|
||||
|
||||
describe('SamlAuthProvidersRoleMapping model', () => {
|
||||
describe('RoleMapping model', () => {
|
||||
it('tableName should return correct name', () => {
|
||||
expect(SamlAuthProvidersRoleMapping.tableName).toBe(
|
||||
'saml_auth_providers_role_mappings'
|
||||
);
|
||||
expect(RoleMapping.tableName).toBe('role_mappings');
|
||||
});
|
||||
|
||||
it('jsonSchema should have the correct schema', () => {
|
||||
expect(SamlAuthProvidersRoleMapping.jsonSchema).toMatchSnapshot();
|
||||
expect(RoleMapping.jsonSchema).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('relationMappings should return correct associations', () => {
|
||||
const relationMappings = SamlAuthProvidersRoleMapping.relationMappings();
|
||||
const relationMappings = RoleMapping.relationMappings();
|
||||
|
||||
const expectedRelations = {
|
||||
samlAuthProvider: {
|
||||
relation: Base.BelongsToOneRelation,
|
||||
modelClass: SamlAuthProvider,
|
||||
join: {
|
||||
from: 'saml_auth_providers_role_mappings.saml_auth_provider_id',
|
||||
from: 'role_mappings.saml_auth_provider_id',
|
||||
to: 'saml_auth_providers.id',
|
||||
},
|
||||
},
|
@@ -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 SamlAuthProvidersRoleMapping from './saml-auth-providers-role-mapping.ee.js';
|
||||
import RoleMapping from './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',
|
||||
},
|
||||
},
|
||||
samlAuthProvidersRoleMappings: {
|
||||
roleMappings: {
|
||||
relation: Base.HasManyRelation,
|
||||
modelClass: SamlAuthProvidersRoleMapping,
|
||||
modelClass: RoleMapping,
|
||||
join: {
|
||||
from: 'saml_auth_providers.id',
|
||||
to: 'saml_auth_providers_role_mappings.saml_auth_provider_id',
|
||||
to: 'role_mappings.saml_auth_provider_id',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -133,27 +133,22 @@ class SamlAuthProvider extends Base {
|
||||
}
|
||||
|
||||
async updateRoleMappings(roleMappings) {
|
||||
return await SamlAuthProvider.transaction(async (trx) => {
|
||||
await this.$relatedQuery('samlAuthProvidersRoleMappings', trx).delete();
|
||||
await this.$relatedQuery('roleMappings').delete();
|
||||
|
||||
if (isEmpty(roleMappings)) {
|
||||
return [];
|
||||
}
|
||||
if (isEmpty(roleMappings)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const samlAuthProvidersRoleMappingsData = roleMappings.map(
|
||||
(samlAuthProvidersRoleMapping) => ({
|
||||
...samlAuthProvidersRoleMapping,
|
||||
samlAuthProviderId: this.id,
|
||||
})
|
||||
);
|
||||
const roleMappingsData = roleMappings.map((roleMapping) => ({
|
||||
...roleMapping,
|
||||
samlAuthProviderId: this.id,
|
||||
}));
|
||||
|
||||
const samlAuthProvidersRoleMappings =
|
||||
await SamlAuthProvidersRoleMapping.query(trx).insertAndFetch(
|
||||
samlAuthProvidersRoleMappingsData
|
||||
);
|
||||
const newRoleMappings = await RoleMapping.query().insertAndFetch(
|
||||
roleMappingsData
|
||||
);
|
||||
|
||||
return samlAuthProvidersRoleMappings;
|
||||
});
|
||||
return newRoleMappings;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,9 +1,14 @@
|
||||
import { vi, describe, it, expect } from 'vitest';
|
||||
import { vi, beforeEach, describe, it, expect } from 'vitest';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import SamlAuthProvider from '../models/saml-auth-provider.ee';
|
||||
import SamlAuthProvidersRoleMapping from '../models/saml-auth-providers-role-mapping.ee';
|
||||
import RoleMapping from '../models/role-mapping.ee';
|
||||
import axios from '../helpers/axios-with-proxy.js';
|
||||
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', () => {
|
||||
@@ -26,12 +31,12 @@ describe('SamlAuthProvider model', () => {
|
||||
to: 'saml_auth_providers.id',
|
||||
},
|
||||
},
|
||||
samlAuthProvidersRoleMappings: {
|
||||
roleMappings: {
|
||||
relation: Base.HasManyRelation,
|
||||
modelClass: SamlAuthProvidersRoleMapping,
|
||||
modelClass: RoleMapping,
|
||||
join: {
|
||||
from: 'saml_auth_providers.id',
|
||||
to: 'saml_auth_providers_role_mappings.saml_auth_provider_id',
|
||||
to: 'role_mappings.saml_auth_provider_id',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -81,4 +86,146 @@ 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -212,6 +212,10 @@ 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,
|
||||
@@ -583,62 +587,6 @@ 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);
|
||||
|
||||
@@ -654,12 +602,68 @@ class User extends Base {
|
||||
return conditionMap;
|
||||
}
|
||||
|
||||
cannot(action, subject) {
|
||||
const cannot = this.ability.cannot(action, subject);
|
||||
lowercaseEmail() {
|
||||
if (this.email) {
|
||||
this.email = this.email.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
if (cannot) throw new NotAuthorizedError();
|
||||
async createUsageData() {
|
||||
if (appConfig.isCloud) {
|
||||
return await this.$relatedQuery('usageData').insertAndFetch({
|
||||
userId: this.id,
|
||||
consumedTaskCount: 0,
|
||||
nextResetAt: DateTime.now().plus({ days: 30 }).toISODate(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return cannot;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
@@ -20,6 +21,7 @@ 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';
|
||||
@@ -205,64 +207,6 @@ 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' });
|
||||
@@ -505,6 +449,76 @@ 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' });
|
||||
@@ -1184,4 +1198,336 @@ 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.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
|
||||
|
||||
const user = await createUser({
|
||||
fullName: 'Sample user',
|
||||
email: 'user@automatisch.io',
|
||||
});
|
||||
|
||||
const usageData = await user.createUsageData();
|
||||
const currentUsageData = await user.$relatedQuery('currentUsageData');
|
||||
|
||||
expect(usageData).toStrictEqual(currentUsageData);
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
@@ -26,7 +26,7 @@ const serializers = {
|
||||
Permission: permissionSerializer,
|
||||
AdminSamlAuthProvider: adminSamlAuthProviderSerializer,
|
||||
SamlAuthProvider: samlAuthProviderSerializer,
|
||||
SamlAuthProvidersRoleMapping: samlAuthProviderRoleMappingSerializer,
|
||||
RoleMapping: samlAuthProviderRoleMappingSerializer,
|
||||
AppAuthClient: appAuthClientSerializer,
|
||||
AppConfig: appConfigSerializer,
|
||||
Flow: flowSerializer,
|
||||
|
@@ -1,16 +1,15 @@
|
||||
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.samlAuthProviderId || (await createSamlAuthProvider()).id;
|
||||
params.remoteRoleName = params.remoteRoleName || faker.person.jobType();
|
||||
|
||||
params.remoteRoleName = params?.remoteRoleName || 'User';
|
||||
const roleMapping = await RoleMapping.query().insertAndFetch(params);
|
||||
|
||||
const samlAuthProviderRoleMapping =
|
||||
await SamlAuthProviderRoleMapping.query().insertAndFetch(params);
|
||||
|
||||
return samlAuthProviderRoleMapping;
|
||||
return roleMapping;
|
||||
};
|
||||
|
@@ -1,16 +0,0 @@
|
||||
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;
|
||||
};
|
@@ -15,7 +15,7 @@ const getRoleMappingsMock = async (roleMappings) => {
|
||||
currentPage: null,
|
||||
isArray: true,
|
||||
totalPages: null,
|
||||
type: 'SamlAuthProvidersRoleMapping',
|
||||
type: 'RoleMapping',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@@ -15,7 +15,7 @@ const createRoleMappingsMock = async (roleMappings) => {
|
||||
currentPage: null,
|
||||
isArray: true,
|
||||
totalPages: null,
|
||||
type: 'SamlAuthProvidersRoleMapping',
|
||||
type: 'RoleMapping',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@@ -2,8 +2,25 @@ 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: 93.41,
|
||||
branches: 93.46,
|
||||
functions: 95.95,
|
||||
lines: 93.41,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@@ -2,6 +2,31 @@
|
||||
# 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"
|
||||
@@ -9,6 +34,19 @@
|
||||
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"
|
||||
@@ -242,18 +280,43 @@
|
||||
wrap-ansi "^8.1.0"
|
||||
wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
|
||||
|
||||
"@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:
|
||||
"@sinclair/typebox" "^0.27.8"
|
||||
"@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/sourcemap-codec@^1.5.0":
|
||||
"@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==
|
||||
dependencies:
|
||||
"@jridgewell/set-array" "^1.2.1"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
"@jridgewell/trace-mapping" "^0.3.24"
|
||||
|
||||
"@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":
|
||||
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"
|
||||
@@ -574,11 +637,6 @@
|
||||
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"
|
||||
@@ -770,49 +828,82 @@
|
||||
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
|
||||
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
|
||||
|
||||
"@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==
|
||||
"@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==
|
||||
dependencies:
|
||||
"@vitest/spy" "1.6.0"
|
||||
"@vitest/utils" "1.6.0"
|
||||
chai "^4.3.10"
|
||||
"@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/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==
|
||||
"@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==
|
||||
dependencies:
|
||||
"@vitest/utils" "1.6.0"
|
||||
p-limit "^5.0.0"
|
||||
pathe "^1.1.1"
|
||||
"@vitest/spy" "2.1.5"
|
||||
"@vitest/utils" "2.1.5"
|
||||
chai "^5.1.2"
|
||||
tinyrainbow "^1.2.0"
|
||||
|
||||
"@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==
|
||||
"@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==
|
||||
dependencies:
|
||||
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"
|
||||
"@vitest/spy" "2.1.5"
|
||||
estree-walker "^3.0.3"
|
||||
loupe "^2.3.7"
|
||||
pretty-format "^29.7.0"
|
||||
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"
|
||||
|
||||
"@xmldom/xmldom@^0.8.5", "@xmldom/xmldom@^0.8.6", "@xmldom/xmldom@^0.8.8":
|
||||
version "0.8.10"
|
||||
@@ -847,14 +938,7 @@ acorn-jsx@^5.3.2:
|
||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||
|
||||
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:
|
||||
acorn@^8.9.0:
|
||||
version "8.14.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0"
|
||||
integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
|
||||
@@ -925,11 +1009,6 @@ 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"
|
||||
@@ -976,10 +1055,10 @@ asap@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
|
||||
integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==
|
||||
|
||||
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==
|
||||
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==
|
||||
|
||||
async@^3.2.3:
|
||||
version "3.2.6"
|
||||
@@ -1211,18 +1290,16 @@ callsites@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
||||
|
||||
chai@^4.3.10:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/chai/-/chai-4.5.0.tgz#707e49923afdd9b13a8b0b47d33d732d13812fd8"
|
||||
integrity sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==
|
||||
chai@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.2.tgz#3afbc340b994ae3610ca519a6c70ace77ad4378d"
|
||||
integrity sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==
|
||||
dependencies:
|
||||
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"
|
||||
assertion-error "^2.0.1"
|
||||
check-error "^2.1.1"
|
||||
deep-eql "^5.0.1"
|
||||
loupe "^3.1.0"
|
||||
pathval "^2.0.0"
|
||||
|
||||
chalk@^4.0.0, chalk@^4.0.2:
|
||||
version "4.1.2"
|
||||
@@ -1237,12 +1314,10 @@ 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@^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"
|
||||
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==
|
||||
|
||||
chokidar@^3.5.2:
|
||||
version "3.6.0"
|
||||
@@ -1379,11 +1454,6 @@ 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"
|
||||
@@ -1441,7 +1511,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.3:
|
||||
cross-spawn@^7.0.0, cross-spawn@^7.0.2:
|
||||
version "7.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
|
||||
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
|
||||
@@ -1488,7 +1558,7 @@ debug@2.6.9, debug@~2.6.9:
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
|
||||
debug@4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7:
|
||||
version "4.3.7"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
|
||||
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
|
||||
@@ -1516,12 +1586,10 @@ decompress-response@^6.0.0:
|
||||
dependencies:
|
||||
mimic-response "^3.1.0"
|
||||
|
||||
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-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-extend@^0.6.0:
|
||||
version "0.6.0"
|
||||
@@ -1590,11 +1658,6 @@ 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"
|
||||
@@ -1722,6 +1785,11 @@ 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"
|
||||
@@ -1885,26 +1953,16 @@ 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"
|
||||
@@ -2209,11 +2267,6 @@ 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"
|
||||
@@ -2235,11 +2288,6 @@ 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"
|
||||
@@ -2264,7 +2312,7 @@ glob-parent@~5.1.2:
|
||||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
glob@^10.2.2, glob@^10.3.10:
|
||||
glob@^10.2.2, glob@^10.3.10, glob@^10.4.1:
|
||||
version "10.4.5"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956"
|
||||
integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==
|
||||
@@ -2384,6 +2432,11 @@ 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"
|
||||
@@ -2445,11 +2498,6 @@ 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"
|
||||
@@ -2624,11 +2672,6 @@ 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"
|
||||
@@ -2651,6 +2694,37 @@ 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"
|
||||
@@ -2675,11 +2749,6 @@ 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"
|
||||
@@ -2797,14 +2866,6 @@ 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"
|
||||
@@ -2896,12 +2957,10 @@ logform@^2.7.0:
|
||||
safe-stable-stringify "^2.3.1"
|
||||
triple-beam "^1.3.0"
|
||||
|
||||
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"
|
||||
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==
|
||||
|
||||
lru-cache@^10.0.1, lru-cache@^10.2.0:
|
||||
version "10.4.3"
|
||||
@@ -2918,13 +2977,22 @@ 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.5:
|
||||
magic-string@^0.30.12:
|
||||
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"
|
||||
@@ -2932,6 +3000,13 @@ 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"
|
||||
@@ -2974,11 +3049,6 @@ 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"
|
||||
@@ -3006,11 +3076,6 @@ 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"
|
||||
@@ -3123,16 +3188,6 @@ 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"
|
||||
@@ -3320,13 +3375,6 @@ 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"
|
||||
@@ -3401,13 +3449,6 @@ 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"
|
||||
@@ -3427,13 +3468,6 @@ 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"
|
||||
@@ -3494,11 +3528,6 @@ 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"
|
||||
@@ -3517,15 +3546,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.1, pathe@^1.1.2:
|
||||
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@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d"
|
||||
integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==
|
||||
pathval@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25"
|
||||
integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==
|
||||
|
||||
pause@0.0.1:
|
||||
version "0.0.1"
|
||||
@@ -3598,7 +3627,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.0.0, picocolors@^1.1.1:
|
||||
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==
|
||||
@@ -3608,15 +3637,6 @@ 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"
|
||||
@@ -3688,15 +3708,6 @@ 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"
|
||||
@@ -3812,11 +3823,6 @@ 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"
|
||||
@@ -3997,7 +4003,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.4:
|
||||
semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.3, 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==
|
||||
@@ -4138,7 +4144,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.1.0:
|
||||
signal-exit@^4.0.1:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
|
||||
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
|
||||
@@ -4193,7 +4199,7 @@ socks@^2.8.3:
|
||||
ip-address "^9.0.5"
|
||||
smart-buffer "^4.2.0"
|
||||
|
||||
source-map-js@^1.2.1:
|
||||
source-map-js@^1.2.0, 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==
|
||||
@@ -4245,7 +4251,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.5.0:
|
||||
std-env@^3.8.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==
|
||||
@@ -4255,7 +4261,16 @@ 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", "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":
|
||||
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:
|
||||
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==
|
||||
@@ -4287,7 +4302,14 @@ string_decoder@~1.1.1:
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"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:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@@ -4301,11 +4323,6 @@ 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"
|
||||
@@ -4316,13 +4333,6 @@ 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"
|
||||
@@ -4409,6 +4419,15 @@ 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"
|
||||
@@ -4424,20 +4443,30 @@ tildify@2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a"
|
||||
integrity sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==
|
||||
|
||||
tinybench@^2.5.1:
|
||||
tinybench@^2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b"
|
||||
integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==
|
||||
|
||||
tinypool@^0.8.3:
|
||||
version "0.8.4"
|
||||
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.4.tgz#e217fe1270d941b39e98c625dcecebb1408c9aa8"
|
||||
integrity sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==
|
||||
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==
|
||||
|
||||
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==
|
||||
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==
|
||||
|
||||
to-regex-range@^5.0.1:
|
||||
version "5.0.1"
|
||||
@@ -4485,11 +4514,6 @@ 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"
|
||||
@@ -4508,11 +4532,6 @@ 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"
|
||||
@@ -4579,15 +4598,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@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==
|
||||
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==
|
||||
dependencies:
|
||||
cac "^6.7.14"
|
||||
debug "^4.3.4"
|
||||
pathe "^1.1.1"
|
||||
picocolors "^1.0.0"
|
||||
debug "^4.3.7"
|
||||
es-module-lexer "^1.5.4"
|
||||
pathe "^1.1.2"
|
||||
vite "^5.0.0"
|
||||
|
||||
vite@^5.0.0:
|
||||
@@ -4601,31 +4620,31 @@ vite@^5.0.0:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.3"
|
||||
|
||||
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==
|
||||
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==
|
||||
dependencies:
|
||||
"@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"
|
||||
"@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"
|
||||
vite "^5.0.0"
|
||||
vite-node "1.6.0"
|
||||
why-is-node-running "^2.2.2"
|
||||
vite-node "2.1.5"
|
||||
why-is-node-running "^2.3.0"
|
||||
|
||||
webidl-conversions@^3.0.0:
|
||||
version "3.0.1"
|
||||
@@ -4654,7 +4673,7 @@ which@^4.0.0:
|
||||
dependencies:
|
||||
isexe "^3.1.1"
|
||||
|
||||
why-is-node-running@^2.2.2:
|
||||
why-is-node-running@^2.3.0:
|
||||
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==
|
||||
@@ -4800,8 +4819,3 @@ 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==
|
||||
|
@@ -2,5 +2,4 @@ POSTGRES_DB=automatisch
|
||||
POSTGRES_USER=automatisch_user
|
||||
POSTGRES_PASSWORD=automatisch_password
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_HOST=localhost
|
||||
BACKEND_APP_URL=http://localhost:3000
|
||||
POSTGRES_HOST=localhost
|
@@ -20,54 +20,44 @@ export class AdminApplicationSettingsPage extends AuthenticatedPage {
|
||||
}
|
||||
|
||||
async allowCustomConnections() {
|
||||
await expect(this.allowCustomConnectionsSwitch).not.toBeChecked();
|
||||
await expect(this.disableConnectionsSwitch).not.toBeChecked();
|
||||
await this.allowCustomConnectionsSwitch.check();
|
||||
await expect(this.allowCustomConnectionsSwitch).toBeChecked();
|
||||
await this.saveButton.click();
|
||||
}
|
||||
|
||||
async allowSharedConnections() {
|
||||
await expect(this.allowSharedConnectionsSwitch).not.toBeChecked();
|
||||
await expect(this.disableConnectionsSwitch).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.allowCustomConnectionsSwitch).toBeChecked();
|
||||
await expect(this.disableConnectionsSwitch).toBeChecked();
|
||||
await this.allowCustomConnectionsSwitch.uncheck();
|
||||
await expect(this.allowCustomConnectionsSwitch).not.toBeChecked();
|
||||
await this.saveButton.click();
|
||||
}
|
||||
|
||||
async disallowSharedConnections() {
|
||||
await expect(this.allowSharedConnectionsSwitch).toBeChecked();
|
||||
await expect(this.disableConnectionsSwitch).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() {
|
||||
const snackbars = await this.successSnackbar.all();
|
||||
for (const snackbar of snackbars) {
|
||||
await expect(await snackbar.getAttribute('data-snackbar-variant')).toBe(
|
||||
'success'
|
||||
);
|
||||
// await snackbar.click();
|
||||
}
|
||||
await expect(this.successSnackbar).toHaveCount(1);
|
||||
await this.successSnackbar.click();
|
||||
await expect(this.successSnackbar).toHaveCount(0);
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,3 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
const { AuthenticatedPage } = require('../authenticated-page');
|
||||
const { RoleConditionsModal } = require('./role-conditions-modal');
|
||||
|
||||
@@ -18,7 +16,6 @@ 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');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,8 +104,4 @@ export class AdminCreateRolePage extends AuthenticatedPage {
|
||||
throw new Error(`${subject} does not have action ${action}`);
|
||||
}
|
||||
}
|
||||
|
||||
async waitForPermissionsCatalogToVisible() {
|
||||
await expect(this.permissionsCatalog).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
@@ -14,12 +14,8 @@ 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,6 +95,7 @@ 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,20 +51,10 @@ 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 closeAllSnackbars() {
|
||||
async closeSnackbar() {
|
||||
const snackbars = await this.snackbar.all();
|
||||
for (const snackbar of snackbars) {
|
||||
await snackbar.click();
|
||||
|
21
packages/e2e-tests/fixtures/execution-details-page.js
Normal file
21
packages/e2e-tests/fixtures/execution-details-page.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const { AuthenticatedPage } = require('./authenticated-page');
|
||||
const { expect } = require('@playwright/test');
|
||||
|
||||
export class ExecutionDetailsPage extends AuthenticatedPage {
|
||||
constructor(page) {
|
||||
super(page);
|
||||
|
||||
this.executionCreatedAt = page.getByTestId('execution-created-at');
|
||||
this.executionId = page.getByTestId('execution-id');
|
||||
this.executionName = page.getByTestId('execution-name');
|
||||
this.executionStep = page.getByTestId('execution-step');
|
||||
}
|
||||
|
||||
async verifyExecutionData(flowId) {
|
||||
await expect(this.executionCreatedAt).toContainText(/\d+ seconds? ago/);
|
||||
await expect(this.executionId).toHaveText(
|
||||
/Execution ID: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
||||
);
|
||||
await expect(this.executionName).toHaveText(flowId);
|
||||
}
|
||||
}
|
93
packages/e2e-tests/fixtures/execution-step-details.js
Normal file
93
packages/e2e-tests/fixtures/execution-step-details.js
Normal file
@@ -0,0 +1,93 @@
|
||||
const { ExecutionDetailsPage } = require('./execution-details-page');
|
||||
const { expect } = require('@playwright/test');
|
||||
|
||||
export class ExecutionStepDetails extends ExecutionDetailsPage {
|
||||
constructor(page, executionStep) {
|
||||
super(page);
|
||||
|
||||
this.executionStep = executionStep;
|
||||
this.stepType = executionStep.getByTestId('step-type');
|
||||
this.stepPositionAndName = executionStep.getByTestId(
|
||||
'step-position-and-name'
|
||||
);
|
||||
this.executionStepId = executionStep.getByTestId('execution-step-id');
|
||||
this.executionStepExecutedAt = executionStep.getByTestId(
|
||||
'execution-step-executed-at'
|
||||
);
|
||||
this.dataInTab = executionStep.getByTestId('data-in-tab');
|
||||
this.dataInPanel = executionStep.getByTestId('data-in-panel');
|
||||
this.dataOutTab = executionStep.getByTestId('data-out-tab');
|
||||
this.dataOutPanel = executionStep.getByTestId('data-out-panel');
|
||||
}
|
||||
|
||||
async expectDataInTabToBeSelectedByDefault() {
|
||||
await expect(this.dataInTab).toHaveClass(/Mui-selected/);
|
||||
}
|
||||
|
||||
async expectDataInToContainText(searchText, desiredText) {
|
||||
await expect(this.dataInPanel).toContainText(desiredText);
|
||||
await this.dataInPanel.locator('#search-input').fill(searchText);
|
||||
await expect(this.dataInPanel).toContainText(desiredText);
|
||||
}
|
||||
|
||||
async expectDataOutToContainText(searchText, desiredText) {
|
||||
await expect(this.dataOutPanel).toContainText(desiredText);
|
||||
await this.dataOutPanel.locator('#search-input').fill(searchText);
|
||||
await expect(this.dataOutPanel).toContainText(desiredText);
|
||||
}
|
||||
|
||||
async verifyTriggerExecutionStep({
|
||||
stepPositionAndName,
|
||||
stepDataInKey,
|
||||
stepDataInValue,
|
||||
stepDataOutKey,
|
||||
stepDataOutValue,
|
||||
}) {
|
||||
await expect(this.stepType).toHaveText('Trigger');
|
||||
await this.verifyExecutionStep({
|
||||
stepPositionAndName,
|
||||
stepDataInKey,
|
||||
stepDataInValue,
|
||||
stepDataOutKey,
|
||||
stepDataOutValue,
|
||||
});
|
||||
}
|
||||
|
||||
async verifyActionExecutionStep({
|
||||
stepPositionAndName,
|
||||
stepDataInKey,
|
||||
stepDataInValue,
|
||||
stepDataOutKey,
|
||||
stepDataOutValue,
|
||||
}) {
|
||||
await expect(this.stepType).toHaveText('Action');
|
||||
await this.verifyExecutionStep({
|
||||
stepPositionAndName,
|
||||
stepDataInKey,
|
||||
stepDataInValue,
|
||||
stepDataOutKey,
|
||||
stepDataOutValue,
|
||||
});
|
||||
}
|
||||
|
||||
async verifyExecutionStep({
|
||||
stepPositionAndName,
|
||||
stepDataInKey,
|
||||
stepDataInValue,
|
||||
stepDataOutKey,
|
||||
stepDataOutValue,
|
||||
}) {
|
||||
await expect(this.stepPositionAndName).toHaveText(stepPositionAndName);
|
||||
await expect(this.executionStepId).toHaveText(
|
||||
/ID: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
||||
);
|
||||
await expect(this.executionStepExecutedAt).toContainText(
|
||||
/executed \d+ seconds? ago/
|
||||
);
|
||||
await this.expectDataInTabToBeSelectedByDefault();
|
||||
await this.expectDataInToContainText(stepDataInKey, stepDataInValue);
|
||||
await this.dataOutTab.click();
|
||||
await expect(this.dataOutPanel).toContainText(stepDataOutValue);
|
||||
await this.expectDataOutToContainText(stepDataOutKey, stepDataOutValue);
|
||||
}
|
||||
}
|
@@ -2,4 +2,11 @@ const { AuthenticatedPage } = require('./authenticated-page');
|
||||
|
||||
export class ExecutionsPage extends AuthenticatedPage {
|
||||
screenshotPath = '/executions';
|
||||
|
||||
constructor(page) {
|
||||
super(page);
|
||||
|
||||
this.executionRow = this.page.getByTestId('execution-row');
|
||||
this.executionsPageLoader = this.page.getByTestId('executions-loader');
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ const { test, expect } = require('@playwright/test');
|
||||
const { ApplicationsPage } = require('./applications-page');
|
||||
const { ConnectionsPage } = require('./connections-page');
|
||||
const { ExecutionsPage } = require('./executions-page');
|
||||
const { ExecutionDetailsPage } = require('./execution-details-page');
|
||||
const { FlowEditorPage } = require('./flow-editor-page');
|
||||
const { UserInterfacePage } = require('./user-interface-page');
|
||||
const { LoginPage } = require('./login-page');
|
||||
@@ -29,6 +30,9 @@ exports.test = test.extend({
|
||||
executionsPage: async ({ page }, use) => {
|
||||
await use(new ExecutionsPage(page));
|
||||
},
|
||||
executionDetailsPage: async ({ page }, use) => {
|
||||
await use(new ExecutionDetailsPage(page));
|
||||
},
|
||||
flowEditorPage: async ({ page }, use) => {
|
||||
await use(new FlowEditorPage(page));
|
||||
},
|
||||
|
@@ -2,7 +2,7 @@ const { expect } = require('../fixtures/index');
|
||||
|
||||
export const getToken = async (apiRequest) => {
|
||||
const tokenResponse = await apiRequest.post(
|
||||
`${process.env.BACKEND_APP_URL}/api/v1/access-tokens`,
|
||||
`http://localhost:${process.env.PORT}/api/v1/access-tokens`,
|
||||
{
|
||||
data: {
|
||||
email: process.env.LOGIN_EMAIL,
|
||||
@@ -11,6 +11,5 @@ export const getToken = async (apiRequest) => {
|
||||
}
|
||||
);
|
||||
await expect(tokenResponse.status()).toBe(200);
|
||||
|
||||
return await tokenResponse.json();
|
||||
};
|
||||
|
@@ -2,7 +2,7 @@ const { expect } = require('../fixtures/index');
|
||||
|
||||
export const createFlow = async (request, token) => {
|
||||
const response = await request.post(
|
||||
`${process.env.BACKEND_APP_URL}/api/v1/flows`,
|
||||
`http://localhost:${process.env.PORT}/api/v1/flows`,
|
||||
{ headers: { Authorization: token } }
|
||||
);
|
||||
await expect(response.status()).toBe(201);
|
||||
@@ -11,7 +11,7 @@ export const createFlow = async (request, token) => {
|
||||
|
||||
export const getFlow = async (request, token, flowId) => {
|
||||
const response = await request.get(
|
||||
`${process.env.BACKEND_APP_URL}/api/v1/flows/${flowId}`,
|
||||
`http://localhost:${process.env.PORT}/api/v1/flows/${flowId}`,
|
||||
{ headers: { Authorization: token } }
|
||||
);
|
||||
await expect(response.status()).toBe(200);
|
||||
@@ -20,7 +20,7 @@ export const getFlow = async (request, token, flowId) => {
|
||||
|
||||
export const updateFlowName = async (request, token, flowId) => {
|
||||
const updateFlowNameResponse = await request.patch(
|
||||
`${process.env.BACKEND_APP_URL}/api/v1/flows/${flowId}`,
|
||||
`http://localhost:${process.env.PORT}/api/v1/flows/${flowId}`,
|
||||
{
|
||||
headers: { Authorization: token },
|
||||
data: { name: flowId },
|
||||
@@ -31,7 +31,7 @@ export const updateFlowName = async (request, token, flowId) => {
|
||||
|
||||
export const updateFlowStep = async (request, token, stepId, requestBody) => {
|
||||
const updateTriggerStepResponse = await request.patch(
|
||||
`${process.env.BACKEND_APP_URL}/api/v1/steps/${stepId}`,
|
||||
`http://localhost:${process.env.PORT}/api/v1/steps/${stepId}`,
|
||||
{
|
||||
headers: { Authorization: token },
|
||||
data: requestBody,
|
||||
@@ -43,7 +43,7 @@ export const updateFlowStep = async (request, token, stepId, requestBody) => {
|
||||
|
||||
export const testStep = async (request, token, stepId) => {
|
||||
const testTriggerStepResponse = await request.post(
|
||||
`${process.env.BACKEND_APP_URL}/api/v1/steps/${stepId}/test`,
|
||||
`http://localhost:${process.env.PORT}/api/v1/steps/${stepId}/test`,
|
||||
{
|
||||
headers: { Authorization: token },
|
||||
}
|
||||
@@ -53,7 +53,7 @@ export const testStep = async (request, token, stepId) => {
|
||||
|
||||
export const publishFlow = async (request, token, flowId) => {
|
||||
const publishFlowResponse = await request.patch(
|
||||
`${process.env.BACKEND_APP_URL}/api/v1/flows/${flowId}/status`,
|
||||
`http://localhost:${process.env.PORT}/api/v1/flows/${flowId}/status`,
|
||||
{
|
||||
headers: { Authorization: token },
|
||||
data: { active: true },
|
||||
@@ -67,3 +67,42 @@ export const triggerFlow = async (request, url) => {
|
||||
const triggerFlowResponse = await request.get(url);
|
||||
await expect(triggerFlowResponse.status()).toBe(204);
|
||||
};
|
||||
|
||||
export const addWebhookFlow = async (request, token) => {
|
||||
let flow = await createFlow(request, token);
|
||||
const flowId = flow.data.id;
|
||||
await updateFlowName(request, token, flowId);
|
||||
flow = await getFlow(request, token, flowId);
|
||||
const flowSteps = flow.data.steps;
|
||||
|
||||
const triggerStepId = flowSteps.find((step) => step.type === 'trigger').id;
|
||||
const actionStepId = flowSteps.find((step) => step.type === 'action').id;
|
||||
|
||||
const triggerStep = await updateFlowStep(request, token, triggerStepId, {
|
||||
appKey: 'webhook',
|
||||
key: 'catchRawWebhook',
|
||||
parameters: {
|
||||
workSynchronously: false,
|
||||
},
|
||||
});
|
||||
await request.get(triggerStep.data.webhookUrl);
|
||||
await testStep(request, token, triggerStepId);
|
||||
|
||||
await updateFlowStep(request, token, actionStepId, {
|
||||
appKey: 'webhook',
|
||||
key: 'respondWith',
|
||||
parameters: {
|
||||
statusCode: '200',
|
||||
body: 'ok',
|
||||
headers: [
|
||||
{
|
||||
key: '',
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
await testStep(request, token, actionStepId);
|
||||
|
||||
return flowId;
|
||||
};
|
||||
|
@@ -1,24 +0,0 @@
|
||||
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,5 +1,3 @@
|
||||
import { knexSnakeCaseMappers } from 'objection';
|
||||
|
||||
const fileExtension = 'js';
|
||||
|
||||
const knexConfig = {
|
||||
@@ -9,7 +7,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,8 +26,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.2.0",
|
||||
"@playwright/test": "1.49.0",
|
||||
"objection": "^3.1.5"
|
||||
"@playwright/test": "^1.45.1"
|
||||
},
|
||||
"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: process.env.CI ? 1 : 0,
|
||||
retries: 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: undefined,
|
||||
workers: process.env.CI ? 1 : 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: 'on-first-retry',
|
||||
trace: 'retain-on-failure',
|
||||
testIdAttribute: 'data-test',
|
||||
viewport: { width: 1280, height: 720 },
|
||||
},
|
||||
|
@@ -2,11 +2,14 @@ const { publicTest: setup, expect } = require('../../fixtures/index');
|
||||
|
||||
setup.describe.serial('Admin setup page', () => {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
setup('should not be able to login if admin is not created', async ({ page, adminSetupPage, loginPage }) => {
|
||||
await expect(async () => {
|
||||
await expect(await page.url()).toContain(adminSetupPage.path);
|
||||
}).toPass();
|
||||
});
|
||||
setup(
|
||||
'should not be able to login if admin is not created',
|
||||
async ({ page, adminSetupPage }) => {
|
||||
await expect(async () => {
|
||||
await expect(await page.url()).toContain(adminSetupPage.path);
|
||||
}).toPass();
|
||||
}
|
||||
);
|
||||
|
||||
setup('should validate the inputs', async ({ adminSetupPage }) => {
|
||||
await adminSetupPage.open();
|
||||
|
@@ -5,18 +5,16 @@ 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');
|
||||
@@ -30,11 +28,10 @@ test.describe('Admin Applications', () => {
|
||||
await adminApplicationsPage.navigateTo();
|
||||
});
|
||||
|
||||
// TODO skip until https://github.com/automatisch/automatisch/pull/2244
|
||||
test.skip('Admin should be able to toggle Application settings', async ({
|
||||
test('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');
|
||||
@@ -60,7 +57,7 @@ test.describe('Admin Applications', () => {
|
||||
adminApplicationsPage,
|
||||
adminApplicationSettingsPage,
|
||||
flowEditorPage,
|
||||
page,
|
||||
page
|
||||
}) => {
|
||||
await adminApplicationsPage.openApplication('Spotify');
|
||||
await expect(page.url()).toContain('/admin-settings/apps/spotify/settings');
|
||||
@@ -78,15 +75,11 @@ 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);
|
||||
@@ -98,7 +91,7 @@ test.describe('Admin Applications', () => {
|
||||
adminApplicationSettingsPage,
|
||||
adminApplicationAuthClientsPage,
|
||||
flowEditorPage,
|
||||
page,
|
||||
page
|
||||
}) => {
|
||||
await adminApplicationsPage.openApplication('Reddit');
|
||||
await expect(page.url()).toContain('/admin-settings/apps/reddit/settings');
|
||||
@@ -108,21 +101,13 @@ 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();
|
||||
@@ -134,15 +119,11 @@ 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();
|
||||
@@ -153,7 +134,7 @@ test.describe('Admin Applications', () => {
|
||||
adminApplicationsPage,
|
||||
adminApplicationSettingsPage,
|
||||
flowEditorPage,
|
||||
page,
|
||||
page
|
||||
}) => {
|
||||
await adminApplicationsPage.openApplication('DeepL');
|
||||
await expect(page.url()).toContain('/admin-settings/apps/deepl/settings');
|
||||
@@ -171,18 +152,12 @@ 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);
|
||||
@@ -193,11 +168,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 {
|
||||
@@ -208,16 +183,14 @@ 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) {
|
||||
@@ -226,9 +199,7 @@ 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();
|
||||
@@ -243,22 +214,14 @@ 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,18 +22,22 @@ 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'
|
||||
);
|
||||
@@ -44,7 +48,8 @@ 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);
|
||||
@@ -52,14 +57,19 @@ 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'
|
||||
@@ -71,7 +81,8 @@ 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);
|
||||
@@ -80,10 +91,14 @@ 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',
|
||||
});
|
||||
@@ -158,45 +173,60 @@ 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 adminUsersPage.navigateTo();
|
||||
await adminUsersPage.createUserButton.click();
|
||||
await adminCreateUserPage.fullNameInput.fill('User Role Test');
|
||||
await adminCreateUserPage.emailInput.fill(
|
||||
'user-role-test@automatisch.io'
|
||||
);
|
||||
await adminCreateUserPage.roleInput.click();
|
||||
await adminCreateUserPage.page
|
||||
.getByRole('option', { name: 'Delete Role', exact: true })
|
||||
.click();
|
||||
await adminCreateUserPage.createButton.click();
|
||||
await adminCreateUserPage.invitationEmailInfoAlert.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-create-user-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
});
|
||||
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();
|
||||
const snackbar = await adminRolesPage.getSnackbarData(
|
||||
'snackbar-delete-role-error'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('error');
|
||||
await modal.close();
|
||||
});
|
||||
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');
|
||||
await adminCreateUserPage.emailInput.fill(
|
||||
'user-role-test@automatisch.io'
|
||||
);
|
||||
await adminCreateUserPage.roleInput.click();
|
||||
await adminCreateUserPage.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',
|
||||
});
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'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 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();
|
||||
}
|
||||
);
|
||||
await test.step('Change the role the user has', async () => {
|
||||
await adminUsersPage.navigateTo();
|
||||
await adminUsersPage.usersLoader.waitFor({
|
||||
@@ -211,10 +241,14 @@ 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();
|
||||
@@ -222,10 +256,14 @@ 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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -239,13 +277,16 @@ 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();
|
||||
@@ -260,6 +301,9 @@ 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',
|
||||
});
|
||||
@@ -267,34 +311,40 @@ 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();
|
||||
const snackbar = await adminRolesPage.getSnackbarData(
|
||||
'snackbar-delete-role-error'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('error');
|
||||
await adminRolesPage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
/*
|
||||
* TODO: await snackbar - make assertions based on product
|
||||
* decisions
|
||||
const snackbar = await adminRolesPage.getSnackbarData();
|
||||
await expect(snackbar.variant).toBe('...');
|
||||
*/
|
||||
await adminRolesPage.closeSnackbar();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -312,13 +362,16 @@ 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 () => {
|
||||
@@ -332,6 +385,9 @@ 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',
|
||||
});
|
||||
@@ -339,46 +395,56 @@ 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 () => {
|
||||
const pageUrl = new URL(page.url());
|
||||
const url = `${pageUrl.origin}/admin-settings/users`;
|
||||
await page.goto(url);
|
||||
await page.waitForTimeout(750);
|
||||
const isUnmounted = await page.evaluate(() => {
|
||||
// eslint-disable-next-line no-undef
|
||||
const root = document.querySelector('#root');
|
||||
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);
|
||||
await page.waitForTimeout(750);
|
||||
const isUnmounted = await page.evaluate(() => {
|
||||
// eslint-disable-next-line no-undef
|
||||
const root = document.querySelector('#root');
|
||||
|
||||
if (root) {
|
||||
// We have react query devtools only in dev env.
|
||||
// In production, there is nothing in root.
|
||||
// That's why `<= 1`.
|
||||
return root.children.length <= 1;
|
||||
}
|
||||
if (root) {
|
||||
// We have react query devtools only in dev env.
|
||||
// In production, there is nothing in root.
|
||||
// That's why `<= 1`.
|
||||
return root.children.length <= 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
await expect(isUnmounted).toBe(true);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
await expect(isUnmounted).toBe(true);
|
||||
}
|
||||
);
|
||||
|
||||
await test.step('Log back into the admin account', async () => {
|
||||
await page.goto('/');
|
||||
@@ -399,10 +465,10 @@ test('Accessibility of role management page', async ({
|
||||
await adminEditUserPage.roleInput.click();
|
||||
await adminEditUserPage.page.getByRole('option', { name: 'Admin' }).click();
|
||||
await adminEditUserPage.updateButton.click();
|
||||
const snackbar = await adminEditUserPage.getSnackbarData(
|
||||
'snackbar-edit-user-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminEditUserPage.snackbar.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
await adminEditUserPage.closeSnackbar();
|
||||
});
|
||||
|
||||
await test.step('Delete the role', async () => {
|
||||
@@ -414,10 +480,14 @@ 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,235 +5,281 @@ 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.closeAllSnackbars();
|
||||
await adminUsersPage.closeSnackbar();
|
||||
});
|
||||
|
||||
test('User creation and deletion process', async ({
|
||||
adminCreateUserPage,
|
||||
adminEditUserPage,
|
||||
adminUsersPage,
|
||||
}) => {
|
||||
adminCreateUserPage.seed(9000);
|
||||
const user = adminCreateUserPage.generateUser();
|
||||
await adminUsersPage.usersLoader.waitFor({
|
||||
state: 'detached' /* Note: state: 'visible' introduces flakiness
|
||||
test(
|
||||
'User creation and deletion process',
|
||||
async ({ adminCreateUserPage, adminEditUserPage, adminUsersPage }) => {
|
||||
adminCreateUserPage.seed(9000);
|
||||
const user = adminCreateUserPage.generateUser();
|
||||
await adminUsersPage.usersLoader.waitFor({
|
||||
state: 'detached' /* Note: state: 'visible' introduces flakiness
|
||||
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 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.createButton.click();
|
||||
await adminCreateUserPage.invitationEmailInfoAlert.waitFor({
|
||||
state: 'attached',
|
||||
*/
|
||||
});
|
||||
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.createButton.click();
|
||||
await adminCreateUserPage.invitationEmailInfoAlert.waitFor({
|
||||
state: 'attached'
|
||||
});
|
||||
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-create-user-success'
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-create-user-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminUsersPage.navigateTo();
|
||||
await adminUsersPage.closeSnackbar();
|
||||
}
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminUsersPage.navigateTo();
|
||||
});
|
||||
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 adminUsersPage.findUserPageWithEmail(user.email);
|
||||
|
||||
let userRow = await adminUsersPage.getUserRowByEmail(user.email);
|
||||
await adminUsersPage.clickEditUser(userRow);
|
||||
await adminEditUserPage.waitForLoad(user.fullName);
|
||||
const newUserInfo = adminEditUserPage.generateUser();
|
||||
await adminEditUserPage.fullNameInput.fill(newUserInfo.fullName);
|
||||
await adminEditUserPage.updateButton.click();
|
||||
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-edit-user-success'
|
||||
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 expect(snackbar.variant).toBe('success');
|
||||
await test.step(
|
||||
'Edit user info and make sure the edit works correctly',
|
||||
async () => {
|
||||
await adminUsersPage.findUserPageWithEmail(user.email);
|
||||
|
||||
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 adminUsersPage.findUserPageWithEmail(user.email);
|
||||
const userRow = await adminUsersPage.getUserRowByEmail(user.email);
|
||||
await adminUsersPage.clickDeleteUser(userRow);
|
||||
const modal = adminUsersPage.deleteUserModal;
|
||||
await modal.deleteButton.click();
|
||||
let userRow = await adminUsersPage.getUserRowByEmail(user.email);
|
||||
await adminUsersPage.clickEditUser(userRow);
|
||||
await adminEditUserPage.waitForLoad(user.fullName);
|
||||
const newUserInfo = adminEditUserPage.generateUser();
|
||||
await adminEditUserPage.fullNameInput.fill(newUserInfo.fullName);
|
||||
await adminEditUserPage.updateButton.click();
|
||||
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-delete-user-success'
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'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 expect(snackbar.variant).toBe('success');
|
||||
await expect(userRow).not.toBeVisible(false);
|
||||
});
|
||||
});
|
||||
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);
|
||||
const modal = adminUsersPage.deleteUserModal;
|
||||
await modal.deleteButton.click();
|
||||
|
||||
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 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 snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-delete-user-success'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
await expect(userRow).not.toBeVisible(false);
|
||||
}
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
});
|
||||
|
||||
await test.step('Delete the created user', async () => {
|
||||
await adminUsersPage.navigateTo();
|
||||
await adminUsersPage.findUserPageWithEmail(testUser.email);
|
||||
const userRow = await adminUsersPage.getUserRowByEmail(testUser.email);
|
||||
await adminUsersPage.clickDeleteUser(userRow);
|
||||
const modal = adminUsersPage.deleteUserModal;
|
||||
await modal.deleteButton.click();
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-delete-user-success'
|
||||
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 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'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
}
|
||||
);
|
||||
await expect(snackbar).not.toBeNull();
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await expect(userRow).not.toBeVisible(false);
|
||||
});
|
||||
|
||||
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.createButton.click();
|
||||
const snackbar = await adminUsersPage.getSnackbarData('snackbar-error');
|
||||
await expect(snackbar.variant).toBe('error');
|
||||
});
|
||||
});
|
||||
|
||||
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 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'
|
||||
await test.step(
|
||||
'Delete the created user',
|
||||
async () => {
|
||||
await adminUsersPage.navigateTo();
|
||||
await adminUsersPage.findUserPageWithEmail(testUser.email);
|
||||
const userRow = await adminUsersPage.getUserRowByEmail(testUser.email);
|
||||
await adminUsersPage.clickDeleteUser(userRow);
|
||||
const modal = adminUsersPage.deleteUserModal;
|
||||
await modal.deleteButton.click();
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-delete-user-success'
|
||||
);
|
||||
await expect(snackbar).not.toBeNull();
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
await expect(userRow).not.toBeVisible(false);
|
||||
}
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
});
|
||||
|
||||
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.createButton.click();
|
||||
|
||||
await expect(page.url()).toBe(createUserPageUrl);
|
||||
const snackbar = await adminUsersPage.getSnackbarData('snackbar-error');
|
||||
await expect(snackbar.variant).toBe('error');
|
||||
});
|
||||
});
|
||||
|
||||
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 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.createButton.click();
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-create-user-success'
|
||||
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.createButton.click();
|
||||
const snackbar = await adminUsersPage.getSnackbarData('snackbar-error');
|
||||
await expect(snackbar.variant).toBe('error');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
}
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminUsersPage.closeAllSnackbars();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
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.createButton.click();
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-create-user-success'
|
||||
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 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'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
}
|
||||
);
|
||||
await expect(snackbar.variant).toBe('success');
|
||||
});
|
||||
|
||||
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);
|
||||
await adminUsersPage.clickEditUser(userRow);
|
||||
await adminEditUserPage.waitForLoad(user2.fullName);
|
||||
await adminEditUserPage.emailInput.fill(user1.email);
|
||||
const editPageUrl = page.url();
|
||||
await adminEditUserPage.updateButton.click();
|
||||
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.createButton.click();
|
||||
|
||||
const snackbar = await adminUsersPage.getSnackbarData('snackbar-error');
|
||||
await expect(snackbar.variant).toBe('error');
|
||||
await expect(page.url()).toBe(editPageUrl);
|
||||
});
|
||||
});
|
||||
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
|
||||
}) => {
|
||||
adminCreateUserPage.seed(9300);
|
||||
const user1 = adminCreateUserPage.generateUser();
|
||||
const user2 = adminCreateUserPage.generateUser();
|
||||
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.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 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.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 adminUsersPage.navigateTo();
|
||||
await adminUsersPage.findUserPageWithEmail(user2.email);
|
||||
let userRow = await adminUsersPage.getUserRowByEmail(user2.email);
|
||||
await adminUsersPage.clickEditUser(userRow);
|
||||
await adminEditUserPage.waitForLoad(user2.fullName);
|
||||
await adminEditUserPage.emailInput.fill(user1.email);
|
||||
const editPageUrl = page.url();
|
||||
await adminEditUserPage.updateButton.click();
|
||||
|
||||
const snackbar = await adminUsersPage.getSnackbarData(
|
||||
'snackbar-error'
|
||||
);
|
||||
await expect(snackbar.variant).toBe('error');
|
||||
await adminUsersPage.closeSnackbar();
|
||||
await expect(page.url()).toBe(editPageUrl);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@@ -1,55 +1,24 @@
|
||||
const { request } = require('@playwright/test');
|
||||
const { test, expect } = require('../../fixtures/index');
|
||||
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');
|
||||
const {AddMattermostConnectionModal} = require('../../fixtures/apps/mattermost/add-mattermost-connection-modal');
|
||||
|
||||
test.describe('Pop-up message on connections', () => {
|
||||
test.beforeEach(async ({ flowEditorPage, page }) => {
|
||||
const apiRequest = await request.newContext();
|
||||
const tokenJsonResponse = await getToken(apiRequest);
|
||||
const token = tokenJsonResponse.data.token;
|
||||
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);
|
||||
|
||||
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.flowName.click();
|
||||
await flowEditorPage.flowNameInput.fill('PopupFlow');
|
||||
await flowEditorPage.createWebhookTrigger(true);
|
||||
|
||||
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.chooseAppAndEvent('Mattermost', 'Send a message to channel');
|
||||
await expect(flowEditorPage.continueButton).toHaveCount(1);
|
||||
await expect(flowEditorPage.continueButton).not.toBeEnabled();
|
||||
|
||||
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,
|
||||
@@ -59,7 +28,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;
|
||||
};
|
||||
@@ -68,10 +37,8 @@ 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 ({
|
||||
@@ -84,15 +51,13 @@ 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,37 +1,138 @@
|
||||
const { request } = require('@playwright/test');
|
||||
const { test, expect } = require('../../fixtures/index');
|
||||
const {
|
||||
triggerFlow,
|
||||
publishFlow,
|
||||
addWebhookFlow,
|
||||
} = require('../../helpers/flow-api-helper');
|
||||
const {
|
||||
ExecutionStepDetails,
|
||||
} = require('../../fixtures/execution-step-details');
|
||||
const { getToken } = require('../../helpers/auth-api-helper');
|
||||
|
||||
test.describe('Executions page', () => {
|
||||
let flowId;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
const apiRequest = await request.newContext();
|
||||
const tokenJsonResponse = await getToken(apiRequest);
|
||||
|
||||
flowId = await addWebhookFlow(apiRequest, tokenJsonResponse.data.token);
|
||||
|
||||
const { data } = await publishFlow(
|
||||
apiRequest,
|
||||
tokenJsonResponse.data.token,
|
||||
flowId
|
||||
);
|
||||
|
||||
const triggerStepWebhookUrl = data.steps.find(
|
||||
(step) => step.type === 'trigger'
|
||||
).webhookUrl;
|
||||
|
||||
await triggerFlow(apiRequest, triggerStepWebhookUrl);
|
||||
});
|
||||
|
||||
// no execution data exists in an empty account
|
||||
test.describe.skip('Executions page', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.getByTestId('executions-page-drawer-link').click();
|
||||
await page.getByTestId('execution-row').first().click();
|
||||
|
||||
await expect(page).toHaveURL(/\/executions\//);
|
||||
});
|
||||
|
||||
test('displays data in by default', async ({ page, executionsPage }) => {
|
||||
await expect(page.getByTestId('execution-step').last()).toBeVisible();
|
||||
await expect(page.getByTestId('execution-step')).toHaveCount(2);
|
||||
test('show correct step data for trigger and actions on test and non-test execution', async ({
|
||||
page,
|
||||
executionsPage,
|
||||
executionDetailsPage,
|
||||
}) => {
|
||||
await executionsPage.executionsPageLoader.waitFor({
|
||||
state: 'detached',
|
||||
});
|
||||
const flowExecutions = await executionsPage.executionRow.filter({
|
||||
hasText: flowId,
|
||||
});
|
||||
await test.step('show only trigger step on test execution', async () => {
|
||||
await expect(flowExecutions.last()).toContainText('Test run');
|
||||
await flowExecutions.last().click();
|
||||
|
||||
await executionsPage.screenshot({
|
||||
path: 'Execution - data in.png',
|
||||
await executionDetailsPage.verifyExecutionData(flowId);
|
||||
await expect(executionDetailsPage.executionStep).toHaveCount(1);
|
||||
|
||||
const executionStepDetails = new ExecutionStepDetails(
|
||||
page,
|
||||
executionDetailsPage.executionStep.last()
|
||||
);
|
||||
await executionStepDetails.verifyTriggerExecutionStep({
|
||||
stepPositionAndName: '1. Webhook',
|
||||
stepDataInKey: 'workSynchronously',
|
||||
stepDataInValue: 'workSynchronously',
|
||||
stepDataOutKey: 'host',
|
||||
stepDataOutValue: 'localhost',
|
||||
});
|
||||
|
||||
await page.goBack();
|
||||
});
|
||||
|
||||
await test.step('show trigger and action step on action test execution', async () => {
|
||||
await expect(flowExecutions.nth(1)).toContainText('Test run');
|
||||
await flowExecutions.nth(1).click();
|
||||
|
||||
await expect(executionDetailsPage.executionStep).toHaveCount(2);
|
||||
await executionDetailsPage.verifyExecutionData(flowId);
|
||||
|
||||
const firstExecutionStepDetails = new ExecutionStepDetails(
|
||||
page,
|
||||
executionDetailsPage.executionStep.first()
|
||||
);
|
||||
await firstExecutionStepDetails.verifyTriggerExecutionStep({
|
||||
stepPositionAndName: '1. Webhook',
|
||||
stepDataInKey: 'workSynchronously',
|
||||
stepDataInValue: 'workSynchronously',
|
||||
stepDataOutKey: 'host',
|
||||
stepDataOutValue: 'localhost',
|
||||
});
|
||||
|
||||
const lastExecutionStepDetails = new ExecutionStepDetails(
|
||||
page,
|
||||
executionDetailsPage.executionStep.last()
|
||||
);
|
||||
await lastExecutionStepDetails.verifyActionExecutionStep({
|
||||
stepPositionAndName: '2. Webhook',
|
||||
stepDataInKey: 'body',
|
||||
stepDataInValue: 'body:"ok"',
|
||||
stepDataOutKey: 'body',
|
||||
stepDataOutValue: 'body:"ok"',
|
||||
});
|
||||
|
||||
await page.goBack();
|
||||
});
|
||||
|
||||
await test.step('show trigger and action step on flow execution', async () => {
|
||||
await expect(flowExecutions.first()).not.toContainText('Test run');
|
||||
await flowExecutions.first().click();
|
||||
|
||||
await expect(executionDetailsPage.executionStep).toHaveCount(2);
|
||||
await executionDetailsPage.verifyExecutionData(flowId);
|
||||
|
||||
const firstExecutionStepDetails = new ExecutionStepDetails(
|
||||
page,
|
||||
executionDetailsPage.executionStep.first()
|
||||
);
|
||||
await firstExecutionStepDetails.verifyTriggerExecutionStep({
|
||||
stepPositionAndName: '1. Webhook',
|
||||
stepDataInKey: 'workSynchronously',
|
||||
stepDataInValue: 'workSynchronously',
|
||||
stepDataOutKey: 'host',
|
||||
stepDataOutValue: 'localhost',
|
||||
});
|
||||
|
||||
const lastExecutionStepDetails = new ExecutionStepDetails(
|
||||
page,
|
||||
executionDetailsPage.executionStep.last()
|
||||
);
|
||||
await lastExecutionStepDetails.verifyActionExecutionStep({
|
||||
stepPositionAndName: '2. Webhook',
|
||||
stepDataInKey: 'body',
|
||||
stepDataInValue: 'body:"ok"',
|
||||
stepDataOutKey: 'body',
|
||||
stepDataOutValue: 'body:"ok"',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('displays data out', async ({ page, executionsPage }) => {
|
||||
const executionStepCount = await page.getByTestId('execution-step').count();
|
||||
for (let i = 0; i < executionStepCount; i++) {
|
||||
await page.getByTestId('data-out-tab').nth(i).click();
|
||||
await expect(page.getByTestId('data-out-panel').nth(i)).toBeVisible();
|
||||
|
||||
await executionsPage.screenshot({
|
||||
path: `Execution - data out - ${i}.png`,
|
||||
animations: 'disabled',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('does not display error', async ({ page }) => {
|
||||
await expect(page.getByTestId('error-tab')).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
@@ -1,17 +1,54 @@
|
||||
const { request } = require('@playwright/test');
|
||||
const { test, expect } = require('../../fixtures/index');
|
||||
const {
|
||||
triggerFlow,
|
||||
publishFlow,
|
||||
addWebhookFlow,
|
||||
} = require('../../helpers/flow-api-helper');
|
||||
const { getToken } = require('../../helpers/auth-api-helper');
|
||||
|
||||
test.describe('Executions page', () => {
|
||||
let flowId;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
const apiRequest = await request.newContext();
|
||||
const tokenJsonResponse = await getToken(apiRequest);
|
||||
|
||||
flowId = await addWebhookFlow(apiRequest, tokenJsonResponse.data.token);
|
||||
|
||||
const { data } = await publishFlow(
|
||||
apiRequest,
|
||||
tokenJsonResponse.data.token,
|
||||
flowId
|
||||
);
|
||||
|
||||
const triggerStepWebhookUrl = data.steps.find(
|
||||
(step) => step.type === 'trigger'
|
||||
).webhookUrl;
|
||||
|
||||
await triggerFlow(apiRequest, triggerStepWebhookUrl);
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.getByTestId('executions-page-drawer-link').click();
|
||||
});
|
||||
|
||||
// no executions exist in an empty account
|
||||
test.skip('displays executions', async ({ page, executionsPage }) => {
|
||||
await page.getByTestId('executions-loader').waitFor({
|
||||
test('should be able to see normal and test executions', async ({
|
||||
executionsPage,
|
||||
}) => {
|
||||
await executionsPage.executionsPageLoader.waitFor({
|
||||
state: 'detached',
|
||||
});
|
||||
await expect(page.getByTestId('execution-row').first()).toBeVisible();
|
||||
const flowExecutions = await executionsPage.executionRow.filter({
|
||||
hasText: flowId,
|
||||
});
|
||||
|
||||
await executionsPage.screenshot({ path: 'Executions.png' });
|
||||
await expect(flowExecutions).toHaveCount(4);
|
||||
await expect(flowExecutions.first()).toContainText('Success');
|
||||
await expect(flowExecutions.first()).not.toContainText('Test run');
|
||||
for (let testFlow = 1; testFlow < 4; testFlow++) {
|
||||
await expect(flowExecutions.nth(testFlow)).toContainText('Test run');
|
||||
await expect(flowExecutions.nth(testFlow)).toContainText('Success');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@@ -1,38 +1,57 @@
|
||||
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 ({ adminCreateUserPage, loginPage, page }) => {
|
||||
let addUserResponse;
|
||||
const apiRequest = await request.newContext();
|
||||
async ({ acceptInvitationPage, adminCreateUserPage, loginPage, page }) => {
|
||||
let acceptInvitationLink;
|
||||
|
||||
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 () => {
|
||||
const tokenJsonResponse = await getToken(apiRequest);
|
||||
addUserResponse = await addUser(
|
||||
apiRequest,
|
||||
tokenJsonResponse.data.token,
|
||||
{
|
||||
fullName: testUser.fullName,
|
||||
email: testUser.email,
|
||||
}
|
||||
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'
|
||||
);
|
||||
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 () => {
|
||||
let acceptToken = addUserResponse.data.acceptInvitationUrl.split('=')[1];
|
||||
await acceptInvitation(apiRequest, {token:acceptToken, password:LoginPage.defaultPassword});
|
||||
await page.goto(acceptInvitationLink);
|
||||
await acceptInvitationPage.acceptInvitation(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.49.0":
|
||||
"@playwright/test@^1.45.1":
|
||||
version "1.49.0"
|
||||
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.49.0.tgz#74227385b58317ee076b86b56d0e1e1b25cff01e"
|
||||
integrity sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw==
|
||||
@@ -101,13 +101,6 @@ 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"
|
||||
@@ -118,16 +111,6 @@ 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"
|
||||
@@ -243,11 +226,6 @@ 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"
|
||||
@@ -426,11 +404,6 @@ 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"
|
||||
@@ -649,11 +622,6 @@ 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"
|
||||
@@ -759,15 +727,6 @@ 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"
|
||||
@@ -974,11 +933,6 @@ 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"
|
||||
|
@@ -10,7 +10,7 @@ import { ExecutionPropType } from 'propTypes/propTypes';
|
||||
|
||||
function ExecutionName(props) {
|
||||
return (
|
||||
<Typography variant="h3" gutterBottom>
|
||||
<Typography data-test="execution-name" variant="h3" gutterBottom>
|
||||
{props.name}
|
||||
</Typography>
|
||||
);
|
||||
@@ -29,7 +29,7 @@ function ExecutionId(props) {
|
||||
);
|
||||
return (
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<Typography variant="body2">
|
||||
<Typography data-test="execution-id" variant="body2">
|
||||
{formatMessage('execution.id', { id })}
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -47,7 +47,7 @@ function ExecutionDate(props) {
|
||||
<Tooltip
|
||||
title={createdAt.toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)}
|
||||
>
|
||||
<Typography variant="body1" gutterBottom>
|
||||
<Typography data-test="execution-created-at" variant="body1" gutterBottom>
|
||||
{relativeCreatedAt}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
|
@@ -36,7 +36,11 @@ function ExecutionStepId(props) {
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex' }} gridArea="id">
|
||||
<Typography variant="caption" fontWeight="bold">
|
||||
<Typography
|
||||
data-test="execution-step-id"
|
||||
variant="caption"
|
||||
fontWeight="bold"
|
||||
>
|
||||
{formatMessage('executionStep.id', { id })}
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -56,7 +60,11 @@ function ExecutionStepDate(props) {
|
||||
<Tooltip
|
||||
title={createdAt.toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)}
|
||||
>
|
||||
<Typography variant="caption" gutterBottom>
|
||||
<Typography
|
||||
data-test="execution-step-executed-at"
|
||||
variant="caption"
|
||||
gutterBottom
|
||||
>
|
||||
{formatMessage('executionStep.executedAt', {
|
||||
datetime: relativeCreatedAt,
|
||||
})}
|
||||
@@ -119,12 +127,12 @@ function ExecutionStep(props) {
|
||||
<ExecutionStepId id={executionStep.step.id} />
|
||||
|
||||
<Box flex="1" gridArea="step">
|
||||
<Typography variant="caption">
|
||||
<Typography data-test="step-type" variant="caption">
|
||||
{isTrigger && formatMessage('flowStep.triggerType')}
|
||||
{isAction && formatMessage('flowStep.actionType')}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2">
|
||||
<Typography data-test="step-position-and-name" variant="body2">
|
||||
{step.position}. {app?.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -152,7 +160,7 @@ function ExecutionStep(props) {
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
<TabPanel value={activeTabIndex} index={0}>
|
||||
<TabPanel value={activeTabIndex} index={0} data-test="data-in-panel">
|
||||
<SearchableJSONViewer data={executionStep.dataIn} />
|
||||
</TabPanel>
|
||||
|
||||
|
@@ -30,7 +30,7 @@ const PermissionCatalogField = ({
|
||||
if (isPermissionCatalogLoading) return <PermissionCatalogFieldLoader />;
|
||||
|
||||
return (
|
||||
<TableContainer data-test="permissions-catalog" component={Paper}>
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
|
@@ -66,8 +66,8 @@ function RoleMappings({ provider, providerLoading }) {
|
||||
const enqueueSnackbar = useEnqueueSnackbar();
|
||||
|
||||
const {
|
||||
mutateAsync: updateSamlAuthProvidersRoleMappings,
|
||||
isPending: isUpdateSamlAuthProvidersRoleMappingsPending,
|
||||
mutateAsync: updateRoleMappings,
|
||||
isPending: isUpdateRoleMappingsPending,
|
||||
} = useAdminUpdateSamlAuthProviderRoleMappings(provider?.id);
|
||||
|
||||
const { data, isLoading: isAdminSamlAuthProviderRoleMappingsLoading } =
|
||||
@@ -79,7 +79,7 @@ function RoleMappings({ provider, providerLoading }) {
|
||||
const handleRoleMappingsUpdate = async (values) => {
|
||||
try {
|
||||
if (provider?.id) {
|
||||
await updateSamlAuthProvidersRoleMappings(
|
||||
await updateRoleMappings(
|
||||
values.roleMappings.map(({ roleId, remoteRoleName }) => ({
|
||||
roleId,
|
||||
remoteRoleName,
|
||||
@@ -148,7 +148,7 @@ function RoleMappings({ provider, providerLoading }) {
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{ boxShadow: 2 }}
|
||||
loading={isUpdateSamlAuthProvidersRoleMappingsPending}
|
||||
loading={isUpdateRoleMappingsPending}
|
||||
>
|
||||
{formatMessage('roleMappingsForm.save')}
|
||||
</LoadingButton>
|
||||
|
Reference in New Issue
Block a user