Skip to content

Development: Add LTI 1.3 interop test coverage and harden Lti13LaunchFilter exception handling - #12778

Merged
krusche merged 6 commits into
developfrom
chore/lti-nightly-interop-coverage
May 24, 2026
Merged

krusche merged 6 commits into
developfrom
chore/lti-nightly-interop-coverage

Conversation

@krusche

@krusche krusche commented May 24, 2026 •

Copy link
Copy Markdown
Member

Summary

Closes the test gaps that allowed #12739 (LTI/Moodle integration broken by Spring Boot 4) to ship undetected, and hardens Lti13LaunchFilter so LTI exceptions no longer leak from the servlet pipeline.

Adds three layers of LTI 1.3 coverage:

  1. Per-PR (server-tests, ~32s incremental): an in-process JWKS server + signed-id-token test covering the full Step 3b filter chain through NimbusJwtDecoder.withJwkSetUri(...).
  2. Nightly (real Moodle, ~60s): boots bitnamilegacy/moodle:5.0.2 + Postgres via Testcontainers, extracts Moodle's actual RSA signing key from its DB, and exercises every step of the OIDC launch end-to-end against the live LMS — including a full success path with auto-created user, course, and exercise.
  3. Production fix: Lti13LaunchFilter now catches JwtException and HttpStatusException (e.g. BadRequestAlertException) so signature-verification failures and "Course not found" responses surface with the correct HTTP status instead of leaking as raw RuntimeExceptions. DispatcherServlet's HandlerExceptionResolver does not run for exceptions thrown inside servlet filters, which is why this slipped through.

Checklist

General

Server

  • Important: I implemented the changes with a very good performance and prevented too many (unnecessary) and too complex database calls.
  • I strictly followed the principle of data economy for all database calls.
  • I strictly followed the server coding and design guidelines.
  • I added multiple integration tests (Spring) related to the features (with a high test coverage).
  • I documented the Java code using JavaDoc style.

Motivation and Context

Issue #12739 was caused by the Spring Boot 4 upgrade (#12381) removing UriComponentsBuilder.fromHttpUrl(String), which the upstream spring-security-lti13 library calls inside the OIDC initiation flow. No server test exercised that code path, so the breakage shipped — Moodle LTI users were locked out until #12769 restored it.

#12769 covered Step 1 with an integration test, but two adjacent gaps remained:

  • Step 3b (JWT signature validation via JWKS): only a single negative test existed. A future Spring Security upgrade that breaks NimbusJwtDecoder.withJwkSetUri(...) would slip past CI.
  • Real LMS interop: no CI ever exercises a real Moodle / Canvas / edX. Claim-shape changes, kid format drift, JWKS encoding quirks all go undetected until a user reports them.

While building the nightly test, two further latent bugs surfaced and are fixed:

  • JwtException from NimbusJwtDecoder escaped Lti13LaunchFilter as an uncaught exception (only OAuth2AuthenticationException was caught).
  • HttpStatusException (carrying its own status code) from lti13Service.performLaunch also escaped — every "Course not found" launch was returning an undefined status instead of the 400 the exception specifies.

Description

src/main/java/de/tum/cit/aet/artemis/lti/config/Lti13LaunchFilter.java — outer catch now also catches JwtException (mapped to 500 via the existing path) and HttpStatusException (mapped to its embedded status, so BadRequestAlertException correctly becomes 400). DispatcherServlet's exception resolver does not run for filter-thrown exceptions, which is why these were leaking.

Per-PR test (src/test/java/.../lti/Lti13Step3JwtValidationIntegrationTest.java) — boots a JDK HttpServer on 127.0.0.1:0 serving a freshly-generated RSA JWKS, signs an id_token with the matching private key, seeds the production DistributedStateAuthorizationRequestRepository, then POSTs to /api/lti/public/lti13/auth-login. Three tests cover happy path, wrong-key rejection, and expired-token rejection. ~32s total when run as part of server-tests (Spring context dominates; per-test cost is <100 ms).

