Skip to content

Feature/atcoder verified voting#3330

Closed
river0525 wants to merge 2 commits intostagingfrom
feature/atcoder-verified-voting
Closed

Feature/atcoder verified voting#3330
river0525 wants to merge 2 commits intostagingfrom
feature/atcoder-verified-voting

Conversation

@river0525
Copy link
Copy Markdown
Collaborator

@river0525 river0525 commented Mar 30, 2026

AtCoderアカウントの認証機能に関するPRです。
以下の変更を行いました。

  • 新しい認証サーバーへの対応
  • coderabbitのレビューに基づく修正(5回)

誤ってPRを作成してしまいました。
このPRは無視してください。

Summary by CodeRabbit

リリースノート

  • バグ修正

    • 投票機能のエラーハンドリングを改善し、ユーザー情報の取得失敗時により適切なエラー応答を返すようになりました。
  • 改善

    • ユーザー削除後、ログインページへ自動的にリダイレクトするように動作を最適化しました。
  • ドキュメント

    • 環境設定ファイルのセキュリティ関連の注意事項を追加しました。

river0525 and others added 2 commits March 30, 2026 10:44
- +page.server.ts: delete action now redirects to /login after successful deletion
- vote_actions.ts: separate locals.user existence check (500) from is_validated
  check (403) to distinguish missing user data from unverified state
- e2e/votes.spec.ts: remove redundant voteForm.toBeVisible() (guaranteed by or() wait)
- .claude/rules/testing.md: remove duplicate Prisma-mock note (appeared twice)
- .claude/rules/testing-e2e.md: simplify toBeChecked({checked:true}) -> toBeChecked();
  collapse double .locator() chain into single selector '.container nav'
- .claude/rules/sveltekit.md: fix naming inconsistency in load() example (both Bad
  and Good now use camelCase-based field names, consistent with Prisma model)

Skip: locals.user?.is_validated -> session?.user.is_validated in page.server.ts files
(session.user type only has userId/username/role; is_validated lives on locals.user)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 30, 2026

📝 Walkthrough

概要

ドキュメント整備と実装の軽微な修正を複合。SvelteKitガイドではレガシーなsnake_caseを「悪い例」に更新、テストガイドでは検証方法を洗練。e2e テストと実装では冗長な処理を削除し、ユーザー削除時のリダイレクト動作を明確化、投票確認時のエラーハンドリングを厳密化。

変更内容

コホート / ファイル(s) 概要
ドキュメント整備
.claude/rules/sveltekit.md, .claude/rules/testing-e2e.md, .claude/rules/testing.md
ガイドラインの例示とベストプラクティス表現を更新。SvelteKitではsnake_case→camelCaseの対比を強化、テストではアサーション API の簡潔化と条件付き適用表記に修正。
エラーハンドリング強化
src/features/votes/actions/vote_actions.ts
投票確認時にlocals.userの存在を明示的に検証。ユーザー情報が存在しない場合は INTERNAL_SERVER_ERROR を返す(従来のオプショナルチェーンから変更)。
ユーザー削除フロー変更
src/routes/users/edit/+page.server.ts
削除完了後の戻り値を廃止し、直接 302 リダイレクト(/login)に変更。成功メッセージの返却フローを削除。
軽微な修正
e2e/votes.spec.ts, src/routes/problems/+page.svelte, .env.example
冗長なアサーション削除、変数のletconst化、環境変数コメント追加。

推定レビュー工数

🎯 3 (Moderate) | ⏱️ ~20 分

✨ 古き snake_case は名を改めて

エラーハンドルはより厳密に

リダイレクトは真っ直ぐと

コードも心も整理整頓 🎯

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed プルリクエストのタイトルは「Feature/atcoder verified voting」で、AtCoderアカウント認証機能に関する複数の変更(投票機能、認証チェック、テスト調整、ドキュメント更新)を含む本PR全体の主要な意図を適切に表現しています。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/atcoder-verified-voting

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/rules/testing-e2e.md:
- Around line 85-87: The guide's toggle assertion is one-sided; update the check
to mirror the implementation pattern used in e2e/workbooks_list.spec.ts by
asserting the toggled state with the previous value inverted: after await
toggleLabel.click() replace the unconditional expect(toggleInput).toBeChecked()
with an assertion that uses the options form to assert the new checked state
(e.g., toBeChecked({ checked: !isChecked })) and ensure the surrounding text
references the isChecked variable and the toggleInput/toggleLabel flow so the
guide matches the implementation.

