collections-config.test.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. /**
  2. * Unit tests for collection config path resolution (PR #190).
  3. *
  4. * Tests that getConfigDir() respects XDG_CONFIG_HOME, QMD_CONFIG_DIR,
  5. * and falls back to ~/.config/qmd.
  6. */
  7. import { describe, test, expect, beforeEach, afterEach } from "vitest";
  8. import { join } from "path";
  9. import { homedir, tmpdir } from "os";
  10. import { mkdtempSync, rmSync, readFileSync } from "fs";
  11. import {
  12. getConfigPath,
  13. setConfigIndexName,
  14. setConfigSource,
  15. loadConfig,
  16. saveConfig,
  17. } from "../src/collections.js";
  18. import type { CollectionConfig } from "../src/collections.js";
  19. // Save/restore env vars around each test
  20. let savedEnv: Record<string, string | undefined>;
  21. beforeEach(() => {
  22. savedEnv = {
  23. QMD_CONFIG_DIR: process.env.QMD_CONFIG_DIR,
  24. XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME,
  25. };
  26. // Reset index name to default
  27. setConfigIndexName("index");
  28. });
  29. afterEach(() => {
  30. // Reset index name to default (prevents leaking into other test files under bun test)
  31. setConfigIndexName("index");
  32. for (const [key, val] of Object.entries(savedEnv)) {
  33. if (val === undefined) {
  34. delete process.env[key];
  35. } else {
  36. process.env[key] = val;
  37. }
  38. }
  39. });
  40. describe("getConfigDir via getConfigPath", () => {
  41. test("defaults to ~/.config/qmd when no env vars are set", () => {
  42. delete process.env.QMD_CONFIG_DIR;
  43. delete process.env.XDG_CONFIG_HOME;
  44. expect(getConfigPath()).toBe(join(homedir(), ".config", "qmd", "index.yml"));
  45. });
  46. test("QMD_CONFIG_DIR takes highest priority", () => {
  47. process.env.QMD_CONFIG_DIR = "/custom/qmd-config";
  48. process.env.XDG_CONFIG_HOME = "/xdg/config";
  49. expect(getConfigPath()).toBe(join("/custom/qmd-config", "index.yml"));
  50. });
  51. test("XDG_CONFIG_HOME is used when QMD_CONFIG_DIR is not set", () => {
  52. delete process.env.QMD_CONFIG_DIR;
  53. process.env.XDG_CONFIG_HOME = "/xdg/config";
  54. expect(getConfigPath()).toBe(join("/xdg/config", "qmd", "index.yml"));
  55. });
  56. test("XDG_CONFIG_HOME appends qmd subdirectory", () => {
  57. delete process.env.QMD_CONFIG_DIR;
  58. process.env.XDG_CONFIG_HOME = "/home/agent/.config";
  59. expect(getConfigPath()).toBe(join("/home/agent/.config", "qmd", "index.yml"));
  60. });
  61. test("QMD_CONFIG_DIR overrides XDG_CONFIG_HOME", () => {
  62. process.env.QMD_CONFIG_DIR = "/override";
  63. process.env.XDG_CONFIG_HOME = "/should-not-use";
  64. expect(getConfigPath()).toBe(join("/override", "index.yml"));
  65. });
  66. test("respects custom index name", () => {
  67. delete process.env.QMD_CONFIG_DIR;
  68. process.env.XDG_CONFIG_HOME = "/xdg/config";
  69. setConfigIndexName("myindex");
  70. expect(getConfigPath()).toBe(join("/xdg/config", "qmd", "myindex.yml"));
  71. });
  72. });
  73. // ============================================================================
  74. // chunkStrategy schema round-trip (Phase 2 — i-bud0h8vu)
  75. // ============================================================================
  76. describe("Collection.chunkStrategy YAML round-trip", () => {
  77. let tmpDir: string;
  78. beforeEach(() => {
  79. tmpDir = mkdtempSync(join(tmpdir(), "qmd-chunkstrategy-"));
  80. process.env.QMD_CONFIG_DIR = tmpDir;
  81. setConfigIndexName("index");
  82. });
  83. afterEach(() => {
  84. // Reset config source so we don't leak inline state
  85. setConfigSource();
  86. try {
  87. rmSync(tmpDir, { recursive: true, force: true });
  88. } catch {
  89. // best-effort
  90. }
  91. });
  92. test("chunkStrategy field persists through save/load cycle", () => {
  93. const config: CollectionConfig = {
  94. collections: {
  95. "oivo-cli": {
  96. path: "/srv/cli/src",
  97. pattern: "**/*.ts",
  98. chunkStrategy: "function",
  99. },
  100. "oivo-docs": {
  101. path: "/srv/docs",
  102. pattern: "**/*.md",
  103. // no chunkStrategy — should remain unset after round-trip
  104. },
  105. },
  106. };
  107. saveConfig(config);
  108. const loaded = loadConfig();
  109. expect(loaded.collections["oivo-cli"]?.chunkStrategy).toBe("function");
  110. expect(loaded.collections["oivo-docs"]?.chunkStrategy).toBeUndefined();
  111. });
  112. test("chunkStrategy 'auto' and 'regex' round-trip", () => {
  113. const config: CollectionConfig = {
  114. collections: {
  115. a: { path: "/a", pattern: "*.ts", chunkStrategy: "auto" },
  116. b: { path: "/b", pattern: "*.ts", chunkStrategy: "regex" },
  117. },
  118. };
  119. saveConfig(config);
  120. const loaded = loadConfig();
  121. expect(loaded.collections.a?.chunkStrategy).toBe("auto");
  122. expect(loaded.collections.b?.chunkStrategy).toBe("regex");
  123. });
  124. test("omitted chunkStrategy does not appear in serialized YAML", () => {
  125. const config: CollectionConfig = {
  126. collections: {
  127. plain: { path: "/p", pattern: "*.md" },
  128. },
  129. };
  130. saveConfig(config);
  131. const yaml = readFileSync(join(tmpDir, "index.yml"), "utf-8");
  132. expect(yaml).not.toContain("chunkStrategy");
  133. });
  134. });