Skip to content

Development: Fix server tests broken by LTI initiation test cleanup - #12772

Closed
krusche wants to merge 1 commit into
developfrom
bugfix/lti/restore-lti-integration-test-row-cleanup
Closed

krusche wants to merge 1 commit into
developfrom
bugfix/lti/restore-lti-integration-test-row-cleanup

Conversation

@krusche

@krusche krusche commented May 24, 2026 •

Copy link
Copy Markdown
Member

Summary

Drop the @AfterEach cleanupPlatforms() introduced in #12769 that calls ltiPlatformConfigurationRepository.deleteAll(). The cleanup was added in response to a static-analysis concern about row leakage but actually broke two unrelated tests in LtiIntegrationTest — and the leakage turns out to be load-bearing infrastructure that those tests implicitly rely on.

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 and the REST API guidelines.
  • I added multiple integration tests (Spring) related to the features (with a high test coverage).
  • I added pre-authorization annotations according to the guidelines and checked the course groups for all new REST Calls (security).
  • I documented the Java code using JavaDoc style.

Motivation and Context

Follow-up to #12769. After that PR landed on develop, the server-tests CI started failing on every run with:

LtiIntegrationTest > getAllConfiguredLtiPlatformsAsAdmin() FAILED
LtiIntegrationTest > updateLtiPlatformConfigurationAsAdmin() FAILED
  org.springframework.orm.ObjectOptimisticLockingFailureException:
    Row was already updated or deleted by another transaction
    for entity [de.tum.cit.aet.artemis.lti.domain.LtiPlatformConfiguration with id '1']

This blocks all PRs targeting develop until reverted.

Description

Root cause. Lti13InitiationIntegrationTest.@AfterEach cleanupPlatforms() (from #12769) calls ltiPlatformConfigurationRepository.deleteAll() to avoid leaking rows into later LTI test classes. That cleanup runs in the shared JVM after every initiation test.

But LtiIntegrationTest.getAllConfiguredLtiPlatformsAsAdmin and LtiIntegrationTest.updateLtiPlatformConfigurationAsAdmin do:

LtiPlatformConfiguration platform1 = new LtiPlatformConfiguration();
platform1.setId(1L);                                  // <-- manually set ID
fillLtiPlatformConfig(platform1);
ltiPlatformConfigurationRepository.save(platform1);

Spring Data JPA's SimpleJpaRepository.save(entity) decides between persist() and merge() via EntityInformation.isNew(entity). LtiPlatformConfiguration extends DomainObject (no Persistable implementation), so isNew() returns true only if the @Id field is null. Setting id=1L makes isNew() return false, which routes the call to em.merge() — and Hibernate's merge of a detached entity that doesn't exist in the DB issues an UPDATE that matches zero rows → StaleObjectStateException → ObjectOptimisticLockingFailureException.

Before #12769, these tests passed because Lti13InitiationIntegrationTest (alphabetically first) inserted rows with auto-generated IDs starting at 1, and those rows persisted across test classes in the same JVM. The "leak" was therefore load-bearing — LtiIntegrationTest was silently depending on it without anyone noticing.

Fix. Drop the @AfterEach and the org.junit.jupiter.api.AfterEach import. Document the inter-class dependency in a comment so the next person who feels the urge to add cleanup at least sees why it's intentional. The truly correct fix is to make LtiIntegrationTest self-contained — either by using persist() directly with an unmanaged entity or by inserting via the test repository's saveAndFlush() without setting id — but that's a larger refactor and not in this PR's scope.

Intra-class collisions remain impossible because each test in Lti13InitiationIntegrationTest uses a UUID-keyed registrationId.

Steps for Testing

  1. Check out this branch.
  2. Run the two test classes together — they execute in the same JVM and exercise the cross-class dependency:
    ./gradlew test --tests Lti13InitiationIntegrationTest --tests LtiIntegrationTest -x webapp
    Result: 19/19 pass. Without this fix, 2 fail with the optimistic-locking exception above.

Testserver States

You can manage test servers using Helios. Check environment statuses in the environment list. To deploy to a test server, go to the CI/CD page, find your PR or branch, and trigger the deployment.

Review Progress

Code Review

  • Code Review 1
  • Code Review 2

Manual Tests

  • Confirm ./gradlew test --tests "de.tum.cit.aet.artemis.lti.*" passes locally with this branch.

Summary by CodeRabbit

  • Tests
    • Updated test infrastructure to improve database state management during test execution.

Review Change Stack

#12769 introduced an @AfterEach cleanupPlatforms() in
Lti13InitiationIntegrationTest that calls
ltiPlatformConfigurationRepository.deleteAll(). The change was
intended to prevent LtiPlatformConfiguration rows from leaking into
later LTI test classes — but the leakage was actually load-bearing:
LtiIntegrationTest.getAllConfiguredLtiPlatformsAsAdmin and
updateLtiPlatformConfigurationAsAdmin do platform.setId(1L); save(...),
which Spring Data JPA treats as merge() because the entity ID is set,
and merge() requires the row to exist. Without an existing row with
id=1, the UPDATE matches zero rows and Hibernate throws
ObjectOptimisticLockingFailureException.

The pre-existing tests relied on the fact that earlier LTI test
classes (alphabetically Lti13InitiationIntegrationTest first) inserted
rows with auto-generated IDs starting at 1. The new cleanup wiped them
between classes and broke this implicit ordering dependency.

Drop the cleanup and document the dependency in a comment. Intra-class
isolation is still preserved by the UUID-keyed registrationIds. The
proper long-term fix is to make LtiIntegrationTest self-contained
(test-local data fixtures), which is out of scope here.
Copilot AI review requested due to automatic review settings May 24, 2026 07:03
@krusche
krusche requested a review from a team as a code owner May 24, 2026 07:03
@krusche krusche added this to the 9.3 milestone May 24, 2026
@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 lti Pull requests that affect the corresponding module labels May 24, 2026
@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

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: 297993ce-eda8-4a25-a2ea-456c809cc9bb

📥 Commits

Reviewing files that changed from the base of the PR and between 8778665 and 1da8966.

📒 Files selected for processing (1)
  • src/test/java/de/tum/cit/aet/artemis/lti/Lti13InitiationIntegrationTest.java

Walkthrough

The PR removes per-test database cleanup from Lti13InitiationIntegrationTest by eliminating the @AfterEach method and replacing it with comments explaining that cleanup is unnecessary because each test uses a UUID-keyed registrationId and relies on shared test base state management.

Changes

Test cleanup removal

Layer / File(s) Summary
Remove per-test LtiPlatformConfiguration cleanup
src/test/java/de/tum/cit/aet/artemis/lti/Lti13InitiationIntegrationTest.java
The @AfterEach import is removed and the cleanupPlatforms() method is replaced with documentation comments explaining that no per-test cleanup is performed because test isolation is provided by UUID-keyed registrationId and managed through the test base class.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Possibly related PRs

Suggested labels

tests, ready for review, server, lti

Suggested reviewers

  • Claudia-Anthropica
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: removing a test cleanup method that was breaking other tests. It accurately reflects the primary purpose of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 bugfix/lti/restore-lti-integration-test-row-cleanup

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.

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

This PR restores stability of the server-side LTI test suite on develop by removing a newly introduced per-test database cleanup in Lti13InitiationIntegrationTest that unintentionally broke other LTI integration tests running in the same JVM.

Changes:

  • Removed the @AfterEach cleanup that called ltiPlatformConfigurationRepository.deleteAll().
  • Dropped the now-unused org.junit.jupiter.api.AfterEach import.
  • Added an explanatory comment documenting why cleanup is intentionally omitted to avoid breaking dependent LTI tests.

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

Comment on lines +43 to +47
// No @AfterEach cleanup: each test uses a UUID-keyed registrationId so intra-class collisions are impossible,
// and LtiIntegrationTest.getAllConfiguredLtiPlatformsAsAdmin / updateLtiPlatformConfigurationAsAdmin implicitly
// rely on auto-generated IDs from prior LTI tests existing in the shared DB. Wiping rows here causes those tests
// to fail with ObjectOptimisticLockingFailureException ("Row was already updated or deleted") because
// Spring Data treats save(entity-with-id-set) as merge(), which requires the row to exist.
@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 11s
Phase 2 (Remaining) ❌ Failed
TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
Phase 2: E2E Test Report247 ran241 passed2 skipped4 failed41m 8s

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)
  • Course management › Manual student selection › Manually adds and removes a student (2m 35s)
  • Course management › Course deletion › Delete summary shows correct values (2m 29s)
  • Exam grading › Instructor sets grades and student receives a grade › Check student grade (3m 30s)
  • Exam participation › Programming exam with Git submissions › Participates in exam by Git submission using https with token (10m 29s)

Flakiness Scores for Failed Tests

Test Flakiness Score Default Branch Failure Rate Combined Failure Rate
e2e/course/CourseManagement.spec.ts#Course management › Manual student selection › Manually adds and removes a student 65.0284090909091% 3.6% 0.4%
e2e/course/CourseManagement.spec.ts#Course management › Course deletion › Delete summary shows correct values 65.50675675675676% 3.2% 0.8%
e2e/exam/ExamAssessment.spec.ts#Exam grading › Instructor sets grades and student receives a grade › Check student grade 0% 0.9% 0.2%
e2e/exam/ExamParticipation.spec.ts#Exam participation › Programming exam with Git submissions › Participates in exam by Git submission using https with token 61.84659090909091% 5.8% 0.7%

Overall: ❌ Phase 2 (remaining tests) failed

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

@krusche

krusche commented May 24, 2026

Copy link
Copy Markdown
Member Author

Close in favor of another PR with a more comprehensive LTI test setup

@krusche krusche closed this May 24, 2026
krusche added a commit that referenced this pull request May 24, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lti Pull requests that affect the corresponding module tests

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants