Skip to content

Commit e8e20aa

Browse files
fix(home): handle GitHub API failures gracefully in releases/changelogs (#3292)
* fix(home): handle GitHub API failures gracefully in releases/changelogs (#3291) Wrap GitHub API calls in try/catch and return empty array on failure. Prevents 422 errors when rate-limited or repos are unreachable. * fix(home): align error tests with graceful GitHub API fallback Tests now expect 200 with empty data when GitHub API fails, matching the service's catch-and-return-empty-array behavior. * fix(home): update JSDoc for releases/changelogs to reflect graceful degradation
1 parent cf58689 commit e8e20aa

2 files changed

Lines changed: 49 additions & 41 deletions

File tree

modules/home/services/home.service.js

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,44 +28,52 @@ const page = async (name) => {
2828
};
2929

3030
/**
31-
* @desc Function to get all versions
32-
* @return {Promise} All versions
31+
* @desc Fetch releases from configured GitHub repos. Returns an empty array on API failure (graceful degradation).
32+
* @return {Promise<Array<{title: string, list: Array}>>} Releases grouped by repo, or [] on error
3333
*/
3434
const releases = async () => {
35-
const requests = config.repos.map((item) =>
36-
axios.get(`https://api.github.com/repos/${item.owner}/${item.repo}/releases`, {
37-
headers: item.token ? { Authorization: `token ${item.token}` } : {},
38-
}),
39-
);
40-
let results = await axios.all(requests);
41-
results = results.map((result, i) => ({
42-
title: config.repos[i].title,
43-
list: result.data.map((release) => ({
44-
name: release.name,
45-
prerelease: release.prerelease,
46-
published_at: release.published_at,
47-
})),
48-
}));
49-
return Promise.resolve(results);
35+
try {
36+
const requests = config.repos.map((item) =>
37+
axios.get(`https://api.github.com/repos/${item.owner}/${item.repo}/releases`, {
38+
headers: item.token ? { Authorization: `token ${item.token}` } : {},
39+
}),
40+
);
41+
let results = await axios.all(requests);
42+
results = results.map((result, i) => ({
43+
title: config.repos[i].title,
44+
list: result.data.map((release) => ({
45+
name: release.name,
46+
prerelease: release.prerelease,
47+
published_at: release.published_at,
48+
})),
49+
}));
50+
return results;
51+
} catch (_err) {
52+
return [];
53+
}
5054
};
5155

5256
/**
53-
* @desc Function to get all changelogs
54-
* @return {Promise} All changelogs
57+
* @desc Fetch changelogs from configured GitHub repos. Returns an empty array on API failure (graceful degradation).
58+
* @return {Promise<Array<{title: string, markdown: string}>>} Changelogs grouped by repo, or [] on error
5559
*/
5660
const changelogs = async () => {
57-
const repos = _.filter(config.repos, (repo) => repo.changelog);
58-
const requests = repos.map((item) =>
59-
axios.get(`https://api.github.com/repos/${item.owner}/${item.repo}/contents/${item.changelog}`, {
60-
headers: item.token ? { Authorization: `token ${item.token}` } : {},
61-
}),
62-
);
63-
let results = await axios.all(requests);
64-
results = results.map((result, i) => ({
65-
title: config.repos[i].title,
66-
markdown: Base64.decode(result.data.content),
67-
}));
68-
return Promise.resolve(results);
61+
try {
62+
const repos = _.filter(config.repos, (repo) => repo.changelog);
63+
const requests = repos.map((item) =>
64+
axios.get(`https://api.github.com/repos/${item.owner}/${item.repo}/contents/${item.changelog}`, {
65+
headers: item.token ? { Authorization: `token ${item.token}` } : {},
66+
}),
67+
);
68+
let results = await axios.all(requests);
69+
results = results.map((result, i) => ({
70+
title: repos[i].title,
71+
markdown: Base64.decode(result.data.content),
72+
}));
73+
return results;
74+
} catch (_err) {
75+
return [];
76+
}
6977
};
7078

7179
/**

modules/home/tests/home.integration.tests.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,20 +91,20 @@ describe('Home integration tests:', () => {
9191
}
9292
});
9393

94-
test('should return 422 when GitHub API fails for releases', async () => {
94+
test('should return empty releases gracefully when GitHub API fails', async () => {
9595
axios.get.mockRejectedValueOnce(new Error('GitHub API unavailable'));
96-
const result = await agent.get('/api/home/releases').expect(422);
97-
expect(result.body.type).toBe('error');
98-
expect(result.body.message).toBe('Unprocessable Entity');
99-
expect(result.body.description).toBe('GitHub API unavailable.');
96+
const result = await agent.get('/api/home/releases').expect(200);
97+
expect(result.body.type).toBe('success');
98+
expect(result.body.message).toBe('releases');
99+
expect(result.body.data).toEqual([]);
100100
});
101101

102-
test('should return 422 when GitHub API fails for changelogs', async () => {
102+
test('should return empty changelogs gracefully when GitHub API fails', async () => {
103103
axios.get.mockRejectedValueOnce(new Error('GitHub API unavailable'));
104-
const result = await agent.get('/api/home/changelogs').expect(422);
105-
expect(result.body.type).toBe('error');
106-
expect(result.body.message).toBe('Unprocessable Entity');
107-
expect(result.body.description).toBe('GitHub API unavailable.');
104+
const result = await agent.get('/api/home/changelogs').expect(200);
105+
expect(result.body.type).toBe('success');
106+
expect(result.body.message).toBe('changelogs');
107+
expect(result.body.data).toEqual([]);
108108
});
109109

110110
test('should use Authorization header when a token is configured for releases', async () => {

0 commit comments

Comments
 (0)