In `@src/routes/problems/`+page.svelte:
- Line 23: The code is mutating the incoming props by calling sort() on
data.taskResults; change the expression that produces taskResults so it sorts a
copy instead (e.g., clone data.taskResults via slice() or spread before calling
sort) so compareByContestIdAndTaskId is used on a non-mutating copy when
creating const taskResults: TaskResults.
🪄 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: b03635b1-2f8d-4926-8800-dcf35b3be308

📥 Commits

Reviewing files that changed from the base of the PR and between c1b4620 and 2fbb459.

📒 Files selected for processing (8)
  • .claude/rules/sveltekit.md
  • .claude/rules/testing-e2e.md
  • .claude/rules/testing.md
  • .env.example
  • e2e/votes.spec.ts
  • src/features/votes/actions/vote_actions.ts
  • src/routes/problems/+page.svelte
  • src/routes/users/edit/+page.server.ts
💤 Files with no reviewable changes (1)
  • e2e/votes.spec.ts

Comment on lines 85 to 87
await toggleLabel.click();
await expect(toggleInput).toBeChecked({ checked: true });
await expect(toggleInput).toBeChecked();
```
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Playwright公式ドキュメントで、expect(locator).toBeChecked() は options.checked を使って true/false の両状態検証をサポートしていますか?推奨されるトグル反転アサーション例も確認してください。

💡 Result:

はい、Playwright公式ドキュメントによると、expect(locator).toBeCheckedはoptions.checkedを使ってtrue/falseの両状態を検証できます。デフォルトではchecked(true)を期待しますが、options.checked: falseを指定するとunchecked状態を検証します。また、indeterminate状態もサポートされていますが、checkedオプションとは併用不可です。 推奨されるトグル反転アサーション例: - チェック状態確認: await expect(locator).toBeChecked; - アンチェック状態確認: await expect(locator).not.toBeChecked; または await expect(locator).toBeChecked({ checked: false }); - トグル例(公式風): await locator.check; // または click await expect(locator).toBeChecked; await locator.uncheck; await expect(locator).not.toBeChecked; 公式ドキュメント(LocatorAssertions.toBeChecked)で明記されており、v1.18以降でoptions.checkedが追加されています。

Citations:


🏁 Script executed:

# Confirm file locations and content
git ls-files | grep -E '(testing-e2e\.md|workbooks_list\.spec\.ts)'

Repository: AtCoder-NoviSteps/AtCoderNoviSteps

Length of output: 133


🏁 Script executed:

# Check line count and read testing-e2e.md around lines 85-87
wc -l .claude/rules/testing-e2e.md
echo "---"
sed -n '80,95p' .claude/rules/testing-e2e.md

Repository: AtCoder-NoviSteps/AtCoderNoviSteps

Length of output: 880


🏁 Script executed:

# Check workbooks_list.spec.ts content around lines 198-200
wc -l e2e/workbooks_list.spec.ts
echo "---"
sed -n '195,205p' e2e/workbooks_list.spec.ts

Repository: AtCoder-NoviSteps/AtCoderNoviSteps

Length of output: 368


トグル検証の一方向性が、実装パターンと乖離しており修正が必要です。

ガイド Line 85-87 の await expect(toggleInput).toBeChecked(); は、初期状態が ON の場合にクリック後 OFF へ遷移する正常系を検証できません。
実装(e2e/workbooks_list.spec.ts Line 198-200)では { checked: !isChecked } で前状態の反転を検証しており、ガイドとの方針が異なります。Playwright 公式ドキュメント(v1.18 以降)も options.checked での両状態検証をサポートしているため、ガイドを実装パターンに合わせて修正してください。

修正案
- await toggleLabel.click();
- await expect(toggleInput).toBeChecked();
+ const wasChecked = await toggleInput.isChecked();
+ await toggleLabel.click();
+ await expect(toggleInput).toBeChecked({ checked: !wasChecked });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await toggleLabel.click();
await expect(toggleInput).toBeChecked({ checked: true });
await expect(toggleInput).toBeChecked();
```
const wasChecked = await toggleInput.isChecked();
await toggleLabel.click();
await expect(toggleInput).toBeChecked({ checked: !wasChecked });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/rules/testing-e2e.md around lines 85 - 87, The guide's toggle
assertion is one-sided; update the check to mirror the implementation pattern
used in e2e/workbooks_list.spec.ts by asserting the toggled state with the
previous value inverted: after await toggleLabel.click() replace the
unconditional expect(toggleInput).toBeChecked() with an assertion that uses the
options form to assert the new checked state (e.g., toBeChecked({ checked:
!isChecked })) and ensure the surrounding text references the isChecked variable
and the toggleInput/toggleLabel flow so the guide matches the implementation.

