Skip to content

feat: OIDC conformance suite の追加#87

Merged
Pugma merged 9 commits intomainfrom
feat/conformance-suite-pugma
Mar 11, 2026
Merged

feat: OIDC conformance suite の追加#87
Pugma merged 9 commits intomainfrom
feat/conformance-suite-pugma

Conversation

@Pugma
Copy link
Collaborator

@Pugma Pugma commented Mar 11, 2026

#57 で conflict などがあったので、とりあえず cherry-pick して衝突のないようにしました
ここから、先の PR で対応しきれなかった部分を追加で編集していきます

Summary by CodeRabbit

  • Chores
    • OIDC準拠性テストの自動化インフラを追加しました:テスト設定テンプレート、クライアント作成と設定置換を行う実行スクリプト、期待スキップ一覧、Docker Compose構成、及び結果ディレクトリを無視する.gitignoreエントリを追加。
  • Tests
    • テスト実行を自動化する脚本を導入し、テストの起動、ログ収集、結果出力までの一連処理をサポートします。

Add CI workflow that runs the OIDC Basic Certification test plan using
pre-built conformance suite images from GHCR. Includes a separate
workflow_dispatch to build and publish the conformance suite images.

Passes 21/34 conformance tests.

Assisted-by: Claude Code (model: claude-opus-4-6)
CONFORMANCE_HTTPD_IMAGE and CONFORMANCE_SERVER_IMAGE were only defined
in the "Start conformance suite" step env. Other steps calling
docker compose (logs in wait step and failure diagnostics) lacked
these variables, causing compose to fail with "service has neither
an image nor a build context".

Assisted-by: Claude Code (model: claude-opus-4-6)
The previous commit moved image env vars to job-level env using
${{ env.X }}, but the env context is not available at job level.
Define full image names directly at workflow level instead.

Assisted-by: Claude Code (model: claude-opus-4-6)
- Add --expected-skips support to skip specified test modules
- Add test result statistics (passed/failed/skipped counts)
- Use jq instead of python3 for JSON parsing in run.sh
- Move JVM property before -jar in docker-compose command
- Use MYSQL_PWD env var instead of -p flag
- Remove unnecessary f-string prefixes
- Add SSL verification comment for clarity

Assisted-by: Claude Code (model: claude-opus-4-6)
Fixes PATH hijacking vulnerability in go.opentelemetry.io/otel/sdk.

Assisted-by: Claude Code (model: claude-opus-4-6)
@Pugma Pugma self-assigned this Mar 11, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 11, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9944ff20-f74e-45ef-b2ba-1a8f1c85deb9

📥 Commits

Reviewing files that changed from the base of the PR and between 2e77ea2 and ee6b9a9.

📒 Files selected for processing (1)
  • .github/scripts/run-test-plan.py

📝 Walkthrough

Walkthrough

OIDCコンフォーマンステスト基盤を追加します。Docker Compose定義と設定テンプレート、スキップリスト、クライアント生成とテスト実行を自動化する Bash/Python スクリプト、及び GitHub Actions ワークフローと成果物除外を追加します。

Changes

Cohort / File(s) Summary
Conformance 設定 & 定義
​.github/conformance/config.template.json, ​.github/conformance/expected-skips.json, ​.github/conformance/docker-compose.yml
OIDC コンフォーマンス用の設定テンプレート、スキップリスト、Docker Compose(mongodb, httpd, conformance server)を追加。
実行スクリプト
​.github/conformance/run.sh, ​.github/scripts/run-test-plan.py
Bashでテストクライアント生成とテンプレート置換を行い、Pythonでテストプラン作成、モジュール起動、ログ取得、ブラウザ相互作用、ポーリング、結果出力を実装。
CI ワークフロー
​.github/workflows/conformance.yaml
GitHub Actions ワークフロー追加:依存サービス起動、portal-oidc ビルド/起動、コンフォーマンススイート起動、テスト実行、ログ/結果収集とアップロード。
プロジェクト設定
​.gitignore, go.mod
コンフォーマンス結果ディレクトリを.gitignoreに追加。go.modの間接依存を調整(go-joseのバージョンダウングレード等)。

