diff --git a/.ai-context-generator-cli/tests/dependencyAnalyzer.test.ts b/.ai-context-generator-cli/tests/dependencyAnalyzer.test.ts new file mode 100644 index 0000000..a9c13fc --- /dev/null +++ b/.ai-context-generator-cli/tests/dependencyAnalyzer.test.ts @@ -0,0 +1,225 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { DependencyAnalyzer } from '../src/analyzers/dependencyAnalyzer'; + +describe('DependencyAnalyzer', () => { + let analyzer: DependencyAnalyzer; + let testDir: string; + + beforeEach(async () => { + analyzer = new DependencyAnalyzer(); + testDir = path.join('/tmp', `ai-context-test-${Date.now()}`); + await fs.promises.mkdir(testDir, { recursive: true }); + }); + + afterEach(async () => { + if (await fs.promises.stat(testDir).catch(() => null)) { + await fs.promises.rm(testDir, { recursive: true }); + } + }); + + describe('analyze', () => { + it('should parse package.json dependencies correctly', async () => { + const packageJson = { + dependencies: { + express: '^4.18.0', + lodash: '^4.17.21', + }, + devDependencies: { + jest: '^29.0.0', + typescript: '^5.0.0', + }, + }; + await fs.promises.writeFile( + path.join(testDir, 'package.json'), + JSON.stringify(packageJson) + ); + + const result = await analyzer.analyze(testDir, false); + + expect(result.total).toBe(2); + expect(result.direct.length).toBe(2); + expect(result.direct.find(d => d.name === 'express')?.version).toBe('^4.18.0'); + expect(result.dev.length).toBe(0); + }); + + it('should include dev dependencies when requested', async () => { + const packageJson = { + dependencies: { express: '^4.18.0' }, + devDependencies: { jest: '^29.0.0' }, + }; + await fs.promises.writeFile( + path.join(testDir, 'package.json'), + JSON.stringify(packageJson) + ); + + const result = await analyzer.analyze(testDir, true); + + expect(result.total).toBe(2); + expect(result.dev.length).toBe(1); + expect(result.dev[0].name).toBe('jest'); + }); + + it('should parse requirements.txt dependencies', async () => { + await fs.promises.writeFile( + path.join(testDir, 'requirements.txt'), + `django>=4.0.0 +flask>=2.0.0 +requests==2.28.0 +numpy~=1.24.0` + ); + + const result = await analyzer.analyze(testDir, false); + + expect(result.total).toBeGreaterThan(0); + const deps = result.direct.map(d => d.name); + expect(deps).toContain('django'); + expect(deps).toContain('flask'); + expect(deps).toContain('requests'); + }); + + it('should parse go.mod dependencies', async () => { + await fs.promises.writeFile( + path.join(testDir, 'go.mod'), + `module example.com/mymodule + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.0 + github.com/stretchr/testify v1.8.0 +)` + ); + + const result = await analyzer.analyze(testDir, false); + + expect(result.total).toBeGreaterThan(0); + const deps = result.direct.map(d => d.name); + expect(deps).toContain('github.com/gin-gonic/gin'); + }); + + it('should parse Cargo.toml dependencies', async () => { + await fs.promises.writeFile( + path.join(testDir, 'Cargo.toml'), + `[package] +name = "myproject" +version = "0.1.0" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +tokio = { version = "1.0", features = ["full"] } + +[dev-dependencies] +assertions = "0.3"` + ); + + const result = await analyzer.analyze(testDir, true); + + expect(result.total).toBe(3); + expect(result.direct.length).toBe(2); + expect(result.dev.length).toBe(1); + }); + + it('should parse pyproject.toml dependencies', async () => { + await fs.promises.writeFile( + path.join(testDir, 'pyproject.toml'), + `[project] +name = "myproject" +version = "0.1.0" +dependencies = [ + "requests>=2.28.0", + "flask>=2.0.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.0.0", + "black>=23.0.0", +]` + ); + + const result = await analyzer.analyze(testDir, false); + + expect(result.total).toBe(2); + expect(result.direct.length).toBe(2); + }); + + it('should parse composer.json dependencies', async () => { + await fs.promises.writeFile( + path.join(testDir, 'composer.json'), + JSON.stringify({ + require: { + php: '^8.0', + 'laravel/framework': '^10.0', + }, + 'require-dev': { + 'phpunit/phpunit': '^10.0', + }, + }) + ); + + const result = await analyzer.analyze(testDir, true); + + expect(result.total).toBe(3); + expect(result.dev.length).toBe(1); + }); + + it('should parse Gemfile dependencies', async () => { + await fs.promises.writeFile( + path.join(testDir, 'Gemfile'), + `source 'https://rubygems.org' + +gem 'rails', '~> 7.0.0' +gem 'nokogiri', '>= 1.13' + +group :development, :test do + gem 'rspec-rails', '~> 6.0' +end` + ); + + const result = await analyzer.analyze(testDir, true); + + expect(result.total).toBe(3); + expect(result.dev.length).toBe(1); + }); + + it('should handle empty project gracefully', async () => { + const result = await analyzer.analyze(testDir, false); + + expect(result.total).toBe(0); + expect(result.direct).toHaveLength(0); + expect(result.dev).toHaveLength(0); + }); + + it('should handle invalid JSON gracefully', async () => { + await fs.promises.writeFile( + path.join(testDir, 'package.json'), + 'invalid json {' + ); + + const result = await analyzer.analyze(testDir, false); + + expect(result.total).toBe(0); + }); + + it('should mark local dependencies correctly', async () => { + const packageJson = { + dependencies: { + './local-module': '*', + '@local/package': '*', + express: '^4.18.0', + }, + }; + await fs.promises.writeFile( + path.join(testDir, 'package.json'), + JSON.stringify(packageJson) + ); + + const result = await analyzer.analyze(testDir, false); + + expect(result.direct.find(d => d.name === './local-module')?.isLocal).toBe(true); + expect(result.direct.find(d => d.name === '@local/package')?.isLocal).toBe(true); + expect(result.direct.find(d => d.name === 'express')?.isLocal).toBe(false); + }); + }); +});