Skip to content

refactor: restructure clients layer with directory organization#3524

Merged
KATO-Hiro merged 7 commits into
stagingfrom
#3523
May 13, 2026
Merged

refactor: restructure clients layer with directory organization#3524
KATO-Hiro merged 7 commits into
stagingfrom
#3523

Conversation

@KATO-Hiro

@KATO-Hiro KATO-Hiro commented May 13, 2026

Copy link
Copy Markdown
Collaborator

close #3523

Summary by CodeRabbit

リリースノート

  • 新機能

    • AIZU Online Judge 用の新しいクライアントと関連ユーティリティ・型定義を追加しました。
  • リファクタリング

    • HTTP クライアント周りと既存クライアントの構成を整理し、API呼び出しの統一と簡素化を行いました。
  • テスト

    • テスト用モック管理を整理・移動し、複数のクライアントテストを実際のHTTPモックで検証するよう改善しました。

KATO-Hiro and others added 4 commits May 13, 2026 09:35
- Reorganize aizu_online_judge and atcoder_problems into subdirectories
- Split AOJ client into clients.ts (API), types.ts (types), utils.ts (helpers)
- Move test data to unified fixtures/ directory
- Extract HTTP client helpers to fixtures/helpers.ts
- Update imports across codebase to reflect new structure
- Add comprehensive plan for clients restructuring (Phase 1 complete)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Remove unused imports from test_helpers files
- Update AOJ utils test cleanup
- Update plan with progress notes

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Update assertion patterns in AOJ clients and utils tests
- Improve fixtures helpers test clarity
- Align test style with project conventions

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@KATO-Hiro has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 36 minutes and 42 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b84777ee-4897-4e5c-ab4c-1e36d5b58c53

📥 Commits

Reviewing files that changed from the base of the PR and between 13ba944 and f26c740.

📒 Files selected for processing (1)
  • src/lib/clients/atcoder/atcoder_problems.test.ts
📝 Walkthrough

Walkthrough

AOJ(Aizu Online Judge)クライアントを型定義、ユーティリティ、実装に分割してディレクトリ再編。古い ContestSiteApiClient を廃止し各クライアントは HttpRequestClient を直接保有へ変更。テストフィクスチャとヘルパーの移動・更新を実施。

Changes

API クライアント再編と AOJ 実装追加

Layer / File(s) Summary
HttpRequestClient / fetch config
src/lib/clients/http_client.ts
ContestSiteApiClient を削除。FetchAPIConfig<T> から baseApiUrl を除き、各クライアントがコンストラクタで HttpRequestClient を保持して fetchApiWithConfig を呼ぶ形へ。
AOJ 型定義
src/lib/clients/aizu_online_judge/types.ts
AOJ の API レスポンス型を追加(コース/チャレンジ/タスク周りの型と PENDING 定数)。
AOJ ユーティリティ関数
src/lib/clients/aizu_online_judge/utils.ts, src/lib/clients/aizu_online_judge/utils.test.ts
buildEndpoint, mapToContest, mapToTask, getCourseName を実装。入力検証・マッピング挙動をユニットテストで検証。
AOJ クライアント実装(集約クライアント含む)
src/lib/clients/aizu_online_judge/clients.ts, src/lib/clients/aizu_online_judge/clients.test.ts
AojApiClient を追加(複数サブクライアントを集約、Promise.allSettled で並列取得)。共通の抽象 AojTasksApiClientBase と具体 AojCoursesApiClient / AojChallengesApiClient を導入。テストはフィクスチャ新パスを使用。
AtCoder クライアントの composition 化
src/lib/clients/atcoder/atcoder_problems.ts, src/lib/clients/atcoder/atcoder_problems.test.ts
AtCoderProblemsApiClientContestSiteApiClient 継承から独立させ、HttpRequestClient をコンポジションで利用するように変更。テストを nock ベースに更新。
フィクスチャ読み込みユーティリティ移動
src/lib/clients/fixtures/helpers.ts, src/lib/clients/fixtures/helpers.test.ts
loadMockDatasrc/lib/clients/fixtures に移動/実装。存在しないファイル・不正JSONのエラーを明示する挙動を追加。
フィクスチャパスと記録スクリプト更新
src/lib/clients/fixtures/record_requests.ts
テストデータ保存先を src/lib/clients/fixtures に変更、クライアント型注釈を TasksApiClient<void> へ更新。
共通テストヘルパー簡素化
src/test/lib/common/test_helpers.ts, src/test/lib/common/test_helpers.test.ts
Node ファイル読み込みヘルパー loadMockData を削除し、ファイル操作関連の import を除去(createTestCase 等は残存)。
エクスポート更新
src/lib/clients/index.ts
AtCoderProblemsApiClient / AojApiClient の新しいパスへ import を更新。

Sequence Diagram(s)

sequenceDiagram
    participant Client as Consumer
    participant AojApi as AojApiClient
    participant Sub as SubClients (Courses/Challenges)
    participant Http as HttpRequestClient
    participant Cache as ContestTaskCache
    participant API as AOJ API

    Client->>AojApi: getContests() / getTasks()
    AojApi->>Sub: call getContests/getTasks (parallel)
    Sub->>Cache: try read cached data
    alt cache miss
        Sub->>Http: fetchApiWithConfig(endpoint)
        Http->>API: HTTP request
        API-->>Http: JSON response
        Http-->>Sub: parsed response
        Sub->>Cache: store normalized results
    end
    Sub-->>AojApi: results (some may be rejected)
    AojApi->>AojApi: log per-source failures
    AojApi-->>Client: merged ContestsForImport / TasksForImport
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🎒 ディレクトリを整え、型を並べ、
🔧 クライアントは合成で進む道、
📦 フィクスチャは新居へ移り、
🧭 テストは戻るべき挙動を示す、
✨ 小さな一歩、コードの整頓。

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed タイトルはクライアント層の再構成とディレクトリ組織化という PR の主要な変更を適切に説明しており、実施された変更内容と合致している。
Linked Issues check ✅ Passed PR はリンク issue #3523 の目標「ディレクトリ規約に従った API クライアントファイルの再編」を達成し、AOJ 対応の拡張性向上に貢献している。
Out of Scope Changes check ✅ Passed すべての変更がディレクトリ組織化とクライアント層の再構成に関連しており、スコープ外の変更は認められない。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch #3523

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/lib/clients/fixtures/helpers.ts (1)

38-49: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

try/finally 内の重複クリーンアップ

Lines 42-48 の try/finally ブロック内で invalidJson.json を削除していますが、Line 16-26 の afterAll でも同じファイルをクリーンアップしています。afterAll が実行されるため、この finally ブロックは冗長です。

