From 54ce6da71c36045bb6ab04c6c98b9b8dd84b4016 Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Wed, 10 Jun 2026 06:35:44 +0000 Subject: [PATCH 1/2] test(contest): add AOJ_UNIVERSITY test cases for new contest_id patterns Add test cases covering 4 previously untested AOJ_UNIVERSITY patterns: OUPC (new university abbreviation), RUPC-in-RUPC (same university as parent), HUPC-in-RUPC (new university pair), and UAPC-in-ACPC (edge case where both sides map to ACPC). Also adds tasks.ts entries for the representative contests. Co-Authored-By: Claude Sonnet 4.6 --- .../add-aoj-university-tests/plan.md | 95 +++++++++++++++++++ prisma/tasks.ts | 84 ++++++++++++++++ .../test_cases/contest_name_and_task_index.ts | 20 ++++ .../utils/test_cases/contest_name_labels.ts | 16 ++++ src/test/lib/utils/test_cases/contest_type.ts | 1 + src/test/lib/utils/test_cases/task_url.ts | 1 + 6 files changed, 217 insertions(+) create mode 100644 docs/dev-notes/2026-06-10/add-aoj-university-tests/plan.md diff --git a/docs/dev-notes/2026-06-10/add-aoj-university-tests/plan.md b/docs/dev-notes/2026-06-10/add-aoj-university-tests/plan.md new file mode 100644 index 000000000..38aed99c7 --- /dev/null +++ b/docs/dev-notes/2026-06-10/add-aoj-university-tests/plan.md @@ -0,0 +1,95 @@ +# AOJ_UNIVERSITY 新規タスクデータに基づく単体テスト追加 + +## 概要 + +PR #3603 で追加された `AOJ_UNIVERSITY` タスク(`prisma/tasks.ts` L11976〜)のうち、 +既存テストでカバーされていない contest_id パターン 4 種に絞ってテストケースを追加する。 +実装コードは変更しない。test_cases ファイルへのケース追加のみ。 + +## 設計方針 + +- テストフレームワーク: 既存の `contest.test.ts` / `task.test.ts` が `.forEach` でテストケースを回す構造。 + test_cases ファイルにケースを足すだけで自動的にカバー対象が増える。 +- 追加対象: 既存テストとの差分が生じる「パターンとして新しい」ケースのみ。 + 年の違いだけのバリエーションは追加しない。 + +## カバレッジ分析 + +### 既にカバー済み(追加不要) + +| contest_id | 根拠 | +| ------------------------------- | ------------------------------------------------------- | +| `AOJ-RUPC2018-in-ACPC2018-day1` | contest_type / labels / task_index / url 全てテスト済み | +| `AOJ-HUPC2020-in-HUPC2020-day1` | 同上 | +| `AOJ-UAPC2019-in-RUPC2019-day2` | 同上 | + +### 未カバーの新パターン(4 種) + +| # | パターン | 代表 contest_id | 追加が必要な関数 | +| --- | ------------------------------------------------------------------ | ------------------------------- | ------------------------------------------------------------------------------ | +| 1 | **OUPC**(新大学略称) | `AOJ-OUPC2012-in-RUPC2012-day2` | classifyContest / getContestNameLabel / addContestNameToTaskIndex / getTaskUrl | +| 2 | **RUPC-in-RUPC**(同大学が親イベント) | `AOJ-RUPC2018-in-RUPC2018-day1` | getContestNameLabel / addContestNameToTaskIndex | +| 3 | **HUPC-in-RUPC**(異大学ペアの新組合せ) | `AOJ-HUPC2014-in-RUPC2014-day3` | getContestNameLabel / addContestNameToTaskIndex | +| 4 | **UAPC-in-ACPC**(UAPC→ACPC 置換で両側が ACPC になるエッジケース) | `AOJ-UAPC2015-in-ACPC2015-day2` | getContestNameLabel / addContestNameToTaskIndex | + +> classifyContest / getTaskUrl は正規表現 `/^AOJ-[A-Z]+PC\d{4}/` で +> RUPC・HUPC・UAPC を既にカバー。OUPC のみ新略称なのでこの 2 関数だけ追加。 + +## 期待値 + +### classifyContest() + +| contest_id | expected | +| ------------------------------- | ---------------------------- | +| `AOJ-OUPC2012-in-RUPC2012-day2` | `ContestType.AOJ_UNIVERSITY` | + +### getContestNameLabel()(= getAojUniversityContestLabel の変換結果) + +| contest_id | expected | 備考 | +| ------------------------------- | --------------------------------- | ----------------------- | +| `AOJ-OUPC2012-in-RUPC2012-day2` | `(OUPC 2012 in RUPC 2012 Day2)` | 新略称 | +| `AOJ-RUPC2018-in-RUPC2018-day1` | `(RUPC 2018 in RUPC 2018 Day1)` | 同大学が親 | +| `AOJ-HUPC2014-in-RUPC2014-day3` | `(HUPC 2014 in RUPC 2014 Day3)` | 異大学ペア、day3 | +| `AOJ-UAPC2015-in-ACPC2015-day2` | `(ACPC 2015 in ACPC 2015 Day2)` | UAPC→ACPC で両側が ACPC | + +### addContestNameToTaskIndex() + +task_table_index は tasks.ts の実際の problem_index を使用。 + +| contest_id | taskTableIndex | expected | +| ------------------------------- | -------------- | ----------------------------------------- | +| `AOJ-OUPC2012-in-RUPC2012-day2` | `2352` | `AOJ 2352(OUPC 2012 in RUPC 2012 Day2)` | +| `AOJ-RUPC2018-in-RUPC2018-day1` | `2880` | `AOJ 2880(RUPC 2018 in RUPC 2018 Day1)` | +| `AOJ-HUPC2014-in-RUPC2014-day3` | `2581` | `AOJ 2581(HUPC 2014 in RUPC 2014 Day3)` | +| `AOJ-UAPC2015-in-ACPC2015-day2` | `1566` | `AOJ 1566(ACPC 2015 in ACPC 2015 Day2)` | + +### getTaskUrl() + +| contest_id | taskId | expected | +| ------------------------------- | ------ | ----------------------- | +| `AOJ-OUPC2012-in-RUPC2012-day2` | `2352` | `${AOJ_TASKS_URL}/2352` | + +## 変更ファイル + +| ファイル | 追加先 | 追加件数 | +| -------------------------------------------------------------- | -------------------------- | ------------ | +| `src/test/lib/utils/test_cases/contest_type.ts` | `aojUniversityContestData` | 1 件(OUPC) | +| `src/test/lib/utils/test_cases/contest_name_labels.ts` | `aojUniversity` 配列 | 4 件 | +| `src/test/lib/utils/test_cases/contest_name_and_task_index.ts` | `AOJ_UNIVERSITY_TEST_DATA` | 4 件 | +| `src/test/lib/utils/test_cases/task_url.ts` | `aojUniversityContests` | 1 件(OUPC) | + +`contest.test.ts` / `task.test.ts` は変更不要(forEach で自動ピックアップ)。 + +## 却下した代替案 + +- 年違いのバリエーション(RUPC2012 vs RUPC2013 など): 同一パターンのため追加しない(YAGNI) +- `compareByContestIdAndTaskId` への AOJ_UNIVERSITY ケース追加: + 優先度 25 は既存 AOJ 系テストで間接的に確認済みのため対象外 + +## 検証 + +```bash +pnpm test:unit +``` + +`contest.test.ts` と `task.test.ts` の aojUniversity ブロックが全パス。 diff --git a/prisma/tasks.ts b/prisma/tasks.ts index b728a0d75..31117ba0b 100755 --- a/prisma/tasks.ts +++ b/prisma/tasks.ts @@ -11973,6 +11973,27 @@ export const tasks = [ name: 'World Trip', title: '2349. World Trip', }, + { + id: '1532', + contest_id: 'AOJ-UAPC2014-in-RUPC2014-day2', + problem_index: '1532', + name: 'Yu-kun Likes Building Block', + title: '1532. Yu-kun Likes Building Block', + }, + { + id: '1566', + contest_id: 'AOJ-UAPC2015-in-ACPC2015-day2', + problem_index: '1566', + name: 'Movie', + title: '1566. Movie', + }, + { + id: '3047', + contest_id: 'AOJ-UAPC2018-in-ACPC2018-day2', + problem_index: '3047', + name: 'Shiritori', + title: '3047. Shiritori', + }, { id: '3058', contest_id: 'AOJ-UAPC2019-in-RUPC2019-day2', @@ -11980,6 +12001,27 @@ export const tasks = [ name: 'Ghost', title: '3058. Ghost', }, + { + id: '2423', + contest_id: 'AOJ-RUPC2012-in-ACPC2012-day1', + problem_index: '2423', + name: 'Code Art Online', + title: '2423. Code Art Online', + }, + { + id: '2520', + contest_id: 'AOJ-RUPC2013-in-ACPC2013-day1', + problem_index: '2520', + name: 'Bicycle', + title: '2520. Bicycle', + }, + { + id: '2880', + contest_id: 'AOJ-RUPC2018-in-RUPC2018-day1', + problem_index: '2880', + name: 'Elevator', + title: '2880. Elevator', + }, { id: '2903', contest_id: 'AOJ-RUPC2018-in-ACPC2018-day1', @@ -11987,6 +12029,34 @@ export const tasks = [ name: 'Board', title: '2903. Board', }, + { + id: '2943', + contest_id: 'AOJ-RUPC2019-in-RUPC2019-day1', + problem_index: '2943', + name: 'Illumination', + title: '2943. Illumination', + }, + { + id: '2581', + contest_id: 'AOJ-HUPC2014-in-RUPC2014-day3', + problem_index: '2581', + name: 'Derangement', + title: '2581. Derangement', + }, + { + id: '2872', + contest_id: 'AOJ-HUPC2018-in-RUPC2018-day3', + problem_index: '2872', + name: 'Ebi-chan Lengthens Shortest Paths', + title: '2872. Ebi-chan Lengthens Shortest Paths', + }, + { + id: '3168', + contest_id: 'AOJ-HUPC2020-in-HUPC2020-day1', + problem_index: '3168', + name: 'Capture Ebichan', + title: '3168. Capture Ebichan', + }, { id: '3171', contest_id: 'AOJ-HUPC2020-in-HUPC2020-day1', @@ -11994,4 +12064,18 @@ export const tasks = [ name: 'Traditional Company', title: '3171. Traditional Company', }, + { + id: '2352', + contest_id: 'AOJ-OUPC2012-in-RUPC2012-day2', + problem_index: '2352', + name: 'Divisor', + title: '2352. Divisor', + }, + { + id: '2496', + contest_id: 'AOJ-OUPC2013-in-RUPC2013-day2', + problem_index: '2496', + name: '1', + title: '2496. 1', + }, ]; diff --git a/src/test/lib/utils/test_cases/contest_name_and_task_index.ts b/src/test/lib/utils/test_cases/contest_name_and_task_index.ts index dd7366111..fbff54268 100644 --- a/src/test/lib/utils/test_cases/contest_name_and_task_index.ts +++ b/src/test/lib/utils/test_cases/contest_name_and_task_index.ts @@ -916,6 +916,26 @@ const AOJ_UNIVERSITY_TEST_DATA = [ taskTableIndex: '1002', expected: 'AOJ 1002(ACPC 2012 Day1)', }, + { + contestId: 'AOJ-OUPC2012-in-RUPC2012-day2', + taskTableIndex: '2352', + expected: 'AOJ 2352(OUPC 2012 in RUPC 2012 Day2)', + }, + { + contestId: 'AOJ-RUPC2018-in-RUPC2018-day1', + taskTableIndex: '2880', + expected: 'AOJ 2880(RUPC 2018 in RUPC 2018 Day1)', + }, + { + contestId: 'AOJ-HUPC2014-in-RUPC2014-day3', + taskTableIndex: '2581', + expected: 'AOJ 2581(HUPC 2014 in RUPC 2014 Day3)', + }, + { + contestId: 'AOJ-UAPC2015-in-ACPC2015-day2', + taskTableIndex: '1566', + expected: 'AOJ 1566(ACPC 2015 in ACPC 2015 Day2)', + }, ]; export const aojUniversity = AOJ_UNIVERSITY_TEST_DATA.map( diff --git a/src/test/lib/utils/test_cases/contest_name_labels.ts b/src/test/lib/utils/test_cases/contest_name_labels.ts index fb7bd7f58..aef8e1b72 100644 --- a/src/test/lib/utils/test_cases/contest_name_labels.ts +++ b/src/test/lib/utils/test_cases/contest_name_labels.ts @@ -150,4 +150,20 @@ export const aojUniversity = [ contestId: 'AOJ-UAPC2012-day1', expected: '(ACPC 2012 Day1)', }), + createTestCaseForContestNameLabel('AOJ, OUPC 2012 in RUPC 2012 Day2')({ + contestId: 'AOJ-OUPC2012-in-RUPC2012-day2', + expected: '(OUPC 2012 in RUPC 2012 Day2)', + }), + createTestCaseForContestNameLabel('AOJ, RUPC 2018 in RUPC 2018 Day1')({ + contestId: 'AOJ-RUPC2018-in-RUPC2018-day1', + expected: '(RUPC 2018 in RUPC 2018 Day1)', + }), + createTestCaseForContestNameLabel('AOJ, HUPC 2014 in RUPC 2014 Day3')({ + contestId: 'AOJ-HUPC2014-in-RUPC2014-day3', + expected: '(HUPC 2014 in RUPC 2014 Day3)', + }), + createTestCaseForContestNameLabel('AOJ, UAPC 2015 in ACPC 2015 Day2')({ + contestId: 'AOJ-UAPC2015-in-ACPC2015-day2', + expected: '(ACPC 2015 in ACPC 2015 Day2)', + }), ]; diff --git a/src/test/lib/utils/test_cases/contest_type.ts b/src/test/lib/utils/test_cases/contest_type.ts index 7463da6ff..602cb4928 100644 --- a/src/test/lib/utils/test_cases/contest_type.ts +++ b/src/test/lib/utils/test_cases/contest_type.ts @@ -673,6 +673,7 @@ const aojUniversityContestData = [ { name: 'AOJ, UAPC 2003', contestId: 'AOJ-UAPC2003' }, { name: 'AOJ, UAPC 2011 Summer', contestId: 'AOJ-UAPC2011-summer' }, { name: 'AOJ, UAPC 2012 Day1', contestId: 'AOJ-UAPC2012-day1' }, + { name: 'AOJ, OUPC 2012 in RUPC 2012 Day2', contestId: 'AOJ-OUPC2012-in-RUPC2012-day2' }, ]; export const aojUniversity = aojUniversityContestData.map(({ name, contestId }) => diff --git a/src/test/lib/utils/test_cases/task_url.ts b/src/test/lib/utils/test_cases/task_url.ts index abbbb8758..909609f5a 100644 --- a/src/test/lib/utils/test_cases/task_url.ts +++ b/src/test/lib/utils/test_cases/task_url.ts @@ -201,6 +201,7 @@ const aojUniversityContests = [ { contestId: 'AOJ-RUPC2018-in-ACPC2018-day1', tasks: ['2903', '2904'] }, { contestId: 'AOJ-UAPC2019-in-RUPC2019-day2', tasks: ['3058', '3059'] }, { contestId: 'AOJ-HUPC2020-in-HUPC2020-day1', tasks: ['3171', '3172'] }, + { contestId: 'AOJ-OUPC2012-in-RUPC2012-day2', tasks: ['2352'] }, ]; export const aojUniversity = aojUniversityContests.flatMap((contest) => From 9989380c1414a3e5a8d1cd4125212322f630ab46 Mon Sep 17 00:00:00 2001 From: "k.hiro1818" Date: Wed, 10 Jun 2026 06:36:14 +0000 Subject: [PATCH 2/2] docs: remove plan.md after AOJ_UNIVERSITY test cases implementation complete Co-Authored-By: Claude Sonnet 4.6 --- .../add-aoj-university-tests/plan.md | 95 ------------------- 1 file changed, 95 deletions(-) delete mode 100644 docs/dev-notes/2026-06-10/add-aoj-university-tests/plan.md diff --git a/docs/dev-notes/2026-06-10/add-aoj-university-tests/plan.md b/docs/dev-notes/2026-06-10/add-aoj-university-tests/plan.md deleted file mode 100644 index 38aed99c7..000000000 --- a/docs/dev-notes/2026-06-10/add-aoj-university-tests/plan.md +++ /dev/null @@ -1,95 +0,0 @@ -# AOJ_UNIVERSITY 新規タスクデータに基づく単体テスト追加 - -## 概要 - -PR #3603 で追加された `AOJ_UNIVERSITY` タスク(`prisma/tasks.ts` L11976〜)のうち、 -既存テストでカバーされていない contest_id パターン 4 種に絞ってテストケースを追加する。 -実装コードは変更しない。test_cases ファイルへのケース追加のみ。 - -## 設計方針 - -- テストフレームワーク: 既存の `contest.test.ts` / `task.test.ts` が `.forEach` でテストケースを回す構造。 - test_cases ファイルにケースを足すだけで自動的にカバー対象が増える。 -- 追加対象: 既存テストとの差分が生じる「パターンとして新しい」ケースのみ。 - 年の違いだけのバリエーションは追加しない。 - -## カバレッジ分析 - -### 既にカバー済み(追加不要) - -| contest_id | 根拠 | -| ------------------------------- | ------------------------------------------------------- | -| `AOJ-RUPC2018-in-ACPC2018-day1` | contest_type / labels / task_index / url 全てテスト済み | -| `AOJ-HUPC2020-in-HUPC2020-day1` | 同上 | -| `AOJ-UAPC2019-in-RUPC2019-day2` | 同上 | - -### 未カバーの新パターン(4 種) - -| # | パターン | 代表 contest_id | 追加が必要な関数 | -| --- | ------------------------------------------------------------------ | ------------------------------- | ------------------------------------------------------------------------------ | -| 1 | **OUPC**(新大学略称) | `AOJ-OUPC2012-in-RUPC2012-day2` | classifyContest / getContestNameLabel / addContestNameToTaskIndex / getTaskUrl | -| 2 | **RUPC-in-RUPC**(同大学が親イベント) | `AOJ-RUPC2018-in-RUPC2018-day1` | getContestNameLabel / addContestNameToTaskIndex | -| 3 | **HUPC-in-RUPC**(異大学ペアの新組合せ) | `AOJ-HUPC2014-in-RUPC2014-day3` | getContestNameLabel / addContestNameToTaskIndex | -| 4 | **UAPC-in-ACPC**(UAPC→ACPC 置換で両側が ACPC になるエッジケース) | `AOJ-UAPC2015-in-ACPC2015-day2` | getContestNameLabel / addContestNameToTaskIndex | - -> classifyContest / getTaskUrl は正規表現 `/^AOJ-[A-Z]+PC\d{4}/` で -> RUPC・HUPC・UAPC を既にカバー。OUPC のみ新略称なのでこの 2 関数だけ追加。 - -## 期待値 - -### classifyContest() - -| contest_id | expected | -| ------------------------------- | ---------------------------- | -| `AOJ-OUPC2012-in-RUPC2012-day2` | `ContestType.AOJ_UNIVERSITY` | - -### getContestNameLabel()(= getAojUniversityContestLabel の変換結果) - -| contest_id | expected | 備考 | -| ------------------------------- | --------------------------------- | ----------------------- | -| `AOJ-OUPC2012-in-RUPC2012-day2` | `(OUPC 2012 in RUPC 2012 Day2)` | 新略称 | -| `AOJ-RUPC2018-in-RUPC2018-day1` | `(RUPC 2018 in RUPC 2018 Day1)` | 同大学が親 | -| `AOJ-HUPC2014-in-RUPC2014-day3` | `(HUPC 2014 in RUPC 2014 Day3)` | 異大学ペア、day3 | -| `AOJ-UAPC2015-in-ACPC2015-day2` | `(ACPC 2015 in ACPC 2015 Day2)` | UAPC→ACPC で両側が ACPC | - -### addContestNameToTaskIndex() - -task_table_index は tasks.ts の実際の problem_index を使用。 - -| contest_id | taskTableIndex | expected | -| ------------------------------- | -------------- | ----------------------------------------- | -| `AOJ-OUPC2012-in-RUPC2012-day2` | `2352` | `AOJ 2352(OUPC 2012 in RUPC 2012 Day2)` | -| `AOJ-RUPC2018-in-RUPC2018-day1` | `2880` | `AOJ 2880(RUPC 2018 in RUPC 2018 Day1)` | -| `AOJ-HUPC2014-in-RUPC2014-day3` | `2581` | `AOJ 2581(HUPC 2014 in RUPC 2014 Day3)` | -| `AOJ-UAPC2015-in-ACPC2015-day2` | `1566` | `AOJ 1566(ACPC 2015 in ACPC 2015 Day2)` | - -### getTaskUrl() - -| contest_id | taskId | expected | -| ------------------------------- | ------ | ----------------------- | -| `AOJ-OUPC2012-in-RUPC2012-day2` | `2352` | `${AOJ_TASKS_URL}/2352` | - -## 変更ファイル - -| ファイル | 追加先 | 追加件数 | -| -------------------------------------------------------------- | -------------------------- | ------------ | -| `src/test/lib/utils/test_cases/contest_type.ts` | `aojUniversityContestData` | 1 件(OUPC) | -| `src/test/lib/utils/test_cases/contest_name_labels.ts` | `aojUniversity` 配列 | 4 件 | -| `src/test/lib/utils/test_cases/contest_name_and_task_index.ts` | `AOJ_UNIVERSITY_TEST_DATA` | 4 件 | -| `src/test/lib/utils/test_cases/task_url.ts` | `aojUniversityContests` | 1 件(OUPC) | - -`contest.test.ts` / `task.test.ts` は変更不要(forEach で自動ピックアップ)。 - -## 却下した代替案 - -- 年違いのバリエーション(RUPC2012 vs RUPC2013 など): 同一パターンのため追加しない(YAGNI) -- `compareByContestIdAndTaskId` への AOJ_UNIVERSITY ケース追加: - 優先度 25 は既存 AOJ 系テストで間接的に確認済みのため対象外 - -## 検証 - -```bash -pnpm test:unit -``` - -`contest.test.ts` と `task.test.ts` の aojUniversity ブロックが全パス。