Skip to content

Commit aa4d1d4

Browse files
committed
Check GitHub API credentials validity when server starts
1 parent 1509492 commit aa4d1d4

4 files changed

Lines changed: 123 additions & 2 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.env
22
node_modules
33
.DS_Store
4-
database.sqlite
4+
database.sqlite
5+
.vscode/

src/index.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,27 @@ import logger from './utils/logger.js';
44
import { API_PORT, APOLLO_PORT } from './config.js';
55
import createApolloServer from './apolloServer.js';
66
import app from './app.js';
7+
import githubClient from './utils/githubClient.js';
8+
9+
const logGithubCredentialStatus = async () => {
10+
const validationResult = await githubClient.validateCredentials();
11+
12+
if (!validationResult.configured) {
13+
logger.info(
14+
'GitHub API credentials are not configured; anonymous GitHub API access with strict rate limit will be used',
15+
);
16+
return;
17+
}
18+
19+
if (validationResult.valid) {
20+
logger.info('GitHub API credentials validated successfully');
21+
return;
22+
}
23+
24+
logger.warn(
25+
'GitHub API credential validation failed; anonymous GitHub API access with strict rate limit will be used',
26+
);
27+
};
728

829
const startServer = async () => {
930
const httpServer = http.createServer(app);
@@ -18,6 +39,8 @@ const startServer = async () => {
1839
httpServer.listen({ port: API_PORT }, resolve),
1940
);
2041

42+
await logGithubCredentialStatus();
43+
2144
logger.info(`Apollo Server ready at http://localhost:${APOLLO_PORT}`);
2245
};
2346

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { GithubClient, GithubError } from '../githubClient.js';
2+
3+
describe('GithubClient.validateCredentials', () => {
4+
it('returns not configured when credentials are missing', async () => {
5+
const githubClient = new GithubClient({
6+
clientId: '',
7+
clientSecret: '',
8+
});
9+
10+
githubClient.httpClient.get = jest.fn();
11+
12+
const result = await githubClient.validateCredentials();
13+
14+
expect(result).toEqual({
15+
configured: false,
16+
valid: false,
17+
});
18+
expect(githubClient.httpClient.get).not.toHaveBeenCalled();
19+
});
20+
21+
it('returns success when GitHub accepts the credentials', async () => {
22+
const githubClient = new GithubClient({
23+
clientId: 'client-id',
24+
clientSecret: 'client-secret',
25+
});
26+
27+
githubClient.httpClient.get = jest.fn().mockResolvedValue({ status: 200 });
28+
29+
const result = await githubClient.validateCredentials({ timeout: 1234 });
30+
31+
expect(result).toEqual({
32+
configured: true,
33+
valid: true,
34+
status: 200,
35+
});
36+
expect(githubClient.httpClient.get).toHaveBeenCalledWith('/rate_limit', {
37+
timeout: 1234,
38+
auth: {
39+
username: 'client-id',
40+
password: 'client-secret',
41+
},
42+
});
43+
});
44+
45+
it('returns failure details when GitHub rejects the credentials', async () => {
46+
const githubClient = new GithubClient({
47+
clientId: 'client-id',
48+
clientSecret: 'client-secret',
49+
});
50+
51+
githubClient.httpClient.get = jest.fn().mockRejectedValue({
52+
response: {
53+
status: 401,
54+
statusText: 'Unauthorized',
55+
headers: {},
56+
data: { message: 'Bad credentials' },
57+
},
58+
});
59+
60+
const result = await githubClient.validateCredentials();
61+
62+
expect(result.configured).toBe(true);
63+
expect(result.valid).toBe(false);
64+
expect(result.status).toBe(401);
65+
expect(result.error).toBeInstanceOf(GithubError);
66+
});
67+
});

src/utils/githubClient.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,12 @@ export class GithubClient {
6565
this.cache = new LRUCache({ max: 100, maxAge: cacheMaxAge });
6666
}
6767

68+
hasCredentials() {
69+
return Boolean(this.clientId && this.clientSecret);
70+
}
71+
6872
getAuth() {
69-
return this.clientId && this.clientSecret
73+
return this.hasCredentials()
7074
? {
7175
username: this.clientId,
7276
password: this.clientSecret,
@@ -127,6 +131,32 @@ export class GithubClient {
127131
throw error;
128132
}
129133
}
134+
135+
async validateCredentials({ timeout = 5000 } = {}) {
136+
if (!this.hasCredentials()) {
137+
return {
138+
configured: false,
139+
valid: false,
140+
};
141+
}
142+
143+
try {
144+
const response = await this.getRequest('/rate_limit', { timeout });
145+
146+
return {
147+
configured: true,
148+
valid: true,
149+
status: response.status,
150+
};
151+
} catch (error) {
152+
return {
153+
configured: true,
154+
valid: false,
155+
status: error.extensions?.response?.status,
156+
error,
157+
};
158+
}
159+
}
130160
}
131161

132162
export const githubClient = new GithubClient();

0 commit comments

Comments
 (0)