2
0

♻️ Remove @typebot.io/schemas from @typebot.io/lib

This commit is contained in:
Baptiste Arnaud
2024-03-15 16:32:29 +01:00
parent b53242ce6a
commit 5073be2439
186 changed files with 809 additions and 581 deletions

View File

@@ -0,0 +1,236 @@
import {
Plan,
Prisma,
PrismaClient,
User,
Workspace,
WorkspaceRole,
} from '@typebot.io/prisma'
import { createId } from '@typebot.io/lib/createId'
import { Typebot, TypebotV6, HttpRequest } from '@typebot.io/schemas'
import { readFileSync } from 'fs'
import { proWorkspaceId, userId } from './databaseSetup'
import {
parseTestTypebot,
parseTypebotToPublicTypebot,
} from './databaseHelpers'
const prisma = new PrismaClient()
type CreateFakeResultsProps = {
typebotId: string
count: number
customResultIdPrefix?: string
isChronological?: boolean
}
export const injectFakeResults = async ({
count,
customResultIdPrefix,
typebotId,
isChronological,
}: CreateFakeResultsProps) => {
const resultIdPrefix = customResultIdPrefix ?? createId()
await prisma.result.createMany({
data: [
...Array.from(Array(count)).map((_, idx) => {
const today = new Date()
const rand = Math.random()
return {
id: `${resultIdPrefix}-result${idx}`,
typebotId,
createdAt: isChronological
? new Date(
today.setTime(today.getTime() + 1000 * 60 * 60 * 24 * idx)
)
: new Date(),
isCompleted: rand > 0.5,
hasStarted: true,
variables: [],
} satisfies Prisma.ResultCreateManyInput
}),
],
})
return createAnswers({ resultIdPrefix, count })
}
const createAnswers = ({
count,
resultIdPrefix,
}: { resultIdPrefix: string } & Pick<CreateFakeResultsProps, 'count'>) => {
return prisma.answer.createMany({
data: [
...Array.from(Array(count)).map((_, idx) => ({
resultId: `${resultIdPrefix}-result${idx}`,
content: `content${idx}`,
blockId: 'block1',
groupId: 'group1',
})),
],
})
}
export const importTypebotInDatabase = async (
path: string,
updates?: Partial<Typebot>
) => {
const typebotFile = JSON.parse(readFileSync(path).toString())
const typebot = {
events: null,
...typebotFile,
workspaceId: proWorkspaceId,
...updates,
}
await prisma.typebot.create({
data: parseCreateTypebot(typebot),
})
return prisma.publicTypebot.create({
data: {
...parseTypebotToPublicTypebot(
updates?.id ? `${updates?.id}-public` : 'publicBot',
typebot
),
events: typebot.events === null ? Prisma.DbNull : typebot.events,
},
})
}
export const deleteWorkspaces = async (workspaceIds: string[]) => {
await prisma.workspace.deleteMany({
where: { id: { in: workspaceIds } },
})
}
export const deleteTypebots = async (typebotIds: string[]) => {
await prisma.typebot.deleteMany({
where: { id: { in: typebotIds } },
})
}
export const deleteCredentials = async (credentialIds: string[]) => {
await prisma.credentials.deleteMany({
where: { id: { in: credentialIds } },
})
}
export const deleteWebhooks = async (webhookIds: string[]) => {
await prisma.webhook.deleteMany({
where: { id: { in: webhookIds } },
})
}
export const createWorkspaces = async (workspaces: Partial<Workspace>[]) => {
const workspaceIds = workspaces.map((workspace) => workspace.id ?? createId())
await prisma.workspace.createMany({
data: workspaces.map((workspace, index) => ({
id: workspaceIds[index],
name: 'Free workspace',
plan: Plan.FREE,
...workspace,
})),
})
await prisma.memberInWorkspace.createMany({
data: workspaces.map((_, index) => ({
userId,
workspaceId: workspaceIds[index],
role: WorkspaceRole.ADMIN,
})),
})
return workspaceIds
}
export const updateUser = (data: Partial<User>) =>
prisma.user.update({
data: {
...data,
onboardingCategories: data.onboardingCategories ?? [],
displayedInAppNotifications:
data.displayedInAppNotifications ?? Prisma.DbNull,
},
where: {
id: userId,
},
})
export const createWebhook = async (
typebotId: string,
webhookProps?: Partial<HttpRequest>
) => {
try {
await prisma.webhook.delete({ where: { id: 'webhook1' } })
} catch {}
return prisma.webhook.create({
data: {
method: 'GET',
typebotId,
id: 'webhook1',
...webhookProps,
queryParams: webhookProps?.queryParams ?? [],
headers: webhookProps?.headers ?? [],
},
})
}
export const createTypebots = async (partialTypebots: Partial<TypebotV6>[]) => {
const typebotsWithId = partialTypebots.map((typebot) => {
const typebotId = typebot.id ?? createId()
return {
...typebot,
id: typebotId,
publicId: typebot.publicId ?? typebotId + '-public',
}
})
await prisma.typebot.createMany({
data: typebotsWithId.map(parseTestTypebot).map(parseCreateTypebot),
})
return prisma.publicTypebot.createMany({
data: typebotsWithId.map((t) => ({
...parseTypebotToPublicTypebot(t.publicId, parseTestTypebot(t)),
})) as any,
})
}
export const updateTypebot = async (
partialTypebot: Partial<Typebot> & { id: string }
) => {
await prisma.typebot.updateMany({
where: { id: partialTypebot.id },
data: parseUpdateTypebot(partialTypebot),
})
return prisma.publicTypebot.updateMany({
where: { typebotId: partialTypebot.id },
data: {
...partialTypebot,
events:
partialTypebot.events === null ? Prisma.DbNull : partialTypebot.events,
},
})
}
export const updateWorkspace = async (
id: string,
data: Prisma.WorkspaceUncheckedUpdateManyInput
) => {
await prisma.workspace.updateMany({
where: { id: proWorkspaceId },
data,
})
}
export const parseCreateTypebot = (typebot: Typebot) => ({
...typebot,
resultsTablePreferences:
typebot.resultsTablePreferences === null
? Prisma.DbNull
: typebot.resultsTablePreferences,
events: typebot.events === null ? Prisma.DbNull : typebot.events,
})
const parseUpdateTypebot = (typebot: Partial<Typebot>) => ({
...typebot,
resultsTablePreferences:
typebot.resultsTablePreferences === null
? Prisma.DbNull
: typebot.resultsTablePreferences,
events: typebot.events === null ? Prisma.DbNull : typebot.events,
})

