test: mysql integration via testcontainers

This commit is contained in:
smartass
2026-06-12 00:27:50 +05:00
parent d5f9eafb35
commit 9ed8e85813
+150
View File
@@ -0,0 +1,150 @@
import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { MySqlContainer, type StartedMySqlContainer } from '@testcontainers/mysql'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import type { Registry } from '../../src/config/registry.js'
import { createRegistry } from '../../src/config/registry.js'
import type { Manager } from '../../src/db/manager.js'
import { createManager } from '../../src/db/manager.js'
describe('mysql integration', () => {
let container: StartedMySqlContainer
let dir: string
let registry: Registry
let manager: Manager
beforeAll(async () => {
container = await new MySqlContainer('mysql:8.4').start()
dir = mkdtempSync(join(tmpdir(), 'dbmole-int-my-'))
registry = createRegistry({ storePath: join(dir, 'connections.json'), env: {} })
manager = createManager(registry)
registry.add({
name: 'my',
type: 'mysql',
host: container.getHost(),
port: container.getPort(),
user: container.getUsername(),
password: container.getUserPassword(),
database: container.getDatabase(),
readonly: false
})
registry.add({
name: 'my-ro',
type: 'mysql',
host: container.getHost(),
port: container.getPort(),
user: container.getUsername(),
password: container.getUserPassword(),
database: container.getDatabase(),
readonly: true
})
})
afterAll(async () => {
await manager.disposeAll()
await container.stop()
rmSync(dir, { recursive: true, force: true })
})
it('runs DDL, DML with lastInsertId and SELECT with params', async () => {
const { driver } = await manager.get('my')
await driver.query({
sql: 'CREATE TABLE users (id int AUTO_INCREMENT PRIMARY KEY, name varchar(64) NOT NULL)',
rowLimit: 10
})
const insert = await driver.query({
sql: "INSERT INTO users (name) VALUES ('ada'), ('bob')",
rowLimit: 10
})
expect(insert.rowCount).toBe(2)
expect(insert.lastInsertId).toBe('1')
const select = await driver.query({
sql: 'SELECT name FROM users WHERE id = ?',
params: [2],
rowLimit: 10
})
expect(select.columns).toEqual(['name'])
expect(select.rows).toEqual([['bob']])
})
it('preserves BIGINT precision beyond 2^53', async () => {
const { driver } = await manager.get('my')
await driver.query({
sql: 'CREATE TABLE big (v bigint)',
rowLimit: 10
})
await driver.query({
sql: 'INSERT INTO big (v) VALUES (9007199254740993)',
rowLimit: 10
})
const result = await driver.query({ sql: 'SELECT v FROM big', rowLimit: 10 })
expect(result.rows[0][0]).toBe('9007199254740993')
})
it('rejects multi-statement and session-level sql', async () => {
const { driver } = await manager.get('my')
await expect(driver.query({ sql: 'SELECT 1; SELECT 2', rowLimit: 10 })).rejects.toThrow(
/one SQL statement/
)
await expect(
driver.query({ sql: 'SET SESSION TRANSACTION READ WRITE', rowLimit: 10 })
).rejects.toThrow(/session-level/)
})
it('lists databases and tables, describes a table', async () => {
const { driver } = await manager.get('my')
const databases = await driver.listDatabases()
expect(databases.map((d) => d.name)).toContain(container.getDatabase())
await driver.query({
sql: `CREATE TABLE orders (
id int AUTO_INCREMENT PRIMARY KEY,
user_id int NOT NULL,
note text,
KEY orders_user_idx (user_id),
CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id)
)`,
rowLimit: 10
})
const tables = await driver.listTables({})
expect(tables.map((t) => t.name)).toEqual(expect.arrayContaining(['orders', 'users']))
const description = await driver.describeTable({ table: 'orders' })
expect(description.primaryKey).toEqual(['id'])
expect(description.columns.map((c) => c.name)).toEqual(['id', 'user_id', 'note'])
expect(description.indexes).toContainEqual(
expect.objectContaining({ name: 'orders_user_idx', unique: false })
)
expect(description.foreignKeys).toContainEqual(
expect.objectContaining({
name: 'fk_orders_user',
columns: ['user_id'],
referencedTable: 'users'
})
)
})
it('describe_table of a missing table fails clearly', async () => {
const { driver } = await manager.get('my')
await expect(driver.describeTable({ table: 'ghost' })).rejects.toThrow(/not found/)
})
it('enforces readonly: INSERT and DDL fail, SELECT works', async () => {
const { driver } = await manager.get('my-ro')
const select = await driver.query({ sql: 'SELECT count(*) FROM users', rowLimit: 10 })
expect(Number(select.rows[0][0])).toBe(2)
await expect(
driver.query({ sql: "INSERT INTO users (name) VALUES ('mallory')", rowLimit: 10 })
).rejects.toThrow(/READ ONLY/i)
await expect(
driver.query({ sql: 'CREATE TABLE hacked (id int)', rowLimit: 10 })
).rejects.toThrow(/READ ONLY/i)
})
it('reports server version', async () => {
const { driver } = await manager.get('my')
expect(await driver.serverVersion()).toMatch(/^8\./)
})
})