let { data } = $props();

let taskResults: TaskResults = $derived(data.taskResults.sort(compareByContestIdAndTaskId));
const taskResults: TaskResults = $derived(data.taskResults.sort(compareByContestIdAndTaskId));
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.

⚠️ Potential issue | 🟡 Minor

$derived 内で props 配列を破壊的に変更しています。

Line 23 の sort() は元配列を書き換えるため、副作用を避けるためにコピー後にソートしてください。

修正案(非破壊ソート)
- const taskResults: TaskResults = $derived(data.taskResults.sort(compareByContestIdAndTaskId));
+ const taskResults: TaskResults = $derived(
+   [...data.taskResults].sort(compareByContestIdAndTaskId)
+ );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const taskResults: TaskResults = $derived(data.taskResults.sort(compareByContestIdAndTaskId));
const taskResults: TaskResults = $derived(
[...data.taskResults].sort(compareByContestIdAndTaskId)
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/problems/`+page.svelte at line 23, The code is mutating the
incoming props by calling sort() on data.taskResults; change the expression that
produces taskResults so it sorts a copy instead (e.g., clone data.taskResults
via slice() or spread before calling sort) so compareByContestIdAndTaskId is
used on a non-mutating copy when creating const taskResults: TaskResults.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

AtCoderアカウント認証(verified)を前提にした投票機能まわりの挙動を整え、関連するUX(削除後の遷移)とドキュメントを更新するPRです。

Changes:

  • 投票アクションで locals.user 取得失敗時のエラーハンドリングを追加
  • ユーザー削除後に /login へリダイレクトするよう変更
  • E2E/環境変数サンプル/Claudeルールの文言を整理

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/routes/users/edit/+page.server.ts アカウント削除成功時にログイン画面へリダイレクト
src/routes/problems/+page.svelte taskResults の宣言を const
src/features/votes/actions/vote_actions.ts locals.user が無い場合の fail 応答を追加
e2e/votes.spec.ts 冗長な可視性チェックを削除
.env.example エンドポイントURLのコミット注意喚起を追加
.claude/rules/testing.md テスト方針ドキュメントの注記を整理
.claude/rules/testing-e2e.md E2Eの記述をより安全寄りに修正
.claude/rules/sveltekit.md 例の命名(snake_case/camelCase)説明を更新

let { data } = $props();

let taskResults: TaskResults = $derived(data.taskResults.sort(compareByContestIdAndTaskId));
const taskResults: TaskResults = $derived(data.taskResults.sort(compareByContestIdAndTaskId));
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

data.taskResults.sort(...) mutates the data.taskResults array in place. Because data comes from load(), mutating it can create hard-to-track side effects (e.g., if the same array reference is reused elsewhere or across reactive updates). Prefer sorting a copy (e.g., copy/toSorted) before passing into $derived.

Suggested change
const taskResults: TaskResults = $derived(data.taskResults.sort(compareByContestIdAndTaskId));
const taskResults: TaskResults = $derived(
data.taskResults.toSorted(compareByContestIdAndTaskId),
);

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +33
return fail(INTERNAL_SERVER_ERROR, {
message: 'ユーザー情報の取得に失敗しました。',
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

When locals.user is missing, this returns INTERNAL_SERVER_ERROR (500). In practice this can happen when the session is valid but the DB user is gone (e.g., deleted) or lookup failed, which is usually better represented as an auth problem (clear the session and return UNAUTHORIZED, or redirect to login) rather than a 500. Consider invalidating the session (locals.auth.setSession(null)) and returning 401/403 with a re-login message.

Suggested change
return fail(INTERNAL_SERVER_ERROR, {
message: 'ユーザー情報の取得に失敗しました。',
await locals.auth.setSession(null);
return fail(UNAUTHORIZED, {
message: 'ユーザー情報の取得に失敗しました。再度ログインしてください。',

Copilot uses AI. Check for mistakes.
@river0525 river0525 closed this Mar 30, 2026
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.

2 participants