activo 0.4.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +203 -1
- package/data/2026-03-04_20-54.json +181 -0
- package/data/2026-03-04_20-56.json +181 -0
- package/data/apex-rulesets/egov.yaml +469 -0
- package/data/apex-rulesets/modernize.yaml +687 -0
- package/data/apex-rulesets/quality.yaml +1677 -0
- package/data/apex-rulesets/rule-schema.yaml +587 -0
- package/data/apex-rulesets/secure.yaml +1688 -0
- package/data/apex-rulesets/spring.yaml +455 -0
- package/data/apex-rulesets/sql-format.yaml +99 -0
- package/data/apex-rulesets/sql-oracle.yaml +281 -0
- package/data/apex-rulesets/sql.yaml +1660 -0
- package/dist/cli/headless.d.ts.map +1 -1
- package/dist/cli/headless.js +32 -10
- package/dist/cli/headless.js.map +1 -1
- package/dist/cli/index.js +31 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/core/agent.d.ts +3 -3
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +203 -384
- package/dist/core/agent.js.map +1 -1
- package/dist/core/commands.d.ts +2 -1
- package/dist/core/commands.d.ts.map +1 -1
- package/dist/core/commands.js +61 -9
- package/dist/core/commands.js.map +1 -1
- package/dist/core/config.d.ts +14 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +41 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/conversation.d.ts +2 -2
- package/dist/core/conversation.d.ts.map +1 -1
- package/dist/core/conversation.js.map +1 -1
- package/dist/core/intentRouter.d.ts +43 -0
- package/dist/core/intentRouter.d.ts.map +1 -0
- package/dist/core/intentRouter.js +804 -0
- package/dist/core/intentRouter.js.map +1 -0
- package/dist/core/llm/anthropic.d.ts +24 -0
- package/dist/core/llm/anthropic.d.ts.map +1 -0
- package/dist/core/llm/anthropic.js +226 -0
- package/dist/core/llm/anthropic.js.map +1 -0
- package/dist/core/llm/ollama.d.ts +5 -14
- package/dist/core/llm/ollama.d.ts.map +1 -1
- package/dist/core/llm/ollama.js +3 -0
- package/dist/core/llm/ollama.js.map +1 -1
- package/dist/core/llm/types.d.ts +22 -0
- package/dist/core/llm/types.d.ts.map +1 -0
- package/dist/core/llm/types.js +2 -0
- package/dist/core/llm/types.js.map +1 -0
- package/dist/core/mcp/client.d.ts +6 -0
- package/dist/core/mcp/client.d.ts.map +1 -1
- package/dist/core/mcp/client.js +16 -0
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/init.d.ts +12 -0
- package/dist/core/mcp/init.d.ts.map +1 -0
- package/dist/core/mcp/init.js +55 -0
- package/dist/core/mcp/init.js.map +1 -0
- package/dist/core/mcp/logger.d.ts +14 -0
- package/dist/core/mcp/logger.d.ts.map +1 -0
- package/dist/core/mcp/logger.js +50 -0
- package/dist/core/mcp/logger.js.map +1 -0
- package/dist/core/tools/analyzePatterns.d.ts +3 -0
- package/dist/core/tools/analyzePatterns.d.ts.map +1 -0
- package/dist/core/tools/analyzePatterns.js +293 -0
- package/dist/core/tools/analyzePatterns.js.map +1 -0
- package/dist/core/tools/apexPaths.d.ts +14 -0
- package/dist/core/tools/apexPaths.d.ts.map +1 -0
- package/dist/core/tools/apexPaths.js +54 -0
- package/dist/core/tools/apexPaths.js.map +1 -0
- package/dist/core/tools/apexUtils.d.ts +36 -0
- package/dist/core/tools/apexUtils.d.ts.map +1 -0
- package/dist/core/tools/apexUtils.js +83 -0
- package/dist/core/tools/apexUtils.js.map +1 -0
- package/dist/core/tools/explainIssue.d.ts +3 -0
- package/dist/core/tools/explainIssue.d.ts.map +1 -0
- package/dist/core/tools/explainIssue.js +181 -0
- package/dist/core/tools/explainIssue.js.map +1 -0
- package/dist/core/tools/fixGen.d.ts +3 -0
- package/dist/core/tools/fixGen.d.ts.map +1 -0
- package/dist/core/tools/fixGen.js +338 -0
- package/dist/core/tools/fixGen.js.map +1 -0
- package/dist/core/tools/generateImprovements.d.ts +21 -0
- package/dist/core/tools/generateImprovements.d.ts.map +1 -0
- package/dist/core/tools/generateImprovements.js +602 -0
- package/dist/core/tools/generateImprovements.js.map +1 -0
- package/dist/core/tools/generateReport.d.ts +3 -0
- package/dist/core/tools/generateReport.d.ts.map +1 -0
- package/dist/core/tools/generateReport.js +315 -0
- package/dist/core/tools/generateReport.js.map +1 -0
- package/dist/core/tools/index.d.ts +7 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +62 -23
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/recommendProfile.d.ts +3 -0
- package/dist/core/tools/recommendProfile.d.ts.map +1 -0
- package/dist/core/tools/recommendProfile.js +334 -0
- package/dist/core/tools/recommendProfile.js.map +1 -0
- package/dist/core/tools/ruleGen.d.ts +3 -0
- package/dist/core/tools/ruleGen.d.ts.map +1 -0
- package/dist/core/tools/ruleGen.js +1103 -0
- package/dist/core/tools/ruleGen.js.map +1 -0
- package/dist/core/tools/standards.d.ts.map +1 -1
- package/dist/core/tools/standards.js +7 -3
- package/dist/core/tools/standards.js.map +1 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +86 -35
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/InputBox.d.ts +1 -3
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +146 -5
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/MessageList.d.ts +3 -1
- package/dist/ui/components/MessageList.d.ts.map +1 -1
- package/dist/ui/components/MessageList.js +13 -7
- package/dist/ui/components/MessageList.js.map +1 -1
- package/dist/ui/components/StatusBar.d.ts +1 -1
- package/dist/ui/components/StatusBar.d.ts.map +1 -1
- package/dist/ui/components/StatusBar.js +3 -2
- package/dist/ui/components/StatusBar.js.map +1 -1
- package/dist/ui/components/ToolStatus.d.ts +3 -1
- package/dist/ui/components/ToolStatus.d.ts.map +1 -1
- package/dist/ui/components/ToolStatus.js +19 -4
- package/dist/ui/components/ToolStatus.js.map +1 -1
- package/package.json +7 -1
- package/demo.gif +0 -0
- package/demo.tape +0 -53
- package/screenshot.png +0 -0
- package/src/cli/banner.ts +0 -38
- package/src/cli/headless.ts +0 -63
- package/src/cli/index.ts +0 -57
- package/src/core/agent.ts +0 -711
- package/src/core/commands.ts +0 -118
- package/src/core/config.ts +0 -98
- package/src/core/conversation.ts +0 -235
- package/src/core/llm/ollama.ts +0 -351
- package/src/core/mcp/client.ts +0 -143
- package/src/core/tools/analyzeAll.ts +0 -482
- package/src/core/tools/ast.ts +0 -826
- package/src/core/tools/builtIn.ts +0 -221
- package/src/core/tools/cache.ts +0 -570
- package/src/core/tools/cssAnalysis.ts +0 -324
- package/src/core/tools/dependencyAnalysis.ts +0 -363
- package/src/core/tools/embeddings.ts +0 -746
- package/src/core/tools/frontendAst.ts +0 -802
- package/src/core/tools/htmlAnalysis.ts +0 -466
- package/src/core/tools/index.ts +0 -160
- package/src/core/tools/javaAst.ts +0 -1030
- package/src/core/tools/javaQuality.integration.test.ts +0 -537
- package/src/core/tools/memory.ts +0 -655
- package/src/core/tools/mybatisAnalysis.ts +0 -322
- package/src/core/tools/openapiAnalysis.ts +0 -431
- package/src/core/tools/pythonAnalysis.ts +0 -477
- package/src/core/tools/sqlAnalysis.ts +0 -298
- package/src/core/tools/standards.test.ts +0 -186
- package/src/core/tools/standards.ts +0 -889
- package/src/core/tools/types.ts +0 -38
- package/src/ui/App.tsx +0 -334
- package/src/ui/components/InputBox.tsx +0 -37
- package/src/ui/components/MessageList.tsx +0 -80
- package/src/ui/components/StatusBar.tsx +0 -36
- package/src/ui/components/ToolStatus.tsx +0 -38
- package/tsconfig.json +0 -21
|
@@ -1,537 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
|
|
5
|
-
// 실제 도구 import
|
|
6
|
-
import { javaTools } from "./javaAst.js";
|
|
7
|
-
import { analyzeAllTools } from "./analyzeAll.js";
|
|
8
|
-
|
|
9
|
-
const SAMPLE_JAVA_SRC = "/Users/mhb8436/Workspaces/sample-java/01.서비스안내팀 소스/src/";
|
|
10
|
-
|
|
11
|
-
// 대상 디렉토리 존재 여부 확인
|
|
12
|
-
const srcExists = fs.existsSync(SAMPLE_JAVA_SRC);
|
|
13
|
-
|
|
14
|
-
// ─── 시나리오 1: java_analyze 단일 파일 품질 이슈 감지 ───
|
|
15
|
-
|
|
16
|
-
describe("시나리오 1: java_analyze 단일 파일 품질 이슈 감지", () => {
|
|
17
|
-
const javaAnalyzeTool = javaTools.find((t) => t.name === "java_analyze")!;
|
|
18
|
-
|
|
19
|
-
describe.skipIf(!srcExists)("NPE 위험 감지 — SvcRequstController.java", () => {
|
|
20
|
-
const targetFile = path.join(
|
|
21
|
-
SAMPLE_JAVA_SRC,
|
|
22
|
-
"main/java/gov/benefit/webview/controller/SvcRequstController.java"
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
it("java_analyze가 성공적으로 실행된다", async () => {
|
|
26
|
-
const result = await javaAnalyzeTool.handler({ filepath: targetFile, format: "json" });
|
|
27
|
-
expect(result.success).toBe(true);
|
|
28
|
-
expect(result.content).toBeTruthy();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("NPE 위험 이슈가 감지된다 (.get().toString())", async () => {
|
|
32
|
-
const result = await javaAnalyzeTool.handler({ filepath: targetFile, format: "json" });
|
|
33
|
-
const analysis = JSON.parse(result.content);
|
|
34
|
-
expect(analysis.issues).toBeDefined();
|
|
35
|
-
expect(Array.isArray(analysis.issues)).toBe(true);
|
|
36
|
-
|
|
37
|
-
const npeIssues = analysis.issues.filter((i: any) => i.type === "npe-risk");
|
|
38
|
-
// SvcRequstController.java L197, L200에 body.get("gbn").toString() 패턴 존재
|
|
39
|
-
expect(npeIssues.length).toBeGreaterThanOrEqual(2);
|
|
40
|
-
|
|
41
|
-
// 각 이슈에 필수 필드 확인
|
|
42
|
-
for (const issue of npeIssues) {
|
|
43
|
-
expect(issue.severity).toBe("error");
|
|
44
|
-
expect(issue.line).toBeGreaterThan(0);
|
|
45
|
-
expect(issue.message).toBeTruthy();
|
|
46
|
-
expect(issue.suggestion).toBeTruthy();
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("text 포맷에 이슈 섹션이 포함된다", async () => {
|
|
51
|
-
const result = await javaAnalyzeTool.handler({ filepath: targetFile, format: "text" });
|
|
52
|
-
expect(result.content).toContain("이슈");
|
|
53
|
-
expect(result.content).toContain("NPE");
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe.skipIf(!srcExists)("예외 처리 안티패턴 감지 — SvcRequstServiceImpl.java", () => {
|
|
58
|
-
const targetFile = path.join(
|
|
59
|
-
SAMPLE_JAVA_SRC,
|
|
60
|
-
"main/java/gov/benefit/webview/service/impl/SvcRequstServiceImpl.java"
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
it("예외 안티패턴 이슈가 감지된다", async () => {
|
|
64
|
-
const result = await javaAnalyzeTool.handler({ filepath: targetFile, format: "json" });
|
|
65
|
-
const analysis = JSON.parse(result.content);
|
|
66
|
-
|
|
67
|
-
const exceptionIssues = analysis.issues.filter(
|
|
68
|
-
(i: any) => i.type === "exception-antipattern"
|
|
69
|
-
);
|
|
70
|
-
// SvcRequstServiceImpl.java에 catch 블록에서 예외 변수 사용 패턴 존재
|
|
71
|
-
expect(exceptionIssues.length).toBeGreaterThanOrEqual(0);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("Spring @Service 어노테이션이 감지된다", async () => {
|
|
75
|
-
const result = await javaAnalyzeTool.handler({ filepath: targetFile, format: "json" });
|
|
76
|
-
const analysis = JSON.parse(result.content);
|
|
77
|
-
|
|
78
|
-
expect(analysis.classes.length).toBeGreaterThanOrEqual(1);
|
|
79
|
-
const mainClass = analysis.classes[0];
|
|
80
|
-
// @Service("SvcRequstService")
|
|
81
|
-
expect(
|
|
82
|
-
mainClass.annotations.some((a: string) => a.toLowerCase().includes("service"))
|
|
83
|
-
).toBe(true);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
describe.skipIf(!srcExists)("주석 코드 블록 감지 — UtzrInfoMngController.java", () => {
|
|
88
|
-
const targetFile = path.join(
|
|
89
|
-
SAMPLE_JAVA_SRC,
|
|
90
|
-
"main/java/gov/benefit/webview/controller/UtzrInfoMngController.java"
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
it("주석 처리된 코드 블록(dead-code)이 감지된다", async () => {
|
|
94
|
-
if (!fs.existsSync(targetFile)) return;
|
|
95
|
-
|
|
96
|
-
const result = await javaAnalyzeTool.handler({ filepath: targetFile, format: "json" });
|
|
97
|
-
const analysis = JSON.parse(result.content);
|
|
98
|
-
|
|
99
|
-
const deadCodeIssues = analysis.issues.filter((i: any) => i.type === "dead-code");
|
|
100
|
-
// UtzrInfoMngController.java에 10줄 이상 주석 블록 다수 존재
|
|
101
|
-
expect(deadCodeIssues.length).toBeGreaterThanOrEqual(1);
|
|
102
|
-
|
|
103
|
-
for (const issue of deadCodeIssues) {
|
|
104
|
-
expect(issue.severity).toBe("info");
|
|
105
|
-
expect(issue.message).toContain("주석 처리된 코드 블록");
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
describe.skipIf(!srcExists)("NPE 위험 감지 — SvcReqstApiController.java", () => {
|
|
111
|
-
const targetFile = path.join(
|
|
112
|
-
SAMPLE_JAVA_SRC,
|
|
113
|
-
"main/java/gov/benefit/api/controller/SvcReqstApiController.java"
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
it("body.get('checkSum').toString() NPE 위험이 감지된다", async () => {
|
|
117
|
-
const result = await javaAnalyzeTool.handler({ filepath: targetFile, format: "json" });
|
|
118
|
-
const analysis = JSON.parse(result.content);
|
|
119
|
-
|
|
120
|
-
const npeIssues = analysis.issues.filter((i: any) => i.type === "npe-risk");
|
|
121
|
-
// checkSum.toString() 패턴 다수 존재
|
|
122
|
-
expect(npeIssues.length).toBeGreaterThanOrEqual(1);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it("RestController 어노테이션이 인식된다", async () => {
|
|
126
|
-
const result = await javaAnalyzeTool.handler({ filepath: targetFile, format: "json" });
|
|
127
|
-
const analysis = JSON.parse(result.content);
|
|
128
|
-
|
|
129
|
-
expect(analysis.classes.length).toBeGreaterThanOrEqual(1);
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
describe.skipIf(!srcExists)("NPE 위험 감지 — BenefitLnkServiceImpl.java", () => {
|
|
134
|
-
const targetFile = path.join(
|
|
135
|
-
SAMPLE_JAVA_SRC,
|
|
136
|
-
"main/java/gov/benefit/lnk/service/impl/BenefitLnkServiceImpl.java"
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
it("map.get().toString() 체인이 감지된다", async () => {
|
|
140
|
-
if (!fs.existsSync(targetFile)) return;
|
|
141
|
-
|
|
142
|
-
const result = await javaAnalyzeTool.handler({ filepath: targetFile, format: "json" });
|
|
143
|
-
const analysis = JSON.parse(result.content);
|
|
144
|
-
|
|
145
|
-
const npeIssues = analysis.issues.filter((i: any) => i.type === "npe-risk");
|
|
146
|
-
expect(npeIssues.length).toBeGreaterThanOrEqual(1);
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// ─── 시나리오 2: spring_check glob 기반 실행 ───
|
|
152
|
-
|
|
153
|
-
describe("시나리오 2: spring_check glob 기반 실행", () => {
|
|
154
|
-
const springCheckTool = javaTools.find((t) => t.name === "spring_check")!;
|
|
155
|
-
|
|
156
|
-
it.skipIf(!srcExists)("spring_check이 pattern 파라미터로 정상 실행된다", async () => {
|
|
157
|
-
const pattern = path.join(SAMPLE_JAVA_SRC, "**/*.java");
|
|
158
|
-
const result = await springCheckTool.handler({ pattern });
|
|
159
|
-
expect(result.success).toBe(true);
|
|
160
|
-
expect(result.content).toBeTruthy();
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it.skipIf(!srcExists)("Controller, Service 컴포넌트가 발견된다", async () => {
|
|
164
|
-
const pattern = path.join(SAMPLE_JAVA_SRC, "**/*.java");
|
|
165
|
-
const result = await springCheckTool.handler({ pattern });
|
|
166
|
-
|
|
167
|
-
// text 포맷이므로 문자열 검사
|
|
168
|
-
expect(result.content).toContain("Controllers");
|
|
169
|
-
expect(result.content).toContain("Services");
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it.skipIf(!srcExists)("filepath 파라미터로 호출 시 실패한다 (버그 재현 방지)", async () => {
|
|
173
|
-
// spring_check은 pattern(glob) 필수 — filepath를 주면 glob에서 빈 배열 반환
|
|
174
|
-
const singleFile = path.join(
|
|
175
|
-
SAMPLE_JAVA_SRC,
|
|
176
|
-
"main/java/gov/benefit/webview/controller/SvcRequstController.java"
|
|
177
|
-
);
|
|
178
|
-
// handler에 filepath가 아닌 pattern을 기대하므로 filepath로 호출 시 pattern이 undefined
|
|
179
|
-
const result = await springCheckTool.handler({ filepath: singleFile });
|
|
180
|
-
// pattern이 없으면 에러 또는 빈 결과
|
|
181
|
-
if (result.success) {
|
|
182
|
-
// glob("undefined") → 빈 배열 → 빈 결과
|
|
183
|
-
expect(result.content).toContain("Spring 컴포넌트를 찾지 못했습니다");
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// ─── 시나리오 3: analyze_all 디렉토리 분석 + 이슈 추출 ───
|
|
189
|
-
|
|
190
|
-
describe("시나리오 3: analyze_all 디렉토리 전체 분석", () => {
|
|
191
|
-
const analyzeAllTool = analyzeAllTools.find((t) => t.name === "analyze_all")!;
|
|
192
|
-
|
|
193
|
-
it.skipIf(!srcExists)(
|
|
194
|
-
"analyze_all이 Java 프로젝트를 성공적으로 분석한다",
|
|
195
|
-
async () => {
|
|
196
|
-
const result = await analyzeAllTool.handler({ path: SAMPLE_JAVA_SRC, include: ["java"] });
|
|
197
|
-
expect(result.success).toBe(true);
|
|
198
|
-
|
|
199
|
-
const output = JSON.parse(result.content);
|
|
200
|
-
expect(output.totalFiles).toBeGreaterThan(0);
|
|
201
|
-
expect(output.fileStats.java).toBeGreaterThan(0);
|
|
202
|
-
expect(output.successful).toBeGreaterThan(0);
|
|
203
|
-
},
|
|
204
|
-
60000
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
it.skipIf(!srcExists)(
|
|
208
|
-
"java_analyze 결과에 issues가 포함된다",
|
|
209
|
-
async () => {
|
|
210
|
-
const result = await analyzeAllTool.handler({ path: SAMPLE_JAVA_SRC, include: ["java"] });
|
|
211
|
-
const output = JSON.parse(result.content);
|
|
212
|
-
|
|
213
|
-
// details에서 java_analyze 결과 확인
|
|
214
|
-
const javaDetail = output.details.find((d: any) => d.tool === "java_analyze");
|
|
215
|
-
expect(javaDetail).toBeDefined();
|
|
216
|
-
expect(javaDetail.summary.samples).toBeDefined();
|
|
217
|
-
expect(javaDetail.summary.samples.length).toBeGreaterThan(0);
|
|
218
|
-
|
|
219
|
-
// 최소 하나의 샘플에 issues 필드가 존재
|
|
220
|
-
const hasIssues = javaDetail.summary.samples.some(
|
|
221
|
-
(s: any) => s.result?.issues && s.result.issues.length > 0
|
|
222
|
-
);
|
|
223
|
-
expect(hasIssues).toBe(true);
|
|
224
|
-
},
|
|
225
|
-
60000
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
it.skipIf(!srcExists)(
|
|
229
|
-
"issuesSummary에 java 이슈가 포함된다",
|
|
230
|
-
async () => {
|
|
231
|
-
const result = await analyzeAllTool.handler({ path: SAMPLE_JAVA_SRC, include: ["java"] });
|
|
232
|
-
const output = JSON.parse(result.content);
|
|
233
|
-
|
|
234
|
-
// issuesSummary에서 java_analyze 이슈 확인
|
|
235
|
-
const javaIssues = output.issuesSummary.find(
|
|
236
|
-
(is: any) => is.tool === "java_analyze"
|
|
237
|
-
);
|
|
238
|
-
expect(javaIssues).toBeDefined();
|
|
239
|
-
expect(javaIssues.issues.length).toBeGreaterThan(0);
|
|
240
|
-
},
|
|
241
|
-
60000
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
it.skipIf(!srcExists)(
|
|
245
|
-
"spring_check이 정상 실행된다 (버그 수정 검증)",
|
|
246
|
-
async () => {
|
|
247
|
-
const result = await analyzeAllTool.handler({ path: SAMPLE_JAVA_SRC, include: ["java"] });
|
|
248
|
-
const output = JSON.parse(result.content);
|
|
249
|
-
|
|
250
|
-
// spring_check이 성공적으로 실행되었는지 확인
|
|
251
|
-
const springDetail = output.details.find((d: any) => d.tool === "spring_check");
|
|
252
|
-
expect(springDetail).toBeDefined();
|
|
253
|
-
expect(springDetail.summary).toBeTruthy();
|
|
254
|
-
|
|
255
|
-
// errors에 spring_check이 없어야 함
|
|
256
|
-
const springError = output.errors.find((e: any) => e.tool === "spring_check");
|
|
257
|
-
expect(springError).toBeUndefined();
|
|
258
|
-
},
|
|
259
|
-
60000
|
|
260
|
-
);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
// ─── 시나리오 4: compressAnalysisResult 이슈 포함 확인 ───
|
|
264
|
-
|
|
265
|
-
describe("시나리오 4: compressAnalysisResult에서 이슈 포함", () => {
|
|
266
|
-
it("analyze_all 결과 JSON에서 issues가 압축 시 유지된다", () => {
|
|
267
|
-
// compressAnalysisResult는 agent.ts 내부 함수이므로 동작을 시뮬레이션
|
|
268
|
-
const mockAnalyzeAllResult = {
|
|
269
|
-
path: "/test",
|
|
270
|
-
fileStats: { java: 10 },
|
|
271
|
-
totalFiles: 10,
|
|
272
|
-
analysesRun: 2,
|
|
273
|
-
successful: 2,
|
|
274
|
-
failed: 0,
|
|
275
|
-
issuesSummary: [
|
|
276
|
-
{
|
|
277
|
-
tool: "java_analyze",
|
|
278
|
-
issues: [".get().toString() — null일 때 NPE 발생"],
|
|
279
|
-
},
|
|
280
|
-
],
|
|
281
|
-
details: [
|
|
282
|
-
{
|
|
283
|
-
tool: "java_analyze",
|
|
284
|
-
summary: {
|
|
285
|
-
analyzedFiles: 3,
|
|
286
|
-
totalJavaFiles: 10,
|
|
287
|
-
samples: [
|
|
288
|
-
{
|
|
289
|
-
file: "Test.java",
|
|
290
|
-
result: {
|
|
291
|
-
issues: [
|
|
292
|
-
{
|
|
293
|
-
type: "npe-risk",
|
|
294
|
-
severity: "error",
|
|
295
|
-
line: 10,
|
|
296
|
-
message: ".get().toString() — null일 때 NPE 발생",
|
|
297
|
-
suggestion: "Objects.toString() 사용",
|
|
298
|
-
},
|
|
299
|
-
],
|
|
300
|
-
},
|
|
301
|
-
},
|
|
302
|
-
],
|
|
303
|
-
},
|
|
304
|
-
},
|
|
305
|
-
],
|
|
306
|
-
errors: [],
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
const resultContent = JSON.stringify(mockAnalyzeAllResult);
|
|
310
|
-
|
|
311
|
-
// compressAnalysisResult 로직 재현 (agent.ts에서 발췌)
|
|
312
|
-
const parsed = JSON.parse(resultContent);
|
|
313
|
-
const compact: Record<string, unknown> = {
|
|
314
|
-
path: parsed.path,
|
|
315
|
-
totalFiles: parsed.totalFiles,
|
|
316
|
-
fileStats: parsed.fileStats,
|
|
317
|
-
analysesRun: parsed.analysesRun,
|
|
318
|
-
successful: parsed.successful,
|
|
319
|
-
failed: parsed.failed,
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
if (parsed.issuesSummary?.length > 0) {
|
|
323
|
-
compact.issues = parsed.issuesSummary.map((is: any) => ({
|
|
324
|
-
tool: is.tool,
|
|
325
|
-
issues: is.issues.slice(0, 5),
|
|
326
|
-
}));
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (parsed.details?.length > 0) {
|
|
330
|
-
compact.analyses = parsed.details.map((d: any) => {
|
|
331
|
-
const s = d.summary;
|
|
332
|
-
const brief: Record<string, unknown> = { tool: d.tool };
|
|
333
|
-
|
|
334
|
-
for (const [k, v] of Object.entries(s)) {
|
|
335
|
-
if (typeof v === "number" || typeof v === "boolean") {
|
|
336
|
-
brief[k] = v;
|
|
337
|
-
} else if (typeof v === "string" && (v as string).length < 100) {
|
|
338
|
-
brief[k] = v;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// issues 추출 로직 (agent.ts 수정 사항)
|
|
343
|
-
if (Array.isArray((s as any).samples)) {
|
|
344
|
-
const allIssues: unknown[] = [];
|
|
345
|
-
for (const sample of (s as any).samples) {
|
|
346
|
-
if (Array.isArray(sample.result?.issues)) {
|
|
347
|
-
allIssues.push(...sample.result.issues.slice(0, 3));
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
if (allIssues.length > 0) {
|
|
351
|
-
brief.issues = allIssues.slice(0, 10);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
return brief;
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
const compressedStr = JSON.stringify(compact, null, 1);
|
|
360
|
-
const compressed = JSON.parse(compressedStr);
|
|
361
|
-
|
|
362
|
-
// 이슈 요약이 포함되어 있는지 확인
|
|
363
|
-
expect(compressed.issues).toBeDefined();
|
|
364
|
-
expect(compressed.issues[0].tool).toBe("java_analyze");
|
|
365
|
-
expect(compressed.issues[0].issues[0]).toContain("NPE");
|
|
366
|
-
|
|
367
|
-
// analyses에 issues가 포함되어 있는지 확인
|
|
368
|
-
const javaAnalysis = (compressed.analyses as any[]).find((a) => a.tool === "java_analyze");
|
|
369
|
-
expect(javaAnalysis).toBeDefined();
|
|
370
|
-
expect(javaAnalysis.issues).toBeDefined();
|
|
371
|
-
expect(javaAnalysis.issues.length).toBeGreaterThan(0);
|
|
372
|
-
expect(javaAnalysis.issues[0].type).toBe("npe-risk");
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
// ─── 시나리오 5: 이슈 타입별 감지 정확도 (단위 수준) ───
|
|
377
|
-
|
|
378
|
-
describe("시나리오 5: 이슈 감지 정확도 — 인라인 코드 검증", () => {
|
|
379
|
-
const javaAnalyzeTool = javaTools.find((t) => t.name === "java_analyze")!;
|
|
380
|
-
|
|
381
|
-
it("NPE 위험 4가지 패턴 모두 감지한다", async () => {
|
|
382
|
-
// 테스트용 Java 코드를 임시 파일로 작성
|
|
383
|
-
const tmpFile = path.join(process.cwd(), ".test-npe.java");
|
|
384
|
-
const javaCode = `package test;
|
|
385
|
-
public class NpeTest {
|
|
386
|
-
public void test(java.util.Map<String, Object> map) {
|
|
387
|
-
String a = map.get("key").toString();
|
|
388
|
-
boolean b = map.get("key").equals("val");
|
|
389
|
-
String c = (String) map.get("key");
|
|
390
|
-
int d = map.get("list").size();
|
|
391
|
-
}
|
|
392
|
-
}`;
|
|
393
|
-
fs.writeFileSync(tmpFile, javaCode);
|
|
394
|
-
|
|
395
|
-
try {
|
|
396
|
-
const result = await javaAnalyzeTool.handler({ filepath: tmpFile, format: "json" });
|
|
397
|
-
const analysis = JSON.parse(result.content);
|
|
398
|
-
const npeIssues = analysis.issues.filter((i: any) => i.type === "npe-risk");
|
|
399
|
-
|
|
400
|
-
// 4가지 패턴 모두 감지
|
|
401
|
-
expect(npeIssues.length).toBeGreaterThanOrEqual(4);
|
|
402
|
-
|
|
403
|
-
// 라인 번호 확인 (4, 5, 6, 7)
|
|
404
|
-
const lines = npeIssues.map((i: any) => i.line);
|
|
405
|
-
expect(lines).toContain(4);
|
|
406
|
-
expect(lines).toContain(5);
|
|
407
|
-
expect(lines).toContain(6);
|
|
408
|
-
expect(lines).toContain(7);
|
|
409
|
-
} finally {
|
|
410
|
-
fs.unlinkSync(tmpFile);
|
|
411
|
-
}
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
it("빈 catch 블록을 감지한다", async () => {
|
|
415
|
-
const tmpFile = path.join(process.cwd(), ".test-catch.java");
|
|
416
|
-
const javaCode = `package test;
|
|
417
|
-
public class CatchTest {
|
|
418
|
-
public void test() {
|
|
419
|
-
try {
|
|
420
|
-
System.out.println("test");
|
|
421
|
-
} catch (Exception e) { }
|
|
422
|
-
}
|
|
423
|
-
}`;
|
|
424
|
-
fs.writeFileSync(tmpFile, javaCode);
|
|
425
|
-
|
|
426
|
-
try {
|
|
427
|
-
const result = await javaAnalyzeTool.handler({ filepath: tmpFile, format: "json" });
|
|
428
|
-
const analysis = JSON.parse(result.content);
|
|
429
|
-
const catchIssues = analysis.issues.filter(
|
|
430
|
-
(i: any) => i.type === "exception-antipattern" && i.message.includes("빈 catch")
|
|
431
|
-
);
|
|
432
|
-
expect(catchIssues.length).toBeGreaterThanOrEqual(1);
|
|
433
|
-
} finally {
|
|
434
|
-
fs.unlinkSync(tmpFile);
|
|
435
|
-
}
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
it("e.printStackTrace() 사용을 감지한다", async () => {
|
|
439
|
-
const tmpFile = path.join(process.cwd(), ".test-stacktrace.java");
|
|
440
|
-
const javaCode = `package test;
|
|
441
|
-
public class StackTraceTest {
|
|
442
|
-
public void test() {
|
|
443
|
-
try {
|
|
444
|
-
System.out.println("test");
|
|
445
|
-
} catch (Exception e) {
|
|
446
|
-
e.printStackTrace();
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}`;
|
|
450
|
-
fs.writeFileSync(tmpFile, javaCode);
|
|
451
|
-
|
|
452
|
-
try {
|
|
453
|
-
const result = await javaAnalyzeTool.handler({ filepath: tmpFile, format: "json" });
|
|
454
|
-
const analysis = JSON.parse(result.content);
|
|
455
|
-
const stackTraceIssues = analysis.issues.filter(
|
|
456
|
-
(i: any) => i.type === "exception-antipattern" && i.message.includes("printStackTrace")
|
|
457
|
-
);
|
|
458
|
-
expect(stackTraceIssues.length).toBeGreaterThanOrEqual(1);
|
|
459
|
-
} finally {
|
|
460
|
-
fs.unlinkSync(tmpFile);
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
it("주석 처리된 코드 블록(5줄 이상)을 감지한다", async () => {
|
|
465
|
-
const tmpFile = path.join(process.cwd(), ".test-deadcode.java");
|
|
466
|
-
const javaCode = `package test;
|
|
467
|
-
public class DeadCodeTest {
|
|
468
|
-
// public void oldMethod() {
|
|
469
|
-
// if (true) {
|
|
470
|
-
// return;
|
|
471
|
-
// }
|
|
472
|
-
// String x = "test";
|
|
473
|
-
// }
|
|
474
|
-
public void activeMethod() {}
|
|
475
|
-
}`;
|
|
476
|
-
fs.writeFileSync(tmpFile, javaCode);
|
|
477
|
-
|
|
478
|
-
try {
|
|
479
|
-
const result = await javaAnalyzeTool.handler({ filepath: tmpFile, format: "json" });
|
|
480
|
-
const analysis = JSON.parse(result.content);
|
|
481
|
-
const deadCodeIssues = analysis.issues.filter((i: any) => i.type === "dead-code");
|
|
482
|
-
expect(deadCodeIssues.length).toBeGreaterThanOrEqual(1);
|
|
483
|
-
expect(deadCodeIssues[0].message).toContain("주석 처리된 코드 블록");
|
|
484
|
-
} finally {
|
|
485
|
-
fs.unlinkSync(tmpFile);
|
|
486
|
-
}
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
it("일반 주석(비코드)은 dead-code로 감지하지 않는다", async () => {
|
|
490
|
-
const tmpFile = path.join(process.cwd(), ".test-normalcomment.java");
|
|
491
|
-
const javaCode = `package test;
|
|
492
|
-
public class NormalCommentTest {
|
|
493
|
-
// 이 클래스는 테스트용입니다.
|
|
494
|
-
// 작성자: 홍길동
|
|
495
|
-
// 작성일: 2024-01-01
|
|
496
|
-
// 버전: 1.0
|
|
497
|
-
// 설명: 간단한 테스트
|
|
498
|
-
// 참고: 없음
|
|
499
|
-
public void test() {}
|
|
500
|
-
}`;
|
|
501
|
-
fs.writeFileSync(tmpFile, javaCode);
|
|
502
|
-
|
|
503
|
-
try {
|
|
504
|
-
const result = await javaAnalyzeTool.handler({ filepath: tmpFile, format: "json" });
|
|
505
|
-
const analysis = JSON.parse(result.content);
|
|
506
|
-
const deadCodeIssues = analysis.issues.filter((i: any) => i.type === "dead-code");
|
|
507
|
-
// 코드 키워드가 없으므로 감지되지 않아야 함
|
|
508
|
-
expect(deadCodeIssues.length).toBe(0);
|
|
509
|
-
} finally {
|
|
510
|
-
fs.unlinkSync(tmpFile);
|
|
511
|
-
}
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
it("주석 내 코드가 아닌 일반 텍스트는 무시한다 (TODO, FIXME 등)", async () => {
|
|
515
|
-
const tmpFile = path.join(process.cwd(), ".test-todo.java");
|
|
516
|
-
const javaCode = `package test;
|
|
517
|
-
public class TodoTest {
|
|
518
|
-
// TODO: 이 부분 수정 필요
|
|
519
|
-
// TODO: 리팩토링 예정
|
|
520
|
-
// TODO: 성능 개선
|
|
521
|
-
// FIXME: 버그 있음
|
|
522
|
-
// NOTE: 참고사항
|
|
523
|
-
public void test() {}
|
|
524
|
-
}`;
|
|
525
|
-
fs.writeFileSync(tmpFile, javaCode);
|
|
526
|
-
|
|
527
|
-
try {
|
|
528
|
-
const result = await javaAnalyzeTool.handler({ filepath: tmpFile, format: "json" });
|
|
529
|
-
const analysis = JSON.parse(result.content);
|
|
530
|
-
const deadCodeIssues = analysis.issues.filter((i: any) => i.type === "dead-code");
|
|
531
|
-
// TODO/FIXME/NOTE 주석은 무시되어야 함
|
|
532
|
-
expect(deadCodeIssues.length).toBe(0);
|
|
533
|
-
} finally {
|
|
534
|
-
fs.unlinkSync(tmpFile);
|
|
535
|
-
}
|
|
536
|
-
});
|
|
537
|
-
});
|