1
0

foundation.test.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. /**
  2. * Foundation Tests
  3. *
  4. * Tests for the CodeGraph foundation layer.
  5. */
  6. import { describe, it, expect, beforeEach, afterEach } from 'vitest';
  7. import * as fs from 'fs';
  8. import * as path from 'path';
  9. import * as os from 'os';
  10. import { CodeGraph } from '../src';
  11. import { Node, Edge } from '../src/types';
  12. import { isInitialized, getCodeGraphDir, validateDirectory } from '../src/directory';
  13. import { DatabaseConnection, getDatabasePath } from '../src/db';
  14. // Create a temporary directory for each test
  15. function createTempDir(): string {
  16. return fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-test-'));
  17. }
  18. // Clean up temporary directory
  19. function cleanupTempDir(dir: string): void {
  20. if (fs.existsSync(dir)) {
  21. fs.rmSync(dir, { recursive: true, force: true });
  22. }
  23. }
  24. describe('CodeGraph Foundation', () => {
  25. let tempDir: string;
  26. beforeEach(() => {
  27. tempDir = createTempDir();
  28. });
  29. afterEach(() => {
  30. cleanupTempDir(tempDir);
  31. });
  32. describe('Initialization', () => {
  33. it('should initialize a new project', () => {
  34. const cg = CodeGraph.initSync(tempDir);
  35. expect(CodeGraph.isInitialized(tempDir)).toBe(true);
  36. expect(fs.existsSync(getCodeGraphDir(tempDir))).toBe(true);
  37. expect(fs.existsSync(getDatabasePath(tempDir))).toBe(true);
  38. cg.close();
  39. });
  40. it('should create .gitignore in .CodeGraph directory', () => {
  41. const cg = CodeGraph.initSync(tempDir);
  42. const gitignorePath = path.join(getCodeGraphDir(tempDir), '.gitignore');
  43. expect(fs.existsSync(gitignorePath)).toBe(true);
  44. const content = fs.readFileSync(gitignorePath, 'utf-8');
  45. // Ignore everything in .codegraph/ except this file itself, so transient
  46. // files (db, daemon.pid, sockets, logs) never show up in git. (#492, #484)
  47. expect(content).toContain('*');
  48. expect(content).toContain('!.gitignore');
  49. cg.close();
  50. });
  51. it('should throw if already initialized', () => {
  52. const cg = CodeGraph.initSync(tempDir);
  53. cg.close();
  54. expect(() => CodeGraph.initSync(tempDir)).toThrow(/already initialized/i);
  55. });
  56. });
  57. describe('Opening Projects', () => {
  58. it('should open an existing project', () => {
  59. // First initialize
  60. const cg1 = CodeGraph.initSync(tempDir);
  61. cg1.close();
  62. // Then open
  63. const cg2 = CodeGraph.openSync(tempDir);
  64. expect(cg2.getProjectRoot()).toBe(path.resolve(tempDir));
  65. cg2.close();
  66. });
  67. it('should throw if not initialized', () => {
  68. expect(() => CodeGraph.openSync(tempDir)).toThrow(/not initialized/i);
  69. });
  70. });
  71. describe('Static Methods', () => {
  72. it('isInitialized should return false for new directory', () => {
  73. expect(CodeGraph.isInitialized(tempDir)).toBe(false);
  74. });
  75. it('isInitialized should return true after init', () => {
  76. const cg = CodeGraph.initSync(tempDir);
  77. expect(CodeGraph.isInitialized(tempDir)).toBe(true);
  78. cg.close();
  79. });
  80. });
  81. describe('Database', () => {
  82. it('should create database with correct schema', () => {
  83. const cg = CodeGraph.initSync(tempDir);
  84. // Check that we can get stats (requires tables to exist)
  85. const stats = cg.getStats();
  86. expect(stats.nodeCount).toBe(0);
  87. expect(stats.edgeCount).toBe(0);
  88. expect(stats.fileCount).toBe(0);
  89. cg.close();
  90. });
  91. it('should return correct database size', () => {
  92. const cg = CodeGraph.initSync(tempDir);
  93. const stats = cg.getStats();
  94. // Database should have some size (at least the schema)
  95. expect(stats.dbSizeBytes).toBeGreaterThan(0);
  96. cg.close();
  97. });
  98. it('should support optimize operation', () => {
  99. const cg = CodeGraph.initSync(tempDir);
  100. // Should not throw
  101. expect(() => cg.optimize()).not.toThrow();
  102. cg.close();
  103. });
  104. it('should support clear operation', () => {
  105. const cg = CodeGraph.initSync(tempDir);
  106. // Should not throw
  107. expect(() => cg.clear()).not.toThrow();
  108. const stats = cg.getStats();
  109. expect(stats.nodeCount).toBe(0);
  110. cg.close();
  111. });
  112. });
  113. describe('Directory Management', () => {
  114. it('should validate directory structure', () => {
  115. const cg = CodeGraph.initSync(tempDir);
  116. cg.close();
  117. const validation = validateDirectory(tempDir);
  118. expect(validation.valid).toBe(true);
  119. expect(validation.errors).toHaveLength(0);
  120. });
  121. it('should detect invalid directory', () => {
  122. const validation = validateDirectory(tempDir);
  123. expect(validation.valid).toBe(false);
  124. expect(validation.errors.length).toBeGreaterThan(0);
  125. });
  126. });
  127. describe('Uninitialize', () => {
  128. it('should remove .CodeGraph directory', () => {
  129. const cg = CodeGraph.initSync(tempDir);
  130. cg.uninitialize();
  131. expect(fs.existsSync(getCodeGraphDir(tempDir))).toBe(false);
  132. expect(CodeGraph.isInitialized(tempDir)).toBe(false);
  133. });
  134. });
  135. describe('Close/Destroy', () => {
  136. it('should close database but keep .CodeGraph directory', () => {
  137. const cg = CodeGraph.initSync(tempDir);
  138. cg.destroy(); // destroy is alias for close
  139. expect(fs.existsSync(getCodeGraphDir(tempDir))).toBe(true);
  140. expect(CodeGraph.isInitialized(tempDir)).toBe(true);
  141. });
  142. });
  143. describe('Graph Query Methods', () => {
  144. it('should throw "Node not found" for non-existent nodes', () => {
  145. const cg = CodeGraph.initSync(tempDir);
  146. // getContext throws for non-existent nodes
  147. expect(() => cg.getContext('non-existent')).toThrow(/not found/i);
  148. cg.close();
  149. });
  150. it('should return empty results for non-existent nodes', () => {
  151. const cg = CodeGraph.initSync(tempDir);
  152. // These methods return empty results instead of throwing
  153. const traverseResult = cg.traverse('non-existent');
  154. expect(traverseResult.nodes.size).toBe(0);
  155. const callGraph = cg.getCallGraph('non-existent');
  156. expect(callGraph.nodes.size).toBe(0);
  157. const typeHierarchy = cg.getTypeHierarchy('non-existent');
  158. expect(typeHierarchy.nodes.size).toBe(0);
  159. const usages = cg.findUsages('non-existent');
  160. expect(usages.length).toBe(0);
  161. cg.close();
  162. });
  163. });
  164. });
  165. describe('Database Connection', () => {
  166. let tempDir: string;
  167. beforeEach(() => {
  168. tempDir = createTempDir();
  169. });
  170. afterEach(() => {
  171. cleanupTempDir(tempDir);
  172. });
  173. it('should initialize new database', () => {
  174. const dbPath = path.join(tempDir, 'test.db');
  175. const db = DatabaseConnection.initialize(dbPath);
  176. expect(db.isOpen()).toBe(true);
  177. expect(fs.existsSync(dbPath)).toBe(true);
  178. db.close();
  179. });
  180. it('should get schema version', () => {
  181. const dbPath = path.join(tempDir, 'test.db');
  182. const db = DatabaseConnection.initialize(dbPath);
  183. const version = db.getSchemaVersion();
  184. expect(version).not.toBeNull();
  185. expect(version?.version).toBe(4);
  186. db.close();
  187. });
  188. it('should support transactions', () => {
  189. const dbPath = path.join(tempDir, 'test.db');
  190. const db = DatabaseConnection.initialize(dbPath);
  191. const result = db.transaction(() => {
  192. return 42;
  193. });
  194. expect(result).toBe(42);
  195. db.close();
  196. });
  197. it('should throw when opening non-existent database', () => {
  198. const dbPath = path.join(tempDir, 'nonexistent.db');
  199. expect(() => DatabaseConnection.open(dbPath)).toThrow(/not found/i);
  200. });
  201. });
  202. describe('Query Builder', () => {
  203. let tempDir: string;
  204. let cg: CodeGraph;
  205. beforeEach(() => {
  206. tempDir = createTempDir();
  207. cg = CodeGraph.initSync(tempDir);
  208. });
  209. afterEach(() => {
  210. cg.close();
  211. cleanupTempDir(tempDir);
  212. });
  213. it('should return null for non-existent node', () => {
  214. const node = cg.getNode('nonexistent');
  215. expect(node).toBeNull();
  216. });
  217. it('should return empty array for nodes in non-existent file', () => {
  218. const nodes = cg.getNodesInFile('nonexistent.ts');
  219. expect(nodes).toEqual([]);
  220. });
  221. it('should return empty array for edges from non-existent node', () => {
  222. const edges = cg.getOutgoingEdges('nonexistent');
  223. expect(edges).toEqual([]);
  224. });
  225. it('should return null for non-existent file', () => {
  226. const file = cg.getFile('nonexistent.ts');
  227. expect(file).toBeNull();
  228. });
  229. it('should return empty array for files when none tracked', () => {
  230. const files = cg.getFiles();
  231. expect(files).toEqual([]);
  232. });
  233. });