View File

@@ -0,0 +1,128 @@
import {
BlockV5,
BlockV6,
PublicTypebot,
Typebot,
TypebotV6,
} from '@typebot.io/schemas'
import { isDefined } from '@typebot.io/lib/utils'
import { createId } from '@typebot.io/lib/createId'
import { proWorkspaceId } from './databaseSetup'
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
import { EventType } from '@typebot.io/schemas/features/events/constants'
export const parseTestTypebot = (partialTypebot: Partial<Typebot>): Typebot => {
const version = partialTypebot.version ?? ('3' as any)
return {
id: createId(),
version,
workspaceId: proWorkspaceId,
folderId: null,
name: 'My typebot',
theme: {},
settings: {},
publicId: null,
updatedAt: new Date(),
createdAt: new Date(),
customDomain: null,
icon: null,
selectedThemeTemplateId: null,
isArchived: false,
isClosed: false,
resultsTablePreferences: null,
whatsAppCredentialsId: null,
riskLevel: null,
events:
version === '6'
? [
{
id: 'group1',
type: EventType.START,
graphCoordinates: { x: 0, y: 0 },
outgoingEdgeId: 'edge1',
},
]
: null,
variables: [{ id: 'var1', name: 'var1' }],
...partialTypebot,
edges: [
{
id: 'edge1',
from: { blockId: 'block0' },
to: { groupId: 'group1' },
},
],
groups: (version === '6'
? partialTypebot.groups ?? []
: [
{
id: 'group0',
title: 'Group #0',
blocks: [
{
id: 'block0',
type: 'start',
label: 'Start',
outgoingEdgeId: 'edge1',
},
],
graphCoordinates: { x: 0, y: 0 },
},
...(partialTypebot.groups ?? []),
]) as any[],
}
}
export const parseTypebotToPublicTypebot = (
id: string,
typebot: Typebot
): Omit<PublicTypebot, 'createdAt' | 'updatedAt'> => ({
id,
version: typebot.version,
groups: typebot.groups,
typebotId: typebot.id,
theme: typebot.theme,
settings: typebot.settings,
variables: typebot.variables,
edges: typebot.edges,
events: typebot.events,
})
type Options = {
withGoButton?: boolean
}
export const parseDefaultGroupWithBlock = (
block: Partial<BlockV6>,
options?: Options
): Pick<TypebotV6, 'groups'> => ({
groups: [
{
graphCoordinates: { x: 200, y: 200 },
id: 'group1',
blocks: [
options?.withGoButton
? {
id: 'block1',
groupId: 'group1',
type: InputBlockType.CHOICE,
items: [
{
id: 'item1',
blockId: 'block1',
content: 'Go',
},
],
options: {},
}
: undefined,
{
id: 'block2',
...block,
} as BlockV5,
].filter(isDefined) as BlockV6[],
title: 'Group #1',
},
],
})

View File

@@ -0,0 +1,191 @@
import {
GraphNavigation,
Plan,
PrismaClient,
WorkspaceRole,
} from '@typebot.io/prisma'
import { encrypt } from '@typebot.io/lib/api/encryption/encrypt'
const prisma = new PrismaClient()
export const apiToken = 'jirowjgrwGREHE'
export const userId = 'userId'
export const otherUserId = 'otherUserId'
export const proWorkspaceId = 'proWorkspace'
export const freeWorkspaceId = 'freeWorkspace'
export const starterWorkspaceId = 'starterWorkspace'
export const lifetimeWorkspaceId = 'lifetimeWorkspaceId'
export const customWorkspaceId = 'customWorkspaceId'
const setupWorkspaces = async () => {
await prisma.workspace.createMany({
data: [
{
id: freeWorkspaceId,
name: 'Free workspace',
plan: Plan.FREE,
},
{
id: starterWorkspaceId,
name: 'Starter workspace',
stripeId: 'cus_LnPDugJfa18N41',
plan: Plan.STARTER,
},
{
id: proWorkspaceId,
name: 'Pro workspace',
plan: Plan.PRO,
},
{
id: lifetimeWorkspaceId,
name: 'Lifetime workspace',
plan: Plan.LIFETIME,
},
{
id: customWorkspaceId,
name: 'Custom workspace',
plan: Plan.CUSTOM,
customChatsLimit: 100000,
customStorageLimit: 50,
customSeatsLimit: 20,
},
],
})
}
export const setupUsers = async () => {
await prisma.user.create({
data: {
id: userId,
email: 'user@email.com',
name: 'John Doe',
graphNavigation: GraphNavigation.TRACKPAD,
onboardingCategories: [],
apiTokens: {
createMany: {
data: [
{
name: 'Token 1',
token: apiToken,
createdAt: new Date(2022, 1, 1),
},
{
name: 'Github',
token: 'jirowjgrwGREHEgdrgithub',
createdAt: new Date(2022, 1, 2),
},
{
name: 'N8n',
token: 'jirowjgrwGREHrgwhrwn8n',
createdAt: new Date(2022, 1, 3),
},
],
},
},
},
})
await prisma.user.create({
data: {
id: otherUserId,
email: 'other-user@email.com',
name: 'James Doe',
onboardingCategories: [],
},
})
return prisma.memberInWorkspace.createMany({
data: [
{
role: WorkspaceRole.ADMIN,
userId,
workspaceId: freeWorkspaceId,
},
{
role: WorkspaceRole.ADMIN,
userId,
workspaceId: starterWorkspaceId,
},
{
role: WorkspaceRole.ADMIN,
userId,
workspaceId: proWorkspaceId,
},
{
role: WorkspaceRole.ADMIN,
userId,
workspaceId: lifetimeWorkspaceId,
},
{
role: WorkspaceRole.ADMIN,
userId,
workspaceId: customWorkspaceId,
},
],
})
}
const setupCredentials = async () => {
const { encryptedData, iv } = await encrypt({
expiry_date: 1642441058842,
access_token:
'ya29.A0ARrdaM--PV_87ebjywDJpXKb77NBFJl16meVUapYdfNv6W6ZzqqC47fNaPaRjbDbOIIcp6f49cMaX5ndK9TAFnKwlVqz3nrK9nLKqgyDIhYsIq47smcAIZkK56SWPx3X3DwAFqRu2UPojpd2upWwo-3uJrod',
// This token is linked to a test Google account (typebot.test.user@gmail.com)
refresh_token:
'1//039xWRt8YaYa3CgYIARAAGAMSNwF-L9Iru9FyuTrDSa7lkSceggPho83kJt2J29G69iEhT1C6XV1vmo6bQS9puL_R2t8FIwR3gek',
})
return prisma.credentials.createMany({
data: [
{
name: 'pro-user@email.com',
type: 'google sheets',
data: encryptedData,
workspaceId: proWorkspaceId,
iv,
},
],
})
}
export const setupDatabase = async () => {
await setupWorkspaces()
await setupUsers()
return setupCredentials()
}
export const teardownDatabase = async () => {
await prisma.webhook.deleteMany({
where: {
typebot: {
workspace: {
members: {
some: { userId: { in: [userId, otherUserId] } },
},
},
},
},
})
await prisma.workspace.deleteMany({
where: {
members: {
some: { userId: { in: [userId, otherUserId] } },
},
},
})
await prisma.workspace.deleteMany({
where: {
id: {
in: [
proWorkspaceId,
freeWorkspaceId,
starterWorkspaceId,
lifetimeWorkspaceId,
customWorkspaceId,
],
},
},
})
await prisma.user.deleteMany({
where: { id: { in: [userId, otherUserId] } },
})
}

