import Docker from 'dockerode'; import { sql } from 'drizzle-orm'; import type { MySql2Database } from 'drizzle-orm/mysql2'; import { drizzle } from 'drizzle-orm/mysql2'; import { reset, seed } from 'drizzle-seed'; import getPort from 'get-port'; import type { Connection } from 'mysql2/promise'; import { createConnection } from 'mysql2/promise'; import { v4 as uuid } from 'uuid'; import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; import * as schema from './mysqlSchema.ts'; let mysqlContainer: Docker.Container; let client: Connection; let db: MySql2Database; async function createDockerDB(): Promise { const docker = new Docker(); const port = await getPort({ port: 3306 }); const image = 'mysql:8'; const pullStream = await docker.pull(image); await new Promise((resolve, reject) => // eslint-disable-next-line @typescript-eslint/no-unsafe-argument docker.modem.followProgress(pullStream, (err) => err ? reject(err) : resolve(err)) ); mysqlContainer = await docker.createContainer({ Image: image, Env: ['MYSQL_ROOT_PASSWORD=mysql', 'MYSQL_DATABASE=drizzle'], name: `drizzle-integration-tests-${uuid()}`, HostConfig: { AutoRemove: true, PortBindings: { '3306/tcp': [{ HostPort: `${port}` }], }, }, }); await mysqlContainer.start(); return `mysql://root:mysql@127.0.0.1:${port}/drizzle`; } const createNorthwindTables = async () => { await db.execute( sql` CREATE TABLE \`customer\` ( \`id\` varchar(256) NOT NULL, \`company_name\` text NOT NULL, \`contact_name\` text NOT NULL, \`contact_title\` text NOT NULL, \`address\` text NOT NULL, \`city\` text NOT NULL, \`postal_code\` text, \`region\` text, \`country\` text NOT NULL, \`phone\` text NOT NULL, \`fax\` text, CONSTRAINT \`customer_id\` PRIMARY KEY(\`id\`) ); `, ); await db.execute( sql` CREATE TABLE \`order_detail\` ( \`unit_price\` float NOT NULL, \`quantity\` int NOT NULL, \`discount\` float NOT NULL, \`order_id\` int NOT NULL, \`product_id\` int NOT NULL ); `, ); await db.execute( sql` CREATE TABLE \`employee\` ( \`id\` int NOT NULL, \`last_name\` text NOT NULL, \`first_name\` text, \`title\` text NOT NULL, \`title_of_courtesy\` text NOT NULL, \`birth_date\` timestamp NOT NULL, \`hire_date\` timestamp NOT NULL, \`address\` text NOT NULL, \`city\` text NOT NULL, \`postal_code\` text NOT NULL, \`country\` text NOT NULL, \`home_phone\` text NOT NULL, \`extension\` int NOT NULL, \`notes\` text NOT NULL, \`reports_to\` int, \`photo_path\` text, CONSTRAINT \`employee_id\` PRIMARY KEY(\`id\`) ); `, ); await db.execute( sql` CREATE TABLE \`order\` ( \`id\` int NOT NULL, \`order_date\` timestamp NOT NULL, \`required_date\` timestamp NOT NULL, \`shipped_date\` timestamp, \`ship_via\` int NOT NULL, \`freight\` float NOT NULL, \`ship_name\` text NOT NULL, \`ship_city\` text NOT NULL, \`ship_region\` text, \`ship_postal_code\` text, \`ship_country\` text NOT NULL, \`customer_id\` varchar(256) NOT NULL, \`employee_id\` int NOT NULL, CONSTRAINT \`order_id\` PRIMARY KEY(\`id\`) ); `, ); await db.execute( sql` CREATE TABLE \`product\` ( \`id\` int NOT NULL, \`name\` text NOT NULL, \`quantity_per_unit\` text NOT NULL, \`unit_price\` float NOT NULL, \`units_in_stock\` int NOT NULL, \`units_on_order\` int NOT NULL, \`reorder_level\` int NOT NULL, \`discontinued\` int NOT NULL, \`supplier_id\` int NOT NULL, CONSTRAINT \`product_id\` PRIMARY KEY(\`id\`) ); `, ); await db.execute( sql` CREATE TABLE \`supplier\` ( \`id\` int NOT NULL, \`company_name\` text NOT NULL, \`contact_name\` text NOT NULL, \`contact_title\` text NOT NULL, \`address\` text NOT NULL, \`city\` text NOT NULL, \`region\` text, \`postal_code\` text NOT NULL, \`country\` text NOT NULL, \`phone\` text NOT NULL, CONSTRAINT \`supplier_id\` PRIMARY KEY(\`id\`) ); `, ); await db.execute( sql` ALTER TABLE \`order_detail\` ADD CONSTRAINT \`order_detail_order_id_order_id_fk\` FOREIGN KEY (\`order_id\`) REFERENCES \`order\`(\`id\`) ON DELETE cascade ON UPDATE no action; `, ); await db.execute( sql` ALTER TABLE \`order_detail\` ADD CONSTRAINT \`order_detail_product_id_product_id_fk\` FOREIGN KEY (\`product_id\`) REFERENCES \`product\`(\`id\`) ON DELETE cascade ON UPDATE no action; `, ); await db.execute( sql` ALTER TABLE \`employee\` ADD CONSTRAINT \`employee_reports_to_employee_id_fk\` FOREIGN KEY (\`reports_to\`) REFERENCES \`employee\`(\`id\`) ON DELETE no action ON UPDATE no action; `, ); await db.execute( sql` ALTER TABLE \`order\` ADD CONSTRAINT \`order_customer_id_customer_id_fk\` FOREIGN KEY (\`customer_id\`) REFERENCES \`customer\`(\`id\`) ON DELETE cascade ON UPDATE no action; `, ); await db.execute( sql` ALTER TABLE \`order\` ADD CONSTRAINT \`order_employee_id_employee_id_fk\` FOREIGN KEY (\`employee_id\`) REFERENCES \`employee\`(\`id\`) ON DELETE cascade ON UPDATE no action; `, ); await db.execute( sql` ALTER TABLE \`product\` ADD CONSTRAINT \`product_supplier_id_supplier_id_fk\` FOREIGN KEY (\`supplier_id\`) REFERENCES \`supplier\`(\`id\`) ON DELETE cascade ON UPDATE no action; `, ); }; const createAllDataTypesTable = async () => { await db.execute( sql` CREATE TABLE \`all_data_types\` ( \`integer\` int, \`tinyint\` tinyint, \`smallint\` smallint, \`mediumint\` mediumint, \`bigint\` bigint, \`bigint_number\` bigint, \`real\` real, \`decimal\` decimal, \`double\` double, \`float\` float, \`serial\` serial AUTO_INCREMENT, \`binary\` binary(255), \`varbinary\` varbinary(256), \`char\` char(255), \`varchar\` varchar(256), \`text\` text, \`boolean\` boolean, \`date_string\` date, \`date\` date, \`datetime\` datetime, \`datetimeString\` datetime, \`time\` time, \`year\` year, \`timestamp_date\` timestamp, \`timestamp_string\` timestamp, \`json\` json, \`popularity\` enum('unknown','known','popular') ); `, ); }; const createAllGeneratorsTables = async () => { await db.execute( sql` CREATE TABLE \`datetime_table\` ( \`datetime\` datetime ); `, ); await db.execute( sql` CREATE TABLE \`year_table\` ( \`year\` year ); `, ); }; beforeAll(async () => { const connectionString = await createDockerDB(); const sleep = 1000; let timeLeft = 40000; let connected = false; let lastError: unknown | undefined; do { try { client = await createConnection(connectionString); await client.connect(); db = drizzle(client); connected = true; break; } catch (e) { lastError = e; await new Promise((resolve) => setTimeout(resolve, sleep)); timeLeft -= sleep; } } while (timeLeft > 0); if (!connected) { console.error('Cannot connect to MySQL'); await client?.end().catch(console.error); await mysqlContainer?.stop().catch(console.error); throw lastError; } await createNorthwindTables(); await createAllDataTypesTable(); await createAllGeneratorsTables(); }); afterAll(async () => { await client?.end().catch(console.error); await mysqlContainer?.stop().catch(console.error); }); afterEach(async () => { await reset(db, schema); }); test('basic seed test', async () => { await seed(db, schema); const customers = await db.select().from(schema.customers); const details = await db.select().from(schema.details); const employees = await db.select().from(schema.employees); const orders = await db.select().from(schema.orders); const products = await db.select().from(schema.products); const suppliers = await db.select().from(schema.suppliers); expect(customers.length).toBe(10); expect(details.length).toBe(10); expect(employees.length).toBe(10); expect(orders.length).toBe(10); expect(products.length).toBe(10); expect(suppliers.length).toBe(10); }); test('seed with options.count:11 test', async () => { await seed(db, schema, { count: 11 }); const customers = await db.select().from(schema.customers); const details = await db.select().from(schema.details); const employees = await db.select().from(schema.employees); const orders = await db.select().from(schema.orders); const products = await db.select().from(schema.products); const suppliers = await db.select().from(schema.suppliers); expect(customers.length).toBe(11); expect(details.length).toBe(11); expect(employees.length).toBe(11); expect(orders.length).toBe(11); expect(products.length).toBe(11); expect(suppliers.length).toBe(11); }); test('redefine(refine) customers count', async () => { await seed(db, schema, { count: 11 }).refine(() => ({ customers: { count: 12, }, })); const customers = await db.select().from(schema.customers); const details = await db.select().from(schema.details); const employees = await db.select().from(schema.employees); const orders = await db.select().from(schema.orders); const products = await db.select().from(schema.products); const suppliers = await db.select().from(schema.suppliers); expect(customers.length).toBe(12); expect(details.length).toBe(11); expect(employees.length).toBe(11); expect(orders.length).toBe(11); expect(products.length).toBe(11); expect(suppliers.length).toBe(11); }); test('redefine(refine) all tables count', async () => { await seed(db, schema, { count: 11 }).refine(() => ({ customers: { count: 12, }, details: { count: 13, }, employees: { count: 14, }, orders: { count: 15, }, products: { count: 16, }, suppliers: { count: 17, }, })); const customers = await db.select().from(schema.customers); const details = await db.select().from(schema.details); const employees = await db.select().from(schema.employees); const orders = await db.select().from(schema.orders); const products = await db.select().from(schema.products); const suppliers = await db.select().from(schema.suppliers); expect(customers.length).toBe(12); expect(details.length).toBe(13); expect(employees.length).toBe(14); expect(orders.length).toBe(15); expect(products.length).toBe(16); expect(suppliers.length).toBe(17); }); test("redefine(refine) orders count using 'with' in customers", async () => { await seed(db, schema, { count: 11 }).refine(() => ({ customers: { count: 4, with: { orders: 2, }, }, orders: { count: 13, }, })); const customers = await db.select().from(schema.customers); const details = await db.select().from(schema.details); const employees = await db.select().from(schema.employees); const orders = await db.select().from(schema.orders); const products = await db.select().from(schema.products); const suppliers = await db.select().from(schema.suppliers); expect(customers.length).toBe(4); expect(details.length).toBe(11); expect(employees.length).toBe(11); expect(orders.length).toBe(8); expect(products.length).toBe(11); expect(suppliers.length).toBe(11); }); test("sequential using of 'with'", async () => { await seed(db, schema, { count: 11 }).refine(() => ({ customers: { count: 4, with: { orders: 2, }, }, orders: { count: 12, with: { details: 3, }, }, })); const customers = await db.select().from(schema.customers); const details = await db.select().from(schema.details); const employees = await db.select().from(schema.employees); const orders = await db.select().from(schema.orders); const products = await db.select().from(schema.products); const suppliers = await db.select().from(schema.suppliers); expect(customers.length).toBe(4); expect(details.length).toBe(24); expect(employees.length).toBe(11); expect(orders.length).toBe(8); expect(products.length).toBe(11); expect(suppliers.length).toBe(11); }); // All data types test ------------------------------- test('basic seed test for all mysql data types', async () => { await seed(db, schema, { count: 1000 }); const allDataTypes = await db.select().from(schema.allDataTypes); // every value in each 10 rows does not equal undefined. const predicate = allDataTypes.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); expect(predicate).toBe(true); }); // All generators test------------------------------- const count = 10000; test('datetime generator test', async () => { await seed(db, { datetimeTable: schema.datetimeTable }).refine((funcs) => ({ datetimeTable: { count, columns: { datetime: funcs.datetime(), }, }, })); const data = await db.select().from(schema.datetimeTable); // every value in each row does not equal undefined. const predicate = data.length !== 0 && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); expect(predicate).toBe(true); }); test('year generator test', async () => { await seed(db, { yearTable: schema.yearTable }).refine((funcs) => ({ yearTable: { count, columns: { year: funcs.year(), }, }, })); const data = await db.select().from(schema.yearTable); // every value in each row does not equal undefined. const predicate = data.length !== 0 && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); expect(predicate).toBe(true); });