-
Notifications
You must be signed in to change notification settings - Fork 0
🧹 [code health] Extract HTTP status magic numbers to constants in github.ts #342
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2288bfa
2a70576
27368e8
fa883bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,31 @@ describe("fetchUserProfile", () => { | |
| expect(e).toBeInstanceOf(RateLimitError); | ||
| } | ||
| }); | ||
|
|
||
| it("GraphQL API without token throws UNAUTHORIZED correctly through API error", async () => { | ||
| const { fetchContributions } = await import("../../github"); | ||
| const { GitHubApiError } = await import("../../types"); | ||
| await expect(fetchContributions("testuser")).rejects.toThrow(GitHubApiError); | ||
| }); | ||
|
Comment on lines
+16
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win エラーテストでHTTPステータスコードの検証を追加してください エラー型の検証は実装されていますが、 例:
♻️ 提案される修正例 it("GraphQL API without token throws UNAUTHORIZED correctly through API error", async () => {
const { fetchContributions } = await import("../../github");
const { GitHubApiError } = await import("../../types");
- await expect(fetchContributions("testuser")).rejects.toThrow(GitHubApiError);
+ try {
+ await fetchContributions("testuser");
+ expect.fail("Should have thrown");
+ } catch (e) {
+ expect(e).toBeInstanceOf(GitHubApiError);
+ expect((e as GitHubApiError).status).toBe(401);
+ }
}); it("無効なトークンフォーマットの場合 GitHubApiError をスローする", async () => {
const { fetchUserProfile } = await import("../../github");
const { GitHubApiError } = await import("../../types");
- await expect(fetchUserProfile("testuser", "invalid token with spaces")).rejects.toThrow(
- GitHubApiError
- );
+ try {
+ await fetchUserProfile("testuser", "invalid token with spaces");
+ expect.fail("Should have thrown");
+ } catch (e) {
+ expect(e).toBeInstanceOf(GitHubApiError);
+ expect((e as GitHubApiError).status).toBe(400);
+ }
});Also applies to: 32-39 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
|
|
||
| it("HTTP_STATUS object contains correct status codes", async () => { | ||
| const { HTTP_STATUS } = await import("../../github"); | ||
| expect(HTTP_STATUS.BAD_REQUEST).toBe(400); | ||
| expect(HTTP_STATUS.UNAUTHORIZED).toBe(401); | ||
| expect(HTTP_STATUS.FORBIDDEN).toBe(403); | ||
| expect(HTTP_STATUS.NOT_FOUND).toBe(404); | ||
| }); | ||
|
|
||
|
|
||
|
|
||
| it("無効なトークンフォーマットの場合 GitHubApiError をスローする", async () => { | ||
| const { fetchUserProfile } = await import("../../github"); | ||
| const { GitHubApiError } = await import("../../types"); | ||
|
|
||
| await expect(fetchUserProfile("testuser", "invalid token with spaces")).rejects.toThrow( | ||
| GitHubApiError | ||
| ); | ||
| }); | ||
| it("プロフィール・組織・ピン留めを正しく取得して結合する", async () => { | ||
| mockFetch | ||
| .mockResolvedValueOnce(jsonResponse(MOCK_USER)) // GET /users/testuser | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -24,14 +24,22 @@ import { | |||||||||||||||||||
| const GITHUB_API = "https://api.github.com"; | ||||||||||||||||||||
| const GITHUB_GRAPHQL = "https://api.github.com/graphql"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| export const HTTP_STATUS = { | ||||||||||||||||||||
| BAD_REQUEST: 400, | ||||||||||||||||||||
| UNAUTHORIZED: 401, | ||||||||||||||||||||
| FORBIDDEN: 403, | ||||||||||||||||||||
| NOT_FOUND: 404, | ||||||||||||||||||||
| } as const; | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial | 💤 Low value 定数宣言直後の空行を削除することを検討してください PRの目的にも記載されている通り、定数宣言の直後に不要な空行があります。コードの一貫性のため削除を推奨します。 ♻️ 提案される修正 export const HTTP_STATUS = {
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
} as const;
-📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||
| export function headers(token?: string): HeadersInit { | ||||||||||||||||||||
|
Comment on lines
+31
to
35
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Prompt To Fix With AIThis is a comment left during a code review.
Path: src/lib/github.ts
Line: 31-35
Comment:
`HTTP_STATUS` 定数宣言の直後に空行が2行連続しています。コードスタイルの統一のため1行にまとめることを推奨します。
```suggestion
NOT_FOUND: 404,
} as const;
export function headers(token?: string): HeadersInit {
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||||||||||||||||||||
| const h: HeadersInit = { | ||||||||||||||||||||
| Accept: "application/vnd.github+json", | ||||||||||||||||||||
| "User-Agent": "github-user-summary", | ||||||||||||||||||||
| }; | ||||||||||||||||||||
| if (token) { | ||||||||||||||||||||
| if (!/^[A-Za-z0-9_=-]+$/.test(token)) { | ||||||||||||||||||||
| throw new GitHubApiError("Invalid token format", 400); | ||||||||||||||||||||
| throw new GitHubApiError("Invalid token format", HTTP_STATUS.BAD_REQUEST); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| h.Authorization = `Bearer ${token}`; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
@@ -45,10 +53,10 @@ export function handleRateLimit(res: Response): never { | |||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| async function handleResponse<T>(res: Response): Promise<T> { | ||||||||||||||||||||
| if (res.status === 404) { | ||||||||||||||||||||
| if (res.status === HTTP_STATUS.NOT_FOUND) { | ||||||||||||||||||||
| throw new UserNotFoundError("unknown"); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| if (res.status === 403) { | ||||||||||||||||||||
| if (res.status === HTTP_STATUS.FORBIDDEN) { | ||||||||||||||||||||
| handleRateLimit(res); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| if (!res.ok) { | ||||||||||||||||||||
|
|
@@ -106,10 +114,7 @@ function calculateMostActiveDay(calendar: { date: string; count: number }[]): st | |||||||||||||||||||
| : ""; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| async function graphql<T>(query: string, token?: string, variables?: Record<string, unknown>): Promise<T> { | ||||||||||||||||||||
| if (!token) { | ||||||||||||||||||||
| throw new GitHubApiError("GraphQL API requires authentication token", 401); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| async function graphql<T>(query: string, token: string, variables?: Record<string, unknown>): Promise<T> { | ||||||||||||||||||||
| const body: { query: string; variables?: Record<string, unknown> } = { query }; | ||||||||||||||||||||
| if (variables) { | ||||||||||||||||||||
| body.variables = variables; | ||||||||||||||||||||
|
|
@@ -121,7 +126,7 @@ async function graphql<T>(query: string, token?: string, variables?: Record<stri | |||||||||||||||||||
| body: JSON.stringify(body), | ||||||||||||||||||||
| next: { revalidate: 300 }, | ||||||||||||||||||||
| }); | ||||||||||||||||||||
| if (res.status === 403) { | ||||||||||||||||||||
| if (res.status === HTTP_STATUS.FORBIDDEN) { | ||||||||||||||||||||
| handleRateLimit(res); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| if (!res.ok) { | ||||||||||||||||||||
|
|
@@ -499,7 +504,7 @@ export async function fetchContributions( | |||||||||||||||||||
| ): Promise<ContributionData> { | ||||||||||||||||||||
| if (!token) { | ||||||||||||||||||||
| // GraphQL 必須なので、token なしの場合はデフォルト値を返す | ||||||||||||||||||||
| throw new GitHubApiError("Contributions data requires authentication", 401); | ||||||||||||||||||||
| throw new GitHubApiError("Contributions data requires authentication", HTTP_STATUS.UNAUTHORIZED); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const now = new Date(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
GitHubApiErrorのステータスコード検証を追加してください
エラー型の検証は実装されていますが、
GitHubApiErrorのstatusフィールドが期待値と一致することを確認するべきです。具体的には:statusが400(BAD_REQUEST)であることstatusが401であることコーディングガイドラインに従い、401、403などの具体的なステータスコードの検証が必要です。
♻️ 提案される修正例
it("GraphQL query fails when forbidden due to bad token", async () => { const { headers } = await import("../../github"); const { GitHubApiError } = await import("../../types"); - expect(() => headers("invalid token format!!")).toThrow(GitHubApiError); + try { + headers("invalid token format!!"); + expect.fail("Should have thrown"); + } catch (e) { + expect(e).toBeInstanceOf(GitHubApiError); + expect((e as GitHubApiError).status).toBe(400); + } });it("GraphQL API throws GitHubApiError on 401 response", async () => { mockFetch.mockResolvedValueOnce(jsonResponse({ message: "Bad credentials" }, 401)); const { fetchRepositories } = await import("../../github"); const { GitHubApiError } = await import("../../types"); - await expect(fetchRepositories("testuser", "fake-token")).rejects.toThrow(GitHubApiError); + try { + await fetchRepositories("testuser", "fake-token"); + expect.fail("Should have thrown"); + } catch (e) { + expect(e).toBeInstanceOf(GitHubApiError); + expect((e as GitHubApiError).status).toBe(401); + } });Also applies to: 37-43, 45-50
🤖 Prompt for AI Agents
Source: Coding guidelines