Skip to content

Commit e97ca92

Browse files
committed
test(recon): add full test suite for deps, tech stack, endpoints, runner
1 parent bf47749 commit e97ca92

1 file changed

Lines changed: 347 additions & 0 deletions

File tree

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
package recon_test
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/hamza-hafeez82/cortex/internal/engine/recon"
9+
"github.com/hamza-hafeez82/cortex/internal/walker"
10+
)
11+
12+
// makeRepo creates a temp directory from a map of path → content,
13+
// walks it, and returns the RepoMap.
14+
func makeRepo(t *testing.T, files map[string]string) *walker.RepoMap {
15+
t.Helper()
16+
root := t.TempDir()
17+
for rel, content := range files {
18+
abs := filepath.Join(root, filepath.FromSlash(rel))
19+
if err := os.MkdirAll(filepath.Dir(abs), 0o755); err != nil {
20+
t.Fatalf("mkdir: %v", err)
21+
}
22+
if err := os.WriteFile(abs, []byte(content), 0o644); err != nil {
23+
t.Fatalf("write %s: %v", rel, err)
24+
}
25+
}
26+
27+
w := walker.New(walker.DefaultOptions())
28+
repo, err := w.Walk(root)
29+
if err != nil {
30+
t.Fatalf("Walk: %v", err)
31+
}
32+
return repo
33+
}
34+
35+
// ── Dependency tests ──────────────────────────────────────────────────────────
36+
37+
func TestParsePackageJSON(t *testing.T) {
38+
repo := makeRepo(t, map[string]string{
39+
"package.json": `{
40+
"dependencies": {
41+
"express": "^4.18.0",
42+
"lodash": "^4.17.21"
43+
},
44+
"devDependencies": {
45+
"jest": "^29.0.0"
46+
}
47+
}`,
48+
})
49+
50+
deps := recon.ParseDependencies(repo)
51+
if len(deps) != 3 {
52+
t.Errorf("expected 3 deps, got %d", len(deps))
53+
}
54+
55+
byName := make(map[string]recon.Dependency)
56+
for _, d := range deps {
57+
byName[d.Name] = d
58+
}
59+
60+
if byName["express"].Ecosystem != "npm" {
61+
t.Error("expected express ecosystem to be npm")
62+
}
63+
if byName["express"].Dev {
64+
t.Error("express should not be a dev dependency")
65+
}
66+
if !byName["jest"].Dev {
67+
t.Error("jest should be a dev dependency")
68+
}
69+
}
70+
71+
func TestParseGoMod(t *testing.T) {
72+
repo := makeRepo(t, map[string]string{
73+
"go.mod": `module github.com/example/app
74+
75+
go 1.22
76+
77+
require (
78+
github.com/gin-gonic/gin v1.9.1
79+
github.com/redis/go-redis/v9 v9.0.0
80+
)
81+
82+
require github.com/stretchr/testify v1.8.4
83+
`,
84+
})
85+
86+
deps := recon.ParseDependencies(repo)
87+
if len(deps) != 3 {
88+
t.Errorf("expected 3 deps, got %d", len(deps))
89+
}
90+
91+
byName := make(map[string]recon.Dependency)
92+
for _, d := range deps {
93+
byName[d.Name] = d
94+
}
95+
96+
if byName["github.com/gin-gonic/gin"].Version != "v1.9.1" {
97+
t.Errorf("expected gin version v1.9.1, got %q", byName["github.com/gin-gonic/gin"].Version)
98+
}
99+
if byName["github.com/gin-gonic/gin"].Ecosystem != "go" {
100+
t.Error("expected go ecosystem")
101+
}
102+
}
103+
104+
func TestParseRequirementsTxt(t *testing.T) {
105+
repo := makeRepo(t, map[string]string{
106+
"requirements.txt": `# Production deps
107+
fastapi==0.100.0
108+
uvicorn>=0.22.0
109+
sqlalchemy~=2.0.0
110+
111+
# skip options
112+
--index-url https://pypi.org/simple
113+
-r base.txt
114+
`,
115+
})
116+
117+
deps := recon.ParseDependencies(repo)
118+
if len(deps) != 3 {
119+
t.Errorf("expected 3 deps, got %d", len(deps))
120+
}
121+
122+
byName := make(map[string]recon.Dependency)
123+
for _, d := range deps {
124+
byName[d.Name] = d
125+
}
126+
127+
if byName["fastapi"].Version != "==0.100.0" {
128+
t.Errorf("expected fastapi version ==0.100.0, got %q", byName["fastapi"].Version)
129+
}
130+
if byName["fastapi"].Ecosystem != "pypi" {
131+
t.Error("expected pypi ecosystem")
132+
}
133+
}
134+
135+
func TestParseCargoToml(t *testing.T) {
136+
repo := makeRepo(t, map[string]string{
137+
"Cargo.toml": `[package]
138+
name = "my-app"
139+
version = "0.1.0"
140+
141+
[dependencies]
142+
serde = { version = "1.0", features = ["derive"] }
143+
tokio = "1.28"
144+
145+
[dev-dependencies]
146+
mockall = "0.11"
147+
`,
148+
})
149+
150+
deps := recon.ParseDependencies(repo)
151+
152+
byName := make(map[string]recon.Dependency)
153+
for _, d := range deps {
154+
byName[d.Name] = d
155+
}
156+
157+
if _, ok := byName["tokio"]; !ok {
158+
t.Error("expected tokio in deps")
159+
}
160+
if byName["tokio"].Ecosystem != "cargo" {
161+
t.Error("expected cargo ecosystem")
162+
}
163+
if !byName["mockall"].Dev {
164+
t.Error("mockall should be a dev dependency")
165+
}
166+
}
167+
168+
// ── Tech stack tests ──────────────────────────────────────────────────────────
169+
170+
func TestDetectTechStackFromFiles(t *testing.T) {
171+
repo := makeRepo(t, map[string]string{
172+
"package.json": `{"name":"app"}`,
173+
"tsconfig.json": `{}`,
174+
"next.config.js": `module.exports = {}`,
175+
"tailwind.config.js": `module.exports = {}`,
176+
})
177+
178+
techs := recon.DetectTechStack(repo)
179+
techNames := make(map[string]bool)
180+
for _, t2 := range techs {
181+
techNames[t2.Name] = true
182+
}
183+
184+
for _, expected := range []string{"Node.js", "TypeScript", "Next.js", "Tailwind CSS"} {
185+
if !techNames[expected] {
186+
t.Errorf("expected %q in tech stack", expected)
187+
}
188+
}
189+
}
190+
191+
func TestDetectTechStackFromImports(t *testing.T) {
192+
repo := makeRepo(t, map[string]string{
193+
"src/app.ts": `import express from "express"
194+
import { PrismaClient } from "@prisma/client"
195+
196+
const app = express()
197+
`,
198+
})
199+
200+
techs := recon.DetectTechStack(repo)
201+
techNames := make(map[string]bool)
202+
for _, t2 := range techs {
203+
techNames[t2.Name] = true
204+
}
205+
206+
if !techNames["Express"] {
207+
t.Error("expected Express in tech stack from imports")
208+
}
209+
if !techNames["Prisma"] {
210+
t.Error("expected Prisma in tech stack from imports")
211+
}
212+
}
213+
214+
func TestDetectInfrastructure(t *testing.T) {
215+
repo := makeRepo(t, map[string]string{
216+
"Dockerfile": "FROM node:18\n",
217+
"docker-compose.yml": "version: '3'\n",
218+
".github/workflows/ci.yml": "name: CI\n",
219+
"infra/main.tf": `provider "aws" {}`,
220+
})
221+
222+
runner := recon.NewRunner()
223+
result, err := runner.Run(repo)
224+
if err != nil {
225+
t.Fatalf("Run: %v", err)
226+
}
227+
228+
if !result.HasDocker {
229+
t.Error("expected HasDocker = true")
230+
}
231+
if !result.HasDockerCompose {
232+
t.Error("expected HasDockerCompose = true")
233+
}
234+
if !result.HasCI {
235+
t.Error("expected HasCI = true")
236+
}
237+
if !result.HasTerraform {
238+
t.Error("expected HasTerraform = true")
239+
}
240+
241+
ciFound := false
242+
for _, ci := range result.CIProviders {
243+
if ci == "github-actions" {
244+
ciFound = true
245+
}
246+
}
247+
if !ciFound {
248+
t.Error("expected github-actions in CI providers")
249+
}
250+
}
251+
252+
// ── Endpoint tests ────────────────────────────────────────────────────────────
253+
254+
func TestDetectExpressEndpoints(t *testing.T) {
255+
repo := makeRepo(t, map[string]string{
256+
"src/routes.js": `const express = require('express')
257+
const router = express.Router()
258+
259+
router.get('/users', getUsers)
260+
router.post('/users', createUser)
261+
router.put('/users/:id', updateUser)
262+
router.delete('/users/:id', deleteUser)
263+
264+
module.exports = router
265+
`,
266+
})
267+
268+
endpoints := recon.DetectEndpoints(repo)
269+
if len(endpoints) != 4 {
270+
t.Errorf("expected 4 endpoints, got %d", len(endpoints))
271+
}
272+
273+
methods := make(map[string]bool)
274+
for _, e := range endpoints {
275+
methods[e.Method] = true
276+
}
277+
for _, m := range []string{"GET", "POST", "PUT", "DELETE"} {
278+
if !methods[m] {
279+
t.Errorf("expected method %s", m)
280+
}
281+
}
282+
}
283+
284+
func TestDetectFastAPIEndpoints(t *testing.T) {
285+
repo := makeRepo(t, map[string]string{
286+
"app/main.py": `from fastapi import FastAPI
287+
app = FastAPI()
288+
289+
@app.get("/health")
290+
async def health_check():
291+
return {"status": "ok"}
292+
293+
@app.post("/users")
294+
async def create_user(user: UserCreate):
295+
pass
296+
`,
297+
})
298+
299+
endpoints := recon.DetectEndpoints(repo)
300+
if len(endpoints) != 2 {
301+
t.Errorf("expected 2 endpoints, got %d", len(endpoints))
302+
}
303+
}
304+
305+
// ── Runner integration test ───────────────────────────────────────────────────
306+
307+
func TestRunnerFullScan(t *testing.T) {
308+
repo := makeRepo(t, map[string]string{
309+
"main.go": `package main
310+
import "github.com/gin-gonic/gin"
311+
func main() {
312+
r := gin.Default()
313+
r.GET("/ping", pingHandler)
314+
}`,
315+
"go.mod": `module example.com/app
316+
go 1.22
317+
require github.com/gin-gonic/gin v1.9.1`,
318+
"Dockerfile": "FROM golang:1.22\n",
319+
})
320+
321+
runner := recon.NewRunner()
322+
result, err := runner.Run(repo)
323+
if err != nil {
324+
t.Fatalf("Run() error: %v", err)
325+
}
326+
327+
if result.TotalFiles == 0 {
328+
t.Error("expected TotalFiles > 0")
329+
}
330+
if len(result.Dependencies) == 0 {
331+
t.Error("expected at least one dependency from go.mod")
332+
}
333+
if !result.HasDocker {
334+
t.Error("expected HasDocker = true")
335+
}
336+
if result.Summary() == "" {
337+
t.Error("Summary() should not be empty")
338+
}
339+
}
340+
341+
func TestRunnerNilRepo(t *testing.T) {
342+
runner := recon.NewRunner()
343+
_, err := runner.Run(nil)
344+
if err == nil {
345+
t.Error("expected error for nil repo")
346+
}
347+
}

0 commit comments

Comments
 (0)