ToolJet/server/test/test.helper.ts
Arpit f0a403e619
Release : Appbuilder - appdefinition architecture revamp (#7448)
* importing service: updated

* import service, categorize and update events with associations

* fix: deleting events associated with pages on page delete

* handle app version: creation, updates, switching versions from app builder

* on version switch: no updates should be triggeted to server

* versioning for query events

* fixes: new components db transaction fails for newly created pages

* fixes: query chaining with events

* map older query ids to new for event action: run query

* fixes: multi-editor support

* fixes/multi-editor: users should be able to edit different version of the app at real time without sync

* minor fixes

* fixes: undo/redo savings with latest app def updates

* fixes: execution of page switch action

* fixes: csa events

* fixes: csa selection dropdown

* fixes: on csa action changed, the action params should also be updated correctly

* fixes: event rendering actions

* fixes: table event - row hovered

* fixes: table event - on search

* fixes: table event - onNewRowsAdded

* fixes: table event - onBulkUpdate

* fixes: table column updates

* fixes: table column updates to component definition

* re-order events

* handle adding widgets to sub containers

* fixes: csa for modals

* fixes: deletes children components on deleting parent

* fixes: components with default children

* fixed events for imported app

* gs- crash fix

* fixes: global settings UI

* fixes: header and user

* fixes: page switch event

* fixes: adapts to new event manager ui

* import app

* add event index for creating app versions

* fixes: table rendering on viewer

* fixes: event execution for viewer

* fixes: loading app with slug

* fixes: Page side bar is not rendered in viewer

* fixes: version manager ui for released versions

* fixes: tabs default children saving

* fixes: app resource mapping for parent-child components

* fixes: duplicate pages

* fixes: page load events for viewer

* fixes: enable and disable pages

* fixes: hide and show pages

* fixes: on maintaince toggle button

* fixes: new version child components are not tied to its parent

* fixes: redo breaks- on deleting a component and undo then redo (editor)

* new export schema and handling apps impport with new and older schemas

* table events: column and actions events

* fixes: query confirmations popup

* fixes: copy/paste

* fixes: cut/paste

* fixes: event mapping for newer versions for new components, pages and queries id

* fixes: app resource mapping for imported apps

* fixes: cascade events for table actions and colulmns

* Migrates the existing JSON-based app definition schema to a structured table-based architecture. This enhancement introduces component and page-specific permissions, improves data organization, and enables fine-grained access control. Additionally, it adds the 'globalSettings' column to the 'appVersion' table.

* cleanup

* fixes: enable and disable pages

* fixes: hide/show page and set saving state for cloning pages

* cleanup

* fixes: page disable menu

* fixes: migrations for data query events

* fixes: switching app version from version creation modal results in editor loading state

* fixes: setting up the page title

* fixes: Page duplication has same page handler name.

* fixes: updating general styles of a component

* fixes: delete component should trigger confirmations box for one widget and for multiple should process deletion

* fixes: CSA for button(component) does not work for page event handler.

* fixes: component name update [calendar]

* fixes: Duplicating pages do not create child components

* improves copy-paste mechanism of widgets

* fix: calendar subcontainer components comes out of the parent container on copy/paste

* fix:Form properties, no option for selecting submit button.

* fixes: Dark mode issue with event handler.

* fixes: display preferences for components

* fixes:have to select the selected version again to create a new

* fixes: Pages menu is not getting disabled when enabled and vice-versa

* fixes: correct naming of duplicating pages

* fixes: 2 action button even with no event attached to one, it gets attached to both.

* fixes: event deletion for action btn removal

* fixes: Keyboard action to move component is not saving

* reduce outbound calls when widget re-positioned with keyboard

* fixes: Not able to delete component from Inspector

* fixes: cloning of widgets

* fixes: Request confirmation before query run toggle is not visible on viewer mode. (can't run query if confirmation toggle is on)

* fixes: event sorting

* fixes: events mapping for versioning: queries and components

* fixes: importing app bug - query running issue when importing apps

* [appdef-2.0] fixes: event action linking for imported apps (#7627)

* fixes: event action linking for imported apps

* cleanup

* fixes: Toggling display preferences is not saving for components. (#7629)

* fixes: dnd issue for mobile view (#7632)

* default page menu settings should be true

* [appdef-2] event manager selector bug (#7631)

* fixes: on selecting query - 3 outbound calls are done to the server, and event manager re-renders 3times resulting in flikering ui

* reduces outbound calls for updating csa actions to 1

* [appdef] - copy associated events for cloned components (#7634)

* fixes: Copying  component is not coping the events associated with the component.

* cleanup

* [appdef-2] : Fixes frontend issues (#7636)

* Fix UI issues

* Fix Scrollbar is not available after we pin the inspector.

* Fix button  jumping places if switched from extended monitor to laptop.

* Fix white background around canvas

* fixes: Component inspector go blank after switching to different pages after dropping components (#7637)

* fixes: general properties of widgets are not getting saved (#7638)

* fixes: selecting the components via selecto (#7653)

* fixes: multiple undo-redo simlut. (#7656)

* fixes: copy associated events for cloned queries (#7657)

* Fixes not able toggle of Listview pagination toggle (#7701)

* Fix UI issues

* Fix Scrollbar is not available after we pin the inspector.

* Fix button  jumping places if switched from extended monitor to laptop.

* fix enable pagination not getting toggled in listview

* Fix form children not being displayed

* fixes: dnd fix for widgets dropped inside subcontainer (#7691)

* [Appdef-2] copy-paste, cut and  clone fix for widgets (#7687)

* fixes: copy/cut/paste and cloning of widgets

* cleanup

* can copy/paste-clone in listview

* fixes: on mulit-widget selected via mouse area selection: widget manager should be rendered (#7688)

* fixes: on deleting tabs widgets should delete its children (#7692)

* fixes: column data generated from restapi does not render correct columns in viewer (#7695)

* [appdef-2] fixes: multiple query confirmations trigger (#7704)

* fixes: multiple query confirmations trigger

* fixes: multiple outbound calls in the inital load, run queries on app load with confirmations:editor&viewer

* fixes: correct confirmations list to the stote

* [appdef-2] fixes:Event handler are running twice for page load (#7705)

* fixes:Event handler are running twice for page load(eg- add 2 show alert and change the page).

* fixes: for viewer page events

* fixes: container widget is not getting saved on drop (#7718)

* fixes: Create app version from is empty if we delete another version. (#7720)

* [appdef-2] fixes: on versioned app (switching or creating) version, the componet layout is wrongly updated to the container dnd (#7721)

* fixes: on versioned app (switching or creating) version, the component layout is wrongly updated to the container dnd

* fixes: container widget is not saving

* fixes: triggering confirmation box for every query with on load trigger (#7728)

* Fixes canvas background and go to app crashing (#7725)

* Fix UI issues

* Fix Scrollbar is not available after we pin the inspector.

* Fix button  jumping places if switched from extended monitor to laptop.

* fix enable pagination not getting toggled in listview

* Fix form children not being displayed

* Fix Go to app is crashing the application.

* Fix fx for canvas background color is not working.

* fixes: cloned/copied table with actions (#7758)

* fixes: calendar and form widgets (#7735)

* fixes: rendering of components in viewer for mobile (#7759)

* fixes: toggling, resizing, dropping widgets in both display preferences (#7760)

* fixes: page switch action via runjs actions (#7762)

* fixes: component validations do not get saved (#7766)

* [appdef-2] subcontainer dnd height outbound fix (#7767)

* fixes: listview children can be dragged outside its outbound limit

* cleanup

* fixes: widget inspector going to empty component (#7768)

* fixes: goToApp not running from runjs in viewer mode (#7770)

* fixes: multi-components cloning or copy/paste have same name (#7761)

* Fix disabled page is being displayed on switch page event dropdown (#7769)

* Fix kanban rendering leading to infinite look

* Fix disabled page is being displayed on switch page event dropdown

* Fix Kanban widget getting into infinite loop (#7808)

* Fix kanban rendering leading to infinite look

* Fix disabled page is being displayed on switch page event dropdown

* Fix kanban getting into infinite loop

* adds support of constants to current state of the ediotr (#7821)

* removes loader added for testing (#7822)

* [appdef] fixes - dnd container cloning edge cases (#7820)

* fixes: copy/pasting components updating wrong display preferences

* fixes: copy/pasting tabs and cloning components inside tabs

* fixes: duplication of calendar component bug

* if components in subcontainer(children) are selected via selecto along with its parent, children should not be going through duplication

* if components in subcontainer(children) are selected via selecto along with its parent, children should not be going through duplication

* fixes: Resolving App Version and Timestamp Update Challenges (#7863)

* Fixes query confirmation issue on viewer (#7862)

* [appdef ]fixes: components copied from template app to a new page or app do not render in canvas (#7867)

* fixes: components copied from template app to a new page or app does not render in canvas

* fixes: table crash on coping from other pages with columns

* adds the column exists check

* fixes: tables crash for imported apps with auto generated cols

* appdefinition refactor/cleanup (#7872)

* cleanup controllers and request calls from frontend

* removing unwanted console logs and unused variables

* revering v1 apis og

* adding length validation for page dto

* adding dtos for components

* updated dtos for components and pages

* added dto for event handlers

* fixes event handler dto

* fixes: page dto

* adds/fixes event handlers creating dtp

* fixes: event handler service and dtos

* [appdef] fixes: Creating page not changing the slug (#7873)

* fixes: Creating page not changing the slug

* removes extra whitespace

* [appdef] fixes: on importing a exported app child components are not present in the parent component (#7864)

* fixes: on importing a exported app child components are not present in the parent component

* handles parent component mapping for tabs and calendar component

* handles parent component mapping for tabs and calendar component for new versions

* [appdef] api endpoint fixes (#7888)

* fixes: moved fetching app version to v2 api

* fixes: app slug api

* Fixes CurrentUser & Mode not present in globals in inspector (#7812)

* Fix current user not being present in inspector

* Add Mode in globas in inspector

* Fix creating page not changing the slug.

* Revert "Fix creating page not changing the slug."

This reverts commit 0ff9c18ab8.

* Fixes  on adding query params in event handler, breaking the app (#7889)

* Fix on version change if left sidebar is open canvas not scrolling right

* Fix on adding query params in event handler, breaking the app

* Fix

* Fix on version change if left sidebar is open canvas not scrolling right (#7884)

* fixes: fixes on on app load switch page action via run queires (#7858)

* fixes: fixes on on app load switch page action via run queires

* Fix

* refactor

* Fix on load event not appearing on viewer

---------

Co-authored-by: Nakul Nagargade <nakul@tooljet.com>

* [appdef] fixes: event actions mapping for import-export (#7895)

* fixes: event actions mapping for import-export

* fixes: updates organisation id

* fixes: templates event mapping

* do not app again for not normalized apps

* [appdef]migrations fix (#7910)

* fixes: page attributes

* fixes: table action and column events for imported apps (prev) and app migrations

* adds processDataInBatches

* fixes: app data migrations

* create a new queryBuilder instance for each batch to ensure that there's no interference between batches

* fix: app migration

* cleanup

* cleanup

* fixes: table column data not updated on boxes changes in container (#7919)

* fixes: creating all pages from all versions (#7905)

* Fix state not changing in chart (#7900)

* Fix in chart, toggles are not working

* Update Chart.jsx

---------

Co-authored-by: Arpit <arpitnath42@gmail.com>

* fix event param not updating (#7902)

* [appdef] Pages attributes are missing on versioning or imported app (#7904)

* fixes: on creating new version pages attributes are not copied

* fixes: on importing apps with  pages attributes are not copied

* fixes: component double duplication issues

* fixes: deleting children components via selecto (#7915)

* fixes: component deletion fixes

* fixes: cloning components to a new version should also create associated events

* fixes: creating components on cloning with general styles or properties

* fixes: creatinng general properties on version

* fixes: imported app

* fixes added to app migrations

* fixes: mobile view

* fixes: Created a new version with multiple pages from second page, the new version shows the homepage with second page URL

* fixes: table crash due to columnDeletionHistory saved as an object instead of an array

* fixes: on creating new version, data_queries should be created (#7975)

* [appdef] fixes: migrations  (#7951)

* refactor migrations with batching

* event actions: switch page should be mapped to correct new page id

* fixes: importing json-schema app with multiple version: same components do not get render in the canvas

* fixes: import/export of legecy apps

* event actions mapping to correct page ids: migrations

* fixes: migrations  children not rendered in subcontainer components

* adapts to main/viewer changes

* fixes: viewer with #6698

* fixes: viewer route

* fixes: page switch via validateRoutes

* fixes: on delete version fetchApp fails

* handle error on saving changes

* skip name opts

* typo fix

* Instead of relying on the schema, we choose to use the Tooljet version as the determining factor for decoupling import flows

* fixes: slug updates from global settings

* fixes: slug app link (#8008)

* fixes: on version changed the preview link should also update (#8009)

* fixes: on cut and paste events should not cascade (#8010)

* fixes: query options to new mapped ids

* [appdef] fixes: cloning apps (#8012)

* fixes: cloning apps

* fixes: slug status from share modal

* fixes: query confirmations list on viewer (#8017)

* undo-fix

* fixes: updates current state with page data on creating new page

* Fix failing specs (#8031)

* [appdef] fixes : ghost child components are being created on imported/cloned apps and while migrating (#8026)

* fixes: ignore ghost components while importing

* added the fix in migrations

* fixes: adding other components

* fixes: table column resizes

* updates layout dto

* update component dto

* fixes: tabs children are not rendered as the are not in their repsective parent container (#8036)

* moving editorFunc to Editor.jsx

* cleanup

* fixes: e2e test for clone

* cleanup

* fixes: toggle maintaince

* bumping version

* multi-edit: ymap-fix-1

* Revert "multi-edit: ymap-fix-1"

This reverts commit 8b799c3c51.

* [appdef] fixes: viewer route: keeps on reloading for private apps (#8051)

* fixes: viewer route: keeps on reloading for pribate apps

* should return the response

* test: ymap updates-1

* fixes: v1 apps with dq queries resuts in app crash

* Updated import spec

* Revert "Updated import spec"

This reverts commit 802136cdc3.

* Fixed failed platform test cases for app desinition re-design (#8053)

* Fix failed platform test cases

* Modify user permission test cases

* fixes: trial-5: fixing vanishing of components

* Revert "fixes: trial-5: fixing vanishing of components"

This reverts commit a22aec12c7.

* fixes: trail-6: fix

* fixes: trail-7: fix

* Revert "fixes: trail-7: fix"

This reverts commit 08f373c415.

* Revert "fixes: trail-6: fix"

This reverts commit c4e19b5d05.

* multi-edit: ymap-fix

* Revert "multi-edit: ymap-fix"

This reverts commit 92f49c0cde.

* fixes: multi-user updates: adding or removing

* event handlers sycned for multi-user

* should take slug instead of appid if slug is present

* updating adding ymap logic

* versioning with multi-user

* fixes: saving issue

* dont skip ymap

* adds delay to ymap

* ymap-update-order-fixed

* ymap-update-order-fixed-1.1

* ymap-update-order-fixed-1.2

* test=fix

* Updated import spec (#8061)

* cleanup

* lint fixed

* fixes: cloning apps with tabs

* veiwer on event should return

* fixes: event should map with show/hide modal component id

* Fix failing appbuilder specs (#8117)

* cherrypicked ee/00195c064

* bumping version to v2.24.0

* fixed modal actionid typo

* fixes: slugs issues for released an public apps (#8119)

* Fix failed test cases (#8121)

* reverting global slug input

* fixes: versioning with cloned page and ghost components (#8122)

---------

Co-authored-by: Nakul Nagargade <133095394+nakulnagargade@users.noreply.github.com>
Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com>
Co-authored-by: Nakul Nagargade <nakul@tooljet.com>
Co-authored-by: Midhun Kumar E <midhun752@gmail.com>
Co-authored-by: nandinisaha13 <nandinisaha13@gmail.com>
Co-authored-by: Mekhla Asopa <dadhichmekhla@gmail.com>
Co-authored-by: Ajith KV <ajith.jaban@gmail.com>
Co-authored-by: Mekhla Asopa <59684099+Mekhla-Asopa@users.noreply.github.com>
2023-11-08 11:09:47 +05:30

793 lines
24 KiB
TypeScript

/* eslint-disable prefer-const */
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { getConnection, getManager, Repository } from 'typeorm';
import { OrganizationUser } from 'src/entities/organization_user.entity';
import { Organization } from 'src/entities/organization.entity';
import { User } from 'src/entities/user.entity';
import { App } from 'src/entities/app.entity';
import { File } from 'src/entities/file.entity';
import { Plugin } from 'src/entities/plugin.entity';
import { INestApplication, ValidationPipe, VersioningType, VERSION_NEUTRAL } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { AppModule } from 'src/app.module';
import { AppVersion } from 'src/entities/app_version.entity';
import { DataQuery } from 'src/entities/data_query.entity';
import { DataSource } from 'src/entities/data_source.entity';
import { PluginsService } from 'src/services/plugins.service';
import { DataSourcesService } from 'src/services/data_sources.service';
import { PluginsModule } from 'src/modules/plugins/plugins.module';
import { DataSourcesModule } from 'src/modules/data_sources/data_sources.module';
import { ThreadRepository } from 'src/repositories/thread.repository';
import { GroupPermission } from 'src/entities/group_permission.entity';
import { UserGroupPermission } from 'src/entities/user_group_permission.entity';
import { AppGroupPermission } from 'src/entities/app_group_permission.entity';
import { AllExceptionsFilter } from 'src/all-exceptions-filter';
import { Logger } from 'nestjs-pino';
import { WsAdapter } from '@nestjs/platform-ws';
import { AppsModule } from 'src/modules/apps/apps.module';
import { LibraryAppCreationService } from '@services/library_app_creation.service';
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { v4 as uuidv4 } from 'uuid';
import { CreateFileDto } from '@dto/create-file.dto';
import { CreatePluginDto } from '@dto/create-plugin.dto';
import * as request from 'supertest';
import { AppEnvironment } from 'src/entities/app_environments.entity';
import { defaultAppEnvironments } from 'src/helpers/utils.helper';
import { DataSourceOptions } from 'src/entities/data_source_options.entity';
import * as cookieParser from 'cookie-parser';
export async function createNestAppInstance(): Promise<INestApplication> {
let app: INestApplication;
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
providers: [],
}).compile();
app = moduleRef.createNestApplication();
app.setGlobalPrefix('api');
app.use(cookieParser());
app.useGlobalFilters(new AllExceptionsFilter(moduleRef.get(Logger)));
app.useWebSocketAdapter(new WsAdapter(app));
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: VERSION_NEUTRAL,
});
await app.init();
return app;
}
export async function createNestAppInstanceWithEnvMock(): Promise<{
app: INestApplication;
mockConfig: DeepMocked<ConfigService>;
}> {
let app: INestApplication;
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
providers: [
{
provide: ConfigService,
useValue: createMock<ConfigService>(),
},
],
}).compile();
app = moduleRef.createNestApplication();
app.setGlobalPrefix('api');
app.use(cookieParser());
app.useGlobalFilters(new AllExceptionsFilter(moduleRef.get(Logger)));
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
app.useWebSocketAdapter(new WsAdapter(app));
await app.init();
return { app, mockConfig: moduleRef.get(ConfigService) };
}
export function authHeaderForUser(user: User, organizationId?: string, isPasswordLogin = true): string {
const configService = new ConfigService();
const jwtService = new JwtService({
secret: configService.get<string>('SECRET_KEY_BASE'),
});
const authPayload = {
username: user.id,
sub: user.email,
organizationId: organizationId || user.defaultOrganizationId,
isPasswordLogin,
};
const authToken = jwtService.sign(authPayload);
return `Bearer ${authToken}`;
}
export async function clearDB() {
const entities = getConnection().entityMetadatas;
for (const entity of entities) {
const repository = getConnection().getRepository(entity.name);
await repository.query(`TRUNCATE ${entity.tableName} RESTART IDENTITY CASCADE;`);
}
}
export async function createApplication(nestApp, { name, user, isPublic, slug }: any, shouldCreateEnvs = true) {
let appRepository: Repository<App>;
appRepository = nestApp.get('AppRepository');
user = user || (await (await createUser(nestApp, {})).user);
if (shouldCreateEnvs) {
await createAppEnvironments(nestApp, user.organizationId);
}
const newApp = await appRepository.save(
appRepository.create({
name,
user,
slug,
isPublic: isPublic || false,
organizationId: user.organizationId,
createdAt: new Date(),
updatedAt: new Date(),
})
);
await maybeCreateAdminAppGroupPermissions(nestApp, newApp);
await maybeCreateAllUsersAppGroupPermissions(nestApp, newApp);
return newApp;
}
export async function importAppFromTemplates(nestApp, user, identifier) {
const service = nestApp.select(AppsModule).get(LibraryAppCreationService);
return service.perform(user, identifier);
}
export async function createApplicationVersion(
nestApp,
application,
{ name = 'v0', definition = null, currentEnvironmentId = null } = {}
) {
let appVersionsRepository: Repository<AppVersion>;
let appEnvironmentsRepository: Repository<AppEnvironment>;
appVersionsRepository = nestApp.get('AppVersionRepository');
appEnvironmentsRepository = nestApp.get('AppEnvironmentRepository');
const environments = await appEnvironmentsRepository.find({
where: {
organizationId: application.organizationId,
},
});
const envId = currentEnvironmentId
? currentEnvironmentId
: defaultAppEnvironments.length > 1
? environments.find((env) => env.priority === 1)?.id
: environments[0].id;
return await appVersionsRepository.save(
appVersionsRepository.create({
app: application,
name,
currentEnvironmentId: envId,
definition,
})
);
}
export async function getAllEnvironments(nestApp, organizationId): Promise<AppEnvironment[]> {
let appEnvironmentRepository: Repository<AppEnvironment>;
appEnvironmentRepository = nestApp.get('AppEnvironmentRepository');
return await appEnvironmentRepository.find({
where: {
organizationId,
},
order: {
priority: 'ASC',
},
});
}
export async function createAppEnvironments(nestApp, organizationId): Promise<AppEnvironment[]> {
let appEnvironmentRepository: Repository<AppEnvironment>;
appEnvironmentRepository = nestApp.get('AppEnvironmentRepository');
return await Promise.all(
defaultAppEnvironments.map(async (env) => {
return await appEnvironmentRepository.save(
appEnvironmentRepository.create({
organizationId,
name: env.name,
priority: env.priority,
isDefault: env.isDefault,
})
);
})
);
}
export async function createUser(
nestApp,
{
firstName,
lastName,
email,
groups,
organization,
status,
invitationToken,
formLoginStatus = true,
organizationName = `${email}'s workspace`,
ssoConfigs = [],
enableSignUp = false,
}: {
firstName?: string;
lastName?: string;
email?: string;
groups?: Array<string>;
organization?: Organization;
status?: string;
invitationToken?: string;
formLoginStatus?: boolean;
organizationName?: string;
ssoConfigs?: Array<any>;
enableSignUp?: boolean;
},
existingUser?: User
) {
let userRepository: Repository<User>;
let organizationRepository: Repository<Organization>;
let organizationUsersRepository: Repository<OrganizationUser>;
userRepository = nestApp.get('UserRepository');
organizationRepository = nestApp.get('OrganizationRepository');
organizationUsersRepository = nestApp.get('OrganizationUserRepository');
organization =
organization ||
(await organizationRepository.save(
organizationRepository.create({
name: organizationName,
enableSignUp,
createdAt: new Date(),
updatedAt: new Date(),
ssoConfigs: [
{
sso: 'form',
enabled: formLoginStatus,
},
...ssoConfigs,
],
})
));
let user: User;
if (!existingUser) {
user = await userRepository.save(
userRepository.create({
firstName: firstName || 'test',
lastName: lastName || 'test',
email: email || 'dev@tooljet.io',
password: 'password',
invitationToken,
defaultOrganizationId: organization.id,
createdAt: new Date(),
updatedAt: new Date(),
status: invitationToken ? 'invited' : 'active',
})
);
} else {
user = existingUser;
}
user.organizationId = organization.id;
const orgUser = await organizationUsersRepository.save(
organizationUsersRepository.create({
user: user,
organization,
invitationToken: status === 'invited' ? uuidv4() : null,
status: status || 'active',
role: 'all_users',
createdAt: new Date(),
updatedAt: new Date(),
})
);
await maybeCreateDefaultGroupPermissions(nestApp, user.organizationId);
await createUserGroupPermissions(
nestApp,
user,
groups || ['all_users', 'admin'] // default groups
);
return { organization, user, orgUser };
}
export async function createUserGroupPermissions(nestApp, user, groups) {
const groupPermissionRepository: Repository<GroupPermission> = nestApp.get('GroupPermissionRepository');
const userGroupPermissionRepository: Repository<UserGroupPermission> = nestApp.get('UserGroupPermissionRepository');
let userGroupPermissions = [];
for (const group of groups) {
let groupPermission: GroupPermission;
if (group == 'admin' || group == 'all_users') {
groupPermission = await groupPermissionRepository.findOneOrFail({
where: {
organizationId: user.organizationId,
group: group,
},
});
} else {
groupPermission =
(await groupPermissionRepository.findOne({
where: {
organizationId: user.organizationId,
group: group,
},
})) ||
groupPermissionRepository.create({
organizationId: user.organizationId,
group: group,
});
await groupPermissionRepository.save(groupPermission);
}
const userGroupPermission = userGroupPermissionRepository.create({
groupPermissionId: groupPermission.id,
userId: user.id,
});
await userGroupPermissionRepository.save(userGroupPermission);
userGroupPermissions.push(userGroupPermission);
}
return userGroupPermissions;
}
export async function createAppGroupPermission(nestApp, app, groupId, permissions) {
const appGroupPermissionRepository: Repository<AppGroupPermission> = nestApp.get('AppGroupPermissionRepository');
const appGroupPermission = appGroupPermissionRepository.create({
groupPermissionId: groupId,
appId: app.id,
...permissions,
});
await appGroupPermissionRepository.save(appGroupPermission);
return appGroupPermission;
}
export async function createGroupPermission(nestApp, params) {
const groupPermissionRepository: Repository<GroupPermission> = nestApp.get('GroupPermissionRepository');
let groupPermission = groupPermissionRepository.create({
...params,
});
await groupPermissionRepository.save(groupPermission);
return groupPermission;
}
export async function maybeCreateDefaultGroupPermissions(nestApp, organizationId) {
const groupPermissionRepository: Repository<GroupPermission> = nestApp.get('GroupPermissionRepository');
const defaultGroups = ['all_users', 'admin'];
for (let group of defaultGroups) {
const orgDefaultGroupPermissions = await groupPermissionRepository.find({
where: {
organizationId: organizationId,
group: group,
},
});
if (orgDefaultGroupPermissions.length == 0) {
const groupPermission = groupPermissionRepository.create({
organizationId: organizationId,
group: group,
appCreate: group == 'admin',
appDelete: group == 'admin',
folderCreate: group == 'admin',
orgEnvironmentVariableCreate: group == 'admin',
orgEnvironmentVariableUpdate: group == 'admin',
orgEnvironmentVariableDelete: group == 'admin',
orgEnvironmentConstantCreate: group == 'admin',
orgEnvironmentConstantDelete: group == 'admin',
folderUpdate: group == 'admin',
folderDelete: group == 'admin',
});
await groupPermissionRepository.save(groupPermission);
}
}
}
export async function maybeCreateAdminAppGroupPermissions(nestApp, app) {
const groupPermissionRepository: Repository<GroupPermission> = nestApp.get('GroupPermissionRepository');
const appGroupPermissionRepository: Repository<AppGroupPermission> = nestApp.get('AppGroupPermissionRepository');
const orgAdminGroupPermissions = await groupPermissionRepository.findOne({
where: {
organizationId: app.organizationId,
group: 'admin',
},
});
if (orgAdminGroupPermissions) {
const adminGroupPermissions = {
read: true,
update: true,
delete: true,
};
const appGroupPermission = appGroupPermissionRepository.create({
groupPermissionId: orgAdminGroupPermissions.id,
appId: app.id,
...adminGroupPermissions,
});
await appGroupPermissionRepository.save(appGroupPermission);
}
}
export async function maybeCreateAllUsersAppGroupPermissions(nestApp, app) {
const groupPermissionRepository: Repository<GroupPermission> = nestApp.get('GroupPermissionRepository');
const appGroupPermissionRepository: Repository<AppGroupPermission> = nestApp.get('AppGroupPermissionRepository');
const allUsersGroup = await groupPermissionRepository.findOne({
where: {
organizationId: app.organizationId,
group: 'all_users',
},
});
if (allUsersGroup) {
const permissions = {
read: false,
update: false,
delete: false,
};
const appGroupPermission = appGroupPermissionRepository.create({
groupPermissionId: allUsersGroup.id,
appId: app.id,
...permissions,
});
await appGroupPermissionRepository.save(appGroupPermission);
}
}
export async function addAppToGroupPermission(app: App, groupPermission: GroupPermission, permissions = {}) {
getManager().create(AppGroupPermission, {
groupPermissionId: groupPermission.id,
appId: app.id,
...permissions,
});
}
export async function addAllUsersGroupToUser(nestApp, user) {
const groupPermissionRepository: Repository<GroupPermission> = nestApp.get('GroupPermissionRepository');
const userGroupPermissionRepository: Repository<UserGroupPermission> = nestApp.get('UserGroupPermissionRepository');
const orgDefaultGroupPermissions = await groupPermissionRepository.findOneOrFail({
where: {
organizationId: user.organizationId,
group: 'all_users',
},
});
const userGroupPermission = userGroupPermissionRepository.create({
groupPermissionId: orgDefaultGroupPermissions.id,
userId: user.id,
});
await userGroupPermissionRepository.save(userGroupPermission);
return user;
}
export async function createDataSource(
nestApp,
{ appVersion, name, kind, type = 'default', options, environmentId = null }: any
) {
let dataSourceRepository: Repository<DataSource>;
dataSourceRepository = nestApp.get('DataSourceRepository');
const dataSource = await dataSourceRepository.save(
dataSourceRepository.create({
name,
kind,
appVersion,
type,
createdAt: new Date(),
updatedAt: new Date(),
})
);
environmentId && (await createDataSourceOption(nestApp, { dataSource, environmentId, options }));
return dataSource;
}
export async function createDataQuery(nestApp, { name = 'defaultquery', dataSource, appVersion, options }: any) {
let dataQueryRepository: Repository<DataQuery>;
dataQueryRepository = nestApp.get('DataQueryRepository');
return await dataQueryRepository.save(
dataQueryRepository.create({
options,
name,
dataSource,
appVersion,
createdAt: new Date(),
updatedAt: new Date(),
})
);
}
export async function createDataSourceOption(nestApp, { dataSource, environmentId, options }: any) {
let dataSourceOptionsRepository: Repository<DataSourceOptions>;
dataSourceOptionsRepository = nestApp.get('DataSourceOptionsRepository');
const dataSourcesService = nestApp.select(DataSourcesModule).get(DataSourcesService);
return await dataSourceOptionsRepository.save(
dataSourceOptionsRepository.create({
options: await dataSourcesService.parseOptionsForCreate(options),
dataSourceId: dataSource.id,
environmentId,
})
);
}
export async function createFile(nestApp: any) {
let fileRepository: Repository<File>;
fileRepository = nestApp.get('FileRepository');
const createFileDto = new CreateFileDto();
createFileDto.filename = 'testfile';
createFileDto.data = Buffer.from([1, 2, 3, 4]);
return await fileRepository.save(fileRepository.create(createFileDto));
}
export async function installPlugin(nestApp: any, { name, description, id, version }: any) {
let pluginRepository: Repository<Plugin>;
pluginRepository = nestApp.get('PluginRepository');
const createPluginDto = new CreatePluginDto();
createPluginDto.id = id;
createPluginDto.name = name;
createPluginDto.version = version;
createPluginDto.description = description;
const pluginsService = nestApp.select(PluginsModule).get(PluginsService);
return await pluginRepository.save(pluginsService.install(createPluginDto));
}
export async function createThread(_nestApp, { appId, x, y, userId, organizationId, appVersionsId }: any) {
const threadRepository = new ThreadRepository();
return await threadRepository.createThread(
{
appId,
x,
y,
isResolved: false,
organizationId,
appVersionsId,
pageId: 'placeholder',
},
userId,
organizationId
);
}
export async function setupOrganization(nestApp) {
const adminUserData = await createUser(nestApp, {
email: 'admin@tooljet.io',
groups: ['all_users', 'admin'],
});
const adminUser = adminUserData.user;
const organization = adminUserData.organization;
const defaultUserData = await createUser(nestApp, {
email: 'developer@tooljet.io',
groups: ['all_users'],
organization,
});
const defaultUser = defaultUserData.user;
const app = await createApplication(nestApp, {
user: adminUser,
name: 'sample app',
isPublic: false,
});
return { adminUser, defaultUser, app };
}
export const generateRedirectUrl = async (
email: string,
current_organization?: Organization,
isOrgInvitation?: boolean,
isSSO = true
) => {
const manager = getManager();
const user = await manager.findOneOrFail(User, { where: { email: email } });
const organizationToken = user.organizationUsers?.find(
(ou) => ou.organizationId === current_organization?.id
)?.invitationToken;
return `${process.env['TOOLJET_HOST']}${
isOrgInvitation ? `/organization-invitations/${organizationToken}` : `/invitations/${user.invitationToken}`
}${
organizationToken
? `${!isOrgInvitation ? `/workspaces/${organizationToken}` : ''}?oid=${current_organization?.id}&`
: isSSO
? '?'
: ''
}${isSSO ? 'source=sso' : ''}`;
};
export const createSSOMockConfig = (mockConfig) => {
jest.spyOn(mockConfig, 'get').mockImplementation((key: string) => {
switch (key) {
case 'SSO_GOOGLE_OAUTH2_CLIENT_ID':
return 'google-client-id';
case 'SSO_GIT_OAUTH2_CLIENT_ID':
return 'git-client-id';
case 'SSO_GIT_OAUTH2_CLIENT_SECRET':
return 'git-secret';
case 'SSO_ACCEPTED_DOMAINS':
return 'tooljet.io,tooljet.com';
default:
return process.env[key];
}
});
};
export const verifyInviteToken = async (app: INestApplication, user: User, verifyForSignup = false) => {
let organizationUsersRepository: Repository<OrganizationUser>;
organizationUsersRepository = app.get('OrganizationUserRepository');
const { invitationToken } = user;
const { invitationToken: orgInviteToken } = await organizationUsersRepository.findOneOrFail({
where: { userId: user.id },
});
const response = await request(app.getHttpServer()).get(
`/api/verify-invite-token?token=${invitationToken}${
!verifyForSignup && orgInviteToken ? `&organizationToken=${orgInviteToken}` : ''
}`
);
const {
body: { onboarding_details },
status,
} = response;
expect(status).toBe(200);
expect(Object.keys(onboarding_details)).toEqual(['password', 'questions']);
await user.reload();
expect(user.status).toBe('verified');
return response;
};
export const setUpAccountFromToken = async (app: INestApplication, user: User, org: Organization, payload) => {
const response = await request(app.getHttpServer()).post('/api/setup-account-from-token').send(payload);
const { status } = response;
expect(status).toBe(201);
const { email, first_name, last_name, current_organization_id } = response.body;
expect(email).toEqual(user.email);
expect(first_name).toEqual(user.firstName);
expect(last_name).toEqual(user.lastName);
expect(current_organization_id).toBe(org.id);
await user.reload();
expect(user.status).toBe('active');
expect(user.defaultOrganizationId).toBe(org.id);
};
export const getPathFromUrl = (url) => {
return url.split('?')[0];
};
export const createFirstUser = async (app: INestApplication) => {
let userRepository: Repository<User> = app.get('UserRepository');
await request(app.getHttpServer())
.post('/api/setup-admin')
.send({ email: 'firstuser@tooljet.com', name: 'Admin', password: 'password', workspace: 'tooljet' });
return await userRepository.findOneOrFail({
where: { email: 'firstuser@tooljet.com' },
relations: ['organizationUsers'],
});
};
export const generateAppDefaults = async (
app: INestApplication,
user: any,
{ isQueryNeeded = true, isDataSourceNeeded = true, isAppPublic = false, dsKind = 'restapi', dsOptions = [{}] }
) => {
const application = await createApplication(
app,
{
name: 'name',
user: user,
isPublic: isAppPublic,
},
false
);
const appEnvironments = await createAppEnvironments(app, user.organizationId);
const appVersion = await createApplicationVersion(app, application);
let dataQuery: any;
let dataSource: any;
if (isDataSourceNeeded) {
dataSource = await createDataSource(app, {
name: 'name',
kind: dsKind,
appVersion,
});
await createDataSourceOption(app, { dataSource, environmentId: appEnvironments[0].id, options: dsOptions });
if (isQueryNeeded) {
dataQuery = await createDataQuery(app, {
dataSource,
appVersion,
options: {
method: 'get',
url: 'https://api.github.com/repos/tooljet/tooljet/stargazers',
url_params: [],
headers: [],
body: [],
},
});
}
}
return { application, appVersion, dataSource, dataQuery };
};
export const getAppWithAllDetails = async (id: string) => {
const app = await getManager()
.createQueryBuilder(App, 'app')
.where('app.id = :id', { id })
.innerJoinAndSelect('app.appVersions', 'versions')
.leftJoinAndSelect('versions.dataSources', 'dataSources')
.leftJoinAndSelect('versions.dataQueries', 'dataQueries')
.getOneOrFail();
const dataQueries = [];
const dataSources = [];
app.appVersions.map((version) => {
dataSources.push(...version.dataSources);
dataQueries.push(...version.dataQueries);
version.dataSources = undefined;
version.dataQueries = undefined;
});
app['dataQueries'] = dataQueries;
app['dataSources'] = dataSources;
return app;
};
export const authenticateUser = async (app: INestApplication, email = 'admin@tooljet.io', password = 'password') => {
const sessionResponse = await request
.agent(app.getHttpServer())
.post('/api/authenticate')
.send({ email, password })
.expect(201);
return { user: sessionResponse.body, tokenCookie: sessionResponse.headers['set-cookie'] };
};
export const logoutUser = async (app: INestApplication, tokenCookie: any, organization_id: string) => {
return await request
.agent(app.getHttpServer())
.get('/api/logout')
.set('tj-workspace-id', organization_id)
.set('Cookie', tokenCookie)
.expect(200);
};