♻️ 簡略化案
  test('expects to throw an error if the file content is not valid JSON', () => {
    const invalidJsonFilePath = path.resolve(__dirname, 'invalidJson.json');
    fs.writeFileSync(invalidJsonFilePath, 'invalid json');
-
-   try {
-     expect(() => loadMockData<typeof mockData>(invalidJsonFilePath)).toThrow();
-   } finally {
-     if (fs.existsSync(invalidJsonFilePath)) {
-       fs.unlinkSync(invalidJsonFilePath);
-     }
-   }
+   expect(() => loadMockData<typeof mockData>(invalidJsonFilePath)).toThrow();
  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/clients/fixtures/helpers.ts` around lines 38 - 49, try/finally ブロック内で
"invalidJson.json" を削除している処理は、既に同ファイルで定義されている afterAll
のクリーンアップと重複しているため冗長です;該当する try/finally の finally セクションから "invalidJson.json"
を削除するコードを取り除き、afterAll による一元的なクリーンアップに任せてください(対象を見つけるために "invalidJson.json" と該当の
try/finally ブロック、そして既存の afterAll を参照してください)。
src/lib/clients/atcoder/atcoder_problems.test.ts (1)

36-43: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

テストで Nock を使用して HTTP モッキングを行うべき

コーディングガイドラインでは「Use Nock for HTTP mocking in unit tests」と明記されています。現在のテストはクライアントメソッドを直接モック(例: client.getContests = async () => contestsMock)していますが、Nock を使用して HTTP リクエストをインターセプトする方式に変更してください。

これにより、実際の HTTP クライアント層の動作を含めたより堅牢なテストが可能になり、プロジェクト全体のテスト標準との一貫性が保たれます。

As per coding guidelines: "Use Nock for HTTP mocking in unit tests."

Also applies to: 54-58, 70-77, 88-92

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/clients/atcoder/atcoder_problems.test.ts` around lines 36 - 43, The
tests currently overwrite client.getContests (and similar client methods)
directly; update these unit tests to use Nock to intercept the HTTP requests
made by the AtCoder problems client instead: remove assignments like
client.getContests = async () => contestsMock and instead create a Nock scope
that matches the external API base URL and path used inside getContests, reply
with contestsMock JSON, then call client.getContests() and assert the response;
apply the same replacement pattern for the other tests that mock client methods
(the tests referencing client.getContests, client.getProblems, etc.) so the HTTP
layer is exercised via Nock rather than method stubbing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/clients/aizu_online_judge/clients.ts`:
- Line 249: The hardcoded magic number assigned to the variable size (10 ** 4)
lacks context; replace it with a named constant (e.g., AOJ_MAX_FETCH or
DEFAULT_FETCH_SIZE) declared near the top of the module or where constants live,
add a comment explaining the rationale (AOJ API max page size or estimated total
problems) and make it configurable (env/parameter) if the limit may change;
update references to use the constant instead of size so future maintainers
understand and can adjust the value safely.

In `@src/lib/clients/aizu_online_judge/utils.ts`:
- Around line 61-69: mapToTask currently assigns the same value to problem_index
and task_id which can cause ambiguity downstream; change mapToTask so
problem_index stays as problem.id but task_id is generated to be unique (e.g.,
combine contestId and problem.id like `${contestId}:${problem.id}`) and add a
brief inline comment mentioning this is a synthetic unique id because AOJ lacks
a separate task identifier; reference AOJTaskAPI, mapToTask, problem_index and
task_id when making the change.
- Around line 18-24: The regex in validateSegment rejects single-character
segments; update the pattern used in validateSegment to allow a single leading
letter by making the final `[a-zA-Z0-9]` optional (or otherwise changing the
grouping so the whole expression matches 1–MAX length) while preserving the
max-length constraint and the existing rules for internal characters and no
consecutive dots; then add/adjust tests in utils.test.ts to include
single-character cases (e.g., 'a', 'v1') to verify the fix.

In `@src/lib/clients/fixtures/helpers.test.ts`:
- Around line 8-26: Tests write and remove fixed filenames under __dirname which
causes flakiness when run in parallel; change the setup/teardown to create a
unique temp directory using fs.mkdtempSync(path.join(os.tmpdir(), '...'))
(replace testDir = __dirname with a per-run tempDir), build mockFilePath and
other file paths (invalidJson.json) inside that tempDir, write files in
beforeAll using mockFilePath, and in afterAll remove the files and rmdir the
temp directory; update references to testDir/mockFilePath in the file (including
the other block that creates invalidJson.json) so each test run uses its own
isolated directory.

In `@src/lib/clients/fixtures/helpers.ts`:
- Line 12: The code uses path.resolve(filePath) to form testDataPath which
resolves relative paths against process.cwd(), causing environment-dependent
behavior; update the helper to resolve relative paths robustly by treating
filePath as module-relative when needed—either require callers to pass an
absolute path (use path.join(__dirname, ...) in clients.test.ts) or change the
helper to resolve via the current module location (use import.meta.url) before
calling path.resolve; locate the testDataPath creation in helpers.ts and adjust
resolution logic or update clients.test.ts to pass an absolute path accordingly.

---

Outside diff comments:
In `@src/lib/clients/atcoder/atcoder_problems.test.ts`:
- Around line 36-43: The tests currently overwrite client.getContests (and
similar client methods) directly; update these unit tests to use Nock to
intercept the HTTP requests made by the AtCoder problems client instead: remove
assignments like client.getContests = async () => contestsMock and instead
create a Nock scope that matches the external API base URL and path used inside
getContests, reply with contestsMock JSON, then call client.getContests() and
assert the response; apply the same replacement pattern for the other tests that
mock client methods (the tests referencing client.getContests,
client.getProblems, etc.) so the HTTP layer is exercised via Nock rather than
method stubbing.

In `@src/lib/clients/fixtures/helpers.ts`:
- Around line 38-49: try/finally ブロック内で "invalidJson.json"
を削除している処理は、既に同ファイルで定義されている afterAll のクリーンアップと重複しているため冗長です;該当する try/finally の
finally セクションから "invalidJson.json" を削除するコードを取り除き、afterAll
による一元的なクリーンアップに任せてください(対象を見つけるために "invalidJson.json" と該当の try/finally
ブロック、そして既存の afterAll を参照してください)。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2056ae85-a1a4-4d8b-b069-ef42c17b55ca

📥 Commits

Reviewing files that changed from the base of the PR and between e618ee9 and 194c0e8.

📒 Files selected for processing (20)
  • src/lib/clients/aizu_online_judge.ts
  • src/lib/clients/aizu_online_judge/clients.test.ts
  • src/lib/clients/aizu_online_judge/clients.ts
  • src/lib/clients/aizu_online_judge/types.ts
  • src/lib/clients/aizu_online_judge/utils.test.ts
  • src/lib/clients/aizu_online_judge/utils.ts
  • src/lib/clients/atcoder/atcoder_problems.test.ts
  • src/lib/clients/atcoder/atcoder_problems.ts
  • src/lib/clients/cache.test.ts
  • src/lib/clients/fixtures/aizu_online_judge/contests.json
  • src/lib/clients/fixtures/aizu_online_judge/tasks.json
  • src/lib/clients/fixtures/atcoder_problems/contests.json
  • src/lib/clients/fixtures/atcoder_problems/tasks.json
  • src/lib/clients/fixtures/helpers.test.ts
  • src/lib/clients/fixtures/helpers.ts
  • src/lib/clients/fixtures/record_requests.ts
  • src/lib/clients/http_client.ts
  • src/lib/clients/index.ts
  • src/test/lib/common/test_helpers.test.ts
  • src/test/lib/common/test_helpers.ts
💤 Files with no reviewable changes (4)
  • src/test/lib/common/test_helpers.test.ts
  • src/lib/clients/http_client.ts
  • src/test/lib/common/test_helpers.ts
  • src/lib/clients/aizu_online_judge.ts

Comment thread src/lib/clients/aizu_online_judge/clients.ts
Comment thread src/lib/clients/aizu_online_judge/utils.ts
Comment thread src/lib/clients/aizu_online_judge/utils.ts
Comment thread src/lib/clients/fixtures/helpers.test.ts
Comment thread src/lib/clients/fixtures/helpers.ts
KATO-Hiro and others added 2 commits May 13, 2026 12:26
…nally

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lient tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/lib/clients/atcoder/atcoder_problems.test.ts (2)

61-66: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

クライアントを呼び出さずモックデータを直接検証しています。

これらのテストは async でテスト名も API レスポンスの検証を示唆していますが、実際には client.getContests() を呼ばずに contestsMock を直接イテレートしています。クライアントのパース・変換ロジックをテストしておらず、誤った安心感を与えます。

各テストで nock モックをセットアップし、client.getContests() を呼び出して戻り値を検証してください。

🔧 修正例(line 61-66)
 test('each contest expects to have id and title', async () => {
+  nock(API_BASE).get(`${API_PATH}contests.json`).reply(200, contestsMock);
+  const contests = await client.getContests();
-  contestsMock.forEach((contest) => {
+  contests.forEach((contest) => {
     expect(contest.id).toBeDefined();
     expect(contest.title).toBeDefined();
   });
 });

同様の修正を line 74-81 にも適用してください。

Also applies to: 74-81

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/clients/atcoder/atcoder_problems.test.ts` around lines 61 - 66, Tests
currently iterate contestsMock directly instead of exercising the parsing logic
in AtCoderProblemsClient; update the tests to set up the existing nock HTTP
mock, call client.getContests(), await its result, and assert on the returned
contests (e.g., each item has id and title) rather than iterating contestsMock.
Replace the direct contestsMock checks in the tests that reference contestsMock
with calls to client.getContests(), ensure the nock fixture used in other tests
is applied, and apply the same change for the second similar test that currently
mirrors lines 74-81 so the client parsing/transform logic is actually validated.

92-99: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

クライアントを呼び出さずモックデータを直接検証しています。

上記のコンテスト関連テストと同じ問題です。client.getTasks() を呼び出して戻り値を検証すべきです。

🔧 修正例(line 92-99)
 test('each task expects to have id, contest_id, problem_index and title', async () => {
+  nock(API_BASE).get(`${API_PATH}problems.json`).reply(200, tasksMock);
+  const tasks = await client.getTasks();
-  tasksMock.forEach((task) => {
+  tasks.forEach((task) => {
     expect(task.id).toBeDefined();
     expect(task.contest_id).toBeDefined();
     expect(task.problem_index).toBeDefined();
     expect(task.title).toBeDefined();
   });
 });

同様の修正を line 107-116 にも適用してください。

Also applies to: 107-116

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/clients/atcoder/atcoder_problems.test.ts` around lines 92 - 99, The
tests are directly iterating tasksMock instead of exercising the client; change
the assertion to call client.getTasks() and validate the returned array items
rather than tasksMock (replace uses of tasksMock in the test named "each task
expects to have id, contest_id, problem_index and title" with the result of
await client.getTasks()). Ensure you use await client.getTasks() and assert each
returned task has id, contest_id, problem_index and title; apply the identical
change to the other later test that currently iterates tasksMock (the similar
block around the other tasks validation).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/lib/clients/atcoder/atcoder_problems.test.ts`:
- Around line 61-66: Tests currently iterate contestsMock directly instead of
exercising the parsing logic in AtCoderProblemsClient; update the tests to set
up the existing nock HTTP mock, call client.getContests(), await its result, and
assert on the returned contests (e.g., each item has id and title) rather than
iterating contestsMock. Replace the direct contestsMock checks in the tests that
reference contestsMock with calls to client.getContests(), ensure the nock
fixture used in other tests is applied, and apply the same change for the second
similar test that currently mirrors lines 74-81 so the client parsing/transform
logic is actually validated.
- Around line 92-99: The tests are directly iterating tasksMock instead of
exercising the client; change the assertion to call client.getTasks() and
validate the returned array items rather than tasksMock (replace uses of
tasksMock in the test named "each task expects to have id, contest_id,
problem_index and title" with the result of await client.getTasks()). Ensure you
use await client.getTasks() and assert each returned task has id, contest_id,
problem_index and title; apply the identical change to the other later test that
currently iterates tasksMock (the similar block around the other tasks
validation).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 874f3531-aa59-417f-810b-455b57a72dc6

📥 Commits

Reviewing files that changed from the base of the PR and between 194c0e8 and 13ba944.

📒 Files selected for processing (2)
  • src/lib/clients/atcoder/atcoder_problems.test.ts
  • src/lib/clients/fixtures/helpers.test.ts

…oblems client

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@KATO-Hiro KATO-Hiro left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@KATO-Hiro KATO-Hiro merged commit eca53b0 into staging May 13, 2026
3 checks passed
@KATO-Hiro KATO-Hiro deleted the #3523 branch May 13, 2026 12:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[refactor] API client の ファイルをディレクトリ規約に従って再編しましょう

1 participant