Sequence Diagram(s)

sequenceDiagram
    participant GHA as "GitHub Actions"
    participant Portal as "Portal OIDC\n(Server)"
    participant Suite as "Conformance\nTest Suite"
    participant DB as "MongoDB"
    participant Browser as "Browser\n(Client)"

    GHA->>DB: 起動・スキーマ適用
    GHA->>Portal: Portal 起動 (env, keys)
    GHA->>Suite: Docker ComposeでSuite起動
    GHA->>Portal: POST /admin/clients (create test client)
    Portal-->>GHA: client_id, client_secret
    GHA->>Suite: POST /api/plan (config + creds)
    Suite-->>GHA: plan_id
    loop 各モジュール
      GHA->>Suite: POST /start (module)
      Suite->>Portal: Authorization リクエスト
      Portal-->>Browser: 認証フロー (ユーザ操作)
      Browser->>Suite: callback/fragment 提出
      Suite->>GHA: モジュールログ/状態
    end
    GHA->>GHA: 結果収集・アップロード
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • anko9801
  • saitenntaisei
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PRのタイトルは日本語で「feat: OIDC conformance suite の追加」であり、変更セット内容と一致しています。このPRの主な変更はOIDC適合性テストスイートの統合ですが、タイトルが日本語とローマ字の混在で読みにくく、また「の追加」は簡潔さに欠けています。

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/conformance-suite-pugma

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.

@Pugma Pugma added the enhancement New feature or request label Mar 11, 2026
@Pugma Pugma marked this pull request as ready for review March 11, 2026 07:44
Copy link

@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: 4

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

Inline comments:
In @.github/conformance/run.sh:
- Around line 28-33: The CLIENT_ID and CLIENT_SECRET extraction using jq -r can
yield the literal "null" which bypasses your empty-check; update the logic
around RESPONSE, CLIENT_ID and CLIENT_SECRET so that extraction treats both
empty string and the string "null" as failures (or use jq -e to make jq fail on
null) and then exit with error if either CLIENT_ID or CLIENT_SECRET is empty or
"null"; adjust the checks that currently reference CLIENT_ID and CLIENT_SECRET
to explicitly reject both "" and "null".
- Around line 39-44: The sed-based substitution (the multi-line sed that
replaces ${DISCOVERY_URL}, ${CLIENT_ID}, ${CLIENT_SECRET} into
config.template.json) is unsafe for values containing &, \, or other special
chars; replace the sed approach with a JSON-aware assignment using a tool like
jq: read the template (config.template.json), set the keys for DISCOVERY_URL,
CLIENT_ID and CLIENT_SECRET from the environment variables (DISCOVERY_URL,
CLIENT_ID, CLIENT_SECRET) with proper JSON escaping, and write the result to the
same output file ("results/config.json")—locate the sed invocation and the
variables DISCOVERY_URL/CLIENT_ID/CLIENT_SECRET in the script and swap it for a
jq-based read/update/write sequence to ensure correct escaping.

