foundation.test.ts 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  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. expect(content).toContain('*.db');
  46. cg.close();
  47. });
  48. it('should throw if already initialized', () => {
  49. const cg = CodeGraph.initSync(tempDir);
  50. cg.close();
  51. expect(() => CodeGraph.initSync(tempDir)).toThrow(/already initialized/i);
  52. });
  53. });
  54. describe('Opening Projects', () => {
  55. it('should open an existing project', () => {
  56. // First initialize
  57. const cg1 = CodeGraph.initSync(tempDir);
  58. cg1.close();
  59. // Then open
  60. const cg2 = CodeGraph.openSync(tempDir);
  61. expect(cg2.getProjectRoot()).toBe(path.resolve(tempDir));
  62. cg2.close();
  63. });
  64. it('should throw if not initialized', () => {
  65. expect(() => CodeGraph.openSync(tempDir)).toThrow(/not initialized/i);
  66. });
  67. });
  68. describe('Static Methods', () => {
  69. it('isInitialized should return false for new directory', () => {
  70. expect(CodeGraph.isInitialized(tempDir)).toBe(false);
  71. });
  72. it('isInitialized should return true after init', () => {
  73. const cg = CodeGraph.initSync(tempDir);
  74. expect(CodeGraph.isInitialized(tempDir)).toBe(true);
  75. cg.close();
  76. });
  77. });
  78. describe('Database', () => {
  79. it('should create database with correct schema', () => {
  80. const cg = CodeGraph.initSync(tempDir);
  81. // Check that we can get stats (requires tables to exist)
  82. const stats = cg.getStats();
  83. expect(stats.nodeCount).toBe(0);
  84. expect(stats.edgeCount).toBe(0);
  85. expect(stats.fileCount).toBe(0);
  86. cg.close();
  87. });
  88. it('should return correct database size', () => {
  89. const cg = CodeGraph.initSync(tempDir);
  90. const stats = cg.getStats();
  91. // Database should have some size (at least the schema)
  92. expect(stats.dbSizeBytes).toBeGreaterThan(0);
  93. cg.close();
  94. });
  95. it('should support optimize operation', () => {
  96. const cg = CodeGraph.initSync(tempDir);
  97. // Should not throw
  98. expect(() => cg.optimize()).not.toThrow();
  99. cg.close();
  100. });
  101. it('should support clear operation', () => {
  102. const cg = CodeGraph.initSync(tempDir);
  103. // Should not throw
  104. expect(() => cg.clear()).not.toThrow();
  105. const stats = cg.getStats();
  106. expect(stats.nodeCount).toBe(0);
  107. cg.close();
  108. });
  109. });
  110. describe('Directory Management', () => {
  111. it('should validate directory structure', () => {
  112. const cg = CodeGraph.initSync(tempDir);
  113. cg.close();
  114. const validation = validateDirectory(tempDir);
  115. expect(validation.valid).toBe(true);
  116. expect(validation.errors).toHaveLength(0);
  117. });
  118. it('should detect invalid directory', () => {
  119. const validation = validateDirectory(tempDir);
  120. expect(validation.valid).toBe(false);
  121. expect(validation.errors.length).toBeGreaterThan(0);
  122. });
  123. });
  124. describe('Uninitialize', () => {
  125. it('should remove .CodeGraph directory', () => {
  126. const cg = CodeGraph.initSync(tempDir);
  127. cg.uninitialize();
  128. expect(fs.existsSync(getCodeGraphDir(tempDir))).toBe(false);
  129. expect(CodeGraph.isInitialized(tempDir)).toBe(false);
  130. });
  131. });
  132. describe('Close/Destroy', () => {
  133. it('should close database but keep .CodeGraph directory', () => {
  134. const cg = CodeGraph.initSync(tempDir);
  135. cg.destroy(); // destroy is alias for close
  136. expect(fs.existsSync(getCodeGraphDir(tempDir))).toBe(true);
  137. expect(CodeGraph.isInitialized(tempDir)).toBe(true);
  138. });
  139. });
  140. describe('Graph Query Methods', () => {
  141. it('should throw "Node not found" for non-existent nodes', () => {
  142. const cg = CodeGraph.initSync(tempDir);
  143. // getContext throws for non-existent nodes
  144. expect(() => cg.getContext('non-existent')).toThrow(/not found/i);
  145. cg.close();
  146. });
  147. it('should return empty results for non-existent nodes', () => {
  148. const cg = CodeGraph.initSync(tempDir);
  149. // These methods return empty results instead of throwing
  150. const traverseResult = cg.traverse('non-existent');
  151. expect(traverseResult.nodes.size).toBe(0);
  152. const callGraph = cg.getCallGraph('non-existent');
  153. expect(callGraph.nodes.size).toBe(0);
  154. const typeHierarchy = cg.getTypeHierarchy('non-existent');
  155. expect(typeHierarchy.nodes.size).toBe(0);
  156. const usages = cg.findUsages('non-existent');
  157. expect(usages.length).toBe(0);
  158. cg.close();
  159. });
  160. });
  161. });
  162. describe('Database Connection', () => {
  163. let tempDir: string;
  164. beforeEach(() => {
  165. tempDir = createTempDir();
  166. });
  167. afterEach(() => {
  168. cleanupTempDir(tempDir);
  169. });
  170. it('should initialize new database', () => {
  171. const dbPath = path.join(tempDir, 'test.db');
  172. const db = DatabaseConnection.initialize(dbPath);
  173. expect(db.isOpen()).toBe(true);
  174. expect(fs.existsSync(dbPath)).toBe(true);
  175. db.close();
  176. });
  177. it('should get schema version', () => {
  178. const dbPath = path.join(tempDir, 'test.db');
  179. const db = DatabaseConnection.initialize(dbPath);
  180. const version = db.getSchemaVersion();
  181. expect(version).not.toBeNull();
  182. expect(version?.version).toBe(4);
  183. db.close();
  184. });
  185. it('should support transactions', () => {
  186. const dbPath = path.join(tempDir, 'test.db');
  187. const db = DatabaseConnection.initialize(dbPath);
  188. const result = db.transaction(() => {
  189. return 42;
  190. });
  191. expect(result).toBe(42);
  192. db.close();
  193. });
  194. it('should throw when opening non-existent database', () => {
  195. const dbPath = path.join(tempDir, 'nonexistent.db');
  196. expect(() => DatabaseConnection.open(dbPath)).toThrow(/not found/i);
  197. });
  198. });
  199. describe('Query Builder', () => {
  200. let tempDir: string;
  201. let cg: CodeGraph;
  202. beforeEach(() => {
  203. tempDir = createTempDir();
  204. cg = CodeGraph.initSync(tempDir);
  205. });
  206. afterEach(() => {
  207. cg.close();
  208. cleanupTempDir(tempDir);
  209. });
  210. it('should return null for non-existent node', () => {
  211. const node = cg.getNode('nonexistent');
  212. expect(node).toBeNull();
  213. });
  214. it('should return empty array for nodes in non-existent file', () => {
  215. const nodes = cg.getNodesInFile('nonexistent.ts');
  216. expect(nodes).toEqual([]);
  217. });
  218. it('should return empty array for edges from non-existent node', () => {
  219. const edges = cg.getOutgoingEdges('nonexistent');
  220. expect(edges).toEqual([]);
  221. });
  222. it('should return null for non-existent file', () => {
  223. const file = cg.getFile('nonexistent.ts');
  224. expect(file).toBeNull();
  225. });
  226. it('should return empty array for files when none tracked', () => {
  227. const files = cg.getFiles();
  228. expect(files).toEqual([]);
  229. });
  230. });