Skip to content

Commit f5e366f

Browse files
feat(svn): 添加SVN工具函数的单元测试
- 新增svn.spec.ts文件,包含对SVN相关工具函数的单元测试 - 测试内容涵盖SVN安装检查、仓库验证、提交信息提取等功能 - 使用vitest框架进行测试,确保各功能模块的正确性和稳定性
1 parent e7e5631 commit f5e366f

File tree

1 file changed

+277
-0
lines changed

1 file changed

+277
-0
lines changed

src/utils/__tests__/svn.spec.ts

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
import { describe, it, expect, vi, beforeEach } from "vitest"
2+
import { promises as fs } from "fs"
3+
import { exec } from "child_process"
4+
import { promisify } from "util"
5+
import * as path from "path"
6+
7+
// Use vi.hoisted to ensure the mock is available at the right time
8+
const { mockExecAsync } = vi.hoisted(() => ({
9+
mockExecAsync: vi.fn(),
10+
}))
11+
12+
// Mock modules before importing the functions
13+
vi.mock("vscode", () => ({
14+
workspace: {
15+
workspaceFolders: [
16+
{
17+
uri: {
18+
fsPath: "/test/workspace",
19+
},
20+
},
21+
],
22+
},
23+
window: {
24+
createOutputChannel: vi.fn(() => ({
25+
appendLine: vi.fn(),
26+
show: vi.fn(),
27+
})),
28+
showErrorMessage: vi.fn(),
29+
showWarningMessage: vi.fn(),
30+
showInformationMessage: vi.fn(),
31+
},
32+
env: {
33+
openExternal: vi.fn(),
34+
},
35+
Uri: {
36+
parse: vi.fn(),
37+
},
38+
}))
39+
40+
vi.mock("fs", () => ({
41+
promises: {
42+
access: vi.fn(),
43+
},
44+
}))
45+
46+
vi.mock("child_process", () => ({
47+
exec: vi.fn(),
48+
}))
49+
50+
vi.mock("util", () => ({
51+
promisify: vi.fn(() => mockExecAsync),
52+
}))
53+
54+
// Import functions after mocking
55+
import {
56+
checkSvnInstalled,
57+
checkSvnRepo,
58+
getSvnRepositoryInfo,
59+
extractSvnRepositoryName,
60+
searchSvnCommits,
61+
getSvnCommitInfoForMentions,
62+
getWorkspaceSvnInfo,
63+
} from "../svn"
64+
65+
describe("SVN Utilities", () => {
66+
let mockFsAccess: any
67+
68+
beforeEach(() => {
69+
vi.clearAllMocks()
70+
71+
// Setup fs.access mock
72+
mockFsAccess = vi.mocked(fs.access)
73+
})
74+
75+
describe("checkSvnInstalled", () => {
76+
it("should return true when SVN is installed", async () => {
77+
mockExecAsync.mockResolvedValue({ stdout: "svn, version 1.14.0\n", stderr: "" })
78+
79+
const result = await checkSvnInstalled()
80+
expect(result).toBe(true)
81+
expect(mockExecAsync).toHaveBeenCalledWith("svn --version")
82+
})
83+
84+
it("should return false when SVN is not installed", async () => {
85+
mockExecAsync.mockRejectedValue(new Error("Command not found"))
86+
87+
const result = await checkSvnInstalled()
88+
expect(result).toBe(false)
89+
})
90+
})
91+
92+
describe("checkSvnRepo", () => {
93+
it("should return true for valid SVN repository", async () => {
94+
mockFsAccess.mockResolvedValue(undefined)
95+
96+
const result = await checkSvnRepo("/test/workspace")
97+
expect(result).toBe(true)
98+
// Use path.join to handle platform-specific path separators
99+
expect(mockFsAccess).toHaveBeenCalledWith(path.join("/test/workspace", ".svn"))
100+
})
101+
102+
it("should return false for non-SVN directory", async () => {
103+
mockFsAccess.mockRejectedValue(new Error("ENOENT"))
104+
105+
const result = await checkSvnRepo("/test/workspace")
106+
expect(result).toBe(false)
107+
})
108+
})
109+
110+
describe("getSvnRepositoryInfo", () => {
111+
it("should return repository info for valid SVN workspace", async () => {
112+
mockFsAccess.mockResolvedValue(undefined)
113+
mockExecAsync.mockResolvedValue({
114+
stdout: `URL: https://svn.example.com/myproject/trunk
115+
Working Copy Root Path: /test/workspace`,
116+
stderr: "",
117+
})
118+
119+
const result = await getSvnRepositoryInfo("/test/workspace")
120+
expect(result.repositoryUrl).toBe("https://svn.example.com/myproject/trunk")
121+
expect(result.repositoryName).toBe("myproject")
122+
expect(result.workingCopyRoot).toBe("/test/workspace")
123+
})
124+
125+
it("should return empty object for non-SVN directory", async () => {
126+
mockFsAccess.mockRejectedValue(new Error("ENOENT"))
127+
128+
const result = await getSvnRepositoryInfo("/test/workspace")
129+
expect(result).toEqual({})
130+
})
131+
132+
it("should handle SVN command failure gracefully", async () => {
133+
mockFsAccess.mockResolvedValue(undefined)
134+
mockExecAsync.mockRejectedValue(new Error("SVN command failed"))
135+
136+
const result = await getSvnRepositoryInfo("/test/workspace")
137+
expect(result).toEqual({})
138+
})
139+
})
140+
141+
describe("extractSvnRepositoryName", () => {
142+
it("should extract repository name from trunk URL", () => {
143+
const result = extractSvnRepositoryName("https://svn.example.com/myproject/trunk")
144+
expect(result).toBe("myproject")
145+
})
146+
147+
it("should extract repository name from branches URL", () => {
148+
const result = extractSvnRepositoryName("https://svn.example.com/myproject/branches/feature")
149+
expect(result).toBe("myproject")
150+
})
151+
152+
it("should extract repository name from tags URL", () => {
153+
const result = extractSvnRepositoryName("https://svn.example.com/myproject/tags/v1.0")
154+
expect(result).toBe("myproject")
155+
})
156+
157+
it("should extract repository name from simple URL", () => {
158+
const result = extractSvnRepositoryName("https://svn.example.com/myproject")
159+
expect(result).toBe("myproject")
160+
})
161+
162+
it("should handle invalid URLs gracefully", () => {
163+
const result = extractSvnRepositoryName("")
164+
expect(result).toBe("")
165+
})
166+
})
167+
168+
describe("searchSvnCommits", () => {
169+
it("should return commits matching search query", async () => {
170+
// Mock checkSvnInstalled to return true
171+
mockExecAsync.mockResolvedValueOnce({ stdout: "svn, version 1.14.0\n", stderr: "" }).mockResolvedValueOnce({
172+
stdout: `<?xml version="1.0" encoding="UTF-8"?>
173+
<log>
174+
<logentry revision="123">
175+
<author>john.doe</author>
176+
<date>2023-01-15T10:30:00.000000Z</date>
177+
<msg>Test commit message</msg>
178+
</logentry>
179+
</log>`,
180+
stderr: "",
181+
})
182+
183+
// Mock checkSvnRepo to return true
184+
mockFsAccess.mockResolvedValue(undefined)
185+
186+
const result = await searchSvnCommits("test", "/test/workspace")
187+
expect(result).toHaveLength(1)
188+
expect(result[0].revision).toBe("123")
189+
expect(result[0].author).toBe("john.doe")
190+
expect(result[0].message).toBe("Test commit message")
191+
})
192+
193+
it("should return empty array when SVN is not available", async () => {
194+
mockExecAsync.mockRejectedValue(new Error("Command not found"))
195+
196+
const result = await searchSvnCommits("test", "/test/workspace")
197+
expect(result).toEqual([])
198+
})
199+
})
200+
201+
describe("getSvnCommitInfoForMentions", () => {
202+
it("should return commit info for valid revision", async () => {
203+
// Mock checkSvnInstalled and checkSvnRepo
204+
mockExecAsync.mockResolvedValueOnce({ stdout: "svn, version 1.14.0\n", stderr: "" }).mockResolvedValueOnce({
205+
stdout: `------------------------------------------------------------------------
206+
r123 | john.doe | 2023-01-15 10:30:00 +0000 (Sun, 15 Jan 2023) | 1 line
207+
208+
Test commit message
209+
------------------------------------------------------------------------`,
210+
stderr: "",
211+
})
212+
213+
mockFsAccess.mockResolvedValue(undefined)
214+
215+
const result = await getSvnCommitInfoForMentions("123", "/test/workspace")
216+
expect(result).toContain("r123")
217+
expect(result).toContain("john.doe")
218+
expect(result).toContain("Test commit message")
219+
})
220+
221+
it("should return error message for invalid revision", async () => {
222+
// Mock checkSvnInstalled to fail
223+
mockExecAsync.mockRejectedValue(new Error("Command not found"))
224+
225+
const result = await getSvnCommitInfoForMentions("invalid", "/test/workspace")
226+
expect(result).toBe("Error: SVN not available or not an SVN repository")
227+
})
228+
})
229+
230+
describe("getWorkspaceSvnInfo", () => {
231+
it("should return SVN info for workspace", async () => {
232+
// Mock fs.access to simulate .svn directory exists
233+
mockFsAccess.mockResolvedValue(undefined)
234+
235+
// Mock execAsync for svn info command
236+
mockExecAsync.mockResolvedValue({
237+
stdout: `URL: https://svn.example.com/workspace/trunk
238+
Working Copy Root Path: /test/workspace`,
239+
stderr: "",
240+
})
241+
242+
const result = await getWorkspaceSvnInfo()
243+
expect(result.repositoryUrl).toBe("https://svn.example.com/workspace/trunk")
244+
expect(result.repositoryName).toBe("workspace")
245+
expect(result.workingCopyRoot).toBe("/test/workspace")
246+
})
247+
248+
it("should return empty object when no workspace folders", async () => {
249+
// Mock vscode workspace with no folders
250+
const vscode = await import("vscode")
251+
const mockWorkspace = vi.mocked(vscode.workspace)
252+
253+
// Use Object.defineProperty to mock the readonly property
254+
Object.defineProperty(mockWorkspace, "workspaceFolders", {
255+
value: undefined,
256+
writable: true,
257+
configurable: true,
258+
})
259+
260+
const result = await getWorkspaceSvnInfo()
261+
expect(result).toEqual({})
262+
263+
// Restore the original value for other tests
264+
Object.defineProperty(mockWorkspace, "workspaceFolders", {
265+
value: [
266+
{
267+
uri: {
268+
fsPath: "/test/workspace",
269+
},
270+
},
271+
],
272+
writable: true,
273+
configurable: true,
274+
})
275+
})
276+
})
277+
})

0 commit comments

Comments
 (0)