In @.github/scripts/run-test-plan.py:
- Around line 215-216: The script currently treats the "REVIEW" result as a
passing status which hides modules requiring manual verification; modify the
logic around the variable result and the all_passed flag so that only ("PASSED",
"WARNING", "SKIPPED") count as passes (remove "REVIEW"), and update any summary
counting/aggregation that groups results (the blocks around the checks at lines
using result, all_passed and the summary accumulation that currently includes
"REVIEW") so that "REVIEW" is tracked separately (e.g., its own counter or
category) and causes a non-zero exit (or at least not counted as success) so CI
reflects manual-review items; ensure references to result, all_passed and the
summary aggregation are changed consistently (both the initial pass check and
the later summary/exit handling) to exclude "REVIEW" from the pass set.
- Around line 110-125: The test runner opens callback_url then posts an empty
body to implicit_url, but fragments (the `#fragment`) are not sent in HTTP
requests so the implicit/hybrid response isn't reproduced; update the flow in
the block handling callback_url/implicit_url so you (1) parse the fragment out
of callback_url (use urlsplit to extract fragment) and include that fragment as
the POST body when calling browser.post(implicit_url, ...), (2) resolve relative
implicit_url against callback_url using urljoin so implicit_url becomes absolute
before posting, and (3) add the required imports from urllib.parse (urljoin,
urlsplit) at top of file; keep the same headers ("Content-Type": "text/plain")
when posting the fragment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3f1f394c-2f28-49a6-8adb-5c7f18cbec31

📥 Commits

Reviewing files that changed from the base of the PR and between 67c540e and f824091.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (8)
  • .github/conformance/config.template.json
  • .github/conformance/docker-compose.yml
  • .github/conformance/expected-skips.json
  • .github/conformance/run.sh
  • .github/scripts/run-test-plan.py
  • .github/workflows/conformance.yaml
  • .gitignore
  • go.mod

Comment on lines +39 to +44
echo "==> Generating test config..."
sed \
-e "s|\${DISCOVERY_URL}|$DISCOVERY_URL|g" \
-e "s|\${CLIENT_ID}|$CLIENT_ID|g" \
-e "s|\${CLIENT_SECRET}|$CLIENT_SECRET|g" \
"$SCRIPT_DIR/config.template.json" > "$SCRIPT_DIR/results/config.json"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

sed で JSON テンプレートを埋めるのは壊れやすいです。

Line 40-43 は置換文字列をエスケープしていないので、CLIENT_SECRET / CLIENT_ID / DISCOVERY_URL&\ が入ると config.json が壊れます。ここは文字列置換ではなく JSON として代入した方が安全です。

修正例
 echo "==> Generating test config..."
-sed \
-  -e "s|\${DISCOVERY_URL}|$DISCOVERY_URL|g" \
-  -e "s|\${CLIENT_ID}|$CLIENT_ID|g" \
-  -e "s|\${CLIENT_SECRET}|$CLIENT_SECRET|g" \
-  "$SCRIPT_DIR/config.template.json" > "$SCRIPT_DIR/results/config.json"
+jq \
+  --arg discovery_url "$DISCOVERY_URL" \
+  --arg client_id "$CLIENT_ID" \
+  --arg client_secret "$CLIENT_SECRET" \
+  '.server.discoveryUrl = $discovery_url
+   | .client.client_id = $client_id
+   | .client.client_secret = $client_secret' \
+  "$SCRIPT_DIR/config.template.json" > "$SCRIPT_DIR/results/config.json"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/conformance/run.sh around lines 39 - 44, The sed-based substitution
(the multi-line sed that replaces ${DISCOVERY_URL}, ${CLIENT_ID},
${CLIENT_SECRET} into config.template.json) is unsafe for values containing &,
\, or other special chars; replace the sed approach with a JSON-aware assignment
using a tool like jq: read the template (config.template.json), set the keys for
DISCOVERY_URL, CLIENT_ID and CLIENT_SECRET from the environment variables
(DISCOVERY_URL, CLIENT_ID, CLIENT_SECRET) with proper JSON escaping, and write
the result to the same output file ("results/config.json")—locate the sed
invocation and the variables DISCOVERY_URL/CLIENT_ID/CLIENT_SECRET in the script
and swap it for a jq-based read/update/write sequence to ensure correct
escaping.

Comment on lines +110 to +125
print(" Browser: following redirect to callback")
try:
cb_resp = browser.get(callback_url)
except httpx.HTTPError as e:
print(f" Browser: callback request failed: {e}")
return

match = re.search(r"xhr\.open\('POST',\s*\"([^\"]+)\"", cb_resp.text)
if not match:
print(" Browser: no implicit submit URL found in callback page")
return

