import { describe, it, expect } from "vitest"; import { IdentifierError } from "../../../src/database/validate.js"; import { generateIndexName, generateCreateIndexSql, generateDropIndexSql, normalizeIndexes, } from "../../../src/plugins/storage-indexes.js"; import { createTestDatabase } from "../../utils/test-db.js"; describe("storage-indexes", () => { const db = createTestDatabase(); describe("generateIndexName", () => { it("should generate deterministic index name for single field", () => { const name = generateIndexName("my-plugin", "items", ["status"]); expect(name).toBe("idx_plugin_my-plugin_items_status"); }); it("should generate deterministic index name for multiple fields", () => { const name = generateIndexName("my-plugin", "items", ["status", "createdAt"]); expect(name).toBe("idx_plugin_my-plugin_items_status_createdAt"); }); it("should truncate long names to 128 characters", () => { const longFieldNames = Array.from({ length: 20 }, (_, i) => `veryLongFieldName${i}`); const name = generateIndexName("my-plugin", "items", longFieldNames); expect(name.length).toBeLessThanOrEqual(128); }); it("should be consistent across calls", () => { const name1 = generateIndexName("plugin", "coll", ["a", "b"]); const name2 = generateIndexName("plugin", "coll", ["a", "b"]); expect(name1).toBe(name2); }); }); describe("generateCreateIndexSql", () => { it("should return a RawBuilder with CREATE INDEX", () => { const result = generateCreateIndexSql(db, "my-plugin", "items", ["status"]); // It should be a RawBuilder (has toOperationNode method) expect(result).toBeDefined(); expect(typeof (result as any).toOperationNode).toBe("function"); }); it("should reject invalid field names", () => { expect(() => generateCreateIndexSql(db, "my-plugin", "items", ["status; DROP TABLE users--"]), ).toThrow(IdentifierError); }); it("should reject invalid collection names", () => { expect(() => generateCreateIndexSql(db, "my-plugin", "items'; DROP TABLE--", ["status"]), ).toThrow(IdentifierError); }); it("should reject invalid plugin IDs", () => { expect(() => generateCreateIndexSql(db, "'; DROP TABLE--", "items", ["status"])).toThrow( IdentifierError, ); }); it("should accept valid identifiers with hyphens in plugin ID", () => { // Should not throw const result = generateCreateIndexSql(db, "my-plugin", "items", ["status"]); expect(result).toBeDefined(); }); it("should accept composite field indexes", () => { // Should not throw const result = generateCreateIndexSql(db, "my-plugin", "items", ["status", "created_at"]); expect(result).toBeDefined(); }); }); describe("generateDropIndexSql", () => { it("should return a RawBuilder", () => { const result = generateDropIndexSql("idx_plugin_my-plugin_items_status"); expect(result).toBeDefined(); expect(typeof (result as any).toOperationNode).toBe("function"); }); }); describe("normalizeIndexes", () => { it("should convert single fields to arrays", () => { const normalized = normalizeIndexes(["status", "category"]); expect(normalized).toEqual([["status"], ["category"]]); }); it("should keep arrays as-is", () => { const normalized = normalizeIndexes([["status", "createdAt"]]); expect(normalized).toEqual([["status", "createdAt"]]); }); it("should handle mixed input", () => { const normalized = normalizeIndexes(["status", ["category", "priority"], "name"]); expect(normalized).toEqual([["status"], ["category", "priority"], ["name"]]); }); it("should return empty array for empty input", () => { const normalized = normalizeIndexes([]); expect(normalized).toEqual([]); }); }); });