Skip to content

Commit

Permalink
getMembers API 리팩토링 (#98)
Browse files Browse the repository at this point in the history
* rename: storeService -> getMembers
* simplify: github configs
* Simplify: API Config 단순화
* Feat: add additional grade level for the submission
* Revert: faker username to userName
* feat: add filters for 0 summit members
* test: removing fixtures from tests
* test: removing fixtures from tests
* refactor: api.service 디렉토리 수정
* refactor: getMembers 디렉토리 위치 변경
* temp: Card 컴포넌트 내 Grade 타입 의존성 임시 작업
* remove: config의 branch, teamPrefix 삭제
  • Loading branch information
HC-kang authored Dec 2, 2024
1 parent c4d2efb commit d67caf2
Show file tree
Hide file tree
Showing 24 changed files with 256 additions and 491 deletions.
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
10 changes: 5 additions & 5 deletions src/api/services/store/storeService.ts → src/api/getMembers.ts
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

0 comments on commit d67caf2

Please sign in to comment.