implicit_url = match.group(1).replace("\\/", "/")
print(" Browser: submitting fragment to implicit endpoint")
try:
browser.post(implicit_url, content="", headers={"Content-Type": "text/plain"})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

フラグメントを送っていないため implicit/hybrid の戻りが再現できません。

Line 112 で callback URL を開いた後、Line 125 では implicit endpoint に空文字を POST しています。#fragment は HTTP リクエストに載らないので、ブラウザを模倣するなら callback_url から取り出して明示的に送る必要があります。相対 implicit_urlcallback_url 基準で解決した方が安全です。

修正例
-    implicit_url = match.group(1).replace("\\/", "/")
+    implicit_url = urljoin(callback_url, match.group(1).replace("\\/", "/"))
+    fragment = urlsplit(callback_url).fragment
     print("  Browser: submitting fragment to implicit endpoint")
     try:
-        browser.post(implicit_url, content="", headers={"Content-Type": "text/plain"})
+        browser.post(
+            implicit_url,
+            content=fragment,
+            headers={"Content-Type": "text/plain"},
+        )
     except httpx.HTTPError as e:
         print(f"  Browser: implicit submit failed: {e}")

あわせて import を追加してください。

from urllib.parse import urljoin, urlsplit
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/scripts/run-test-plan.py around lines 110 - 125, The test runner
opens callback_url then posts an empty body to implicit_url, but fragments (the
`#fragment`) are not sent in HTTP requests so the implicit/hybrid response isn't
reproduced; update the flow in the block handling callback_url/implicit_url so
you (1) parse the fragment out of callback_url (use urlsplit to extract
fragment) and include that fragment as the POST body when calling
browser.post(implicit_url, ...), (2) resolve relative implicit_url against
callback_url using urljoin so implicit_url becomes absolute before posting, and
(3) add the required imports from urllib.parse (urljoin, urlsplit) at top of
file; keep the same headers ("Content-Type": "text/plain") when posting the
fragment.

@Pugma Pugma requested a review from saitenntaisei March 11, 2026 10:06
Copy link

@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: 1

🧹 Nitpick comments (1)
.github/scripts/run-test-plan.py (1)

227-243: オプション: REVIEW 結果を FAIL と区別して表示することを検討してください。

現在の実装では REVIEW[FAIL] として表示されますが、手動確認が必要なテストと実際の失敗を区別できると、CI 結果の解釈が容易になります。

♻️ REVIEW を分離して表示する例
     print("\n=== Summary ===")
     passed_count = 0
     failed_count = 0
+    review_count = 0
     skipped_count = 0
     for r in results:
         if r["result"] == "SKIPPED":
             status_mark = "SKIP"
             skipped_count += 1
         elif r["result"] in ("PASSED", "WARNING"):
             status_mark = "PASS"
             passed_count += 1
+        elif r["result"] == "REVIEW":
+            status_mark = "REVIEW"
+            review_count += 1
         else:
             status_mark = "FAIL"
             failed_count += 1
         print(f"  [{status_mark}] {r['module']}: {r['result']}")

     total = len(results)
