test: add Phase 3 SQL render tests for all dialects

Cover getRenderer().renderDDL across MySQL, MariaDB, PostgreSQL, SQLite,
Oracle and SQL Server. A shared DatabaseType fixture (users + posts with a
primary key, auto-increment, NOT NULL UNIQUE column with a length, DEFAULT
and a posts to users foreign key) is rendered per dialect, asserting the
emitted DDL creates both tables and columns and carries the primary key,
unique constraint, dialect-specific auto-increment spelling (AUTO_INCREMENT,
SERIAL, AUTOINCREMENT, IDENTITY), variable-length text type, DEFAULT value
and the foreign key with ON DELETE CASCADE.

The fixture is built from the seed data types so field type ids hydrate the
way the app hydrates them, and embeds relationship source/target objects
because the SQLite renderer orders tables from the raw database before the
migration step re-hydrates. Assertions match quote-agnostic patterns rather
than exact strings, since identifier quoting and formatting differ by dialect.
This commit is contained in:
Alberto Arena
2026-08-05 07:26:31 +02:00
parent 585f5c7a7f
commit 91fe5d6cf2
2 changed files with 289 additions and 0 deletions
+111
View File
@@ -0,0 +1,111 @@
import { describe, it, expect } from "vitest";
import { DatabaseDialect } from "@/lib/database";
import { getRenderer } from "@/utils/render/render-uttils";
import { getDataTypes } from "@/test/fixtures/data-types";
import { buildSampleDatabase } from "@/test/fixtures/database";
// getRenderer().renderDDL() takes a DatabaseType and emits dialect DDL. It runs
// the migration diff (empty database -> the fixture), so the output is the full
// CREATE for every table plus the foreign key. Assertions match on substrings
// and quote-agnostic patterns rather than exact strings, since formatting and
// identifier quoting differ per dialect and are not what these tests pin down.
interface RenderCase {
name: string;
dialect: DatabaseDialect;
// Dialect-specific spelling of an auto-increment / identity column.
autoIncrement: RegExp;
// Dialect-specific spelling of a variable-length string type.
varchar: RegExp;
}
const cases: RenderCase[] = [
{
name: "MySQL",
dialect: DatabaseDialect.MYSQL,
autoIncrement: /AUTO_INCREMENT/,
varchar: /VARCHAR\s*\(\s*255\s*\)/i,
},
{
name: "MariaDB",
dialect: DatabaseDialect.MARIADB,
autoIncrement: /AUTO_INCREMENT/,
varchar: /VARCHAR\s*\(\s*255\s*\)/i,
},
{
name: "PostgreSQL",
dialect: DatabaseDialect.POSTGRES,
// auto-increment integers become SERIAL in Postgres
autoIncrement: /SERIAL/,
varchar: /VARCHAR\s*\(\s*255\s*\)/i,
},
{
name: "SQLite",
dialect: DatabaseDialect.SQLITE,
autoIncrement: /AUTOINCREMENT/,
// SQLite has no VARCHAR; the text column renders as TEXT
varchar: /TEXT\s*\(\s*255\s*\)/i,
},
{
name: "Oracle",
dialect: DatabaseDialect.ORACLE,
autoIncrement: /IDENTITY/,
varchar: /VARCHAR2\s*\(\s*255\s*\)/i,
},
{
name: "SQL Server",
dialect: DatabaseDialect.MSSQL,
autoIncrement: /IDENTITY/,
varchar: /VARCHAR\s*\(\s*255\s*\)/i,
},
];
const render = (dialect: DatabaseDialect): Promise<string> =>
getRenderer(dialect, getDataTypes(dialect))!.renderDDL(
buildSampleDatabase(dialect),
);
describe("getRenderer().renderDDL - emitted DDL across dialects", () => {
for (const c of cases) {
describe(c.name, () => {
it("creates both tables with their columns", async () => {
const sql = await render(c.dialect);
expect(sql).toMatch(/CREATE TABLE\s+[`"]?users[`"]?/i);
expect(sql).toMatch(/CREATE TABLE\s+[`"]?posts[`"]?/i);
for (const col of ["id", "email", "status", "user_id", "title"]) {
expect(sql).toContain(col);
}
});
it("emits primary key, unique and auto-increment", async () => {
const sql = await render(c.dialect);
expect(sql).toMatch(/PRIMARY KEY/i);
expect(sql).toMatch(/UNIQUE/i);
expect(sql).toMatch(c.autoIncrement);
});
it("emits the text column type with its length", async () => {
const sql = await render(c.dialect);
expect(sql).toMatch(c.varchar);
});
it("emits the DEFAULT value", async () => {
const sql = await render(c.dialect);
expect(sql).toMatch(/DEFAULT\s+'active'/i);
});
it("emits the foreign key from posts to users with ON DELETE CASCADE", async () => {
const sql = await render(c.dialect);
expect(sql).toMatch(/FOREIGN KEY\s*\(\s*user_id\s*\)/i);
expect(sql).toMatch(/REFERENCES\s+[`"]?users[`"]?\s*\(\s*id\s*\)/i);
expect(sql).toMatch(/ON DELETE CASCADE/i);
});
});
}
});