Skip to content

Commit f4243d4

Browse files
committed
Add tests for Azure DevOps components and ProjectMapper
Add comprehensive tests for ProjectMapper, Azure DevOps client, blob providers, and repository data sources. Fix AzureDevOpsError prototype chain to ensure instanceof checks work correctly.
1 parent 3319461 commit f4243d4

11 files changed

+2023
-1148
lines changed

__test__/common/azure-devops/AzureDevOpsClient.test.ts

Lines changed: 418 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { AzureDevOpsError } from "@/common/azure-devops/AzureDevOpsError"
2+
3+
test("It sets the error name to AzureDevOpsError", () => {
4+
const error = new AzureDevOpsError("Test error", 400, false)
5+
expect(error.name).toEqual("AzureDevOpsError")
6+
})
7+
8+
test("It stores the error message", () => {
9+
const error = new AzureDevOpsError("Something went wrong", 500, false)
10+
expect(error.message).toEqual("Something went wrong")
11+
})
12+
13+
test("It stores the HTTP status code", () => {
14+
const error = new AzureDevOpsError("Unauthorized", 401, true)
15+
expect(error.status).toEqual(401)
16+
})
17+
18+
test("It stores the isAuthError flag when true", () => {
19+
const error = new AzureDevOpsError("Auth failed", 401, true)
20+
expect(error.isAuthError).toBe(true)
21+
})
22+
23+
test("It stores the isAuthError flag when false", () => {
24+
const error = new AzureDevOpsError("Not found", 404, false)
25+
expect(error.isAuthError).toBe(false)
26+
})
27+
28+
test("It is an instance of Error", () => {
29+
const error = new AzureDevOpsError("Test error", 400, false)
30+
expect(error).toBeInstanceOf(Error)
31+
})
32+
33+
test("It is an instance of AzureDevOpsError", () => {
34+
const error = new AzureDevOpsError("Test error", 400, false)
35+
expect(error).toBeInstanceOf(AzureDevOpsError)
36+
})
37+
38+
test("It can be caught as an Error", () => {
39+
let caught: Error | undefined
40+
try {
41+
throw new AzureDevOpsError("Test", 500, false)
42+
} catch (e) {
43+
caught = e as Error
44+
}
45+
expect(caught).toBeDefined()
46+
expect(caught?.message).toEqual("Test")
47+
})
48+
49+
test("It works correctly with instanceof after being thrown", () => {
50+
let isAzureDevOpsError = false
51+
try {
52+
throw new AzureDevOpsError("Test", 401, true)
53+
} catch (e) {
54+
isAzureDevOpsError = e instanceof AzureDevOpsError
55+
}
56+
expect(isAzureDevOpsError).toBe(true)
57+
})
58+
59+
test("It preserves status and isAuthError after being thrown", () => {
60+
let status: number | undefined
61+
let isAuthError: boolean | undefined
62+
try {
63+
throw new AzureDevOpsError("Unauthorized", 401, true)
64+
} catch (e) {
65+
if (e instanceof AzureDevOpsError) {
66+
status = e.status
67+
isAuthError = e.isAuthError
68+
}
69+
}
70+
expect(status).toEqual(401)
71+
expect(isAuthError).toBe(true)
72+
})
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
import OAuthTokenRefreshingAzureDevOpsClient from "@/common/azure-devops/OAuthTokenRefreshingAzureDevOpsClient"
2+
import { AzureDevOpsError } from "@/common/azure-devops/AzureDevOpsError"
3+
import IAzureDevOpsClient from "@/common/azure-devops/IAzureDevOpsClient"
4+
5+
function createMockClient(overrides: Partial<IAzureDevOpsClient> = {}): IAzureDevOpsClient {
6+
return {
7+
async getRepositories() {
8+
return []
9+
},
10+
async getRefs() {
11+
return []
12+
},
13+
async getItems() {
14+
return []
15+
},
16+
async getFileContent() {
17+
return null
18+
},
19+
...overrides
20+
}
21+
}
22+
23+
test("It forwards a request to getRepositories", async () => {
24+
let didForwardRequest = false
25+
const sut = new OAuthTokenRefreshingAzureDevOpsClient({
26+
oauthTokenDataSource: {
27+
async getOAuthToken() {
28+
return { accessToken: "foo", refreshToken: "bar" }
29+
}
30+
},
31+
oauthTokenRefresher: {
32+
async refreshOAuthToken() {
33+
return { accessToken: "newAccessToken", refreshToken: "newRefreshToken" }
34+
}
35+
},
36+
client: createMockClient({
37+
async getRepositories() {
38+
didForwardRequest = true
39+
return [{ id: "1", name: "test", webUrl: "https://test", project: { id: "1", name: "proj" } }]
40+
}
41+
})
42+
})
43+
await sut.getRepositories()
44+
expect(didForwardRequest).toBeTruthy()
45+
})
46+
47+
test("It forwards a request to getRefs", async () => {
48+
let forwardedRepoId: string | undefined
49+
const sut = new OAuthTokenRefreshingAzureDevOpsClient({
50+
oauthTokenDataSource: {
51+
async getOAuthToken() {
52+
return { accessToken: "foo", refreshToken: "bar" }
53+
}
54+
},
55+
oauthTokenRefresher: {
56+
async refreshOAuthToken() {
57+
return { accessToken: "newAccessToken", refreshToken: "newRefreshToken" }
58+
}
59+
},
60+
client: createMockClient({
61+
async getRefs(repositoryId) {
62+
forwardedRepoId = repositoryId
63+
return [{ name: "refs/heads/main", objectId: "abc123" }]
64+
}
65+
})
66+
})
67+
await sut.getRefs("repo-123")
68+
expect(forwardedRepoId).toEqual("repo-123")
69+
})
70+
71+
test("It forwards a request to getItems", async () => {
72+
let forwardedParams: { repoId?: string, path?: string, version?: string } = {}
73+
const sut = new OAuthTokenRefreshingAzureDevOpsClient({
74+
oauthTokenDataSource: {
75+
async getOAuthToken() {
76+
return { accessToken: "foo", refreshToken: "bar" }
77+
}
78+
},
79+
oauthTokenRefresher: {
80+
async refreshOAuthToken() {
81+
return { accessToken: "newAccessToken", refreshToken: "newRefreshToken" }
82+
}
83+
},
84+
client: createMockClient({
85+
async getItems(repositoryId, scopePath, version) {
86+
forwardedParams = { repoId: repositoryId, path: scopePath, version }
87+
return []
88+
}
89+
})
90+
})
91+
await sut.getItems("repo-123", "/", "main")
92+
expect(forwardedParams).toEqual({ repoId: "repo-123", path: "/", version: "main" })
93+
})
94+
95+
test("It forwards a request to getFileContent", async () => {
96+
let forwardedParams: { repoId?: string, path?: string, version?: string } = {}
97+
const sut = new OAuthTokenRefreshingAzureDevOpsClient({
98+
oauthTokenDataSource: {
99+
async getOAuthToken() {
100+
return { accessToken: "foo", refreshToken: "bar" }
101+
}
102+
},
103+
oauthTokenRefresher: {
104+
async refreshOAuthToken() {
105+
return { accessToken: "newAccessToken", refreshToken: "newRefreshToken" }
106+
}
107+
},
108+
client: createMockClient({
109+
async getFileContent(repositoryId, path, version) {
110+
forwardedParams = { repoId: repositoryId, path, version }
111+
return "file content"
112+
}
113+
})
114+
})
115+
await sut.getFileContent("repo-123", "openapi.yml", "main")
116+
expect(forwardedParams).toEqual({ repoId: "repo-123", path: "openapi.yml", version: "main" })
117+
})
118+
119+
test("It retries with a refreshed OAuth token when receiving an auth error", async () => {
120+
let didRefreshOAuthToken = false
121+
let didRespondWithAuthError = false
122+
const sut = new OAuthTokenRefreshingAzureDevOpsClient({
123+
oauthTokenDataSource: {
124+
async getOAuthToken() {
125+
return { accessToken: "foo", refreshToken: "bar" }
126+
}
127+
},
128+
oauthTokenRefresher: {
129+
async refreshOAuthToken() {
130+
didRefreshOAuthToken = true
131+
return { accessToken: "newAccessToken", refreshToken: "newRefreshToken" }
132+
}
133+
},
134+
client: createMockClient({
135+
async getRepositories() {
136+
if (!didRespondWithAuthError) {
137+
didRespondWithAuthError = true
138+
throw new AzureDevOpsError("Unauthorized", 401, true)
139+
}
140+
return []
141+
}
142+
})
143+
})
144+
await sut.getRepositories()
145+
expect(didRefreshOAuthToken).toBeTruthy()
146+
})
147+
148+
test("It only retries a request once when receiving auth errors", async () => {
149+
let requestCount = 0
150+
const sut = new OAuthTokenRefreshingAzureDevOpsClient({
151+
oauthTokenDataSource: {
152+
async getOAuthToken() {
153+
return { accessToken: "foo", refreshToken: "bar" }
154+
}
155+
},
156+
oauthTokenRefresher: {
157+
async refreshOAuthToken() {
158+
return { accessToken: "newAccessToken", refreshToken: "newRefreshToken" }
159+
}
160+
},
161+
client: createMockClient({
162+
async getRepositories() {
163+
requestCount += 1
164+
throw new AzureDevOpsError("Unauthorized", 401, true)
165+
}
166+
})
167+
})
168+
// When receiving the second auth error the call should fail.
169+
await expect(sut.getRepositories()).rejects.toThrow("Unauthorized")
170+
// We expect two requests:
171+
// 1. The initial request that failed after which we refreshed the OAuth token.
172+
// 2. The second request that failed after which we gave up.
173+
expect(requestCount).toEqual(2)
174+
})
175+
176+
test("It does not refresh an OAuth token when the initial request was successful", async () => {
177+
let didRefreshOAuthToken = false
178+
const sut = new OAuthTokenRefreshingAzureDevOpsClient({
179+
oauthTokenDataSource: {
180+
async getOAuthToken() {
181+
return { accessToken: "foo", refreshToken: "bar" }
182+
}
183+
},
184+
oauthTokenRefresher: {
185+
async refreshOAuthToken() {
186+
didRefreshOAuthToken = true
187+
return { accessToken: "newAccessToken", refreshToken: "newRefreshToken" }
188+
}
189+
},
190+
client: createMockClient({
191+
async getRepositories() {
192+
return []
193+
}
194+
})
195+
})
196+
await sut.getRepositories()
197+
expect(didRefreshOAuthToken).toBeFalsy()
198+
})
199+
200+
test("It does not refresh OAuth token for non-auth errors", async () => {
201+
let didRefreshOAuthToken = false
202+
const sut = new OAuthTokenRefreshingAzureDevOpsClient({
203+
oauthTokenDataSource: {
204+
async getOAuthToken() {
205+
return { accessToken: "foo", refreshToken: "bar" }
206+
}
207+
},
208+
oauthTokenRefresher: {
209+
async refreshOAuthToken() {
210+
didRefreshOAuthToken = true
211+
return { accessToken: "newAccessToken", refreshToken: "newRefreshToken" }
212+
}
213+
},
214+
client: createMockClient({
215+
async getRepositories() {
216+
throw new AzureDevOpsError("Not Found", 404, false)
217+
}
218+
})
219+
})
220+
await expect(sut.getRepositories()).rejects.toThrow("Not Found")
221+
expect(didRefreshOAuthToken).toBeFalsy()
222+
})
223+
224+
test("It does not refresh OAuth token for non-AzureDevOpsError errors", async () => {
225+
let didRefreshOAuthToken = false
226+
const sut = new OAuthTokenRefreshingAzureDevOpsClient({
227+
oauthTokenDataSource: {
228+
async getOAuthToken() {
229+
return { accessToken: "foo", refreshToken: "bar" }
230+
}
231+
},
232+
oauthTokenRefresher: {
233+
async refreshOAuthToken() {
234+
didRefreshOAuthToken = true
235+
return { accessToken: "newAccessToken", refreshToken: "newRefreshToken" }
236+
}
237+
},
238+
client: createMockClient({
239+
async getRepositories() {
240+
throw new Error("Some random error")
241+
}
242+
})
243+
})
244+
await expect(sut.getRepositories()).rejects.toThrow("Some random error")
245+
expect(didRefreshOAuthToken).toBeFalsy()
246+
})

0 commit comments

Comments
 (0)