diff --git a/app/tests/dependencyAnalyzer.test.ts b/app/tests/dependencyAnalyzer.test.ts new file mode 100644 index 0000000..40a0f60 --- /dev/null +++ b/app/tests/dependencyAnalyzer.test.ts @@ -0,0 +1,219 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import { analyzeDependencies } from '../src/analyzers/dependencyAnalyzer'; + +describe('DependencyAnalyzer', () => { + let tempDir: string; + + beforeEach(() => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-context-dep-test-')); + }); + + afterEach(() => { + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + + describe('Node.js dependencies', () => { + it('should parse package.json dependencies', async () => { + const packageJson = { + name: 'test-project', + version: '1.0.0', + dependencies: { + express: '^4.18.2', + lodash: '^4.17.21' + }, + devDependencies: { + jest: '^29.7.0', + typescript: '^5.3.3' + } + }; + fs.writeFileSync( + path.join(tempDir, 'package.json'), + JSON.stringify(packageJson) + ); + + const result = await analyzeDependencies(tempDir, 'node'); + + expect(result.dependencies).toHaveLength(4); + expect(result.dependencies.find(d => d.name === 'express')).toEqual({ + name: 'express', + version: '^4.18.2', + type: 'prod' + }); + expect(result.dependencies.find(d => d.name === 'jest')).toEqual({ + name: 'jest', + version: '^29.7.0', + type: 'dev' + }); + expect(result.packageManager).toBe('npm'); + }); + + it('should detect yarn package manager', async () => { + const packageJson = { + name: 'test-project', + dependencies: { express: '^4.18.0' } + }; + fs.writeFileSync( + path.join(tempDir, 'package.json'), + JSON.stringify(packageJson) + ); + fs.writeFileSync(path.join(tempDir, 'yarn.lock'), '# yarn lock file'); + + const result = await analyzeDependencies(tempDir, 'node'); + + expect(result.packageManager).toBe('yarn'); + expect(result.lockFile).toBe('yarn.lock'); + }); + + it('should detect pnpm package manager', async () => { + const packageJson = { + name: 'test-project', + dependencies: { express: '^4.18.0' } + }; + fs.writeFileSync( + path.join(tempDir, 'package.json'), + JSON.stringify(packageJson) + ); + fs.writeFileSync(path.join(tempDir, 'pnpm-lock.yaml'), '# pnpm lock file'); + + const result = await analyzeDependencies(tempDir, 'node'); + + expect(result.packageManager).toBe('pnpm'); + }); + }); + + describe('Python dependencies', () => { + it('should parse requirements.txt', async () => { + fs.writeFileSync( + path.join(tempDir, 'requirements.txt'), + `flask>=2.0.0 +requests==2.28.0 +numpy~=1.24.0 +pandas # pandas for data analysis +django<4.0 +black; python_version >= "3.6" +` + ); + + const result = await analyzeDependencies(tempDir, 'python'); + + expect(result.dependencies.length).toBeGreaterThan(0); + const flaskDep = result.dependencies.find(d => d.name === 'flask'); + expect(flaskDep).toBeDefined(); + expect(flaskDep?.type).toBe('prod'); + expect(result.packageManager).toBe('pip'); + }); + + it('should detect poetry package manager', async () => { + fs.writeFileSync( + path.join(tempDir, 'pyproject.toml'), + `[tool.poetry] +name = "test-project" +version = "0.1.0" + +[tool.poetry.dependencies] +python = "^3.8" +fastapi = "^0.100.0" +` + ); + fs.writeFileSync(path.join(tempDir, 'poetry.lock'), '# poetry lock'); + + const result = await analyzeDependencies(tempDir, 'python'); + + expect(result.packageManager).toBe('poetry'); + }); + }); + + describe('Go dependencies', () => { + it('should parse go.mod', async () => { + const goMod = `module github.com/test/project + +go 1.20 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/gofiber/fiber v2.52.0 +) +`; + fs.writeFileSync(path.join(tempDir, 'go.mod'), goMod); + + const result = await analyzeDependencies(tempDir, 'go'); + + expect(result.dependencies.length).toBe(2); + expect(result.packageManager).toBe('go'); + const ginDep = result.dependencies.find(d => d.name.includes('gin-gonic/gin')); + expect(ginDep).toBeDefined(); + }); + }); + + describe('Rust dependencies', () => { + it('should parse Cargo.toml', async () => { + const cargoToml = `[package] +name = "test" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = "1.0" +tokio = "1.0" + +[dev.dependencies] +tempfile = "3.0" +`; + fs.writeFileSync(path.join(tempDir, 'Cargo.toml'), cargoToml); + + const result = await analyzeDependencies(tempDir, 'rust'); + + expect(result.dependencies.length).toBe(3); + expect(result.packageManager).toBe('cargo'); + const serdeDep = result.dependencies.find(d => d.name === 'serde'); + expect(serdeDep).toBeDefined(); + expect(serdeDep?.type).toBe('prod'); + const tempfileDep = result.dependencies.find(d => d.name === 'tempfile'); + expect(tempfileDep?.type).toBe('dev'); + }); + }); + + describe('Java dependencies', () => { + it('should parse pom.xml', async () => { + const pomXml = ` + + 4.0.0 + com.test + test + 1.0.0 + + + + org.springframework.boot + spring-boot-starter + 3.1.0 + + + com.fasterxml.jackson.core + jackson-databind + 2.15.0 + + +`; + fs.writeFileSync(path.join(tempDir, 'pom.xml'), pomXml); + + const result = await analyzeDependencies(tempDir, 'java'); + + expect(result.dependencies.length).toBe(2); + expect(result.packageManager).toBe('maven'); + const springDep = result.dependencies.find(d => d.name.includes('spring-boot-starter')); + expect(springDep).toBeDefined(); + }); + }); + + describe('Empty project', () => { + it('should return empty dependencies for unknown project type', async () => { + const result = await analyzeDependencies(tempDir, 'unknown'); + + expect(result.dependencies).toHaveLength(0); + expect(result.packageManager).toBeNull(); + }); + }); +});