import { strict as assert } from "node:assert"; import test from "node:test"; import { buildSqlCompletionItems, shouldAutoOpenSqlCompletion, type SqlCompletionColumn, type SqlCompletionTable, } from "../src/lib/sqlCompletion.ts"; const tables: SqlCompletionTable[] = [ { name: "users", schema: "public", type: "table" }, { name: "user_profiles", schema: "public", type: "table" }, { name: "orders", schema: "public", type: "table" }, ]; const columnsByTable = new Map([ [ "public.users", [ { name: "id", table: "users", schema: "public", dataType: "bigint" }, { name: "name", table: "users", schema: "public", dataType: "varchar" }, { name: "email", table: "users", schema: "public", dataType: "varchar" }, ], ], [ "public.orders", [ { name: "id", table: "orders", schema: "public", dataType: "bigint" }, { name: "user_id", table: "orders", schema: "public", dataType: "bigint" }, { name: "status", table: "orders", schema: "public", dataType: "varchar" }, ], ], ]); test("suggests SQL keywords for generic keyword input", () => { const items = buildSqlCompletionItems("sel", 3, { tables, columnsByTable, }); assert.equal(items[0]?.label, "SELECT"); assert.equal(items[0]?.type, "keyword"); }); test("suggests matching table names after FROM", () => { const sql = "select * from us"; const items = buildSqlCompletionItems(sql, sql.length, { tables, columnsByTable, }); assert.deepEqual( items.slice(0, 2).map((item) => item.label), ["users", "user_profiles"], ); }); test("suggests columns for an explicit alias qualifier", () => { const sql = "select u. from public.users u"; const cursor = "select u.".length; const items = buildSqlCompletionItems(sql, cursor, { tables, columnsByTable, }); const columnItems = items.filter((item) => item.type === "column"); assert.deepEqual( columnItems.map((item) => item.label), ["id", "name", "email"], ); }); test("suggests columns from referenced tables in select list", () => { const sql = "select na from public.users u join public.orders o on u.id = o.user_id"; const cursor = "select na".length; const items = buildSqlCompletionItems(sql, cursor, { tables, columnsByTable, }); assert.equal(items[0]?.label, "name"); assert.equal(items[0]?.type, "column"); }); test("suggests tables after LEFT JOIN", () => { const sql = "select * from users left join us"; const items = buildSqlCompletionItems(sql, sql.length, { tables, columnsByTable, }); assert.ok(items.some((item) => item.label === "users" && item.type === "table")); assert.ok(items.some((item) => item.label === "user_profiles" && item.type === "table")); }); test("suggests tables after comma in FROM clause", () => { const sql = "select * from users, or"; const items = buildSqlCompletionItems(sql, sql.length, { tables, columnsByTable, }); assert.ok(items.some((item) => item.label === "orders" && item.type === "table")); }); test("suggests keywords when typing without context", () => { const sql = "us"; const items = buildSqlCompletionItems(sql, sql.length, { tables, columnsByTable, }); assert.ok(items.some((item) => item.type === "keyword" && item.label === "USING")); }); test("always includes keywords alongside table suggestions", () => { const sql = "select * from us"; const items = buildSqlCompletionItems(sql, sql.length, { tables, columnsByTable, }); assert.ok(items.some((item) => item.type === "table")); assert.ok(items.some((item) => item.type === "keyword" && item.label === "USING")); }); test("suggests SQL Server IF keyword for conditional DDL", () => { const sql = "DROP TABLE I"; const items = buildSqlCompletionItems(sql, sql.length, { tables, columnsByTable, }); assert.ok(items.some((item) => item.type === "keyword" && item.label === "IF")); }); test("suggests SQL Server IIF and CHOOSE scalar functions", () => { const iifItems = buildSqlCompletionItems("SELECT II", "SELECT II".length, { tables, columnsByTable, }); const chooseItems = buildSqlCompletionItems("SELECT CHO", "SELECT CHO".length, { tables, columnsByTable, }); assert.ok(iifItems.some((item) => item.type === "keyword" && item.label === "IIF")); assert.ok(chooseItems.some((item) => item.type === "keyword" && item.label === "CHOOSE")); }); test("suggests SQL Server data types in CREATE TABLE column definitions", () => { const sql = "CREATE TABLE dbo.jobs (id "; const items = buildSqlCompletionItems(sql, sql.length, { tables, columnsByTable, }); assert.ok(items.some((item) => item.type === "keyword" && item.label === "INT")); assert.ok(items.some((item) => item.type === "keyword" && item.label === "BIGINT")); assert.ok(items.some((item) => item.type === "keyword" && item.label === "NVARCHAR")); }); test("does not auto-open completion after structural punctuation", () => { for (const sql of ["select count(*)", "select * from users;", "select * from users,"]) { assert.equal(shouldAutoOpenSqlCompletion(sql, sql.length), false, sql); } }); test("auto-opens completion after word characters and explicit dot qualifiers", () => { for (const sql of ["sel", "select * from us", "select u."]) { assert.equal(shouldAutoOpenSqlCompletion(sql, sql.length), true, sql); } }); test("limits table suggestions for large schemas after filtering by prefix", () => { const largeTables: SqlCompletionTable[] = Array.from({ length: 500 }, (_, index) => ({ name: `erp_invoice_${String(index).padStart(4, "0")}`, schema: "dbo", type: "table", })); const sql = "select * from erp_invoice_"; const items = buildSqlCompletionItems(sql, sql.length, { tables: largeTables, columnsByTable, }); const tableItems = items.filter((item) => item.type === "table"); assert.equal(tableItems.length, 200); assert.equal(tableItems[0]?.label, "erp_invoice_0000"); assert.equal(tableItems.at(-1)?.label, "erp_invoice_0199"); });