-    print(f"\n  Total: {total}  Passed: {passed_count}  Failed: {failed_count}  Skipped: {skipped_count}")
+    print(f"\n  Total: {total}  Passed: {passed_count}  Failed: {failed_count}  Review: {review_count}  Skipped: {skipped_count}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/scripts/run-test-plan.py around lines 227 - 243, Update the loop
that summarizes test results to treat "REVIEW" as its own status instead of
mapping it to FAIL: add a review_count variable (e.g., review_count = 0), add an
elif branch checking if r["result"] == "REVIEW" that sets status_mark = "REVIEW"
and increments review_count, ensure "REVIEW" is not counted in failed_count, and
update the final print/f-string that prints totals to include review_count in
the summary; modify the loop handling around results, passed_count,
failed_count, skipped_count, and the final print statement accordingly so the
output shows a separate REVIEW column.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/scripts/run-test-plan.py:
- Around line 153-158: load_expected_skips currently trusts json.load and the
structure of entries and will crash on invalid JSON or malformed entries; update
load_expected_skips to catch json.JSONDecodeError and OSError when
opening/reading the file, validate that the parsed value is a list, iterate
entries checking each is a dict with a "test_module" key (skip or log malformed
items), and return a set of test_module values while raising or logging a clear,
user-friendly error if the file is not valid JSON or the top-level structure is
not an array.

---

Nitpick comments:
In @.github/scripts/run-test-plan.py:
- Around line 227-243: Update the loop that summarizes test results to treat
"REVIEW" as its own status instead of mapping it to FAIL: add a review_count
variable (e.g., review_count = 0), add an elif branch checking if r["result"] ==
"REVIEW" that sets status_mark = "REVIEW" and increments review_count, ensure
"REVIEW" is not counted in failed_count, and update the final print/f-string
that prints totals to include review_count in the summary; modify the loop
handling around results, passed_count, failed_count, skipped_count, and the
final print statement accordingly so the output shows a separate REVIEW column.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ce0780e5-e507-4480-892e-59ff8b4c81b6

📥 Commits

Reviewing files that changed from the base of the PR and between f824091 and 2e77ea2.

📒 Files selected for processing (2)
  • .github/conformance/run.sh
  • .github/scripts/run-test-plan.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/conformance/run.sh

Comment on lines +153 to +158
def load_expected_skips(path: str | None) -> set[str]:
if not path or not os.path.exists(path):
return set()
with open(path) as f:
entries = json.load(f)
return {e["test_module"] for e in entries}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

JSON パース時のエラーハンドリングが不足しています。

expected-skips.json の形式が不正な場合(JSONパースエラー、test_module キーの欠落、配列でない場合など)、分かりにくいエラーでクラッシュします。

🛡️ エラーハンドリングの追加案
 def load_expected_skips(path: str | None) -> set[str]:
     if not path or not os.path.exists(path):
         return set()
-    with open(path) as f:
-        entries = json.load(f)
-    return {e["test_module"] for e in entries}
+    try:
+        with open(path) as f:
+            entries = json.load(f)
+        if not isinstance(entries, list):
+            print(f"Warning: expected-skips file should contain a JSON array: {path}")
+            return set()
+        return {e["test_module"] for e in entries if "test_module" in e}
+    except (json.JSONDecodeError, KeyError) as e:
+        print(f"Warning: failed to load expected-skips from {path}: {e}")
+        return set()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/scripts/run-test-plan.py around lines 153 - 158, load_expected_skips
currently trusts json.load and the structure of entries and will crash on
invalid JSON or malformed entries; update load_expected_skips to catch
json.JSONDecodeError and OSError when opening/reading the file, validate that
the parsed value is a list, iterate entries checking each is a dict with a
"test_module" key (skip or log malformed items), and return a set of test_module
values while raising or logging a clear, user-friendly error if the file is not
valid JSON or the top-level structure is not an array.

status_mark = "SKIP"
skipped_count += 1
elif r["result"] in ("PASSED", "WARNING", "REVIEW"):
elif r["result"] in ("PASSED", "WARNING"):
Copy link
Collaborator

@saitenntaisei saitenntaisei Mar 11, 2026

Choose a reason for hiding this comment

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

本当に申し訳ないのですが, これREVIEWは人が画像の確認しないといけないのでpassでよいです. REVIEWをPASSED扱いにしていると勘違いしていました。

@saitenntaisei saitenntaisei self-requested a review March 11, 2026 10:41
saitenntaisei
saitenntaisei previously approved these changes Mar 11, 2026
@Pugma Pugma merged commit 27dcb90 into main Mar 11, 2026
7 of 8 checks passed
@Pugma Pugma deleted the feat/conformance-suite-pugma branch March 11, 2026 10:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants