Studio.test.ts•20.1 kB
import fs from 'node:fs'
import path from 'node:path'
import { defaultTestConfig, PrismaConfigInternal } from '@prisma/config'
import { jestConsoleContext, jestContext } from '@prisma/get-platform'
import * as miniProxy from '@prisma/mini-proxy'
import fetch from 'node-fetch'
import { DbPush } from '../../../../migrate/src/commands/DbPush'
import { Studio } from '../../Studio'
const originalEnv = { ...process.env }
function restoreEnv() {
  for (const key of Object.keys(process.env)) {
    if (!(key in originalEnv)) {
      delete process.env[key]
    }
  }
  for (const [key, value] of Object.entries(originalEnv)) {
    if (value === undefined) {
      delete process.env[key]
    } else {
      process.env[key] = value
    }
  }
}
const ctx = jestContext.new().add(jestConsoleContext()).assemble()
const STUDIO_TEST_PORT = 5678
const testIf = (condition: boolean) => (condition ? test : test.skip)
const describeIf = (condition: boolean) => (condition ? describe : describe.skip)
const rmSync = (path: string) => fs.rmSync(path, { recursive: true, force: true })
async function sendRequest(message: any): Promise<any> {
  const res = await fetch(`http://localhost:${STUDIO_TEST_PORT}/api`, {
    method: 'POST',
    headers: {
      'Content-Type': 'text/plain',
    },
    body: JSON.stringify(message),
  })
  return res.json()
}
let studio: Studio
// Prisma Studio ignores env vars for overriding engine paths, skipping test for now
describeIf(!process.env.PRISMA_QUERY_ENGINE_LIBRARY && !process.env.PRISMA_QUERY_ENGINE_BINARY)(
  'studio with alternative urls and prisma://',
  () => {
    afterEach(() => {
      restoreEnv()
    })
    test('queries work if url is prisma:// and directUrl is set', async () => {
      process.env.PDP_URL = 'prisma://aws-us-east-1.prisma-data.com/?api_key=MY_API_KEY'
      process.env.DATABASE_URL = process.env.TEST_POSTGRES_URI!.replace('tests', `tests-${Date.now()}-studio`)
      ctx.fixture('schema-only-data-proxy-direct-url')
      const studio = Studio.new()
      await DbPush.new().parse(['--schema', 'schema.prisma', '--skip-generate'], defaultTestConfig())
      const result = studio.parse(['--port', `${STUDIO_TEST_PORT}`, '--browser', 'none'], defaultTestConfig())
      await expect(result).resolves.not.toThrow()
      const res = await sendRequest({
        requestId: 1,
        channel: 'prisma',
        action: 'clientRequest',
        payload: {
          data: {
            modelName: 'SomeUser',
            operation: 'findMany',
            args: {
              select: {
                id: true,
              },
            },
          },
        },
      })
      expect(res).toMatchSnapshot()
      studio.instance?.stop()
    })
    testIf(process.platform !== 'win32')('queries work if url is prisma:// via the mini-proxy', async () => {
      process.env.DATABASE_URL = process.env.TEST_POSTGRES_URI!.replace('tests', `tests-${Date.now()}-studio`)
      process.env.PDP_URL = miniProxy.generateConnectionString({
        envVar: 'PDP_URL',
        databaseUrl: process.env.DATABASE_URL,
        port: miniProxy.defaultServerConfig.port,
      })
      ctx.fixture('schema-only-data-proxy')
      await DbPush.new().parse(['--schema', 'schema.prisma', '--skip-generate'], defaultTestConfig())
      delete process.env.DATABASE_URL
      const studio = Studio.new()
      const result = studio.parse(['--port', `${STUDIO_TEST_PORT}`, '--browser', 'none'], defaultTestConfig())
      await expect(result).resolves.not.toThrow()
      const res = await sendRequest({
        requestId: 1,
        channel: 'prisma',
        action: 'clientRequest',
        payload: {
          data: {
            modelName: 'SomeUser',
            operation: 'findMany',
            args: {
              select: {
                id: true,
              },
            },
          },
        },
      })
      expect(res).toMatchSnapshot()
      studio.instance?.stop()
      expect(ctx.mocked['console.warn'].mock.calls.join('\n')).toMatchInlineSnapshot(`""`)
    })
  },
)
describe('studio with default schema.prisma filename', () => {
  jest.setTimeout(20_000)
  beforeAll(async () => {
    // Before every test, we'd like to reset the DB.
    // We do this by duplicating the original SQLite DB file, and using the duplicate as the datasource in our schema
    rmSync(path.join(__dirname, '../fixtures/studio-test-project/dev_tmp.db'))
    fs.copyFileSync(
      path.join(__dirname, '../fixtures/studio-test-project/dev.db'),
      path.join(__dirname, '../fixtures/studio-test-project/dev_tmp.db'),
    )
    // Clean up Client generation directory
    rmSync(path.join(__dirname, '../prisma-client'))
    studio = Studio.new()
    await studio.parse(
      [
        '--schema',
        path.join(__dirname, '../fixtures/studio-test-project/schema.prisma'),
        '--port',
        `${STUDIO_TEST_PORT}`,
        '--browser',
        'none',
      ],
      defaultTestConfig(),
    )
    // Give Studio time to start
    await new Promise((r) => setTimeout(() => r(null), 2_000))
  })
  afterAll(() => {
    studio.instance!.stop()
  })
  test('can start up correctly', async () => {
    const res = await fetch(`http://localhost:${STUDIO_TEST_PORT}`)
    expect(res.status).toEqual(200)
  })
  test('can respond to `findMany` queries', async () => {
    const res = await sendRequest({
      requestId: 1,
      channel: 'prisma',
      action: 'clientRequest',
      payload: {
        data: {
          modelName: 'with_all_field_types',
          operation: 'findMany',
          args: {
            select: {
              id: true,
              string: true,
              int: true,
              float: true,
              datetime: true,
              relation: true,
              relation_list: true,
            },
          },
        },
      },
    })
    expect(res).toMatchSnapshot()
  })
  test('can respond to `create` queries', async () => {
    const res = await sendRequest({
      requestId: 2,
      channel: 'prisma',
      action: 'clientRequest',
      payload: {
        data: {
          modelName: 'with_all_field_types',
          operation: 'create',
          args: {
            data: {
              id: 3,
              string: '',
              int: 0,
              float: 0.0,
              datetime: '2020-08-03T00:00:00.000Z',
              relation: {
                connect: {
                  id: 3,
                },
              },
              relation_list: {
                connect: {
                  id: 3,
                },
              },
            },
            select: {
              id: true,
              string: true,
              int: true,
              float: true,
              datetime: true,
              relation: true,
              relation_list: true,
            },
          },
        },
      },
    })
    expect(res).toMatchSnapshot()
  })
  test('can respond to `update` queries', async () => {
    const res = await sendRequest({
      requestId: 3,
      channel: 'prisma',
      action: 'clientRequest',
      payload: {
        data: {
          modelName: 'with_all_field_types',
          operation: 'update',
          args: {
            where: {
              id: 1,
            },
            data: {
              string: 'Changed String',
              int: 100,
              float: 100.5,
              datetime: '2025-08-03T00:00:00.000Z',
              relation: {
                connect: {
                  id: 3,
                },
              },
              relation_list: {
                connect: {
                  id: 3,
                },
              },
            },
            select: {
              id: true,
              string: true,
              int: true,
              float: true,
              datetime: true,
              relation: true,
              relation_list: true,
            },
          },
        },
      },
    })
    expect(res).toMatchSnapshot()
  })
  test('can respond to `delete` queries', async () => {
    const res = await sendRequest({
      requestId: 4,
      channel: 'prisma',
      action: 'clientRequest',
      payload: {
        data: {
          modelName: 'with_all_field_types',
          operation: 'delete',
          args: {
            where: { id: 2 },
            select: {
              id: true,
              string: true,
              int: true,
              float: true,
              datetime: true,
              relation: true,
              relation_list: true,
            },
          },
        },
      },
    })
    expect(res).toMatchSnapshot()
  })
})
describe('studio with custom schema.prisma filename', () => {
  jest.setTimeout(20_000)
  beforeAll(async () => {
    // Before every test, we'd like to reset the DB.
    // We do this by duplicating the original SQLite DB file, and using the duplicate as the datasource in our schema
    rmSync(path.join(__dirname, '../fixtures/studio-test-project-custom-filename/dev_tmp.db'))
    fs.copyFileSync(
      path.join(__dirname, '../fixtures/studio-test-project-custom-filename/dev.db'),
      path.join(__dirname, '../fixtures/studio-test-project-custom-filename/dev_tmp.db'),
    )
    // Clean up Client generation directory
    rmSync(path.join(__dirname, '../prisma-client'))
    studio = Studio.new()
    await studio.parse(
      [
        '--schema',
        path.join(__dirname, '../fixtures/studio-test-project-custom-filename/schema1.prisma'),
        '--port',
        `${STUDIO_TEST_PORT}`,
        '--browser',
        'none',
      ],
      defaultTestConfig(),
    )
    // Give Studio time to start
    await new Promise((r) => setTimeout(() => r(null), 2_000))
  })
  afterAll(() => {
    studio.instance!.stop()
  })
  test('can start up correctly', async () => {
    const res = await fetch(`http://localhost:${STUDIO_TEST_PORT}`)
    expect(res.status).toEqual(200)
  })
  test('can respond to `findMany` queries', async () => {
    const res = await sendRequest({
      requestId: 1,
      channel: 'prisma',
      action: 'clientRequest',
      payload: {
        data: {
          modelName: 'with_all_field_types',
          operation: 'findMany',
          args: {
            select: {
              id: true,
              string: true,
              int: true,
              float: true,
              datetime: true,
              relation: true,
              relation_list: true,
            },
          },
        },
      },
    })
    expect(res).toMatchSnapshot()
  })
  test('can respond to `create` queries', async () => {
    const res = await sendRequest({
      requestId: 2,
      channel: 'prisma',
      action: 'clientRequest',
      payload: {
        data: {
          modelName: 'with_all_field_types',
          operation: 'create',
          args: {
            data: {
              id: 3,
              string: '',
              int: 0,
              float: 0.0,
              datetime: '2020-08-03T00:00:00.000Z',
              relation: {
                connect: {
                  id: 3,
                },
              },
              relation_list: {
                connect: {
                  id: 3,
                },
              },
            },
            select: {
              id: true,
              string: true,
              int: true,
              float: true,
              datetime: true,
              relation: true,
              relation_list: true,
            },
          },
        },
      },
    })
    expect(res).toMatchSnapshot()
  })
  test('can respond to `update` queries', async () => {
    const res = await sendRequest({
      requestId: 3,
      channel: 'prisma',
      action: 'clientRequest',
      payload: {
        data: {
          modelName: 'with_all_field_types',
          operation: 'update',
          args: {
            where: {
              id: 1,
            },
            data: {
              string: 'Changed String',
              int: 100,
              float: 100.5,
              datetime: '2025-08-03T00:00:00.000Z',
              relation: {
                connect: {
                  id: 3,
                },
              },
              relation_list: {
                connect: {
                  id: 3,
                },
              },
            },
            select: {
              id: true,
              string: true,
              int: true,
              float: true,
              datetime: true,
              relation: true,
              relation_list: true,
            },
          },
        },
      },
    })
    expect(res).toMatchSnapshot()
  })
  test('can respond to `delete` queries', async () => {
    const res = await sendRequest({
      requestId: 4,
      channel: 'prisma',
      action: 'clientRequest',
      payload: {
        data: {
          modelName: 'with_all_field_types',
          operation: 'delete',
          args: {
            where: { id: 2 },
            select: {
              id: true,
              string: true,
              int: true,
              float: true,
              datetime: true,
              relation: true,
              relation_list: true,
            },
          },
        },
      },
    })
    expect(res).toMatchSnapshot()
  })
})
describeIf(process.env.PRISMA_CLIENT_ENGINE_TYPE !== 'binary')('studio with schema folder', () => {
  jest.setTimeout(20_000)
  beforeAll(async () => {
    // Before every test, we'd like to reset the DB.
    // We do this by duplicating the original SQLite DB file, and using the duplicate as the datasource in our schema
    rmSync(path.join(__dirname, '../fixtures/studio-test-project-schema-folder/dev_tmp.db'))
    fs.copyFileSync(
      path.join(__dirname, '../fixtures/studio-test-project-schema-folder/dev.db'),
      path.join(__dirname, '../fixtures/studio-test-project-schema-folder/dev_tmp.db'),
    )
    // Clean up Client generation directory
    rmSync(path.join(__dirname, '../prisma-client'))
    studio = Studio.new()
    await studio.parse(
      [
        '--schema',
        path.join(__dirname, '../fixtures/studio-test-project-schema-folder/schema'),
        '--port',
        `${STUDIO_TEST_PORT}`,
        '--browser',
        'none',
      ],
      defaultTestConfig(),
    )
    // Give Studio time to start
    await new Promise((r) => setTimeout(() => r(null), 2_000))
  })
  afterAll(() => {
    studio.instance!.stop()
  })
  test('can start up correctly', async () => {
    const res = await fetch(`http://localhost:${STUDIO_TEST_PORT}`)
    expect(res.status).toEqual(200)
  })
  test('can respond to `findMany` queries', async () => {
    const res = await sendRequest({
      requestId: 1,
      channel: 'prisma',
      action: 'clientRequest',
      payload: {
        data: {
          modelName: 'with_all_field_types',
          operation: 'findMany',
          args: {
            select: {
              id: true,
              string: true,
              int: true,
              float: true,
              datetime: true,
              relation: true,
              relation_list: true,
            },
          },
        },
      },
    })
    expect(res).toMatchSnapshot()
  })
  test('can respond to `create` queries', async () => {
    const res = await sendRequest({
      requestId: 2,
      channel: 'prisma',
      action: 'clientRequest',
      payload: {
        data: {
          modelName: 'with_all_field_types',
          operation: 'create',
          args: {
            data: {
              id: 3,
              string: '',
              int: 0,
              float: 0.0,
              datetime: '2020-08-03T00:00:00.000Z',
              relation: {
                connect: {
                  id: 3,
                },
              },
              relation_list: {
                connect: {
                  id: 3,
                },
              },
            },
            select: {
              id: true,
              string: true,
              int: true,
              float: true,
              datetime: true,
              relation: true,
              relation_list: true,
            },
          },
        },
      },
    })
    expect(res).toMatchSnapshot()
  })
  test('can respond to `update` queries', async () => {
    const res = await sendRequest({
      requestId: 3,
      channel: 'prisma',
      action: 'clientRequest',
      payload: {
        data: {
          modelName: 'with_all_field_types',
          operation: 'update',
          args: {
            where: {
              id: 1,
            },
            data: {
              string: 'Changed String',
              int: 100,
              float: 100.5,
              datetime: '2025-08-03T00:00:00.000Z',
              relation: {
                connect: {
                  id: 3,
                },
              },
              relation_list: {
                connect: {
                  id: 3,
                },
              },
            },
            select: {
              id: true,
              string: true,
              int: true,
              float: true,
              datetime: true,
              relation: true,
              relation_list: true,
            },
          },
        },
      },
    })
    expect(res).toMatchSnapshot()
  })
  test('can respond to `delete` queries', async () => {
    const res = await sendRequest({
      requestId: 4,
      channel: 'prisma',
      action: 'clientRequest',
      payload: {
        data: {
          modelName: 'with_all_field_types',
          operation: 'delete',
          args: {
            where: { id: 2 },
            select: {
              id: true,
              string: true,
              int: true,
              float: true,
              datetime: true,
              relation: true,
              relation_list: true,
            },
          },
        },
      },
    })
    expect(res).toMatchSnapshot()
  })
})
describeIf(process.env.PRISMA_CLIENT_ENGINE_TYPE !== 'binary')(
  'studio with driver adapter from prisma.config.ts',
  () => {
    jest.setTimeout(20_000)
    afterEach(() => {
      process.env = { ...originalEnv }
    })
    beforeAll(async () => {
      // Before every test, we'd like to reset the DB.
      // We do this by duplicating the original SQLite DB file, and using the duplicate as the datasource in our schema
      rmSync(path.join(__dirname, '../fixtures/studio-test-project-driver-adapter/dev_tmp.db'))
      fs.copyFileSync(
        path.join(__dirname, '../fixtures/studio-test-project-driver-adapter/dev.db'),
        path.join(__dirname, '../fixtures/studio-test-project-driver-adapter/dev_tmp.db'),
      )
      // Clean up Client generation directory
      rmSync(path.join(__dirname, '../prisma-client'))
      studio = Studio.new()
      const config = (
        await import(path.join(__dirname, '../fixtures/studio-test-project-driver-adapter/prisma.config.ts'))
      ).default as PrismaConfigInternal
      await studio.parse(['--port', `${STUDIO_TEST_PORT}`, '--browser', 'none'], config)
      // Give Studio time to start
      await new Promise((r) => setTimeout(() => r(null), 2_000))
    })
    afterAll(() => {
      studio.instance!.stop()
    })
    test('starts up correctly', async () => {
      const res = await fetch(`http://localhost:${STUDIO_TEST_PORT}`)
      expect(res.status).toEqual(200)
    })
    test('responds to `findMany` queries', async () => {
      const res = await sendRequest({
        requestId: 1,
        channel: 'prisma',
        action: 'clientRequest',
        payload: {
          data: {
            modelName: 'with_all_field_types',
            operation: 'findMany',
            args: {
              select: {
                id: true,
                string: true,
                int: true,
                float: true,
                relation: true,
                relation_list: true,
              },
            },
          },
        },
      })
      expect(res).toMatchSnapshot()
    })
  },
)