Skip to content
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

getMembers API 리팩토링 #98

Merged
merged 14 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 11 additions & 19 deletions src/api/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
import type { Grade } from "../services/common/types";
import type { Grade } from "../services/types";
import type { Config } from "./types";

export const CONFIG: Config = {
study: {
organization: "DaleStudy",
repository: "leetcode-study",
branchName: "main",
teamPrefix: "leetcode",
totalProblemCount: 75,
gradeThresholds: [
["BIG_TREE", 70],
["SMALL_TREE", 60],
["SPROUT", 50],
["SEED", 0],
] as [Grade, number][],
},
gitHub: {
baseUrl: "https://api.github.com",
mediaType: "application/vnd.github+json",
token: import.meta.env.VITE_GITHUB_API_TOKEN,
},
totalProblemCount: 75,
gradeThresholds: [
["TREE", 70], // 나무
["FRUIT", 60], // 열매
["BRANCH", 45], // 가지
["LEAF", 30], // 잎새
["SPROUT", 15], // 새싹
["SEED", 0], // 씨앗
] as [Grade, number][],
gitHubToken: import.meta.env.VITE_GITHUB_API_TOKEN,
} as const;
20 changes: 3 additions & 17 deletions src/api/config/types.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,7 @@
import type { Grade } from "../services/common/types";
import type { Grade } from "../services/types";