Nightly suite (src/test/java/.../lti/nightly/NightlyLtiMoodleInteropTest.java) — uses Testcontainers to boot Moodle 5.0.2 (bitnamilegacy image, pinned tag) + Postgres on a shared docker network. The PHP signer (src/test/resources/lti/nightly/moodle-sign-jwt.php) is MountableFile-copied into the Moodle container and invoked via docker exec moodle php /tmp/moodle-sign-jwt.php ... to call Moodle's own lti_sign_jwt() function. Six tests cover:

  • Step 1 redirect URL construction against Moodle's real /mod/lti/auth.php
  • Step 3a redirect proxy with a Moodle-signed JWT
  • Step 3b validation with Moodle's actual signature + claim shape
  • Synthetic-claim variant (extracts Moodle's private key, isolates plumbing from claim shape)
  • Full success path with Course + OnlineCourseConfiguration + TextExercise fixture — verifies user is auto-created and joined to the course's student group
  • Independent JWKS-document parse check

Workflow (.github/workflows/nightly-lti-interop.yml) — cron 0 3 * * * UTC + manual dispatch, strategy: matrix: over moodle/canvas/edx. Posts to LTI_NIGHTLY_SLACK_WEBHOOK on failure. Canvas and edX slots run disabled stubs (@Disabled) — enabling them is one annotation change per file. Operational requirement: add LTI_NIGHTLY_SLACK_WEBHOOK as a repo secret before the first scheduled run.

gradle/test.gradle — adds excludeTags "nightly-lti" to the default per-PR run so the nightly suite stays out of the regular pipeline. Opt in with -DincludeTags='nightly-lti'.

Plan document (documentation/docs/developer/lti-nightly-interop-plan.md) — original design notes; kept as historical context.

Steps for Testing

Prerequisites:

  • Local Docker Desktop (or any Docker daemon) running
  1. Per-PR test — runs automatically in server-tests; runs in 32s standalone:
    ./gradlew test --tests "de.tum.cit.aet.artemis.lti.Lti13Step3JwtValidationIntegrationTest" -x webapp
  2. Nightly Moodle interop — opt in via the tag:
    ./gradlew test --tests "de.tum.cit.aet.artemis.lti.nightly.NightlyLtiMoodleInteropTest" -DincludeTags='nightly-lti' -x webapp
    First run pulls the bitnamilegacy/moodle:5.0.2 image (~1.8 GB, one-time). Subsequent runs reuse the cached image. Wall clock ≈ 60 s (Spring boot ~30 s, Moodle install ~15 s, 6 tests <1 s).
  3. Verify the production fix — observe that LTI launches with invalid signatures now return a clean 500 (not an unhandled exception) and that "Course not found" returns 400 (not 500 or undefined). The existing Lti13LaunchIntegrationTest continues to pass.
  4. Trigger the workflow manually once LTI_NIGHTLY_SLACK_WEBHOOK is configured (via GitHub Actions → Nightly LTI Interop → Run workflow).

Testserver States

N/A — this PR only adds tests + a small filter robustness fix. No UI or schema changes.

Review Progress

Code Review

  • Code Review 1
  • Code Review 2

Summary by CodeRabbit

Release Notes

  • Documentation

    • Added comprehensive LTI 1.3 interoperability testing design notes outlining implementation phases and operational approach.
  • Bug Fixes

    • Improved LTI authentication error handling to correctly respond to HTTP status exceptions and JWT validation failures.

Review Change Stack

krusche and others added 2 commits May 24, 2026 14:58
Both classes escaped the filter as raw RuntimeExceptions because
DispatcherServlet's HandlerExceptionResolver does not run for exceptions
thrown inside servlet filters. The leak surfaced two ways:

1. JwtException (BadJwtException, JwtValidationException) from
   NimbusJwtDecoder during Step 3b signature/expiry validation — every
   real LMS that sends an invalid token would hit this. Now mapped to 500
   alongside OAuth2AuthenticationException.

2. HttpStatusException (BadRequestAlertException et al.) from
   lti13Service.performLaunch — e.g. "Course not found" was misleadingly
   bubbling up as an uncaught RuntimeException instead of the 400 the
   exception itself carries. Now mapped to its embedded status code so
   clients see the correct response.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the test gaps that allowed #12739 (Spring Boot 4 broke
UriComponentsBuilder.fromHttpUrl) to ship undetected:

Per-PR (server-tests, ~32s incremental):
- Lti13Step3JwtValidationIntegrationTest: signs an id_token with a
  local RSA key, serves the matching JWKS over an in-process JDK
  HttpServer, and POSTs to /auth-login. Exercises the full Spring
  Security filter chain through NimbusJwtDecoder.withJwkSetUri() with
  no mocks. Three tests cover the happy path, wrong-key rejection, and
  expired-token rejection.

Nightly (new workflow, ~60s wall clock):
- NightlyLtiMoodleInteropTest: boots bitnamilegacy/moodle:5.0.2 +
  postgres via Testcontainers, extracts Moodle's RSA private key from
  mdl_config_plugins, and verifies every step of the LTI 1.3 OIDC
  launch end-to-end:
  - Step 1 (initiate-login) builds the auth-request URL Moodle expects
  - Step 3a (redirect proxy) accepts a real Moodle-signed JWT
  - Step 3b (auth-login) fetches Moodle's JWKS over real HTTP and
    validates a JWT signed by Moodle's actual lti_sign_jwt() PHP
    function (invoked via docker exec on a script copied into the
    container)
  - Full success path with a Course + OnlineCourseConfiguration +
    TextExercise fixture — performLaunch auto-creates the user and
    joins them to the course's student group
- Disabled Canvas + edX stubs reserve matrix slots in the workflow.
- .github/workflows/nightly-lti-interop.yml: cron 03:00 UTC, matrix
  over (moodle, canvas, edx), Slack failure alert via
  LTI_NIGHTLY_SLACK_WEBHOOK.
- gradle/test.gradle: exclude `nightly-lti` tag from the default
  per-PR run; opt in with -DincludeTags='nightly-lti'.

Design notes in documentation/docs/developer/lti-nightly-interop-plan.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 24, 2026 13:00
@krusche
krusche requested review from a team as code owners May 24, 2026 13:00
@krusche krusche self-assigned this May 24, 2026
@github-project-automation github-project-automation Bot moved this to Work In Progress in Artemis Development May 24, 2026
@github-actions github-actions Bot added tests server Pull requests that update Java code. (Added Automatically!) documentation lti Pull requests that affect the corresponding module labels May 24, 2026
@krusche krusche added this to the 9.4 milestone May 24, 2026
@krusche krusche changed the title General: Add LTI 1.3 interop test coverage and harden Lti13LaunchFilter exception handling Development: Add LTI 1.3 interop test coverage and harden Lti13LaunchFilter exception handling May 24, 2026
@krusche krusche modified the milestones: 9.4, 9.3 May 24, 2026
@krusche

krusche commented May 24, 2026

Copy link
Copy Markdown
Member Author

@Claudia-Anthropica review

@Claudia-Anthropica

Copy link
Copy Markdown
Contributor

@krusche on it! Taking a look now 👀

Copilot AI 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.

Pull request overview

Adds in-process JWKS-based test coverage for LTI 1.3 Step 3 JWT validation, a nightly real-Moodle Testcontainers interop suite (with disabled Canvas/edX placeholders), and hardens Lti13LaunchFilter to catch JwtException and HttpStatusException so signature failures and BadRequestAlertExceptions from Lti13Service.performLaunch surface with the correct HTTP status instead of leaking past DispatcherServlet's exception resolver.

Changes:

  • New Lti13Step3JwtValidationIntegrationTest exercising the full Spring Security filter chain against a JDK-hosted JWKS endpoint with real RSA-signed tokens.
  • New nightly-lti-tagged Moodle Testcontainers suite (with stub Canvas/edX classes), a cron-triggered GitHub Actions workflow, a Gradle exclusion for the tag in the default run, and a PHP signing helper invoked via docker exec.
  • Lti13LaunchFilter now catches HttpStatusException (status forwarded) and JwtException (mapped to 500) in addition to the existing exception types.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/main/java/de/tum/cit/aet/artemis/lti/config/Lti13LaunchFilter.java Adds HttpStatusException outer catch and JwtException inner catch so JWT/LTI errors map to proper HTTP statuses.
src/test/java/de/tum/cit/aet/artemis/lti/Lti13Step3JwtValidationIntegrationTest.java Per-PR Step 3b coverage with in-process JWKS server and RSA-signed tokens.
src/test/java/de/tum/cit/aet/artemis/lti/nightly/NightlyLtiMoodleInteropTest.java Nightly Moodle 5.0.2 + Postgres Testcontainers interop test driving the full LTI flow.
src/test/java/de/tum/cit/aet/artemis/lti/nightly/NightlyLtiCanvasInteropTest.java Disabled placeholder for future Canvas interop coverage.
src/test/java/de/tum/cit/aet/artemis/lti/nightly/NightlyLtiEdxInteropTest.java Disabled placeholder for future edX interop coverage.
src/test/resources/lti/nightly/moodle-sign-jwt.php CLI helper executed inside the Moodle container to call lti_sign_jwt() for realistic JWT signing.
gradle/test.gradle Excludes nightly-lti tag from the default per-PR run.
.github/workflows/nightly-lti-interop.yml Cron + matrix workflow running the three nightly suites and posting Slack alerts on failure.
documentation/docs/developer/lti-nightly-interop-plan.md Plan document describing the phased coverage approach.
Comments suppressed due to low confidence (1)

gradle/test.gradle:132

  • The nightly-lti tag is only excluded in the runAllTests branch. Running module-targeted tests like ./gradlew test -DincludeModules="lti" (line 118-131 branch) or the convenience testMysql / testPostgres tasks (line 158-173) will include the nightly-lti-tagged classes and attempt to boot the Moodle/Postgres Testcontainers, breaking these workflows in environments where that is not desired. Consider applying excludeTags "nightly-lti" in those branches/tasks as well (or moving the nightly tests out of the default test source set).
    if (runAllTests) {
        // Default per-PR run: exclude long-running nightly suites. Opt in via -DincludeTags="nightly-lti".
        useJUnitPlatform() {
            excludeTags "nightly-lti"
        }
        exclude "**/*IT*", "**/*IntTest*"
    } else if (includedModules.size() == 0) {
        // not running all tests, but not module-specific ones -> use tags
        useJUnitPlatform() {
            includeTags includedTags
        }
    } else {
        useJUnitPlatform()
        // Always execute "shared"-folder when executing module-specifc tests
        includedModules += "shared"
        filter { testFilter ->
            includedModules.each { val ->
                testFilter.includeTestsMatching("de.tum.cit.aet.artemis.$val.*")
            }
        }
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/test/java/de/tum/cit/aet/artemis/lti/nightly/NightlyLtiMoodleInteropTest.java Outdated
Comment thread .github/workflows/nightly-lti-interop.yml Outdated
Comment thread .github/workflows/nightly-lti-interop.yml Outdated
Comment thread documentation/docs/developer/lti-nightly-interop-plan.md Outdated
@github-actions

github-actions Bot commented May 24, 2026 •

Copy link
Copy Markdown

End-to-End Test Results

Phase Status Details
Phase 1 (Relevant) ✅ Passed
TestsPassed ✅SkippedFailedTime ⏱
Phase 1: E2E Test Report14 ran14 passed0 skipped0 failed2m 4s
Phase 2 (Remaining) ❌ Failed
TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
Phase 2: E2E Test Report247 ran244 passed2 skipped1 failed29m 48s

Test Strategy: Two-phase execution

  • Phase 1: e2e/Login.spec.ts e2e/Logout.spec.ts e2e/SystemHealth.spec.ts
  • Phase 2: e2e/admin/ e2e/atlas/ e2e/course/ e2e/exam/ExamAssessment.spec.ts e2e/exam/ExamChecklists.spec.ts e2e/exam/ExamCreationDeletion.spec.ts e2e/exam/ExamDateVerification.spec.ts e2e/exam/ExamManagement.spec.ts e2e/exam/ExamParticipation.spec.ts e2e/exam/ExamResults.spec.ts e2e/exam/ExamTestRun.spec.ts e2e/exam/test-exam/ e2e/exercise/ExerciseImport.spec.ts e2e/exercise/file-upload/ e2e/exercise/modeling/ e2e/exercise/programming/ e2e/exercise/quiz-exercise/ e2e/exercise/text/ e2e/lecture/
❌ Failed Tests (Phase 2)
  • Import exercises › Imports non-programming exercises › Imports short answer quiz exercise (2m 25s)

Flakiness Scores for Failed Tests

Test Flakiness Score Default Branch Failure Rate Combined Failure Rate
e2e/exercise/ExerciseImport.spec.ts#Import exercises › Imports non-programming exercises › Imports short answer quiz exercise 93.75% 3.1% 3.1%

Overall: ❌ Phase 2 (remaining tests) failed

🔗 Workflow Run · 📊 Test Report Phase 1 · 📊 Test Report Phase 2

@coderabbitai

coderabbitai Bot commented May 24, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 50e84a0b-ee6a-4021-ab5c-f36fc8322618

📥 Commits

Reviewing files that changed from the base of the PR and between 54b8c0e and c917bb1.

⛔ Files ignored due to path filters (1)
  • .github/workflows/nightly-lti-interop.yml is excluded by !**/*.yml
📒 Files selected for processing (2)
  • documentation/docs/developer/lti-nightly-interop-plan.md
  • src/test/java/de/tum/cit/aet/artemis/lti/Lti13Step3JwtValidationIntegrationTest.java

Walkthrough

This PR completes Phase 1 and Phase 2 of a phased LTI 1.3 interoperability testing plan. It adds per-PR JWT validation tests exercising a real JWKS HTTP endpoint, a nightly Testcontainers-based Moodle integration suite with in-container JWT signing, updates Lti13LaunchFilter to handle JWT/HTTP exceptions distinctly, configures Gradle to exclude nightly tests by default, and documents the full three-phase approach. The initiation test cleanup removes per-test platform deletion to prevent conflicts with shared integration test state.

Changes

LTI Interoperability Testing and Exception Handling

Layer / File(s) Summary
LTI filter exception handling
src/main/java/de/tum/cit/aet/artemis/lti/config/Lti13LaunchFilter.java
Lti13LaunchFilter now catches HttpStatusException separately and returns the exception's HTTP status code; broadens generic catches to include JwtException and IllegalArgumentException; adds clarifying comments about JWT validation failures being mapped within the filter.
JUnit test configuration
gradle/test.gradle
Main test task and Testcontainers convenience tasks (testMysql, testPostgres) now exclude tests tagged nightly-lti by default; opt-in via -DincludeTags="nightly-lti".
Phase 1: Per-PR JWT validation tests
src/test/java/de/tum/cit/aet/artemis/lti/Lti13Step3JwtValidationIntegrationTest.java
New integration test class that starts an in-process JWKS HTTP server with a generated RSA keypair, persists a dynamic LtiPlatformConfiguration, seeds cached OAuth2 state, and validates three scenarios: valid RS256 JWT (asserts HTTP 400 to confirm downstream reach), wrong-key signature (asserts HTTP 5xx), and expired token (asserts HTTP 5xx). Includes helpers for JWKS lifecycle, platform persistence, authorization request seeding, and JWT signing.
Phase 2: Nightly Moodle Testcontainers interop suite
src/test/java/de/tum/cit/aet/artemis/lti/nightly/NightlyLtiMoodleInteropTest.java, src/test/resources/lti/nightly/moodle-sign-jwt.php
New @Tag("nightly-lti") integration test class that bootstraps Moodle + Postgres containers on a shared Docker network, extracts Moodle RSA private key and kid from Postgres via psql, creates a dynamic LtiPlatformConfiguration, and validates: initiate-login redirects to Moodle auth endpoint, auth-callback accepts Moodle-signed JWT, Moodle-signed JWT fails downstream validation (HTTP 400) when target link unresolved, synthetic JWT signed with Moodle key triggers JWKS/signature validation (HTTP 5xx), and full launch with course/exercise fixtures succeeds with authenticated session. Includes in-container moodle-sign-jwt.php helper for Moodle-signed JWT generation and helpers for Postgres/container management.
Implementation plan documentation
documentation/docs/developer/lti-nightly-interop-plan.md
Developer-facing plan detailing Phase 0 (tracking issue), Phase 1 (per-PR in-process JWKS tests, shipped), Phase 2 (nightly Moodle Testcontainers workflow with GitHub Actions trigger, Slack notifications, and operational requirements such as flake retry budget and Moodle version pinning, shipped), Phase 3 (optional Canvas/edX expansion, deferred), and explicit exit criteria and references.
Test suite maintenance
src/test/java/de/tum/cit/aet/artemis/lti/Lti13InitiationIntegrationTest.java
Removes per-test database cleanup (deleteAll LtiPlatformConfiguration) and replaces with a comment explaining that shared integration test base does not rollback DB between tests, preventing optimistic-lock conflicts with hardcoded platform id.

Sequence Diagram

sequenceDiagram
  participant Test as NightlyLtiMoodleInteropTest
  participant Postgres as Postgres Container
  participant Moodle as Moodle Container
  participant Artemis as Artemis (Lti13LaunchFilter)

  Test->>Postgres: start container & wait for health
  Test->>Moodle: start container (on shared network)
  Test->>Postgres: psql SELECT privatekey, kid
  Test->>Moodle: exec moodle-sign-jwt.php (audience, nonce)
  Moodle-->>Test: return Moodle-signed id_token
  Test->>Artemis: POST /api/lti/public/lti13/auth-login (id_token, state)
  Artemis->>Moodle: GET certs.php -> fetch JWKS
  Moodle-->>Artemis: JWKS JSON
  Artemis->>Artemis: verify RS256 signature & claims
  Artemis-->>Test: HTTP 200 (success) or 4xx/5xx (validation failure)
  Test->>Postgres: cleanup: DELETE LtiPlatformConfiguration row
  Test->>Moodle: stop container
  Test->>Postgres: stop container
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ls1intum/Artemis#12769: Conflicting changes to Lti13InitiationIntegrationTest cleanup—this PR removes the @AfterEach cleanup, while the referenced PR adds it.

Suggested reviewers

  • Claudia-Anthropica
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% 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 The title accurately reflects the primary changes: adding LTI 1.3 interop test coverage (Step 3 JWT validation, nightly Moodle interop) and hardening Lti13LaunchFilter exception handling (JwtException, HttpStatusException).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 chore/lti-nightly-interop-coverage

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 PMD (7.24.0)
src/test/java/de/tum/cit/aet/artemis/lti/Lti13Step3JwtValidationIntegrationTest.java

[WARN] Warning at ruleset.xml:46:5
44|
45|
46|
^^^^^ Use Rule name category/java/errorprone.xml/AvoidCatchingGenericException instead of the deprecated Rule name category/java/design.xml/AvoidCatchingGenericException. PMD 8.0.0 will remove support for this deprecated Rule name usage.

47|
48|
[WARN] Warning at ruleset.xml:47:5
45|
46|
47|
^^^^^ Discontinue using Rule name category/java/errorprone.xml/AvoidCa

... [truncated 13303 characters] ...

rone.xml/InaccurateNumericLiteral instead of the deprecated Rule name category/ecmascript/errorprone.xml/InnaccurateNumericLiteral. PMD 8.0.0 will remove support for this deprecated Rule name usage.

185|
186|
[ERROR] Cannot load ruleset category/vm/bestpractices.xml: Cannot resolve rule/ruleset reference 'category/vm/bestpractices.xml'. Make sure the resource is a valid file or URL and is on the CLASSPATH. Use --debug (or a fine log level) to see the current classpath.
[WARN] Progressbar rendering conflicts with reporting to STDOUT. No progressbar will be shown. Try running with argument -r to output the report to a file instead.


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

🤖 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 `@documentation/docs/developer/lti-nightly-interop-plan.md`:
- Around line 303-308: Add a language identifier (for example "text") to the
fenced code block that lists .github/workflows/nightly-lti-interop.yml and the
NightlyLtiMoodleInteropTest.java / MoodleSetup.java entries so the block becomes
```text ... ``` and resolves markdownlint MD040; update the fenced block in the
documentation file where that list appears.

In `@src/main/java/de/tum/cit/aet/artemis/lti/config/Lti13LaunchFilter.java`:
- Line 96: In Lti13LaunchFilter update the log.error call that currently uses
placeholders and passes the exception as an extra parameter; replace the
formatted message with a plain String (e.g., "LTI 1.3 launch request failed with
status: " + ex.getStatusCode().value()) and use the error(String, Throwable)
overload by passing ex as the throwable argument so the call becomes
log.error(<message-without-placeholders>, ex); locate the existing
log.error(...) in Lti13LaunchFilter and make this change.

In
`@src/test/java/de/tum/cit/aet/artemis/lti/Lti13Step3JwtValidationIntegrationTest.java`:
- Line 141: The test currently asserts a generic server error on the POST to
/api/lti/public/lti13/auth-login (the request.performMvcRequest call in
Lti13Step3JwtValidationIntegrationTest), which masks unrelated failures
collapsed into a 500 by Lti13LaunchFilter; replace each
status().is5xxServerError() assertion with a specific expectation that matches
the exact failure under test (e.g., expect a precise HTTP status like
400/401/502 that your filter produces for invalid claims, signature verification
failures, or JWKS fetch errors respectively) and/or assert the response body
contains the specific error identifier/message your filter emits; update all
three occurrences (the performMvcRequest assertions at lines corresponding to
the three tests) to check the exact status and a distinctive response content
fragment rather than a generic 5xx.

In
`@src/test/java/de/tum/cit/aet/artemis/lti/nightly/NightlyLtiMoodleInteropTest.java`:
- Around line 170-177: The test stops the moodle and moodleDb containers but
never closes the Testcontainers Network created via Network.newNetwork(),
leaking Docker networks; update the cleanup() method to close the Network
instance used to start the containers (call network.close() or
network.closeQuietly() on the Network returned by Network.newNetwork()) after
stopping moodle and moodleDb so the custom Docker network is removed; locate the
Network reference created when calling Network.newNetwork() (the variable used
to start moodle/moodleDb) and invoke its close() in cleanup().
- Around line 303-305: The test moodleJwksDocumentIsParseable() currently only
builds a NimbusJwtDecoder via
NimbusJwtDecoder.withJwkSetUri(moodleJwksUri).build() and asserts non-null;
change it to actually fetch/parse the remote JWKS by creating a Moodle-signed
JWT with the existing signWithMoodle(...) helper and calling
decoder.decode(signedJwt) (or decoder.decode(tokenValue)) and asserting the
decode call succeeds (no exception) and returns a Jwt with expected claims; use
the same decoder variable and replace the assertThat(decoder).isNotNull() with
an assert on decoder.decode(...) result.
🪄 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: CHILL

Plan: Pro

Run ID: 1217f52d-7b2c-441f-937c-2cec60c85145

📥 Commits

Reviewing files that changed from the base of the PR and between 8778665 and 690e28e.

⛔ Files ignored due to path filters (1)
  • .github/workflows/nightly-lti-interop.yml is excluded by !**/*.yml
📒 Files selected for processing (8)
  • documentation/docs/developer/lti-nightly-interop-plan.md
  • gradle/test.gradle
  • src/main/java/de/tum/cit/aet/artemis/lti/config/Lti13LaunchFilter.java
  • src/test/java/de/tum/cit/aet/artemis/lti/Lti13Step3JwtValidationIntegrationTest.java
  • src/test/java/de/tum/cit/aet/artemis/lti/nightly/NightlyLtiCanvasInteropTest.java
  • src/test/java/de/tum/cit/aet/artemis/lti/nightly/NightlyLtiEdxInteropTest.java
  • src/test/java/de/tum/cit/aet/artemis/lti/nightly/NightlyLtiMoodleInteropTest.java
  • src/test/resources/lti/nightly/moodle-sign-jwt.php

Comment thread documentation/docs/developer/lti-nightly-interop-plan.md Outdated
Comment thread src/test/java/de/tum/cit/aet/artemis/lti/nightly/NightlyLtiMoodleInteropTest.java Outdated
Comment thread src/test/java/de/tum/cit/aet/artemis/lti/nightly/NightlyLtiMoodleInteropTest.java Outdated
@github-project-automation github-project-automation Bot moved this from Work In Progress to Ready For Review in Artemis Development May 24, 2026

@Claudia-Anthropica Claudia-Anthropica 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.

@krusche Solid PR. The Lti13LaunchFilter fix is correct — HttpStatusException maps to its embedded status (so "Course not found" becomes a real 400) and JwtException rides the wrapped IllegalStateException path to 500, and the catch ordering compiles fine since none of the other caught types is a supertype. The layered per-PR + nightly Moodle coverage is genuinely thorough and well-documented. Just two minor test-quality nits inline, nothing blocking. Heads up that server-tests/client-tests/E2E were still pending when I reviewed, so this assumes they go green.

Comment thread src/test/java/de/tum/cit/aet/artemis/lti/nightly/NightlyLtiMoodleInteropTest.java Outdated
- Lti13LaunchFilter: also catch IllegalArgumentException so malformed
  claim shapes from Lti13Service.launchRequestFrom no longer leak past
  the filter (same servlet-filter escape concern as JwtException and
  HttpStatusException).
- gradle/test.gradle: hoist excludeTags 'nightly-lti' to the testMysql
  and testPostgres convenience tasks so they don't trigger a 1.8 GB
  Moodle pull by default.
- NightlyLtiMoodleInteropTest: each teardown step in @afterall is now
  try-wrapped so a docker hiccup in moodle.stop() does not orphan the
  Postgres container. signWithMoodle now rejects empty / malformed
  stdout. platformDbId() replaced with the existing
  findByRegistrationId repository method. Fully qualified class names
  replaced with imports. fullLaunchSucceeds... now uses UUID-suffixed
  email + userPrefix so re-runs in the same DB don't short-circuit
  user creation.
- moodle-sign-jwt.php: validate argc, accept optional email arg, and
  non-zero exit if lti_sign_jwt returns no id_token. Combined with the
  Java-side check this turns silent-empty-token failures into clear
  errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Align MOODLE_IMAGE to the same tag .env uses (5.0.2-debian-12-r2),
  removing the divergent source of truth.
- Workflow: drop the Canvas/edX matrix entries until their @disabled
  stubs are implemented (no point burning runners on empty test runs),
  bump upload-artifact to v7 to match the rest of the repo.
- Remove `moodleJwksDocumentIsParseable` — NimbusJwtDecoder.withJwkSetUri
  is lazy and never hits the network until .decode(), so the original
  test was a no-op. The Moodle-signed end-to-end test already exercises
  the real JWKS fetch.
- Remove the `ltiPlatformConfigurationRepository` @Autowired in both new
  test classes — it shadowed the @MockitoSpyBean already provided by
  AbstractSpringIntegrationIndependentTest, which would have caused the
  field to bypass the inherited spy reset.
- Track Course and User created by fullLaunchSucceedsWith... and delete
  them in @afterall along with the docker network; the shared LTI base
  doesn't roll back DB state, so without this they accumulate across
  nightly invocations sharing the same workspace.
- Plan doc: status now reflects shipped state (Phase 1+2 in #12778,
  Phase 3 deferred). Code fence at line 303 tagged as `text` for
  markdownlint MD040.

Co-Authored-By: Claude Opus 4.7 (1M context) <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.

Actionable comments posted: 1

🤖 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 `@gradle/test.gradle`:
- Around line 163-166: The testMysql and testPostgres tasks currently call
useJUnitPlatform and unconditionally excludeTags "nightly-lti", preventing
-DincludeTags from opting into nightly tests; update their useJUnitPlatform
blocks to mirror the main test task's logic (check runAllTests/project property
"includeTags") so that when runAllTests is true or an includeTags property is
provided you set includedTags = project.findProperty("includeTags") (or
equivalent) and only call excludeTags "nightly-lti" when no includeTags is
present; locate the useJUnitPlatform blocks inside the testMysql and
testPostgres task configurations and apply the same runAllTests/includedTags
branching as the main test task.
🪄 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: CHILL

Plan: Pro

Run ID: 9f28a4ea-cd2d-466e-bd9a-bc6f6f1078ae

📥 Commits

Reviewing files that changed from the base of the PR and between 690e28e and bb58acf.

📒 Files selected for processing (4)
  • gradle/test.gradle
  • src/main/java/de/tum/cit/aet/artemis/lti/config/Lti13LaunchFilter.java
  • src/test/java/de/tum/cit/aet/artemis/lti/nightly/NightlyLtiMoodleInteropTest.java
  • src/test/resources/lti/nightly/moodle-sign-jwt.php

Comment thread gradle/test.gradle
krusche and others added 2 commits May 24, 2026 16:11
This @AfterEach was introduced alongside the integration test in
#12769 to prevent platform-row leakage, but other LTI tests
(LtiIntegrationTest.getAllConfiguredLtiPlatformsAsAdmin and
updateLtiPlatformConfigurationAsAdmin) hardcode platform id 1 and
implicitly rely on persistent rows — the deleteAll() resets the
sequence and causes optimistic-locking failures on subsequent
inserts with the same id.

The same fix is the entire content of #12772, which now becomes
redundant once this PR lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove the @disabled NightlyLtiCanvasInteropTest and
  NightlyLtiEdxInteropTest skeleton files. When either LMS gets a
  real harness, write the test class against the established Moodle
  pattern instead of resurrecting a stub.
- Remove the matrix comment referencing them from
  nightly-lti-interop.yml.
- Plan doc: clarify that no skeleton classes remain.
- Lti13Step3JwtValidationIntegrationTest happy path now includes the
  roles claim so OidcTokenValidator passes and the request reaches
  lti13Service.performLaunch, which throws BadRequestAlertException
  ("Course not found") -> 400 via the HttpStatusException catch added
  earlier. The distinct 400 (vs. 500 for the two rejection tests)
  satisfies the reviewer concern about all three assertions
  collapsing onto a generic 5xx.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@krusche

krusche commented May 24, 2026

Copy link
Copy Markdown
Member Author

@Claudia-Anthropica review

@Claudia-Anthropica

Copy link
Copy Markdown
Contributor

@krusche acknowledged, queuing this up for immediate review!

@Claudia-Anthropica Claudia-Anthropica 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.

@krusche Re-reviewed after the latest commits and this is in good shape. Both of my earlier notes are addressed — the lazy-decoder isNotNull assertion is gone and the shadowed ltiPlatformConfigurationRepository field is removed. The happy-path Step 3 test now asserts a concrete 400 (course-not-found via BadRequestAlertException), which means it actually drives the JWKS fetch + RS256 verify + claim validation and would catch a future Spring Security regression in NimbusJwtDecoder.withJwkSetUri(...) — that's the real sentinel here, nicely done. The Lti13LaunchFilter catch ordering reads correctly (HttpStatusException -> embedded status before the generic 5xx branch). Approving.

@krusche
krusche merged commit 01be782 into develop May 24, 2026
30 of 32 checks passed
@krusche
krusche deleted the chore/lti-nightly-interop-coverage branch May 24, 2026 20:12
@github-project-automation github-project-automation Bot moved this from Ready For Review to Merged in Artemis Development May 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation lti Pull requests that affect the corresponding module ready for review server Pull requests that update Java code. (Added Automatically!) tests

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

3 participants