mirror of
https://github.com/giancarloerra/socraticode.git
synced 2026-07-03 14:05:21 +02:00
374 lines
11 KiB
TypeScript
374 lines
11 KiB
TypeScript
// SPDX-License-Identifier: AGPL-3.0-only
|
|
// Copyright (C) 2026 Giancarlo Erra - Altaire Limited
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
CHUNK_OVERLAP,
|
|
CHUNK_SIZE,
|
|
getLanguageFromExtension,
|
|
INDEX_BATCH_SIZE,
|
|
MAX_AVG_LINE_LENGTH,
|
|
MAX_CHUNK_CHARS,
|
|
MAX_FILE_BYTES,
|
|
mergeExtraExtensions,
|
|
OLLAMA_CONTAINER_NAME,
|
|
OLLAMA_HOST,
|
|
OLLAMA_IMAGE,
|
|
OLLAMA_PORT,
|
|
parseExtraExtensions,
|
|
QDRANT_CONTAINER_NAME,
|
|
QDRANT_GRPC_PORT,
|
|
QDRANT_HOST,
|
|
QDRANT_IMAGE,
|
|
QDRANT_MODE,
|
|
QDRANT_PORT,
|
|
QDRANT_URL,
|
|
SEARCH_DEFAULT_LIMIT,
|
|
SEARCH_MIN_SCORE,
|
|
SPECIAL_FILES,
|
|
SUPPORTED_EXTENSIONS,
|
|
} from "../../src/constants.js";
|
|
|
|
describe("constants", () => {
|
|
describe("Qdrant configuration", () => {
|
|
it("has default port 16333", () => {
|
|
expect(QDRANT_PORT).toBe(16333);
|
|
});
|
|
|
|
it("has default gRPC port 16334", () => {
|
|
expect(QDRANT_GRPC_PORT).toBe(16334);
|
|
});
|
|
|
|
it("defaults host to localhost", () => {
|
|
expect(QDRANT_HOST).toBe("localhost");
|
|
});
|
|
|
|
it("has correct container name", () => {
|
|
expect(QDRANT_CONTAINER_NAME).toBe("socraticode-qdrant");
|
|
});
|
|
|
|
it("uses pinned Qdrant image version", () => {
|
|
expect(QDRANT_IMAGE).toMatch(/^qdrant\/qdrant:v\d+\.\d+\.\d+$/);
|
|
});
|
|
|
|
it("defaults mode to managed", () => {
|
|
expect(QDRANT_MODE).toBe("managed");
|
|
});
|
|
|
|
it("defaults URL to undefined", () => {
|
|
expect(QDRANT_URL).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("Ollama configuration", () => {
|
|
it("has default port 11435", () => {
|
|
expect(OLLAMA_PORT).toBe(11435);
|
|
});
|
|
|
|
it("defaults host to localhost with configured port", () => {
|
|
expect(OLLAMA_HOST).toBe(`http://localhost:${OLLAMA_PORT}`);
|
|
});
|
|
|
|
it("has correct container name", () => {
|
|
expect(OLLAMA_CONTAINER_NAME).toBe("socraticode-ollama");
|
|
});
|
|
|
|
it("uses latest Ollama image", () => {
|
|
expect(OLLAMA_IMAGE).toBe("ollama/ollama:latest");
|
|
});
|
|
});
|
|
|
|
describe("chunking configuration", () => {
|
|
it("has reasonable chunk size", () => {
|
|
expect(CHUNK_SIZE).toBeGreaterThan(0);
|
|
expect(CHUNK_SIZE).toBeLessThanOrEqual(500);
|
|
});
|
|
|
|
it("has overlap smaller than chunk size", () => {
|
|
expect(CHUNK_OVERLAP).toBeGreaterThan(0);
|
|
expect(CHUNK_OVERLAP).toBeLessThan(CHUNK_SIZE);
|
|
});
|
|
|
|
it("defaults to 100 lines per chunk", () => {
|
|
expect(CHUNK_SIZE).toBe(100);
|
|
});
|
|
|
|
it("defaults to 10 lines overlap", () => {
|
|
expect(CHUNK_OVERLAP).toBe(10);
|
|
});
|
|
});
|
|
|
|
describe("search configuration", () => {
|
|
it("defaults SEARCH_DEFAULT_LIMIT to 10", () => {
|
|
expect(SEARCH_DEFAULT_LIMIT).toBe(10);
|
|
});
|
|
|
|
it("clamps SEARCH_DEFAULT_LIMIT between 1 and 50", () => {
|
|
// Can't easily test env override in-process, but verify the default is within bounds
|
|
expect(SEARCH_DEFAULT_LIMIT).toBeGreaterThanOrEqual(1);
|
|
expect(SEARCH_DEFAULT_LIMIT).toBeLessThanOrEqual(50);
|
|
});
|
|
|
|
it("defaults SEARCH_MIN_SCORE to 0.10", () => {
|
|
expect(SEARCH_MIN_SCORE).toBeCloseTo(0.10);
|
|
});
|
|
|
|
it("clamps SEARCH_MIN_SCORE between 0 and 1", () => {
|
|
expect(SEARCH_MIN_SCORE).toBeGreaterThanOrEqual(0);
|
|
expect(SEARCH_MIN_SCORE).toBeLessThanOrEqual(1);
|
|
});
|
|
});
|
|
|
|
describe("indexing configuration", () => {
|
|
it("defaults INDEX_BATCH_SIZE to 50", () => {
|
|
expect(INDEX_BATCH_SIZE).toBe(50);
|
|
});
|
|
|
|
it("defaults MAX_FILE_BYTES to 5MB", () => {
|
|
expect(MAX_FILE_BYTES).toBe(5_000_000);
|
|
});
|
|
|
|
it("MAX_AVG_LINE_LENGTH is a positive number", () => {
|
|
expect(MAX_AVG_LINE_LENGTH).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("defaults MAX_AVG_LINE_LENGTH to 500", () => {
|
|
expect(MAX_AVG_LINE_LENGTH).toBe(500);
|
|
});
|
|
|
|
it("MAX_CHUNK_CHARS is a positive number", () => {
|
|
expect(MAX_CHUNK_CHARS).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("defaults MAX_CHUNK_CHARS to 2000", () => {
|
|
expect(MAX_CHUNK_CHARS).toBe(2000);
|
|
});
|
|
|
|
it("MAX_CHUNK_CHARS is less than MAX_AVG_LINE_LENGTH * CHUNK_SIZE (sanity check)", () => {
|
|
// Ensures MAX_CHUNK_CHARS is a tighter limit than unconstrained line-based chunks
|
|
expect(MAX_CHUNK_CHARS).toBeLessThan(MAX_AVG_LINE_LENGTH * CHUNK_SIZE);
|
|
});
|
|
});
|
|
|
|
describe("SUPPORTED_EXTENSIONS", () => {
|
|
it("is a Set", () => {
|
|
expect(SUPPORTED_EXTENSIONS).toBeInstanceOf(Set);
|
|
});
|
|
|
|
it("contains common programming language extensions", () => {
|
|
const expected = [".js", ".ts", ".py", ".java", ".go", ".rs", ".rb", ".php", ".swift", ".c", ".cpp"];
|
|
for (const ext of expected) {
|
|
expect(SUPPORTED_EXTENSIONS.has(ext), `missing: ${ext}`).toBe(true);
|
|
}
|
|
});
|
|
|
|
it("contains TypeScript variants", () => {
|
|
expect(SUPPORTED_EXTENSIONS.has(".ts")).toBe(true);
|
|
expect(SUPPORTED_EXTENSIONS.has(".tsx")).toBe(true);
|
|
expect(SUPPORTED_EXTENSIONS.has(".mjs")).toBe(true);
|
|
expect(SUPPORTED_EXTENSIONS.has(".cjs")).toBe(true);
|
|
});
|
|
|
|
it("contains web extensions", () => {
|
|
expect(SUPPORTED_EXTENSIONS.has(".html")).toBe(true);
|
|
expect(SUPPORTED_EXTENSIONS.has(".css")).toBe(true);
|
|
expect(SUPPORTED_EXTENSIONS.has(".scss")).toBe(true);
|
|
expect(SUPPORTED_EXTENSIONS.has(".vue")).toBe(true);
|
|
expect(SUPPORTED_EXTENSIONS.has(".svelte")).toBe(true);
|
|
});
|
|
|
|
it("contains config/data extensions", () => {
|
|
expect(SUPPORTED_EXTENSIONS.has(".json")).toBe(true);
|
|
expect(SUPPORTED_EXTENSIONS.has(".yaml")).toBe(true);
|
|
expect(SUPPORTED_EXTENSIONS.has(".yml")).toBe(true);
|
|
expect(SUPPORTED_EXTENSIONS.has(".toml")).toBe(true);
|
|
expect(SUPPORTED_EXTENSIONS.has(".xml")).toBe(true);
|
|
});
|
|
|
|
it("contains documentation extensions", () => {
|
|
expect(SUPPORTED_EXTENSIONS.has(".md")).toBe(true);
|
|
expect(SUPPORTED_EXTENSIONS.has(".mdx")).toBe(true);
|
|
expect(SUPPORTED_EXTENSIONS.has(".rst")).toBe(true);
|
|
expect(SUPPORTED_EXTENSIONS.has(".txt")).toBe(true);
|
|
});
|
|
|
|
it("does not contain binary or image extensions", () => {
|
|
expect(SUPPORTED_EXTENSIONS.has(".png")).toBe(false);
|
|
expect(SUPPORTED_EXTENSIONS.has(".jpg")).toBe(false);
|
|
expect(SUPPORTED_EXTENSIONS.has(".exe")).toBe(false);
|
|
expect(SUPPORTED_EXTENSIONS.has(".pdf")).toBe(false);
|
|
});
|
|
|
|
it("has at least 50 extensions", () => {
|
|
expect(SUPPORTED_EXTENSIONS.size).toBeGreaterThanOrEqual(50);
|
|
});
|
|
});
|
|
|
|
describe("SPECIAL_FILES", () => {
|
|
it("is a Set", () => {
|
|
expect(SPECIAL_FILES).toBeInstanceOf(Set);
|
|
});
|
|
|
|
it("includes Dockerfile", () => {
|
|
expect(SPECIAL_FILES.has("Dockerfile")).toBe(true);
|
|
});
|
|
|
|
it("includes Makefile", () => {
|
|
expect(SPECIAL_FILES.has("Makefile")).toBe(true);
|
|
});
|
|
|
|
it("includes Gemfile", () => {
|
|
expect(SPECIAL_FILES.has("Gemfile")).toBe(true);
|
|
});
|
|
|
|
it("includes .gitignore", () => {
|
|
expect(SPECIAL_FILES.has(".gitignore")).toBe(true);
|
|
});
|
|
|
|
it("includes .env.example", () => {
|
|
expect(SPECIAL_FILES.has(".env.example")).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("getLanguageFromExtension", () => {
|
|
it("maps .ts to typescript", () => {
|
|
expect(getLanguageFromExtension(".ts")).toBe("typescript");
|
|
});
|
|
|
|
it("maps .tsx to typescript", () => {
|
|
expect(getLanguageFromExtension(".tsx")).toBe("typescript");
|
|
});
|
|
|
|
it("maps .js to javascript", () => {
|
|
expect(getLanguageFromExtension(".js")).toBe("javascript");
|
|
});
|
|
|
|
it("maps .py to python", () => {
|
|
expect(getLanguageFromExtension(".py")).toBe("python");
|
|
});
|
|
|
|
it("maps .java to java", () => {
|
|
expect(getLanguageFromExtension(".java")).toBe("java");
|
|
});
|
|
|
|
it("maps .go to go", () => {
|
|
expect(getLanguageFromExtension(".go")).toBe("go");
|
|
});
|
|
|
|
it("maps .rs to rust", () => {
|
|
expect(getLanguageFromExtension(".rs")).toBe("rust");
|
|
});
|
|
|
|
it("maps .rb to ruby", () => {
|
|
expect(getLanguageFromExtension(".rb")).toBe("ruby");
|
|
});
|
|
|
|
it("maps .cpp to cpp", () => {
|
|
expect(getLanguageFromExtension(".cpp")).toBe("cpp");
|
|
});
|
|
|
|
it("maps .cs to csharp", () => {
|
|
expect(getLanguageFromExtension(".cs")).toBe("csharp");
|
|
});
|
|
|
|
it("maps .swift to swift", () => {
|
|
expect(getLanguageFromExtension(".swift")).toBe("swift");
|
|
});
|
|
|
|
it("maps .sh to shell", () => {
|
|
expect(getLanguageFromExtension(".sh")).toBe("shell");
|
|
});
|
|
|
|
it("maps .md to markdown", () => {
|
|
expect(getLanguageFromExtension(".md")).toBe("markdown");
|
|
});
|
|
|
|
it("maps .dart to dart", () => {
|
|
expect(getLanguageFromExtension(".dart")).toBe("dart");
|
|
});
|
|
|
|
it("maps .lua to lua", () => {
|
|
expect(getLanguageFromExtension(".lua")).toBe("lua");
|
|
});
|
|
|
|
it("maps .sql to sql", () => {
|
|
expect(getLanguageFromExtension(".sql")).toBe("sql");
|
|
});
|
|
|
|
it("returns plaintext for unknown extensions", () => {
|
|
expect(getLanguageFromExtension(".xyz")).toBe("plaintext");
|
|
expect(getLanguageFromExtension(".abc")).toBe("plaintext");
|
|
expect(getLanguageFromExtension("")).toBe("plaintext");
|
|
});
|
|
|
|
it("maps .r and .R to r", () => {
|
|
expect(getLanguageFromExtension(".r")).toBe("r");
|
|
expect(getLanguageFromExtension(".R")).toBe("r");
|
|
});
|
|
|
|
it("maps .kt to kotlin", () => {
|
|
expect(getLanguageFromExtension(".kt")).toBe("kotlin");
|
|
});
|
|
|
|
it("maps .scala to scala", () => {
|
|
expect(getLanguageFromExtension(".scala")).toBe("scala");
|
|
});
|
|
|
|
it("maps .php to php", () => {
|
|
expect(getLanguageFromExtension(".php")).toBe("php");
|
|
});
|
|
});
|
|
|
|
describe("parseExtraExtensions", () => {
|
|
it("returns empty set for undefined", () => {
|
|
expect(parseExtraExtensions(undefined).size).toBe(0);
|
|
});
|
|
|
|
it("returns empty set for empty string", () => {
|
|
expect(parseExtraExtensions("").size).toBe(0);
|
|
});
|
|
|
|
it("returns empty set for whitespace-only", () => {
|
|
expect(parseExtraExtensions(" ").size).toBe(0);
|
|
});
|
|
|
|
it("parses comma-separated extensions with dots", () => {
|
|
const result = parseExtraExtensions(".tpl,.blade,.hbs");
|
|
expect(result).toEqual(new Set([".tpl", ".blade", ".hbs"]));
|
|
});
|
|
|
|
it("adds leading dots when missing", () => {
|
|
const result = parseExtraExtensions("tpl,blade");
|
|
expect(result).toEqual(new Set([".tpl", ".blade"]));
|
|
});
|
|
|
|
it("trims whitespace and lowercases", () => {
|
|
const result = parseExtraExtensions(" .TPL , .Blade ");
|
|
expect(result).toEqual(new Set([".tpl", ".blade"]));
|
|
});
|
|
|
|
it("deduplicates entries", () => {
|
|
const result = parseExtraExtensions(".tpl,.tpl,tpl");
|
|
expect(result.size).toBe(1);
|
|
expect(result.has(".tpl")).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("mergeExtraExtensions", () => {
|
|
it("returns env var default when no tool param", () => {
|
|
// EXTRA_EXTENSIONS env var is not set in test environment, so should be empty
|
|
const result = mergeExtraExtensions();
|
|
expect(result.size).toBe(0);
|
|
});
|
|
|
|
it("returns tool param extensions when provided", () => {
|
|
const result = mergeExtraExtensions(".tpl,.blade");
|
|
expect(result).toEqual(new Set([".tpl", ".blade"]));
|
|
});
|
|
|
|
it("returns empty for undefined tool param", () => {
|
|
const result = mergeExtraExtensions(undefined);
|
|
expect(result.size).toBe(0);
|
|
});
|
|
});
|
|
});
|