export type StudyConfig = {
organization: string;
repository: string;
branchName: string;
teamPrefix: string;
export type Config = {
totalProblemCount: number;
gradeThresholds: [Grade, number][];
};

export type GitHubConfig = {
baseUrl: string;
mediaType: string;
token: string;
};

export type Config = {
study: StudyConfig;
gitHub: GitHubConfig;
gitHubToken: string;
};
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import { beforeEach, expect, test, vi } from "vitest";
import { mockMembers } from "../common/fixtures";
import { createFetchService } from "../fetch/fetchService";
import { createProcessService } from "../process/processService";
import { getMembers } from "./storeService";
import { mock } from "vitest-mock-extended";
import { createFetchService } from "./services/fetch/fetchService";
import { createProcessService } from "./services/process/processService";
import { getMembers } from "./getMembers";
import { type Member } from "./services/types";

// Mock data
const mockMembers = mock<Member[]>();

// Mock services
const mockFetchMembers = vi.fn();
const mockFetchSubmissions = vi.fn();
const mockGetMembers = vi.fn();

vi.mock("../fetch/fetchService");
vi.mock("./services/fetch/fetchService");
vi.mocked(createFetchService).mockReturnValue({
fetchMembers: mockFetchMembers,
fetchSubmissions: mockFetchSubmissions,
});

vi.mock("../process/processService");
vi.mock("./services/process/processService");
vi.mocked(createProcessService).mockReturnValue({
getMembers: mockGetMembers,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { CONFIG } from "../../config";
import type { Member } from "../common/types";
import { createFetchService } from "../fetch/fetchService";
import { createProcessService } from "../process/processService";
import { CONFIG } from "./config";
import { createFetchService } from "./services/fetch/fetchService";
import { createProcessService } from "./services/process/processService";
import { type Member } from "./services/types";

export async function getMembers(): Promise<Member[]> {
const fetchService = createFetchService(CONFIG);
const processService = createProcessService(CONFIG);

const [memberIdentities, submissions] = await Promise.all([
fetchService.fetchMembers(),
fetchService.fetchSubmissions(CONFIG.study.repository),
fetchService.fetchSubmissions("leetcode-study"),
]);

return processService.getMembers(memberIdentities, submissions);
Expand Down
2 changes: 0 additions & 2 deletions src/api/index.ts

This file was deleted.

98 changes: 0 additions & 98 deletions src/api/infra/gitHub/fixtures.ts

This file was deleted.

47 changes: 24 additions & 23 deletions src/api/infra/gitHub/gitHubClient.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { test, expect, beforeEach, vi } from "vitest";
import { mock } from "vitest-mock-extended";
import { createGitHubClient } from "./gitHubClient";
import { CONFIG } from "../../config";
import { mockGitHubTeams, mockGitHubMembers, mockGitHubTree } from "./fixtures";
import { GitHubMember, GitHubTeam, GitHubTree } from "./types";

const mockConfig = {
...CONFIG.gitHub,
token: "test-token",
};
// Mock data
const mockGitHubTeams = mock<GitHubTeam[]>();
const mockGitHubMembers = mock<GitHubMember[]>();
const mockGitHubTrees = mock<GitHubTree[]>();

const mockFetch = vi.fn();
global.fetch = mockFetch;
Expand All @@ -21,11 +21,11 @@ test("getTeamNames should fetch and return team names", async () => {
ok: true,
json: () => Promise.resolve(mockGitHubTeams),
});
const client = createGitHubClient(mockConfig);
const expectedUrl = `${mockConfig.baseUrl}/orgs/test-org/teams`;
const client = createGitHubClient("test-token");
const expectedUrl = `https://api.github.com/orgs/test-org/teams`;
const expectedHeaders = {
Accept: mockConfig.mediaType,
Authorization: `token ${mockConfig.token}`,
Accept: "application/vnd.github+json",
Authorization: "token test-token",
};

// Act
Expand All @@ -35,7 +35,7 @@ test("getTeamNames should fetch and return team names", async () => {
expect(mockFetch).toHaveBeenCalledWith(expectedUrl, {
headers: expectedHeaders,
});
expect(result).toEqual(["leetcode01", "leetcode02"]);
expect(result).toEqual(mockGitHubTeams.map((team) => team.name));
});

test("getTeamNames should throw error when fetch fails", async () => {
Expand All @@ -45,7 +45,7 @@ test("getTeamNames should throw error when fetch fails", async () => {
status: 404,
statusText: "Not Found",
});
const client = createGitHubClient(mockConfig);
const client = createGitHubClient("test-token");

// Act & Assert
await expect(client.getTeamNames("test-org")).rejects.toThrow(
Expand All @@ -59,11 +59,11 @@ test("getTeamMembers should fetch and return team members", async () => {
ok: true,
json: () => Promise.resolve(mockGitHubMembers),
});
const client = createGitHubClient(mockConfig);
const expectedUrl = `${mockConfig.baseUrl}/orgs/test-org/teams/test-team/members`;
const client = createGitHubClient("test-token");
const expectedUrl = `https://api.github.com/orgs/test-org/teams/test-team/members`;
const expectedHeaders = {
Accept: mockConfig.mediaType,
Authorization: `token ${mockConfig.token}`,
Accept: "application/vnd.github+json",
Authorization: "token test-token",
};

// Act
Expand All @@ -80,12 +80,13 @@ test("getDirectoryTree should fetch and return directory tree", async () => {
// Arrange
mockFetch.mockResolvedValue({
ok: true,
json: () => Promise.resolve({ tree: mockGitHubTree }),
json: () => Promise.resolve({ tree: mockGitHubTrees }),
});
const client = createGitHubClient(mockConfig);
const expectedUrl = `${mockConfig.baseUrl}/repos/test-owner/test-repo/git/trees/main?recursive=1`;
const client = createGitHubClient("test-token");
const expectedUrl = `https://api.github.com/repos/test-owner/test-repo/git/trees/main?recursive=1`;
const expectedHeaders = {
Accept: mockConfig.mediaType,
Accept: "application/vnd.github+json",
Authorization: "token test-token",
};

// Act
Expand All @@ -99,7 +100,7 @@ test("getDirectoryTree should fetch and return directory tree", async () => {
expect(mockFetch).toHaveBeenCalledWith(expectedUrl, {
headers: expectedHeaders,
});
expect(result).toEqual(mockGitHubTree);
expect(result).toEqual(mockGitHubTrees);
});

test("error should include detailed information", async () => {
Expand All @@ -111,8 +112,8 @@ test("error should include detailed information", async () => {
status,
statusText,
});
const client = createGitHubClient(mockConfig);
const expectedErrorMessage = `Failed to fetch url: ${mockConfig.baseUrl}/orgs/test-org/teams, status: ${status}, statusText: ${statusText}`;
const client = createGitHubClient("test-token");
const expectedErrorMessage = `Failed to fetch url: https://api.github.com/orgs/test-org/teams, status: ${status}, statusText: ${statusText}`;

// Act & Assert
await expect(client.getTeamNames("test-org")).rejects.toThrow(
Expand Down
19 changes: 8 additions & 11 deletions src/api/infra/gitHub/gitHubClient.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import type { GitHubConfig } from "../../config/types";
import type {
GitHubMember,
GitHubTeam,
GitHubTree,
GitHubTreeResponse,
} from "./types";

export function createGitHubClient(config: GitHubConfig) {
const request = async (url: string, token?: string): Promise<unknown> => {
export function createGitHubClient(token: string) {
const request = async (url: string): Promise<unknown> => {
const headers: Record<string, string> = {
Accept: config.mediaType,
Accept: "application/vnd.github+json",
};

if (token) {
Expand All @@ -29,18 +28,16 @@ export function createGitHubClient(config: GitHubConfig) {

return {
getTeamNames: async (organization: string): Promise<string[]> =>
request(
`${config.baseUrl}/orgs/${organization}/teams`,
config.token,
).then((response) => (response as GitHubTeam[]).map((team) => team.name)),
request(`https://api.github.com/orgs/${organization}/teams`).then(
(response) => (response as GitHubTeam[]).map((team) => team.name),
),

getTeamMembers: async (
organization: string,
teamName: string,
): Promise<GitHubMember[]> =>
request(
`${config.baseUrl}/orgs/${organization}/teams/${teamName}/members`,
config.token,
`https://api.github.com/orgs/${organization}/teams/${teamName}/members`,
).then((response) => response as GitHubMember[]),

getDirectoryTree: async (
Expand All @@ -50,7 +47,7 @@ export function createGitHubClient(config: GitHubConfig) {
recursive = 1,
): Promise<GitHubTree[]> =>
request(
`${config.baseUrl}/repos/${owner}/${repo}/git/trees/${treeSha}?recursive=${recursive}`,
`https://api.github.com/repos/${owner}/${repo}/git/trees/${treeSha}?recursive=${recursive}`,
).then((response) => (response as GitHubTreeResponse).tree),
};
}
Loading