View File

@@ -0,0 +1,6 @@
import { setupDatabase, teardownDatabase } from './databaseSetup'
export const globalSetup = async () => {
await teardownDatabase()
await setupDatabase()
}

View File

@@ -0,0 +1,19 @@
{
"name": "@typebot.io/playwright",
"version": "1.0.0",
"description": "",
"scripts": {},
"keywords": [],
"author": "Baptiste Arnaud",
"license": "ISC",
"dependencies": {
"@playwright/test": "^1.42.1",
"@typebot.io/lib": "workspace:*",
"@typebot.io/prisma": "workspace:*",
"@typebot.io/schemas": "workspace:*"
},
"devDependencies": {
"@types/node": "^20.11.26",
"@typebot.io/tsconfig": "workspace:*"
}
}

View File

@@ -0,0 +1,30 @@
import type { Page } from '@playwright/test'
export const mockSessionResponsesToOtherUser = async (page: Page) =>
page.route('/api/auth/session', (route) => {
if (route.request().method() === 'GET') {
return route.fulfill({
status: 200,
body: '{"user":{"id":"otherUserId","name":"James Doe","email":"other-user@email.com","emailVerified":null,"image":"https://avatars.githubusercontent.com/u/16015833?v=4","stripeId":null,"graphNavigation": "TRACKPAD"}}',
})
}
return route.continue()
})
export const typebotViewer = (page: Page) =>
page.frameLocator('#typebot-iframe')
export const waitForSuccessfulPutRequest = (page: Page) =>
page.waitForResponse(
(resp) => resp.request().method() === 'PUT' && resp.status() === 200
)
export const waitForSuccessfulPostRequest = (page: Page) =>
page.waitForResponse(
(resp) => resp.request().method() === 'POST' && resp.status() === 200
)
export const waitForSuccessfulDeleteRequest = (page: Page) =>
page.waitForResponse(
(resp) => resp.request().method() === 'DELETE' && resp.status() === 200
)

View File

@@ -0,0 +1,8 @@
{
"extends": "@typebot.io/tsconfig/base.json",
"include": ["**/*.ts"],
"exclude": ["node_modules"],
"compilerOptions": {
"lib": ["ES2021", "DOM"]
}
}