Skip to content

Programming exercises: Exchange of Artemis programming exercises via CodeAbility Sharing Platform #10989

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from

Conversation

Wallenstein61
Copy link

@Wallenstein61 Wallenstein61 commented Jun 6, 2025

This pull resolves #9909

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

Client

  • Important: I implemented the changes with a very good performance, prevented too many (unnecessary) REST calls and made sure the UI is responsive, even with large data (e.g. using paging).
  • I strictly followed the principle of data economy for all client-server REST calls.
  • I strictly followed the client coding and design guidelines.
  • Following the theming guidelines, I specified colors only in the theming variable files and checked that the changes look consistent in both the light and the dark theme.
  • I added multiple integration tests (Jest) related to the features (with a high test coverage), while following the test guidelines.
  • I added authorities to all new routes and checked the course groups for displaying navigation elements (links, buttons).
  • I documented the TypeScript code using JSDoc style.
  • I added multiple screenshots/screencasts of my UI changes.
  • I translated all newly inserted strings into English and German.

Changes affecting Programming Exercises

  • High priority: I tested all changes and their related features with all corresponding user types on a test server configured with the integrated lifecycle setup (LocalVC and LocalCI).
  • I tested all changes and their related features with all corresponding user types on a test server configured with LocalVC and Jenkins.

Motivation and Context

The CodeAbility Project currently operates and maintains a platform for sharing (among other content) programming exercises between interested parties. It is open to the programming teaching community.

CodeAbility also operates an Artemis instance that serves its 6 Austrian partners as a platform for programming teaching.
Artemis is currently a major source of programming exercises, as well as a potential target for re-using programming exercises.
However the exchange between separated instances of Artemis is only supported by a manual exchange of zipped exercises files.

We have implemented a REST-based connector interface in the sharing platform to import and export exercises between interested applications.
This connector interface allows among other features the import and export of programming exercises in an appropriate format.

We have already implemented an prototype integration of Artemis with the sharing platform based on the connector interface in order to allow for the smooth sharing of exercises.

Description

An additional spring profile "sharing" is implemented, that enables the functionality to export/import programming exercises via the sharing plattform.

The sharing requires an registration of the artemis instance at [email protected]. During the registration a pre shared key is exchanged, that allows the communication between artemis and the sharing plattform.

When the sharing profile is enabled, (and configuration is initialized properly), the the sharing platform connects regularly with the artemis instance to query the connector configuration.
Additional the artemis instance can trigger an configuration request for the sharing plattform at application startup.

Details can be found at https://sharing-codeability.uibk.ac.at/development/sharing/codeability-sharing-platform/-/wikis/Artemis-Connection/Connector-Interface .

A preliminary setup guide can be found at https://development.pages.sharing-codeability.uibk.ac.at/artemis/artemis/admin/setup/sharing.html .

Steps for Testing

Prerequisites:

Follow the steps at https://development.pages.sharing-codeability.uibk.ac.at/artemis/artemis/user/sharing.html for export to Artemis and the import back to artemis.

Exam Mode Testing

This feature is not relevant for exam mode testing. (Just exercise imports)

Testserver States

TODO: In progress.

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

Performance Review

Preliminary Remark: Import and Export of programming exercises does not influence any performance with student features.

  • I (as a reviewer) confirm that the client changes (in particular related to REST calls and UI responsiveness) are implemented with a very good performance even for very large courses with more than 2000 students.
  • I (as a reviewer) confirm that the server changes (in particular related to database calls) are implemented with a very good performance even for very large courses with more than 2000 students.

Code Review

  • Code Review 1
  • Code Review 2

Manual Tests

Exam Mode Test

  • not relevant. However imported programming exercises should work in exams mode

Performance Tests

  • not really relevant. There may be some delays, when transferring exercises with complex repositories

Test Coverage

Screenshots

Summary by CodeRabbit

  • New Features

    • Integrated Artemis with an external Sharing Platform for importing and exporting programming exercises.
    • Added UI components, routes, and services to support sharing workflows, including a dedicated sharing page and export buttons in exercise details.
    • Implemented backend REST APIs and services for sharing configuration, exercise export, import processing, and secure file delivery.
    • Introduced health indicators for monitoring the sharing connector status in the administration interface.
  • Documentation

    • Provided comprehensive user and admin guides detailing sharing setup, usage, troubleshooting, and workflows with visual aids and external references.
    • Updated documentation indexes to include sharing-related topics.
  • Style

    • Added new SCSS styles enhancing the sharing feature’s page layout and interactive elements.
  • Tests

    • Delivered extensive backend and frontend tests validating sharing import/export processes, service logic, REST endpoints, UI components, and health checks.
  • Chores

    • Added localization files and translation keys for sharing UI in English and German.
    • Updated build scripts, Docker environment, and configuration files to enable and support the sharing profile and dependencies.

@Wallenstein61 Wallenstein61 requested review from a team and krusche as code owners June 6, 2025 08:26
@github-project-automation github-project-automation bot moved this to Work In Progress in Artemis Development Jun 6, 2025
@github-actions github-actions bot added tests server Pull requests that update Java code. (Added Automatically!) client Pull requests that update TypeScript code. (Added Automatically!) documentation config-change Pull requests that change the config in a way that they require a deployment via Ansible. docker core Pull requests that affect the corresponding module programming Pull requests that affect the corresponding module labels Jun 6, 2025
Copy link

github-actions bot commented Jun 6, 2025

End-to-End (E2E) Test Results Summary

Copy link
Contributor

coderabbitai bot commented Jun 6, 2025

## Walkthrough

This change introduces a comprehensive integration between Artemis and the CodeAbility Sharing Platform, gated by a new "sharing" Spring profile. It adds backend services, REST controllers, DTOs, and health checks for secure import/export of programming exercises, along with Angular components, services, routes, and UI elements for instructors. Extensive documentation, localization, and automated tests are included.

## Changes

| File(s) / Group                                         | Change Summary |
|---------------------------------------------------------|---------------|
| `build.gradle`, `docker/...env`                         | Added Sharing Platform dependency; enabled "sharing" profile in Docker env. |
| `docs/admin/extension-services.rst`, `docs/admin/setup/sharing.rst`, `docs/index.rst`, `docs/user/sharing.rst` | Added/updated documentation for Sharing Platform integration and user guide. |
| `src/main/java/.../config/Constants.java`, `ProfileService.java` | Added `PROFILE_SHARING` and related profile-check methods. |
| `src/main/java/.../config/RestTemplateConfiguration.java` | Added `sharingRestTemplate` bean for Sharing Platform. |
| `src/main/java/.../config/SecurityConfiguration.java`    | Allowed unauthenticated access to `/api/programming/sharing/**` and `/api/core/sharing/**` endpoints. |
| `src/main/java/.../core/dto/SharingInfoDTO.java`         | New DTO record for sharing info, with checksum validation. |
| `src/main/java/.../core/web/SharingSupportResource.java` | New REST controller for sharing config exchange. |
| `src/main/java/.../programming/service/ProgrammingExerciseImportFromFileService.java` | Added sharing-specific import logic and method overload. |
| `src/main/java/.../programming/service/sharing/`         | Added new sharing services: `ExerciseSharingService`, `ProgrammingExerciseImportFromSharingService`, `SharingConnectorService`, `SharingException`, `SharingHealthIndicator`, `SharingMultipartZipFile`, `SharingSetupInfo`. |
| `src/main/java/.../programming/web/ExerciseSharingResource.java` | New REST controller for sharing import/export endpoints. |
| `src/main/webapp/app/...` (multiple files)               | Added Angular sharing service, routes, components, UI logic, SCSS, and model for sharing; integrated sharing import/export into programming exercise management. |
| `src/main/webapp/i18n/.../programmingExercise.json`, `sharing.json` | Added localization keys for sharing in EN/DE. |
| `src/test/java/.../programming/service/sharing/`         | Added integration and unit tests for sharing backend, DTOs, mock provider, and health checks. |
| `src/test/resources/.../sampleBasket.json`               | Added sample sharing basket test data. |
| `src/test/javascript/spec/helpers/.../programming-exercise-creation-config-mock.ts` | Added sharing flag to test config mock. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Instructor
    participant Artemis_UI
    participant Artemis_Backend
    participant SharingPlatform

    Instructor->>Artemis_UI: Click "Export to Sharing"
    Artemis_UI->>Artemis_Backend: POST /api/programming/sharing/export/{exerciseId}
    Artemis_Backend->>SharingPlatform: (if needed) Register/validate API key
    Artemis_Backend-->>Artemis_UI: Return sharing platform redirect URL
    Artemis_UI->>SharingPlatform: Redirect user for metadata/namespace selection
    SharingPlatform->>Artemis_Backend: GET exported exercise via callback URL
    Artemis_Backend-->>SharingPlatform: Return exercise ZIP
sequenceDiagram
    participant Instructor
    participant SharingPlatform
    participant Artemis_UI
    participant Artemis_Backend

    Instructor->>SharingPlatform: Search/select exercise to import
    SharingPlatform->>Artemis_UI: Redirect with basketToken, apiBaseURL, etc.
    Artemis_UI->>Artemis_Backend: GET /api/programming/sharing/import/basket
    Artemis_Backend->>SharingPlatform: GET basket info using token
    SharingPlatform-->>Artemis_Backend: Return basket data
    Artemis_Backend-->>Artemis_UI: Return basket data
    Instructor->>Artemis_UI: Select exercise and course, trigger import
    Artemis_UI->>Artemis_Backend: POST /api/programming/sharing/setup-import
    Artemis_Backend->>SharingPlatform: Download ZIP for exercise
    Artemis_Backend->>Artemis_Backend: Import exercise into selected course
    Artemis_Backend-->>Artemis_UI: Return imported exercise
Loading

Assessment against linked issues

Objective (Issue) Addressed Explanation
Implement a new "sharing" Spring profile for import/export via CodeAbility Sharing Platform (#9909)
Integrate Artemis with the CodeAbility Sharing Platform using REST-based connector (#9909)
Require Artemis registration with CodeAbility and secure API key for communication (#9909)
Enable sharing platform to query Artemis for connector config and allow Artemis to trigger config requests (#9909)
Add UI elements for instructors to export programming exercises, including metadata and GitLab namespace selection (#9909)
Support importing programming exercises from sharing platform into Artemis courses with editable pre-filled configuration (#9909)
Server/client changes follow performance, data economy, coding/design, theming, and i18n guidelines; include documentation and JavaDoc (#9909)
Provide automated tests for export/import workflows and sharing integration (#9909)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
None No out-of-scope changes were identified. All functional code changes are directly related to the sharing platform integration as described in the linked issue. Documentation, localization, and test additions are appropriate and scoped to the new feature.

Possibly related PRs

Suggested labels

feature

Suggested reviewers

  • krusche
  • HawKhiem
  • tobias-lippert
  • cremertim


<!-- walkthrough_end -->
<!-- This is an auto-generated comment: all tool run failures by coderabbit.ai -->

> [!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.
> 
> <details>
> <summary>🔧 ESLint</summary>
> 
> > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.
> 
> npm error Exit handler never called!
> npm error This is an error with npm itself. Please report this error at:
> npm error   <https://github.com/npm/cli/issues>
> npm error A complete log of this run can be found in: /.npm/_logs/2025-06-12T14_33_02_934Z-debug-0.log
> 
> 
> 
> </details>

<!-- end of auto-generated comment: all tool run failures by coderabbit.ai -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings
<details open="true">
<summary>🧪 Generate Unit Tests</summary>

- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Create PR with Unit Tests
- [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Commit Unit Tests in branch `feature/connecting-to-sharing-platform`
- [ ] <!-- {"checkboxId": "07f1e7d6-8a8e-4e23-9900-8731c2c87f58", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Post Copyable Unit Tests in Comment

</details>

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=ls1intum/Artemis&utm_content=10989):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai auto-generate unit tests` to generate unit tests for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` or `@coderabbitai title` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

Copy link
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: 44

🧹 Nitpick comments (63)
src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSetupInfo.java (3)

9-11: Improve JavaDoc for better clarity.

The JavaDoc comment could be more descriptive and follow standard conventions.

Consider this improvement:

-/**
- * the sharing info, wrapping the original sharing Info from the sharing platform and adding course and exercise info.
- */
+/**
+ * Data wrapper that combines sharing information from the sharing platform with additional context.
+ * 
+ * @param exercise the programming exercise to be shared or imported
+ * @param course the course context for the exercise
+ * @param sharingInfo the original sharing information from the sharing platform
+ */

9-11: Improve JavaDoc formatting and add parameter documentation.

The JavaDoc comment should start with a capital letter and include parameter documentation for the record components.

 /**
- * the sharing info, wrapping the original sharing Info from the sharing platform and adding course and exercise info.
+ * The sharing info, wrapping the original sharing info from the sharing platform and adding course and exercise info.
+ *
+ * @param exercise    the programming exercise to be shared
+ * @param course      the course containing the exercise
+ * @param sharingInfo the original sharing information from the sharing platform
  */

13-13: Consider whether entities should be included in this data wrapper.

The record contains ProgrammingExercise and Course entities, which goes against the coding guideline "dtos:{java_records,no_entities,min_data,single_resp}". Since this appears to be a service-layer data holder rather than a REST DTO, consider whether this design is intentional or if separate DTOs should be used instead.

src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingException.java (5)

16-31: Improve JavaDoc comments for accuracy.

The JavaDoc comments are misleading - they all mention "connection failures" but the constructors are generic.

Consider this improvement:

-    /**
-     * Creates a SharingException for connection failures.
-     */
+    /**
+     * Creates a SharingException with the specified message.
+     *
+     * @param message the detail message
+     */
     public SharingException(String message) {
         super(message);
     }

-    /**
-     * Creates a SharingException for connection failures.
-     *
-     * @param cause The underlying cause
-     */
+    /**
+     * Creates a SharingException with the specified message and cause.
+     *
+     * @param message the detail message
+     * @param cause the underlying cause
+     */
     public SharingException(String message, Throwable cause) {
         super(message, cause);
     }

16-31: Fix misleading and repetitive JavaDoc comments.

The JavaDoc comments are repetitive and misleading. The second constructor is not specifically for "connection failures" as stated.

 /**
- * Creates a SharingException for connection failures.
+ * Creates a SharingException with a message.
+ *
+ * @param message the detail message
  */
 public SharingException(String message) {
     super(message);
 }

 /**
- * Creates a SharingException for connection failures.
+ * Creates a SharingException with a message and cause.
  *
+ * @param message the detail message
  * @param cause The underlying cause
  */

39-41: Consider a more generic factory method name.

The connectionError method is specific to connection errors, but the class might be used for other sharing-related errors as well.

Consider renaming for better reusability:

-public static SharingException connectionError(String endpoint, Throwable cause) {
+public static SharingException withEndpoint(String endpoint, Throwable cause) {
     return new SharingException("Failed to connect to sharing platform at " + endpoint, cause);
 }

This makes the method more generic while maintaining its current functionality.


16-21: Improve JavaDoc documentation for constructor.

The JavaDoc comment says "Creates a SharingException for connection failures" but this constructor is generic and can be used for any sharing-related error, not just connection failures.

/**
- * Creates a SharingException for connection failures.
+ * Creates a SharingException with the specified message.
+ *
+ * @param message the detail message
 */

23-30: Improve JavaDoc documentation for constructor with cause.

Similar to the previous constructor, the JavaDoc is too specific about "connection failures" when this constructor can be used for any sharing exception.

/**
- * Creates a SharingException for connection failures.
+ * Creates a SharingException with the specified message and cause.
 *
+ * @param message the detail message
 * @param cause The underlying cause
 */
src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingHealthIndicator.java (5)

14-16: Improve JavaDoc documentation format.

The JavaDoc comment should follow proper formatting conventions with a capital letter and proper punctuation.

-/**
- * health indicator that shows the status of the sharing platform connector.
- */
+/**
+ * Health indicator that shows the status of the sharing platform connector.
+ */

27-30: Remove unnecessary super() call.

The explicit super() call is redundant in Java constructors when calling the no-argument constructor of the parent class.

 public SharingHealthIndicator(SharingConnectorService sharingConnectorService) {
-    super();
     this.sharingConnectorService = sharingConnectorService;
 }

32-35: Improve JavaDoc documentation format and grammar.

The JavaDoc has formatting and grammatical issues that should be corrected.

-/**
- * returns the main health status (up/down or unknown if config request from sharing platform is to long ago), together
- * with a list of the 10 last log events for the sharing connector.
- */
+/**
+ * Returns the main health status (up/down or unknown if config request from sharing platform is too long ago), together
+ * with a list of the 10 most recent log events for the sharing connector.
+ */

43-43: Consider making the timeout configurable.

The 11-minute threshold is hardcoded, which makes it difficult to adjust for different environments or requirements without code changes.

Consider extracting this to a configurable property:

+@Value("${artemis.sharing.health-timeout-minutes:11}")
+private int healthTimeoutMinutes;

-else if (lastHealthStati.getLastConnect().isBefore(Instant.now().minus(11, ChronoUnit.MINUTES))) {
+else if (lastHealthStati.getLastConnect().isBefore(Instant.now().minus(healthTimeoutMinutes, ChronoUnit.MINUTES))) {

50-55: Optimize iteration and formatting.

The current approach with reverse iteration and string formatting could be simplified and made more efficient.

-for (int i = lastHealthStati.size() - 1; i >= 0; i--) {
-    SharingConnectorService.HealthStatus hs = lastHealthStati.get(i);
+int counter = lastHealthStati.size();
+for (int i = lastHealthStati.size() - 1; i >= 0; i--) {
+    SharingConnectorService.HealthStatus hs = lastHealthStati.get(i);
     ZonedDateTime zonedTimestamp = hs.getTimeStamp().atZone(UTC);
     String timeStamp = TIME_STAMP_FORMATTER.format(zonedTimestamp);
-    health.withDetail(String.format("%3d: %s", i + 1, timeStamp), hs.getStatusMessage());
+    health.withDetail(String.format("%3d: %s", counter--, timeStamp), hs.getStatusMessage());
 }
src/main/webapp/app/sharing/sharing.model.ts (3)

13-13: Fix typo in comment.

There's a spelling error in the comment.

-    /** checksum fo apiBaseURL and returnURL */
+    /** checksum for apiBaseURL and returnURL */

26-31: Consider including checksum in clear method.

The clear() method resets most properties but doesn't reset the checksum property, which might lead to inconsistent state.

 public clear(): void {
     this.basketToken = '';
     this.selectedExercise = 0;
     this.returnURL = '';
     this.apiBaseURL = '';
+    this.checksum = '';
 }

50-54: Consider using readonly for interface properties.

The ShoppingBasket interface properties could be marked as readonly to prevent accidental mutations, especially for data coming from an external API.

 export interface ShoppingBasket {
-    exerciseInfo: Array<SearchResultDTO>;
-    userInfo: UserInfo;
-    tokenValidUntil: Date;
+    readonly exerciseInfo: readonly SearchResultDTO[];
+    readonly userInfo: UserInfo;
+    readonly tokenValidUntil: Date;
 }
src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorHealthCheckRegistryTest.java (3)

29-36: Improve test method name and add setup verification.

The test method name could be more descriptive, and the test should verify the health indicator setup before asserting the result.

 @Test
-void healthUp() throws Exception {
+void shouldReportHealthUpWhenSharingPlatformIsConnected() throws Exception {
+    // given
     sharingPlatformMockProvider.mockStatus(true);
 
+    // when
     final Health health = sharingHealthIndicator.health();
+    
+    // then
     assertThat(health.getStatus()).isEqualTo(Status.UP);
 }

38-49: Improve test method name and add intermediate assertions.

The test method name should be more descriptive, and the test could benefit from intermediate assertions to make the test more robust.

 @Test
-void testMaxStatusInfo() throws Exception {
+void shouldLimitHealthHistorySizeToConfiguredMaximum() throws Exception {
+    // given
     sharingPlatformMockProvider.mockStatus(true);
 
     SharingConnectorService.HealthStatusWithHistory lastHealthStati = sharingConnectorService.getLastHealthStati();
+    int initialSize = lastHealthStati.size();
 
+    // when - adding more entries than the limit
     for (int i = 0; i < SharingConnectorService.HEALTH_HISTORY_LIMIT * 2; i++) {
         lastHealthStati.add(new SharingConnectorService.HealthStatus("Just Testing"));
         assertThat(lastHealthStati).size().isLessThanOrEqualTo(SharingConnectorService.HEALTH_HISTORY_LIMIT);
     }
+    
+    // then
     assertThat(lastHealthStati).size().isEqualTo(SharingConnectorService.HEALTH_HISTORY_LIMIT);
 }

51-57: Improve test method name and structure.

The test method name should be more descriptive and follow the established testing pattern.

 @Test
-void healthDown() throws Exception {
+void shouldReportHealthDownWhenSharingPlatformIsDisconnected() throws Exception {
+    // given
     sharingPlatformMockProvider.mockStatus(false);
 
+    // when
     final Health health = sharingHealthIndicator.health();
+    
+    // then
     assertThat(health.getStatus()).isEqualTo(Status.DOWN);
 }
src/main/webapp/app/programming/shared/actions/programming-exercise-instructor-exercise-sharing.component.ts (1)

59-59: Remove commented code.

The commented line should be removed to maintain code cleanliness.

-                    //                    const sharingWindow = window.open(redirectURL, 'sharing');
src/main/webapp/app/sharing/sharing.component.ts (1)

154-155: Remove redundant return statement.

The return statement in the navigation promise is unnecessary since the method doesn't return anything.

             .then((nav) => {
-                return true; // true if navigation is successful
+                // Navigation completed
             });
src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSupportResourceTest.java (2)

99-104: Improve test method naming to follow BDD conventions.

Test method names should clearly describe the scenario being tested using a more descriptive pattern.

Consider renaming tests to be more descriptive:

-    public void connectRequestFromSharingPlatform() throws Exception {
+    public void shouldReturnConfigurationWhenValidApiKeyProvided() throws Exception {

-    public void connectRequestFromSharingPlatformWithWrongApiKey() throws Exception {
+    public void shouldReturnUnauthorizedWhenInvalidApiKeyProvided() throws Exception {

-    public void connectRequestFromSharingPlatformWithMissingApiKey() throws Exception {
+    public void shouldReturnUnauthorizedWhenApiKeyMissing() throws Exception {

Also applies to: 111-115, 122-127, 134-138, 145-149


103-103: Remove unused variable or add assertions.

The content variable is extracted but never used, suggesting missing assertions or dead code.

Either add assertions on the content:

         String content = result.getResponse().getContentAsString();
+        assertThat(content).isNotEmpty();
+        // Add specific content assertions

Or remove the unused variable:

-        String content = result.getResponse().getContentAsString();
src/main/webapp/app/sharing/sharing.component.html (2)

48-48: Move complex template expression to component method.

The expression getTokenExpiryDate().toLocaleString() should be moved to a component method for better testability and performance.

Create a getter method in the component:

get formattedExpiryDate(): string {
  return this.getTokenExpiryDate().toLocaleString();
}

Then update the template:

-<span jhiTranslate="artemisApp.sharing.expiresAt" [translateValues]="{ expirationDate: getTokenExpiryDate().toLocaleString() }">expires at: </span>
+<span jhiTranslate="artemisApp.sharing.expiresAt" [translateValues]="{ expirationDate: formattedExpiryDate }">expires at: </span>

3-3: Remove inline styles in favor of CSS classes.

Inline styles should be avoided in favor of CSS classes for better maintainability and consistency with theming guidelines.

Move inline styles to the component's CSS file:

.sharing-table {
  border-spacing: 50px 0px;
  border-collapse: separate;
}

.vertical-align-top {
  vertical-align: top;
}

.course-color-indicator {
  width: 15px;
  height: 20px;
}

Then update the template:

-<table style="border-spacing: 50px 0px; border-collapse: separate">
+<table class="sharing-table">

Also applies to: 83-83

src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportFromFileService.java (3)

124-124: Remove unused variable declaration.

The variable oldShortName is declared but never used, which violates the coding guidelines about avoiding unused variables.

-var oldShortName = getProgrammingExerciseFromDetailsFile(importExerciseDir).getShortName();

If this variable is needed for future functionality, add a TODO comment explaining its purpose.


97-98: Update method documentation to reflect the new parameter.

The Javadoc should be updated to properly document the new isImportFromSharing parameter.

 /**
  * Imports a programming exercise from an uploaded zip file that has previously been downloaded from an Artemis instance.
  * It first extracts the contents of the zip file, then creates a programming exercise (same process as creating a new one),
  * then deletes the template content initially pushed to the repositories and copies over the extracted content
  *
  * @param originalProgrammingExercise the programming exercise that should be imported
  * @param zipFile                     the zip file that contains the exercise
  * @param course                      the course to which the exercise should be added
  * @param user                        the user initiating the import
- * @param isImportFromSharing         flag whether file import (false) of sharing import
+ * @param isImportFromSharing         flag indicating whether this is a sharing platform import (true) or regular file import (false)
  * @return the imported programming exercise
  **/

175-181: Improve Javadoc for the overloaded method.

The overloaded method should have more complete documentation explaining its purpose and relationship to the main method.

 /**
- * Overloaded method setting the isImportFromSharing flag to false as default
+ * Imports a programming exercise from an uploaded zip file with default settings.
+ * This is a convenience method that calls the main import method with isImportFromSharing set to false.
+ *
+ * @param programmingExerciseForImport the programming exercise that should be imported
+ * @param zipFile                      the zip file that contains the exercise
+ * @param course                       the course to which the exercise should be added
+ * @param user                         the user initiating the import
+ * @return the imported programming exercise
+ * @throws IOException        if there is an error reading the files
+ * @throws GitAPIException    if there is an error with Git operations
+ * @throws URISyntaxException if there is an error with URI parsing
  */
src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingPlatformMockProvider.java (3)

20-22: Complete the class-level Javadoc documentation.

The class documentation is incomplete and doesn't properly describe the class's purpose and functionality.

 /**
- * provides infrastructure to set up and shutdown
+ * Test utility class that provides infrastructure to mock the sharing platform
+ * for testing purposes. This mock provider simulates connections between Artemis
+ * and the sharing platform, allowing for isolated testing of sharing functionality.
  */

76-79: Complete the method documentation.

The Javadoc comment for the mockStatus method is incomplete and unclear.

 /**
- * registers or shuts down the required
+ * Mocks the sharing platform connection status.
+ * When success is true, establishes a connection to simulate a successful platform state.
+ * When success is false, resets the connection to simulate platform unavailability.
  *
- * @param success Successful response or timeout.
+ * @param success true to simulate successful connection, false to simulate failure/timeout
+ * @throws Exception if the mock operation fails
  */

27-31: Consider making constants more configurable.

The hardcoded URLs and test installation name could be made configurable through properties to improve test flexibility.

-protected static final String TEST_INSTALLATION_NAME = "ArtemisTestInstance";
+@Value("${artemis.sharing.test.installation-name:ArtemisTestInstance}")
+private String testInstallationName;

-public static final String SHARING_BASEURL = "http://localhost:9001/api";
+@Value("${artemis.sharing.test.base-url:http://localhost:9001/api}")
+private String sharingBaseUrl;

-public static final String SHARING_BASEURL_PLUGIN = SHARING_BASEURL + "/pluginIF/v0.1";
+private String getSharingPluginUrl() {
+    return sharingBaseUrl + "/pluginIF/v0.1";
+}
src/main/webapp/app/sharing/sharing.scss (2)

16-17: Define magic numbers as CSS custom properties.

The stylesheet contains many magic numbers that should be defined as CSS variables for better maintainability.

Add CSS custom properties at the top of the file:

:host {
  --curve-height: 100px;
  --shadow-offset: 1px;
  --shadow-blur: 40px;
  --font-size-large: 5em;
  --font-size-medium: 3em;
  --font-size-small: 2em;
  --spacing-small: 20px;
  --spacing-medium: 35px;
  --border-radius: 10px;
  --box-height: 250px;
  --icon-size-large: 80px;
  --icon-size-small: 40px;
}

Then replace magic numbers:

 .curve {
     fill: #fff;
-    height: 100px;
+    height: var(--curve-height);
     width: 100%;
     position: absolute;
     bottom: 0;
     left: 0;
 }

Also applies to: 28-28, 66-66


210-214: Consolidate repetitive hover animations.

The hover animations for feature descriptions are duplicated and could be consolidated using a mixin or shared class.

Create a SCSS mixin for the slide-up animation:

@mixin slide-up-animation($top-position: 100px, $opacity: 1) {
  transition: 0.5s;
  
  &:hover {
    top: $top-position;
    opacity: $opacity;
    text-align: center;
  }
}

.container .box .feature-short-description {
  position: absolute;
  top: 100%;
  height: calc(100% - 120px);
  width: calc(100% - 40px);
  box-sizing: border-box;
  font-size: 18px;
  opacity: 0;
  
  @include slide-up-animation(100px, 1);
}
src/main/webapp/app/sharing/sharing.component.spec.ts (1)

24-203: Use single quotes consistently throughout the file.

The TypeScript style guide requires single quotes for strings, but the file uses a mix of single and double quotes.

Examples of lines that need to be updated:

  • Line 72: { id: 1, title: 'testCouse 1' },{ id: 1, title: 'testCouse 1' },
  • Line 73: { id: 2, title: 'testCouse 2' },{ id: 2, title: 'testCouse 2' },
  • Line 88: courseReq.flush(courses); uses double quotes in the expectOne
  • Line 161: { message: 'Not Found' }{ message: 'Not Found' }

Apply this pattern throughout the file for consistency.

src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (3)

139-262: Use consistent and descriptive test method naming.

Test methods have inconsistent naming - some start with "test" prefix while others don't. Follow a consistent naming pattern that describes what is being tested and the expected outcome.

Examples of improved naming:

-    void isSharingPlatformUp()
+    void shouldReturnTrueWhenSharingPlatformIsEnabled()

-    void testImportBasket()
+    void shouldSuccessfullyImportBasketFromSharingPlatform()

-    void testImportBasketFail()
+    void shouldReturnNotFoundWhenBasketImportFails()

-    void importBasketNotFound()
+    void shouldReturnNotFoundWhenBasketDoesNotExist()

-    void importBasketWrongChecksum()
+    void shouldReturnBadRequestWhenChecksumIsInvalid()

141-178: Remove commented out debug statements.

Remove the commented .andDo(print()) statements throughout the test file. These appear to be temporary debug statements that should not be committed.

Lines to clean up:

  • Line 141: // .andDo(print())
  • Line 177: /* .andDo(print()) */
  • Line 195: /* .andDo(print()) */
  • Line 309: /* .andDo(print()) */
  • Line 350: .andDo(print()) (this one is not commented and should either be removed or justified)

Also applies to: 192-196, 309-310, 349-350, 402-402


359-390: Consider using DTOs instead of modifying entities for serialization.

The makeCourseJSONSerializable method is a workaround that modifies entity state to make it serializable. This violates the principle of using DTOs for data transfer and indicates potential architectural issues with directly serializing entities.

Consider:

  1. Creating proper DTOs for data transfer instead of using entities directly
  2. Using @JsonIgnore or @JsonView annotations to control serialization
  3. Configuring Jackson to handle circular references properly

This would eliminate the need for this workaround method and improve the separation of concerns between persistence and presentation layers.

src/main/java/de/tum/cit/aet/artemis/core/web/SharingSupportResource.java (3)

50-50: Remove unnecessary @SuppressWarnings annotation.

The @SuppressWarnings("unused") annotation on the constructor is unnecessary since this is a standard dependency injection constructor that will be used by Spring.

-    @SuppressWarnings("unused")
     public SharingSupportResource(SharingConnectorService sharingConnectorService) {

73-73: Use a more descriptive variable name.

The variable name apiBaseUrl1 is not descriptive and suggests this is a quick fix rather than intentional naming.

-            URL apiBaseUrl1;
+            URL parsedApiBaseUrl;
             try {
-                apiBaseUrl1 = URI.create(apiBaseUrl).toURL();
+                parsedApiBaseUrl = URI.create(apiBaseUrl).toURL();
             }
             catch (IllegalArgumentException | MalformedURLException e) {
                 log.error("Bad URL", e);
                 return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
             }
-            return ResponseEntity.ok(sharingConnectorService.getPluginConfig(apiBaseUrl1, installationName));
+            return ResponseEntity.ok(sharingConnectorService.getPluginConfig(parsedApiBaseUrl, installationName));

69-81: Consider extracting URL validation logic to improve readability.

The URL parsing and validation logic could be extracted to a private method to improve the main method's readability and testability.

+    private Optional<URL> parseAndValidateUrl(String apiBaseUrl) {
+        try {
+            return Optional.of(URI.create(apiBaseUrl).toURL());
+        } catch (IllegalArgumentException | MalformedURLException e) {
+            log.error("Bad URL: {}", apiBaseUrl, e);
+            return Optional.empty();
+        }
+    }

     @GetMapping(SHARINGCONFIG_RESOURCE_PATH)
     public ResponseEntity<SharingPluginConfig> getConfig(@RequestHeader("Authorization") Optional<String> sharingApiKey, @RequestParam String apiBaseUrl,
             @RequestParam Optional<String> installationName) {
         if (sharingApiKey.isPresent() && sharingConnectorService.validate(sharingApiKey.get())) {
             log.info("Delivered Sharing Config ");
-            URL apiBaseUrl1;
-            try {
-                apiBaseUrl1 = URI.create(apiBaseUrl).toURL();
-            }
-            catch (IllegalArgumentException | MalformedURLException e) {
-                log.error("Bad URL", e);
-                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
-            }
-            return ResponseEntity.ok(sharingConnectorService.getPluginConfig(apiBaseUrl1, installationName));
+            Optional<URL> parsedUrl = parseAndValidateUrl(apiBaseUrl);
+            if (parsedUrl.isEmpty()) {
+                return ResponseEntity.badRequest().build();
+            }
+            return ResponseEntity.ok(sharingConnectorService.getPluginConfig(parsedUrl.get(), installationName));
         }
src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingInfoDTOTest.java (4)

16-16: Use more descriptive test method name.

The method name someEqualsTests is not descriptive enough. Test method names should clearly indicate what behavior is being tested.

-    void someEqualsTests() {
+    void testSharingInfoDTOEquals_shouldRespectAllFieldsExceptChecksum() {

20-22: Use more specific assertions for equals() contract.

The equals() method test should be more comprehensive and use more specific assertions to clearly document the expected behavior.

-        assertThat(si.equals(si2)).isTrue();
-        assertThat(si.equals(si)).isTrue();
-        assertThat(si.equals(null)).isFalse();
+        assertThat(si).isEqualTo(si2);
+        assertThat(si).isEqualTo(si);
+        assertThat(si).isNotEqualTo(null);
+        assertThat(si).isNotEqualTo("different type");

70-72: Split long lines for better readability.

The test lines are too long and should be split for better readability according to coding standards.

         assertThatExceptionOfType(IllegalArgumentException.class)
-                .isThrownBy(() -> new SharingMultipartZipFile(null, this.getClass().getResource("./basket/sampleExercise.zip").openStream()));
-        assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> new SharingMultipartZipFile("no stream", null));
+                .isThrownBy(() -> new SharingMultipartZipFile(null, 
+                    this.getClass().getResource("./basket/sampleExercise.zip").openStream()));
+        assertThatExceptionOfType(IllegalArgumentException.class)
+                .isThrownBy(() -> new SharingMultipartZipFile("no stream", null));

15-50: Consider using parameterized tests for equals() method testing.

The equals() method testing could be more maintainable and comprehensive using JUnit 5's parameterized tests.

This would improve test maintainability and make it easier to add new test cases:

@ParameterizedTest
@MethodSource("provideEqualsTestCases")
void testSharingInfoDTOEquals(SharingInfoDTO dto1, SharingInfoDTO dto2, boolean expected) {
    if (expected) {
        assertThat(dto1).isEqualTo(dto2);
    } else {
        assertThat(dto1).isNotEqualTo(dto2);
    }
}

private static Stream<Arguments> provideEqualsTestCases() {
    SharingInfoDTO base = new SharingInfoDTO();
    SharingInfoDTO withPosition = new SharingInfoDTO();
    withPosition.setExercisePosition(2);
    // ... more test cases
    
    return Stream.of(
        Arguments.of(base, new SharingInfoDTO(), true),
        Arguments.of(base, withPosition, false),
        // ... more test cases
    );
}
src/main/webapp/app/programming/manage/grading/charts/programming-exercise-detail.component.with-sharing.spec.ts (1)

34-36: Consider consolidating test files if this becomes stable.

The comment suggests this could be merged into the main test file once stabilized. This approach helps avoid test fragmentation.

src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingMultipartZipFile.java (1)

49-67: Note limitation with available() method usage.

The isEmpty() and getSize() methods rely on InputStream.available(), which only returns immediately available bytes and may not accurately reflect the total stream size or emptiness. However, based on previous feedback indicating this is a dummy implementation, this limitation may be acceptable for the current use case.

src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ProgrammingExerciseImportFromSharingService.java (1)

65-86: Consider more specific exception handling.

The method has good input validation and delegation patterns, but the catch-all exception block (lines 82-85) that just logs and re-throws may not add significant value. Consider handling specific exceptions or removing the try-catch if it's only for logging.

-        try {
-            return this.programmingExerciseImportFromFileService.importProgrammingExerciseFromFile(sharingSetupInfo.exercise(), zipFileO.get(), sharingSetupInfo.course(), user,
-                    true);
-        }
-        catch (Exception e) {
-            log.error("Cannot create exercise", e);
-            throw e;
-        }
+        return this.programmingExerciseImportFromFileService.importProgrammingExerciseFromFile(sharingSetupInfo.exercise(), zipFileO.get(), sharingSetupInfo.course(), user,
+                true);
src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceExportTest.java (1)

92-92: Consider moving ObjectMapper to setup method.

The ObjectMapper field could be initialized in a @beforeeach method for better organization, or made static if it's immutable and reusable across test instances.

-    private final com.fasterxml.jackson.databind.ObjectMapper objectMapper = new ObjectMapper().registerModule(new com.fasterxml.jackson.datatype.jsr310.JavaTimeModule());
+    private ObjectMapper objectMapper;
+    
+    @BeforeEach
+    void setupObjectMapper() {
+        objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());
+    }
src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java (4)

147-150: Improve error message for generic REST exceptions.

The error message "Unrecognized property when importing exercise from Sharing" is misleading as it implies a deserialization issue, but this catch block handles all RestClientException types.

-        catch (RestClientException rpe) {
-            log.warn("Unrecognized property when importing exercise from Sharing", rpe);
+        catch (RestClientException rpe) {
+            log.warn("Failed to retrieve basket from sharing platform", rpe);
             return Optional.empty();
         }

227-239: Consider a more robust solution for Docker URL handling.

The current implementation is described as "just a weak implementation for local testing". For production use, consider using environment-specific configuration instead of string replacement.

Consider using Spring profiles or environment variables to configure the correct host URL for different environments (local, Docker, production) rather than runtime string replacement.


278-281: Remove redundant empty check.

The empty check is immediately followed by throwing an exception, making the if statement unnecessary.

             Optional<String> entryFromBasket = this.getEntryFromBasket(pattern, sharingInfo);
-            if (entryFromBasket.isEmpty())
-                throw new NotFoundException("Could not retrieve exercise details from imported exercise");
-
-            String exerciseDetailString = entryFromBasket.get();
+            String exerciseDetailString = entryFromBasket.orElseThrow(
+                () -> new NotFoundException("Could not retrieve exercise details from imported exercise")
+            );

322-322: Extract buffer size as a constant.

The hardcoded buffer size should be defined as a class constant for better maintainability.

Add a constant at the class level:

private static final int BUFFER_SIZE = 102400;

Then use it:

-                    byte[] buffer = new byte[102400];
+                    byte[] buffer = new byte[BUFFER_SIZE];
src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorService.java (4)

109-117: Fix duplicate field documentation.

Both fields have the same comment "the shared secret api key", which is confusing.

     /**
-     * the shared secret api key
+     * the action name for the sharing platform
      */
     @Value("${artemis.sharing.actionname:Export to Artemis@somewhere}")
     private String actionName;

199-204: Improve installation name resolution for containerized environments.

InetAddress.getLocalHost() may return unhelpful values like "localhost" or container IDs in containerized environments.

         try {
-            this.installationName = installationName.orElse(InetAddress.getLocalHost().getCanonicalHostName());
+            this.installationName = installationName
+                .or(() -> Optional.ofNullable(System.getenv("HOSTNAME")))
+                .orElse(InetAddress.getLocalHost().getCanonicalHostName());
         }
         catch (UnknownHostException e) {
+            log.warn("Failed to determine hostname", e);
             this.installationName = UNKNOWN_INSTALLATIONAME;
         }

219-224: Document or make configurable the API key length limit.

The hardcoded length limit of 200 is arbitrary and undocumented.

+    private static final int MAX_API_KEY_LENGTH = 200;
+
     public boolean validate(String apiKey) {
-        if (apiKey == null || apiKey.length() > 200) {
+        if (apiKey == null || apiKey.length() > MAX_API_KEY_LENGTH) {
             // this is just in case, somebody tries an attack
             lastHealthStati.add(new HealthStatus("Failed api Key validation"));

275-278: Log the full exception for better debugging.

Only logging the exception message loses valuable stack trace information.

             catch (Exception e) {
-                log.info("Failed to request reinitialization from Sharing Platform: {}", e.getMessage());
+                log.warn("Failed to request reinitialization from Sharing Platform", e);
             }
src/main/webapp/app/programming/manage/update/programming-exercise-update.component.ts (3)

82-96: Consider using a facade pattern to reduce component dependencies.

The component has 14 service dependencies, which suggests it might be handling too many responsibilities. Consider creating a facade service to encapsulate related operations.

Group related services (e.g., all exercise-related services) behind a facade to simplify the component and improve testability.


132-135: Integrate sharing field visibility logic into the general approach.

The special case for SHORT_NAME field when sharingInfo is present seems like a workaround that breaks the component's field visibility pattern.

Consider adding sharing import as a proper mode in the IS_DISPLAYED_IN_SIMPLE_MODE mapping or creating a more flexible field visibility system that can handle different import types consistently.


352-353: Consolidate import type checks to reduce duplication.

The repeated checks for isImportFromFile || isImportFromSharing violate the DRY principle.

Add a computed property or getter:

get isImportFromExternal(): boolean {
    return this.isImportFromFile || this.isImportFromSharing;
}

Then replace all occurrences of this.isImportFromFile || this.isImportFromSharing with this.isImportFromExternal.

Also applies to: 373-374, 520-520

src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java (1)

53-71: Remove redundant JavaDoc comments for fields.

The JavaDoc comments for the fields simply restate the field names without adding value. Consider removing them to reduce noise in the code.

-    /**
-     * a logger
-     */
     private final Logger log = LoggerFactory.getLogger(ExerciseSharingResource.class);

-    /**
-     * the exercise sharing service
-     */
     private final ExerciseSharingService exerciseSharingService;

-    /**
-     * the sharing connector service
-     */
     private final SharingConnectorService sharingConnectorService;

-    /**
-     * the programming-exercise import from Sharing Service
-     */
     private final ProgrammingExerciseImportFromSharingService programmingExerciseImportFromSharingService;
src/main/webapp/app/sharing/sharing.route.ts (1)

16-23: Simplify the route structure.

The current implementation creates unnecessary complexity with SHARING_ROUTES constant and nested structure for a single route. Consider simplifying the export.

-const SHARING_ROUTES = [...sharingRoutes];
-
 export const featureOverviewState: Routes = [
     {
         path: '',
-        children: SHARING_ROUTES,
+        children: sharingRoutes,
     },
 ];

This reduces the intermediate constant while maintaining the same functionality.

src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorServiceTest.java (2)

18-22: Consider using static mocks instead of dependency injection.

The test uses @Autowired for SharingPlatformMockProvider and SharingConnectorService, but the coding guidelines recommend "mock_strategy: static_mocks". Consider using @MockBean or static mocking frameworks like Mockito's static mocking capabilities.


57-57: Optimize the huge key generation for better performance.

The current approach creates a very large string using repeat(50) which may be inefficient. Consider using a more concise approach for generating an oversized key.

-String hugeKey = "huge" + "0123456789".repeat(50);
+String hugeKey = "x".repeat(1000); // More concise way to create a large key
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a46be85 and d05f0ee.

⛔ Files ignored due to path filters (16)
  • .github/workflows/build.yml is excluded by !**/*.yml
  • docs/admin/setup/sharing/sharingButtonArtemis.png is excluded by !**/*.png, !**/*.png
  • docs/admin/setup/sharing/sharingButtonSharing.png is excluded by !**/*.png, !**/*.png
  • docs/admin/setup/sharing/sharing_health1.png is excluded by !**/*.png, !**/*.png
  • docs/admin/setup/sharing/sharing_health2.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/artemis_import.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/artemis_import2.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_export.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_metadata.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_metadata2.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_namespace.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_search.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_success.png is excluded by !**/*.png, !**/*.png
  • src/main/resources/config/application-dev.yml is excluded by !**/*.yml
  • src/main/resources/config/application-sharing.yml is excluded by !**/*.yml
  • src/test/resources/de/tum/cit/aet/artemis/programming/service/sharing/basket/sampleExercise.zip is excluded by !**/*.zip, !**/*.zip
📒 Files selected for processing (54)
  • build.gradle (2 hunks)
  • docker/artemis/config/dev-local-vc-local-ci.env (1 hunks)
  • docs/admin/extension-services.rst (1 hunks)
  • docs/admin/setup/sharing.rst (1 hunks)
  • docs/index.rst (1 hunks)
  • docs/user/sharing.rst (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/config/Constants.java (3 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/config/RestTemplateConfiguration.java (2 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/config/SecurityConfiguration.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/dto/SharingInfoDTO.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/service/ProfileService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/web/SharingSupportResource.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportFromFileService.java (4 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ProgrammingExerciseImportFromSharingService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingException.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingHealthIndicator.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingMultipartZipFile.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSetupInfo.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java (1 hunks)
  • src/main/webapp/app/app.routes.ts (1 hunks)
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.html (1 hunks)
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.ts (7 hunks)
  • src/main/webapp/app/programming/manage/exercise/programming-exercise.component.ts (1 hunks)
  • src/main/webapp/app/programming/manage/grading/charts/programming-exercise-detail.component.with-sharing.spec.ts (1 hunks)
  • src/main/webapp/app/programming/manage/programming-exercise-management.route.ts (1 hunks)
  • src/main/webapp/app/programming/manage/services/programming-exercise-sharing.service.ts (1 hunks)
  • src/main/webapp/app/programming/manage/update/programming-exercise-creation-config.ts (1 hunks)
  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.html (3 hunks)
  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.ts (17 hunks)
  • src/main/webapp/app/programming/shared/actions/programming-exercise-instructor-exercise-sharing.component.ts (1 hunks)
  • src/main/webapp/app/sharing/search-result-dto.model.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.html (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.spec.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.model.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.route.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.scss (1 hunks)
  • src/main/webapp/i18n/de/programmingExercise.json (1 hunks)
  • src/main/webapp/i18n/de/sharing.json (1 hunks)
  • src/main/webapp/i18n/en/programmingExercise.json (1 hunks)
  • src/main/webapp/i18n/en/sharing.json (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceExportTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingServiceTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorHealthCheckRegistryTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorServiceTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingInfoDTOTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingPlatformMockProvider.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSupportResourceTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationIndependentTest.java (2 hunks)
  • src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationLocalCILocalVCTest.java (3 hunks)
  • src/test/resources/de/tum/cit/aet/artemis/programming/service/sharing/basket/sampleBasket.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
`src/main/webapp/**/*.ts`: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalC...

src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

  • src/main/webapp/app/programming/manage/exercise/programming-exercise.component.ts
  • src/main/webapp/app/app.routes.ts
  • src/main/webapp/app/programming/manage/programming-exercise-management.route.ts
  • src/main/webapp/app/sharing/sharing.route.ts
  • src/main/webapp/app/sharing/sharing.model.ts
  • src/main/webapp/app/sharing/search-result-dto.model.ts
  • src/main/webapp/app/programming/manage/update/programming-exercise-creation-config.ts
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.ts
  • src/main/webapp/app/sharing/sharing.component.ts
  • src/main/webapp/app/programming/manage/services/programming-exercise-sharing.service.ts
  • src/main/webapp/app/sharing/sharing.component.spec.ts
  • src/main/webapp/app/programming/manage/grading/charts/programming-exercise-detail.component.with-sharing.spec.ts
  • src/main/webapp/app/programming/shared/actions/programming-exercise-instructor-exercise-sharing.component.ts
  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.ts
`src/main/webapp/**/*.html`: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.html
  • src/main/webapp/app/sharing/sharing.component.html
  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.html
`src/main/webapp/i18n/de/**/*.json`: German language translations should be informal (dutzen) and should never be formal (sietzen). So the user should always be addressed with "du/...

src/main/webapp/i18n/de/**/*.json: German language translations should be informal (dutzen) and should never be formal (sietzen). So the user should always be addressed with "du/dein" and never with "sie/ihr".

  • src/main/webapp/i18n/de/programmingExercise.json
  • src/main/webapp/i18n/de/sharing.json
`src/main/java/**/*.java`: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,de...

src/main/java/**/*.java: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSetupInfo.java
  • src/main/java/de/tum/cit/aet/artemis/core/service/ProfileService.java
  • src/main/java/de/tum/cit/aet/artemis/core/config/RestTemplateConfiguration.java
  • src/main/java/de/tum/cit/aet/artemis/core/dto/SharingInfoDTO.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingHealthIndicator.java
  • src/main/java/de/tum/cit/aet/artemis/core/config/SecurityConfiguration.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportFromFileService.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingMultipartZipFile.java
  • src/main/java/de/tum/cit/aet/artemis/core/web/SharingSupportResource.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingException.java
  • src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ProgrammingExerciseImportFromSharingService.java
  • src/main/java/de/tum/cit/aet/artemis/core/config/Constants.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorService.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java
`src/test/java/**/*.java`: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_...

src/test/java/**/*.java: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_use: enforce_package_rules; db_query_count_tests: track_performance; util_service_factory_pattern: true; avoid_db_access: true; mock_strategy: static_mocks; context_restart_minimize: true

  • src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationLocalCILocalVCTest.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorServiceTest.java
  • src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationIndependentTest.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSupportResourceTest.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorHealthCheckRegistryTest.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingPlatformMockProvider.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingInfoDTOTest.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingServiceTest.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceExportTest.java
🧠 Learnings (2)
src/main/webapp/app/sharing/search-result-dto.model.ts (3)
Learnt from: Wallenstein61
PR: ls1intum/Artemis#9909
File: src/main/webapp/app/sharing/search-result-dto.model.ts:17-42
Timestamp: 2025-02-11T15:46:08.133Z
Learning: The types in `UserProvidedMetadataDTO` interface in `search-result-dto.model.ts` must match the sharing platform's contract exactly to maintain compatibility. Avoid modifying these types even if they don't follow internal TypeScript conventions.
Learnt from: Wallenstein61
PR: ls1intum/Artemis#9909
File: src/main/webapp/app/sharing/search-result-dto.model.ts:57-65
Timestamp: 2025-02-11T15:40:53.440Z
Learning: The ProjectDTO interface in src/main/webapp/app/sharing/search-result-dto.model.ts uses snake_case property names to maintain compatibility with the external sharing platform's API contract, which is an intentional deviation from Angular's camelCase convention.
Learnt from: Wallenstein61
PR: ls1intum/Artemis#9909
File: src/main/webapp/app/sharing/search-result-dto.model.ts:50-55
Timestamp: 2025-02-11T15:46:35.616Z
Learning: The `IExerciseType` enum in `src/main/webapp/app/sharing/search-result-dto.model.ts` must maintain its current naming (with 'I' prefix) and lowercase string values to ensure compatibility with the external sharing platform connector interface. This is an intentional exception to our TypeScript naming conventions.
src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingMultipartZipFile.java (1)
Learnt from: Wallenstein61
PR: ls1intum/Artemis#10593
File: src/main/java/de/tum/cit/aet/artemis/sharing/SharingMultipartZipFile.java:60-68
Timestamp: 2025-04-11T07:12:15.912Z
Learning: The SharingMultipartZipFile class in the sharing package is a dummy implementation that's not actually used in practice, so optimizations aren't necessary.
🧬 Code Graph Analysis (4)
src/main/java/de/tum/cit/aet/artemis/core/service/ProfileService.java (2)
src/main/webapp/app/core/layouts/profiles/shared/profile.service.ts (1)
  • isProfileActive (30-32)
src/main/java/de/tum/cit/aet/artemis/core/config/Constants.java (1)
  • Constants (8-523)
src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.ts (1)
src/main/webapp/app/shared/detail-overview-list/detail-overview-list.component.ts (1)
  • DetailOverviewSection (26-29)
src/main/webapp/app/programming/manage/services/programming-exercise-sharing.service.ts (2)
src/main/webapp/app/sharing/sharing.model.ts (2)
  • Injectable (3-45)
  • ShoppingBasket (50-54)
src/main/webapp/app/core/course/shared/entities/course.model.ts (1)
  • Course (59-180)
src/main/webapp/app/sharing/sharing.component.spec.ts (4)
src/test/javascript/spec/helpers/mocks/service/mock-alert.service.ts (1)
  • MockAlertService (3-8)
src/test/javascript/spec/helpers/mocks/mock-router.ts (1)
  • MockRouter (5-30)
src/main/webapp/app/sharing/sharing.model.ts (1)
  • ShoppingBasket (50-54)
src/main/webapp/app/core/course/shared/entities/course.model.ts (1)
  • Course (59-180)
🪛 GitHub Check: client-tests
src/main/webapp/app/programming/manage/grading/charts/programming-exercise-detail.component.with-sharing.spec.ts

[failure] 20-20:
Cannot find module '../../helpers/mocks/service/mock-sync-storage.service' or its corresponding type declarations.


[failure] 18-18:
Cannot find module '../../helpers/mocks/service/mock-ngb-modal.service' or its corresponding type declarations.


[failure] 14-14:
Cannot find module '../../helpers/mocks/service/mock-programming-exercise.service' or its corresponding type declarations.


[failure] 12-12:
Cannot find module '../../helpers/mocks/service/mock-profile.service' or its corresponding type declarations.


[failure] 11-11:
Cannot find module 'app/shared/layouts/profiles/profile.service' or its corresponding type declarations.


[failure] 9-9:
Cannot find module 'app/shared/statistics-graph/statistics.service' or its corresponding type declarations.


[failure] 7-7:
Cannot find module 'app/entities/course.model' or its corresponding type declarations.


[failure] 6-6:
Cannot find module '../../helpers/mocks/activated-route/mock-activated-route' or its corresponding type declarations.


[failure] 5-5:
Cannot find module 'app/entities/programming/programming-exercise.model' or its corresponding type declarations.


[failure] 4-4:
Cannot find module 'app/programming/manage/programming-exercise-detail.component' or its corresponding type declarations.

🪛 GitHub Check: client-tests-selected
src/main/webapp/app/programming/manage/grading/charts/programming-exercise-detail.component.with-sharing.spec.ts

[failure] 20-20:
Cannot find module '../../helpers/mocks/service/mock-sync-storage.service' or its corresponding type declarations.


[failure] 18-18:
Cannot find module '../../helpers/mocks/service/mock-ngb-modal.service' or its corresponding type declarations.


[failure] 14-14:
Cannot find module '../../helpers/mocks/service/mock-programming-exercise.service' or its corresponding type declarations.


[failure] 12-12:
Cannot find module '../../helpers/mocks/service/mock-profile.service' or its corresponding type declarations.


[failure] 11-11:
Cannot find module 'app/shared/layouts/profiles/profile.service' or its corresponding type declarations.


[failure] 9-9:
Cannot find module 'app/shared/statistics-graph/statistics.service' or its corresponding type declarations.


[failure] 7-7:
Cannot find module 'app/entities/course.model' or its corresponding type declarations.


[failure] 6-6:
Cannot find module '../../helpers/mocks/activated-route/mock-activated-route' or its corresponding type declarations.


[failure] 5-5:
Cannot find module 'app/entities/programming/programming-exercise.model' or its corresponding type declarations.


[failure] 4-4:
Cannot find module 'app/programming/manage/programming-exercise-detail.component' or its corresponding type declarations.

🪛 ast-grep (0.38.1)
src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java

[warning] 404-404: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: Mac.getInstance(algorithm)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html

(use-of-aes-ecb-java)


[warning] 404-404: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Mac.getInstance(algorithm)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA

(desede-is-deprecated-java)

⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Build .war artifact
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Analyse
  • GitHub Check: client-style
  • GitHub Check: server-style
  • GitHub Check: server-tests
🔇 Additional comments (70)
src/main/webapp/app/programming/manage/exercise/programming-exercise.component.ts (2)

57-57: Verify SharingInfo is injectable
Ensure the imported SharingInfo class from app/sharing/sharing.model is decorated with @Injectable(); otherwise providing it here will cause a runtime injection error.


61-61: Confirm provider scope
Adding SharingInfo to providers gives each component instance its own copy. If you need a singleton across the app, prefer using providedIn: 'root' in the service definition instead of per-component providers.

docs/index.rst (1)

42-42: Good integration in User Guide toctree
The new user/sharing entry correctly surfaces the sharing platform guide alongside other user docs.

docs/admin/extension-services.rst (1)

36-36: Extension Services toctree updated
Including setup/sharing here properly advertises the sharing extension in the administration docs.

docker/artemis/config/dev-local-vc-local-ci.env (1)

5-5: Activate sharing profile in Docker config
Appending sharing to SPRING_PROFILES_ACTIVE correctly enables the new Spring profile in the local CI/VC Docker environment.

src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.html (1)

95-97: Verify conditional rendering
Confirm that isExportToSharingEnabled is declared and updated in the component class (via the ProgrammingExerciseSharingService) so the sharing button toggles as expected.

src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationIndependentTest.java (2)

11-11: Add PROFILE_SHARING import for shared tests
The new sharing profile constant is correctly imported alongside existing profiles to enable sharing-related beans in independent tests.


47-48: Activate sharing profile in base test
Including PROFILE_SHARING in @ActiveProfiles ensures independent tests load sharing beans and contexts as intended.

src/main/java/de/tum/cit/aet/artemis/core/config/SecurityConfiguration.java (1)

242-243: Permit all for sharing endpoints
Explicitly allowing unauthenticated access to /api/sharing/** is acceptable here since controllers enforce security tokens.

src/main/webapp/app/programming/manage/update/programming-exercise-creation-config.ts (1)

25-25: Add isImportFromSharing flag
Introducing isImportFromSharing aligns the creation config with other import flags and supports the sharing workflow.

build.gradle (2)

160-162: Add SharingPluginPlatformAPI dependency
The new dependency on org.codeability:SharingPluginPlatformAPI:1.1.1 is necessary for sharing platform integration.


455-457: Apply test.gradle after dependencies block
Moving the apply from: "gradle/test.gradle" ensures the mockitoAgent configuration is applied without conflicts.

src/main/webapp/i18n/de/sharing.json (1)

1-22: LGTM! German localization follows informal language guidelines.

The German translations correctly use informal language (dutzen) as required by the coding guidelines:

  • "Bitte melden dich als Instruktor an!" uses "dich" (informal)
  • Other strings are appropriately informal and professional

The JSON structure is well-organized and follows established i18n patterns.

src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationLocalCILocalVCTest.java (3)

13-13: LGTM! Proper import of sharing profile constant.

The import follows the established pattern for other profile constants and is necessary for the sharing feature integration.


78-78: LGTM! Sharing profile properly added to test configuration.

The PROFILE_SHARING addition to active profiles ensures that sharing-related functionality is available during integration tests, which aligns with the comprehensive testing approach mentioned in the PR objectives.


136-140: LGTM! Well-documented DockerClient field for test infrastructure.

The protected DockerClient field with clear documentation allows subclasses to dynamically mock Docker operations, which supports the testing requirements for the local CI functionality mentioned in the PR.

src/main/webapp/i18n/en/programmingExercise.json (1)

745-751: LGTM! Well-structured English localization for sharing functionality.

The sharing localization strings are:

  • Clearly written and professional
  • Properly structured within the artemisIntelligence section
  • Include appropriate error handling with dynamic message placeholder
  • Consistent with existing i18n patterns in the file
src/main/webapp/app/programming/manage/update/programming-exercise-update.component.html (1)

3-3: LGTM! Consistent integration of sharing import support.

The addition of isImportFromSharing flag is implemented consistently across all relevant conditions in the template. The changes properly extend the existing import logic to support the new sharing platform import source while maintaining the correct conditional display behavior.

Also applies to: 7-7, 19-19, 65-65

src/main/webapp/i18n/de/programmingExercise.json (1)

745-752: LGTM! Proper German localization for sharing feature.

The new sharing-related translations are well-structured and use appropriate German terminology. The translations don't directly address the user, so the informal/formal language guideline doesn't apply here. The error message template correctly uses interpolation syntax.

src/main/java/de/tum/cit/aet/artemis/core/config/RestTemplateConfiguration.java (2)

8-8: LGTM! Proper import addition.

The import for PROFILE_SHARING follows the existing pattern and is correctly placed with other profile imports.


63-74: LGTM! Well-implemented RestTemplate bean.

The sharingRestTemplate() method follows the established pattern in this configuration class and adheres to Java coding guidelines:

  • Proper JavaDoc documentation explaining the purpose
  • Correct use of @Bean and @Profile annotations
  • Code reuse by calling existing createRestTemplate() method
  • Consistent with other RestTemplate bean definitions
src/main/webapp/app/programming/manage/programming-exercise-management.route.ts (1)

59-70: LGTM! Consistent route configuration for sharing import.

The new route follows the established pattern in this file and adheres to Angular coding guidelines:

  • Uses lazy loading with proper component import
  • Correctly applies the existing resolver, authorities, and guards
  • Consistent structure with other import routes
  • Proper page title configuration
src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSetupInfo.java (2)

12-14: LGTM! Well-structured record implementation.

The record follows Java naming conventions and coding guidelines perfectly. The @Profile annotation ensures proper activation scope, and the record serves as an appropriate DTO combining related data.


12-13: LGTM! Well-designed record for DTO pattern.

This record effectively follows the coding guidelines for DTOs using Java records with minimal data and single responsibility. The profile annotation correctly gates the sharing feature.

src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorServiceTest.java (4)

13-32: LGTM! Excellent test class structure.

The test class follows all coding guidelines perfectly:

  • Extends appropriate base test class
  • Uses constructor injection via @Autowired
  • Implements proper setup/teardown with mock provider
  • Uses descriptive test naming conventions

34-45: Good configuration properties testing.

The test properly verifies configuration properties and API key behavior using the recommended assertThat assertions.


47-73: Comprehensive API key validation test coverage.

Excellent test coverage for various API key scenarios:

  • Null keys
  • Invalid large keys
  • Valid keys (with and without Bearer prefix)
  • Fake/invalid keys

All tests use proper assertThat assertions as required by coding guidelines.


47-74: Excellent API key validation test coverage.

The API key validation tests cover all edge cases appropriately with descriptive names and specific assertions. The test scenarios (null, oversized, valid, and fake keys) provide comprehensive coverage.

src/main/webapp/app/sharing/sharing.route.ts (5)

1-4: LGTM! Proper imports and conventions.

The imports follow Angular conventions and use the required single quotes for strings. Good use of authority constants for role-based access control.


5-14: Well-structured route configuration.

The route configuration follows Angular routing best practices:

  • Proper authority restrictions for EDITOR, INSTRUCTOR, and ADMIN roles
  • Correct page title configuration for localization
  • Clean route structure

16-23: Good routing architecture pattern.

The use of spread operator and nested route structure provides flexibility for future route additions while maintaining clean organization.


5-14: Well-structured route configuration.

The route configuration follows Angular style guidelines correctly with proper authority restrictions, localization key usage, and single quotes for strings.


1-23: LGTM! Clean routing implementation.

The routing configuration follows Angular best practices and coding guidelines effectively:

  • Uses single quotes for strings ✓
  • Proper authority restrictions for EDITOR, INSTRUCTOR, and ADMIN ✓
  • Clean route structure with appropriate nesting ✓
  • Follows Angular style guide conventions ✓
src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingException.java (4)

10-15: LGTM! Proper exception class structure.

Good use of @Profile annotation and proper serialVersionUID declaration following Java serialization best practices.


32-42: Good static factory method pattern.

The connectionError static factory method is well-designed and properly documented. It provides a convenient way to create connection-specific exceptions with a consistent message format.


10-14: Well-designed exception class structure.

The exception class properly extends Exception, uses the sharing profile, and includes the required serialVersionUID for serialization compatibility.


39-41: LGTM! Well-designed static factory method.

The connectionError static factory method provides a clear and convenient way to create connection-specific exceptions with proper formatting.

src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingServiceTest.java (1)

17-17: Approve the test class structure.

The test class properly extends the appropriate base test class and follows the established testing patterns with proper setup and teardown methods.

src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingPlatformMockProvider.java (1)

58-67: 🛠️ Refactor suggestion

Improve error handling and method documentation.

The method should handle potential JSON parsing errors more gracefully and validate the response content.

 public SharingPluginConfig connectRequestFromSharingPlatform() throws Exception {
+    if (sharingApiKey == null || sharingApiKey.isBlank()) {
+        throw new IllegalStateException("Sharing API key is not configured for testing");
+    }
+    
     MvcResult result = restMockMvc
-            .perform(get("/api/sharing/config").queryParam("apiBaseUrl", SHARING_BASEURL_PLUGIN).queryParam("installationName", TEST_INSTALLATION_NAME)
+            .perform(get("/api/sharing/config").queryParam("apiBaseUrl", getSharingPluginUrl()).queryParam("installationName", testInstallationName)
                     .header("Authorization", sharingApiKey).contentType(MediaType.APPLICATION_JSON))
             .andExpect(content().contentType(MediaType.APPLICATION_JSON)).andExpected(status().isOk()).andReturn();
     String content = result.getResponse().getContentAsString();
+    
+    if (content == null || content.isBlank()) {
+        throw new IllegalStateException("Empty response received from sharing config endpoint");
+    }
+    
     SharingPluginConfig sharingPluginConfig = objectMapper.readerFor(SharingPluginConfig.class).readValue(content);
     assertThat(sharingPluginConfig.pluginName).isEqualTo("Artemis Sharing Connector");
     return sharingPluginConfig;
 }

Likely an incorrect or invalid review comment.

src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.ts (4)

55-55: LGTM! Clean integration of sharing functionality.

The imports and component registration follow Angular best practices. The sharing component is properly added to the imports array for standalone component usage.

Also applies to: 69-70, 92-92


159-159: Good implementation of feature flag pattern.

The isExportToSharingEnabled flag and subscription management follow Angular patterns correctly. Constructor injection of the sharing service is appropriate.

Also applies to: 173-173, 179-179


275-286: Well-implemented subscription with proper error handling.

The subscription to isSharingEnabled() includes appropriate error handling that defaults to false when the sharing service is unavailable. The mapping and error handling are robust.


294-294: Memory leak prevention properly implemented.

The sharing subscription is correctly unsubscribed in ngOnDestroy(), preventing memory leaks.

src/main/java/de/tum/cit/aet/artemis/core/config/Constants.java (2)

369-372: Well-documented profile constant.

The PROFILE_SHARING constant follows Java naming conventions and includes clear documentation.


375-377: Consistent API path constants.

The sharing-related path constants are well-organized and follow the existing pattern. The SHARINGCONFIG_RESOURCE_IS_ENABLED constant correctly builds upon the base path.

Also applies to: 484-497

src/main/webapp/app/programming/manage/services/programming-exercise-sharing.service.ts (5)

20-27: Service structure follows Angular best practices.

The service uses proper dependency injection and follows the singleton pattern with providedIn: 'root'. The API endpoint definitions are appropriately organized.


32-39: HTTP request properly structured.

The getSharedExercises method correctly uses observables and HTTP client patterns. The parameter passing and response mapping are well-implemented.


72-93: Excellent circular reference handling.

The convertDataFromClient method properly handles circular dependencies by removing the exercise reference from participations. This prevents serialization issues when sending data to the backend.


101-110: Robust date conversion handling.

The date conversion method properly handles potential undefined values and uses dayjs consistently with the rest of the application.


128-132: Simple and effective feature detection.

The isSharingEnabled method provides a clean way to check if sharing functionality is available. The HTTP response approach allows for proper error handling upstream.

src/main/webapp/app/sharing/search-result-dto.model.ts (4)

4-12: Well-structured main interface.

The SearchResultDTO interface provides a comprehensive structure for sharing platform search results, including all necessary metadata and statistics.


65-70: Enum naming follows external API requirements.

The IExerciseType enum maintains compatibility with the sharing platform connector interface. The naming convention (with 'I' prefix and lowercase values) is intentionally different from internal TypeScript conventions to ensure external API compatibility.


75-83: Snake_case naming required for API compatibility.

The ProjectDTO interface correctly uses snake_case property names to maintain compatibility with the external sharing platform's API contract, which is an intentional and necessary deviation from Angular's camelCase convention.


26-51: Comprehensive metadata interface.

The UserProvidedMetadataDTO interface correctly matches the sharing platform's contract. The types and structure are essential for maintaining API compatibility.

docs/admin/setup/sharing.rst (1)

1-95: Excellent documentation structure and completeness.

This setup guide provides comprehensive coverage of the sharing integration configuration, including:

  • Clear prerequisites and access requirements
  • Multiple configuration options (YAML and environment variables)
  • Instructor access setup with EduID and GitLab options
  • Troubleshooting guidance with health indicators
  • Visual aids to guide administrators

The documentation follows a logical flow and provides practical examples for both Docker and non-Docker deployments.

docs/user/sharing.rst (1)

1-111: Well-structured user guide with clear workflows.

This user documentation effectively guides users through the sharing functionality with:

  • Clear background information and prerequisites
  • Step-by-step export process with metadata guidance
  • Detailed import workflow from the sharing platform
  • Helpful screenshots and visual aids throughout
  • Practical tips about login requirements and potential issues

The documentation balances technical accuracy with user-friendliness.

src/main/webapp/app/programming/manage/grading/charts/programming-exercise-detail.component.with-sharing.spec.ts (3)

110-153: Well-structured test setup with comprehensive mocking.

The beforeEach setup properly configures all necessary mocks and dependencies for testing the sharing functionality. The use of MockProvider and specific mock services follows Angular testing best practices.


173-184: Good test coverage for successful sharing configuration.

The test properly verifies that the component sets isExportToSharingEnabled to true when the sharing API returns a successful response.


186-203: Effective error handling test for sharing configuration.

The test correctly validates that the component handles 404 responses gracefully by setting isExportToSharingEnabled to false.

src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingMultipartZipFile.java (3)

15-19: Well-structured class with appropriate profile activation.

The class properly implements required interfaces and uses the sharing profile for conditional activation.


25-31: Excellent defensive programming in constructor.

Proper null validation and buffering of the input stream improve reliability and performance.


86-90: Proper resource cleanup implementation.

The close method safely handles null checks and properly closes the underlying stream.

src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ProgrammingExerciseImportFromSharingService.java (2)

21-57: Excellent Spring service structure with proper dependency injection.

The service correctly uses constructor injection, profile activation, and has well-defined dependencies with clear responsibilities.


75-77: Good defensive programming for course assignment.

The code properly handles the case where the exercise doesn't have an associated course and assigns it from the sharing setup info.

src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceExportTest.java (4)

39-63: Well-structured integration test setup.

The test class properly extends the base integration test class and correctly manages the sharing platform mock with appropriate setup and teardown methods.


77-90: Effective error scenario testing.

The test properly verifies error handling when repositories are missing, checking both status code and error message content.


94-125: Comprehensive successful export test.

The test thoroughly validates the export workflow, including redirect URL parsing, parameter validation, and ZIP file download verification.


127-137: Well-implemented URL parsing helper method.

The helper method properly handles URI parsing and parameter encoding for MockMvc request building. Good separation of concerns.

src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java (2)

105-117: Security properly implemented with PreAuthorize.

Good use of PreAuthorize annotation to restrict access to instructors, editors, and admins. The method properly delegates the business logic to the service layer.


119-147:

✅ Verification successful

Verify security configuration for sharing endpoints.

The AI summary mentions that security configuration allows unauthenticated access to /api/sharing/** endpoints. Please verify if the missing PreAuthorize annotations on getProblemStatement and getExerciseDetails are intentional, given that these endpoints handle checksum validation internally.


🏁 Script executed:

#!/bin/bash
# Description: Check security configuration for sharing endpoints

# Search for security configuration related to sharing endpoints
rg -A 10 -B 5 "/api/sharing" --glob "**/*Security*.java" --glob "**/*Config*.java"

# Check if there are any security filters or configurations specifically for sharing
fd -e java -x grep -l "sharing.*permit" {} \;

# Look for WebSecurityConfig or similar files that might configure these endpoints
ast-grep --pattern 'class $_ {
  $$$
  "/api/sharing"
  $$$
}'

Length of output: 2716


Security configuration for /api/sharing/** is intentional
The SecurityConfiguration explicitly calls .requestMatchers("/api/sharing/**").permitAll() with the note that sharing endpoints enforce their own checksum‐based token validation. The absence of @PreAuthorize on getProblemStatement and getExerciseDetails is therefore expected—no changes needed.

Comment on lines 35 to 39
@Test
void validationFailure() {
assertThat(exerciseSharingService.validate("invalidToken", "invalid sec")).isFalse();
assertThat(exerciseSharingService.getExportedExerciseByToken("invalidToken")).isNull();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve test method name and add more comprehensive validation tests.

The test method name validationFailure() is not very descriptive according to the coding guidelines requiring descriptive test naming. Also, the test could be more comprehensive by testing edge cases and different validation scenarios.

 @Test
-void validationFailure() {
+void shouldReturnFalseForInvalidTokenAndSecurityString() {
     assertThat(exerciseSharingService.validate("invalidToken", "invalid sec")).isFalse();
     assertThat(exerciseSharingService.getExportedExerciseByToken("invalidToken")).isNull();
 }
+
+@Test
+void shouldReturnFalseForNullTokenAndSecurityString() {
+    assertThat(exerciseSharingService.validate(null, null)).isFalse();
+    assertThat(exerciseSharingService.getExportedExerciseByToken(null)).isNull();
+}
+
+@Test
+void shouldReturnFalseForEmptyTokenAndSecurityString() {
+    assertThat(exerciseSharingService.validate("", "")).isFalse();
+    assertThat(exerciseSharingService.getExportedExerciseByToken("")).isNull();
+}
🤖 Prompt for AI Agents
In
src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingServiceTest.java
around lines 35 to 39, rename the test method from validationFailure() to a more
descriptive name that clearly indicates it tests invalid token and security
string validation. Additionally, expand the test to cover more validation
scenarios and edge cases, such as null or empty tokens, valid tokens with
invalid security strings, and any other relevant cases to ensure comprehensive
validation coverage.

Comment on lines 171 to 210
/**
* GET /sharing/export/{exerciseToken}: Endpoint exposing an exported exercise zip to Sharing
*
* @param token in base64 format and used to retrieve the exercise
* @return a stream of the zip file
* @throws FileNotFoundException if zip file does not exist anymore
*/
@GetMapping(SHARINGEXPORT_RESOURCE_PATH + "/{token}")
// Custom Key validation is applied
public ResponseEntity<Resource> exportExerciseToSharing(@PathVariable("token") String token, @RequestParam("sec") String sec) throws FileNotFoundException {
if (sec.isEmpty() || !exerciseSharingService.validate(token, sec)) {
log.warn("Security Token {} is not valid", sec);
return ResponseEntity.status(401).body(null);
}
File zipFile = exerciseSharingService.getExportedExerciseByToken(token);

if (zipFile == null) {
return ResponseEntity.notFound().build();
}

/*
* Customized FileInputStream to delete and therefore clean up the returned files
*/
class NewFileInputStream extends FileInputStream {

final File file;

public NewFileInputStream(@NotNull File file) throws FileNotFoundException {
super(file);
this.file = file;
}

public void close() throws IOException {
super.close();
if (!file.delete()) {
log.warn("Could not delete imported file from Sharing-Platform");
}
}
}

InputStreamResource resource = new InputStreamResource(new NewFileInputStream(zipFile));

return ResponseEntity.ok().contentLength(zipFile.length()).contentType(MediaType.APPLICATION_OCTET_STREAM).header("filename", zipFile.getName()).body(resource);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract inner class and improve file handling.

  1. The inner class NewFileInputStream violates single responsibility principle and should be extracted
  2. File deletion might fail silently and is not OS-independent
  3. Return proper error response instead of null

Extract the inner class to a separate utility class:

// In a separate file or as a private static class
public class AutoDeletingFileInputStream extends FileInputStream {
    private final File file;
    
    public AutoDeletingFileInputStream(@NotNull File file) throws FileNotFoundException {
        super(file);
        this.file = file;
    }
    
    @Override
    public void close() throws IOException {
        try {
            super.close();
        } finally {
            try {
                Files.deleteIfExists(file.toPath());
            } catch (IOException e) {
                log.warn("Could not delete imported file from Sharing-Platform: {}", e.getMessage());
            }
        }
    }
}

Update the method:

         if (sec.isEmpty() || !exerciseSharingService.validate(token, sec)) {
             log.warn("Security Token {} is not valid", sec);
-            return ResponseEntity.status(401).body(null);
+            return ResponseEntity.status(401).build();
         }

And use the extracted class:

-        InputStreamResource resource = new InputStreamResource(new NewFileInputStream(zipFile));
+        InputStreamResource resource = new InputStreamResource(new AutoDeletingFileInputStream(zipFile));
🤖 Prompt for AI Agents
In
src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java
around lines 171 to 214, extract the inner class NewFileInputStream into a
separate utility class named AutoDeletingFileInputStream to adhere to single
responsibility principle. Replace the file deletion logic with
Files.deleteIfExists(file.toPath()) inside a finally block in the close method
to handle OS-independent deletion and catch any IOException to log a warning
with the exception message. Also, update the exportExerciseToSharing method to
use this new class and return a proper error response instead of null when the
security token is invalid.

@github-project-automation github-project-automation bot moved this from Work In Progress to Ready For Review in Artemis Development Jun 6, 2025
Copy link
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.

Review continued from previous batch...

Comment on lines 87 to 117
/**
* GET .../sharing-import/basket
*
* @return the ResponseEntity with status 200 (OK) and with body the Shopping Basket, or with status 404 (Not Found)
*/
@GetMapping("/sharing/import/basket")
public ResponseEntity<ShoppingBasket> loadShoppingBasket(@RequestParam String basketToken, @RequestParam String returnURL, @RequestParam String apiBaseURL,
@RequestParam String checksum) {
if (SecretChecksumCalculator.checkChecksum(Map.of("returnURL", returnURL, "apiBaseURL", apiBaseURL), sharingConnectorService.getSharingApiKeyOrNull(), checksum)) {
Optional<ShoppingBasket> sharingInfoDTO = exerciseSharingService.getBasketInfo(basketToken, apiBaseURL);
return ResponseUtil.wrapOrNotFound(sharingInfoDTO);
}
else {
log.warn("Checksum validation failed for basketToken={}", basketToken);
return ResponseEntity.badRequest().body(null);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Return proper error response instead of null body.

When checksum validation fails, the method returns a null body which is not RESTful. Consider returning a proper error message.

         else {
             log.warn("Checksum validation failed for basketToken={}", basketToken);
-            return ResponseEntity.badRequest().body(null);
+            return ResponseEntity.badRequest().build();
         }
🤖 Prompt for AI Agents
In
src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java
between lines 87 and 103, the method loadShoppingBasket returns a ResponseEntity
with a null body and bad request status when checksum validation fails. Instead
of returning a null body, modify the response to include a meaningful error
message or error object in the body to provide a proper RESTful error response.

Copy link

github-actions bot commented Jun 6, 2025

End-to-End (E2E) Test Results Summary

Copy link
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: 9

♻️ Duplicate comments (5)
src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorService.java (1)

242-251: Use Spring's task executor instead of creating a new executor.

This has already been identified in past reviews. Creating a new single-thread executor for one task is inefficient. Spring provides task execution infrastructure that should be used instead.

src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (1)

264-264: Fix typo in comment.

This typo has already been identified in past reviews and should be corrected.

src/main/webapp/app/sharing/sharing.component.ts (3)

80-83: Add null safety check for token expiry calculation.

The method should handle the case where tokenValidUntil might be undefined or invalid.


88-95: Improve error handling to avoid exposing internal details.

The error handling directly exposes server error messages to users, which can reveal sensitive information.


173-180: Improve error handling consistency.

The error handling here is inconsistent with the pattern used elsewhere in the component.

🧹 Nitpick comments (7)
src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts (2)

34-36: Consider consolidating test files for better maintainability.

The comment indicates this is a temporary separation for sharing-specific tests. Consider either merging with the main test file or making this separation permanent with clear documentation.

If this separation is intended to be permanent, update the comment to reflect the rationale:

-/*
- * just use a separate file for sharing aspects, could be merged into programming-exercise-detail.component.spec.ts if stabilized.
- */
+/**
+ * Dedicated test suite for sharing functionality in ProgrammingExerciseDetailComponent.
+ * Separated to maintain focused testing of the sharing feature integration.
+ */

173-184: Enhance test assertions for better validation.

The tests correctly verify the isExportToSharingEnabled flag but could benefit from additional assertions to ensure complete behavior validation.

Consider adding assertions to verify the HTTP request details and component state:

         it('should be in sharing mode', async () => {
             // WHEN
             comp.ngOnInit();
             comp.programmingExercise = mockProgrammingExercise;
             comp.programmingExerciseBuildConfig = mockProgrammingExercise.buildConfig;
 
             const req = httpMock.expectOne({ method: 'GET', url: 'api/sharing/config/isEnabled' });
+            expect(req.request.method).toBe('GET');
+            expect(req.request.url).toBe('api/sharing/config/isEnabled');
             req.flush(true);
 
             // THEN
             expect(comp.isExportToSharingEnabled).toBeTruthy();
+            expect(comp.programmingExercise).toEqual(mockProgrammingExercise);
         });

Apply similar enhancements to the negative test case for consistency.

Also applies to: 186-203

src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorService.java (1)

37-37: Fix typo in constant name.

The constant name has a typo that affects readability and professionalism.

-    public static final String UNKNOWN_INSTALLATIONAME = "unknown installationame";
+    public static final String UNKNOWN_INSTALLATION_NAME = "unknown installation name";
src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (2)

355-386: Extract utility method to test utility class.

The makeCourseJSONSerializable method is a utility that modifies course entities for JSON serialization. This violates the single responsibility principle for test classes.

Consider extracting this method to a dedicated test utility class like CourseTestUtil or JsonSerializationTestUtil to promote reusability across test classes and improve maintainability.

public static void makeCourseJSONSerializable(Course course) {
    // existing implementation
}

223-248: Consider extracting checksum utility methods.

The parseParamsToMap, calculateCorrectChecksum, and addCorrectChecksum methods are utility functions that could be reused across other sharing-related tests.

Consider extracting these to a shared test utility class like SharingTestUtil to promote code reuse and reduce duplication across test classes in the sharing module.

src/main/webapp/app/sharing/sharing.component.ts (2)

109-111: Consider more descriptive method name and validation.

The method name courseId() could be more descriptive, and there's no validation for course selection state.

Consider renaming and adding validation:

-    courseId(): number {
-        return this.selectedCourse?.id ?? 0;
-    }
+    getSelectedCourseId(): number {
+        if (!this.selectedCourse) {
+            throw new Error('No course selected');
+        }
+        return this.selectedCourse.id;
+    }

126-128: Fix return type annotation for trackId method.

The trackId method should explicitly return a number type to match Angular's TrackByFunction interface requirements.

-    trackId(index: number, item: Course) {
+    trackId(index: number, item: Course): number {
         return item.id;
     }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d05f0ee and b8a6109.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (22)
  • src/main/java/de/tum/cit/aet/artemis/core/service/ProfileService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/web/SharingSupportResource.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportFromFileService.java (4 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingException.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingHealthIndicator.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSetupInfo.java (1 hunks)
  • src/main/webapp/app/app.routes.ts (1 hunks)
  • src/main/webapp/app/environments/environment.override.ts (1 hunks)
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts (1 hunks)
  • src/main/webapp/app/programming/shared/actions/programming-exercise-instructor-exercise-sharing.component.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.html (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.spec.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.model.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.scss (1 hunks)
  • src/main/webapp/i18n/en/sharing.json (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorHealthCheckRegistryTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingInfoDTOTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingPlatformMockProvider.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSupportResourceTest.java (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • src/main/webapp/app/environments/environment.override.ts
  • src/main/webapp/app/sharing/sharing.component.html
  • src/main/webapp/app/sharing/sharing.model.ts
🚧 Files skipped from review as they are similar to previous changes (15)
  • src/main/java/de/tum/cit/aet/artemis/core/service/ProfileService.java
  • src/main/webapp/i18n/en/sharing.json
  • src/main/webapp/app/app.routes.ts
  • src/main/webapp/app/sharing/sharing.scss
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSetupInfo.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingHealthIndicator.java
  • src/main/webapp/app/sharing/sharing.component.spec.ts
  • src/main/java/de/tum/cit/aet/artemis/core/web/SharingSupportResource.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportFromFileService.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingException.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSupportResourceTest.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingPlatformMockProvider.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorHealthCheckRegistryTest.java
  • src/main/webapp/app/programming/shared/actions/programming-exercise-instructor-exercise-sharing.component.ts
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingInfoDTOTest.java
🧰 Additional context used
📓 Path-based instructions (3)
`src/main/webapp/**/*.ts`: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalC...

src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

  • src/main/webapp/app/sharing/sharing.component.ts
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts
`src/test/java/**/*.java`: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_...

src/test/java/**/*.java: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_use: enforce_package_rules; db_query_count_tests: track_performance; util_service_factory_pattern: true; avoid_db_access: true; mock_strategy: static_mocks; context_restart_minimize: true

  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java
`src/main/java/**/*.java`: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,de...

src/main/java/**/*.java: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorService.java
🪛 GitHub Check: client-tests-selected
src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts

[failure] 49-49:
'submissionPolicyServiceStub' is declared but its value is never read.


[failure] 48-48:
'profileServiceStub' is declared but its value is never read.


[failure] 47-47:
'gitDiffReportStub' is declared but its value is never read.


[failure] 46-46:
'statisticsServiceStub' is declared but its value is never read.


[failure] 42-42:
'alertService' is declared but its value is never read.


[failure] 30-30:
Cannot find module 'app/entities/programming/build-log-statistics-dto' or its corresponding type declarations.


[failure] 27-27:
'"app/programming/shared/services/programming-language-feature/programming-language-feature.service"' has no exported member named 'ProgrammingLanguageFeature'. Did you mean 'ProgrammingLanguageFeatureService'?


[failure] 26-26:
Cannot find module 'app/shared/layouts/profiles/profile-info.model' or its corresponding type declarations.


[failure] 24-24:
Cannot find module 'app/entities/participation/solution-programming-exercise-participation.model' or its corresponding type declarations.


[failure] 23-23:
Cannot find module 'app/entities/participation/template-programming-exercise-participation.model' or its corresponding type declarations.

🪛 GitHub Check: client-tests
src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts

[failure] 49-49:
'submissionPolicyServiceStub' is declared but its value is never read.


[failure] 48-48:
'profileServiceStub' is declared but its value is never read.


[failure] 47-47:
'gitDiffReportStub' is declared but its value is never read.


[failure] 46-46:
'statisticsServiceStub' is declared but its value is never read.


[failure] 42-42:
'alertService' is declared but its value is never read.


[failure] 30-30:
Cannot find module 'app/entities/programming/build-log-statistics-dto' or its corresponding type declarations.


[failure] 27-27:
'"app/programming/shared/services/programming-language-feature/programming-language-feature.service"' has no exported member named 'ProgrammingLanguageFeature'. Did you mean 'ProgrammingLanguageFeatureService'?


[failure] 26-26:
Cannot find module 'app/shared/layouts/profiles/profile-info.model' or its corresponding type declarations.


[failure] 24-24:
Cannot find module 'app/entities/participation/solution-programming-exercise-participation.model' or its corresponding type declarations.


[failure] 23-23:
Cannot find module 'app/entities/participation/template-programming-exercise-participation.model' or its corresponding type declarations.

⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: client-style
  • GitHub Check: Analyse
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
🔇 Additional comments (3)
src/main/webapp/app/sharing/sharing.component.ts (3)

1-21: LGTM - Clean imports and proper dependency structure.

The imports are well-organized and follow Angular conventions. Good use of standalone component imports and proper separation of concerns.


25-31: LGTM - Proper standalone component configuration.

The component decorator correctly configures the standalone component with all necessary imports. The selector follows kebab-case naming convention as required.


33-62: LGTM - Well-structured component properties.

Properties follow camelCase naming convention and avoid private prefixes as required by the coding guidelines. Good use of TypeScript typing and proper initialization.

Comment on lines 197 to 218
public SharingPluginConfig getPluginConfig(URL apiBaseUrl, Optional<String> installationName) {
this.sharingApiBaseUrl = apiBaseUrl;
try {
this.installationName = installationName.orElse(InetAddress.getLocalHost().getCanonicalHostName());
}
catch (UnknownHostException e) {
this.installationName = UNKNOWN_INSTALLATIONAME;
}
SharingPluginConfig.Action action = new SharingPluginConfig.Action("Import", "/sharing/import", actionName,
"metadata.format.stream().anyMatch(entry->entry=='artemis' || entry=='Artemis').get()");
lastHealthStati.add(new HealthStatus("Delivered Sharing Config Status to " + apiBaseUrl));
lastHealthStati.setLastConnect();
return new SharingPluginConfig("Artemis Sharing Connector", new SharingPluginConfig.Action[] { action });
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider thread safety for getPluginConfig method.

This method mutates instance state (sharingApiBaseUrl and installationName) which could be problematic in concurrent scenarios, especially in a Spring service that's typically singleton-scoped.

Consider making this method thread-safe by either:

  1. Synchronizing the method
  2. Using local variables and returning them in the config object
  3. Making the state immutable after initialization
🤖 Prompt for AI Agents
In
src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorService.java
around lines 197 to 210, the getPluginConfig method modifies instance variables
sharingApiBaseUrl and installationName, which risks thread safety in a singleton
Spring service. To fix this, avoid mutating instance state inside this method by
using local variables for apiBaseUrl and installationName instead, and construct
the SharingPluginConfig object solely from these locals. Remove or refactor any
instance state changes to ensure the method is thread-safe and side-effect free.

Comment on lines 218 to 243
public boolean validate(String apiKey) {
if (apiKey == null || apiKey.length() > 200) {
// this is just in case, somebody tries an attack
lastHealthStati.add(new HealthStatus("Failed api Key validation"));

return false;
}
Pattern p = Pattern.compile("Bearer\\s(.+)");
Matcher m = p.matcher(apiKey);
if (m.matches()) {
apiKey = m.group(1);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve API key validation robustness.

The current validation has some weaknesses:

  1. The hardcoded length limit of 200 seems arbitrary
  2. The regex pattern could be more robust
 public boolean validate(String apiKey) {
-    if (apiKey == null || apiKey.length() > 200) {
+    if (apiKey == null || apiKey.trim().isEmpty() || apiKey.length() > 500) {
         // this is just in case, somebody tries an attack
         lastHealthStati.add(new HealthStatus("Failed api Key validation"));
         return false;
     }
-    Pattern p = Pattern.compile("Bearer\\s(.+)");
+    Pattern p = Pattern.compile("^Bearer\\s+(.+)$");
     Matcher m = p.matcher(apiKey);
     if (m.matches()) {
         apiKey = m.group(1);
     }
📝 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
public boolean validate(String apiKey) {
if (apiKey == null || apiKey.length() > 200) {
// this is just in case, somebody tries an attack
lastHealthStati.add(new HealthStatus("Failed api Key validation"));
return false;
}
Pattern p = Pattern.compile("Bearer\\s(.+)");
Matcher m = p.matcher(apiKey);
if (m.matches()) {
apiKey = m.group(1);
}
public boolean validate(String apiKey) {
- if (apiKey == null || apiKey.length() > 200) {
+ if (apiKey == null || apiKey.trim().isEmpty() || apiKey.length() > 500) {
// this is just in case, somebody tries an attack
lastHealthStati.add(new HealthStatus("Failed api Key validation"));
return false;
}
- Pattern p = Pattern.compile("Bearer\\s(.+)");
+ Pattern p = Pattern.compile("^Bearer\\s+(.+)$");
Matcher m = p.matcher(apiKey);
if (m.matches()) {
apiKey = m.group(1);
}
🤖 Prompt for AI Agents
In
src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorService.java
between lines 218 and 230, improve the API key validation by replacing the
arbitrary length limit of 200 with a configurable or more reasonable limit based
on expected API key lengths. Also, enhance the regex pattern to more strictly
and clearly match the "Bearer " prefix followed by a valid token, ensuring it
does not allow invalid formats or whitespace issues. Adjust the validation logic
accordingly to handle these improvements.

Comment on lines 261 to 334
@Test
@WithMockUser(username = INSTRUCTORNAME + "1", roles = "INSTRUCTOR")
void importExerciseCompleteProcess() throws Exception {
userUtilService.addInstructor("Sharing", INSTRUCTORNAME); // unfortunatelly this utility extends the username by "1"
User instructor = userUtilService.getUserByLogin(INSTRUCTORNAME + "1");

ObjectMapper objectMapper = new ObjectMapper().registerModule(new com.fasterxml.jackson.datatype.jsr310.JavaTimeModule());

String basketToken = importBasket();

byte[] zippedBytes = IOUtils.toByteArray(Objects.requireNonNull(this.getClass().getResource("./basket/sampleExercise.zip")).openStream());
URI basketURI = new URI(SharingPlatformMockProvider.SHARING_BASEURL_PLUGIN + "/basket/" + basketToken + "/repository/0?format=artemis");
MockRestServiceServer.MockRestServiceServerBuilder builder = MockRestServiceServer.bindTo(restTemplate);
builder.ignoreExpectOrder(true);
mockServer = builder.build();

final ResponseActions responseActions = mockServer.expect(ExpectedCount.once(), requestTo(basketURI)).andExpect(method(HttpMethod.GET));
responseActions.andRespond(MockRestResponseCreators.withSuccess(zippedBytes, MediaType.APPLICATION_OCTET_STREAM));

SharingInfoDTO sharingInfo = new SharingInfoDTO();
sharingInfo.setApiBaseURL(SharingPlatformMockProvider.SHARING_BASEURL_PLUGIN);
sharingInfo.setReturnURL(TEST_RETURN_URL);
sharingInfo.setBasketToken(basketToken);
sharingInfo.setChecksum(calculateCorrectChecksum("returnURL", TEST_RETURN_URL, "apiBaseURL", sharingConnectorService.getSharingApiBaseUrlOrNull().toString()));

Course course1 = programmingExerciseUtilService.addCourseWithOneProgrammingExerciseAndTestCases();

// get Exercise Details

MvcResult resultED = restMockMvc
.perform(post("/api/sharing/import/basket/exerciseDetails").contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(sharingInfo))
.accept(MediaType.APPLICATION_JSON)) /* .andDo(print()) */
.andExpect(content().contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()).andReturn();
String contentED = resultED.getResponse().getContentAsString();
ObjectMapper programmingExerciseObjectMapper = new ObjectMapper();
programmingExerciseObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
programmingExerciseObjectMapper.findAndRegisterModules();

ProgrammingExercise exercise = programmingExerciseObjectMapper.readerFor(ProgrammingExercise.class).readValue(contentED);
assertThat(exercise.getTitle()).isEqualTo("JUnit IO Tests");
assertThat(exercise.getProgrammingLanguage()).isEqualTo(ProgrammingLanguage.JAVA);

// get Problem Statement
MvcResult resultPS = restMockMvc.perform(post("/api/sharing/import/basket/problemStatement").contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(sharingInfo)).accept(MediaType.APPLICATION_JSON)).andExpect(content().contentType(MediaType.TEXT_PLAIN))
.andExpect(status().isOk()).andReturn();
// Zip file is cached, no extra request to sharing platform required!
String contentPS = resultPS.getResponse().getContentAsString();
assertThat(contentPS).startsWith("# Simpler IO Test");

SharingSetupInfo setupInfo = new SharingSetupInfo(exercise, course1, sharingInfo);

// last step: do Exercise Import
makeCourseJSONSerializable(course1);
// just deactivate programmingLanguageFeatureService validation
ProgrammingLanguageFeature trivialProgrammingLanguageFeatures = new ProgrammingLanguageFeature(exercise.getProgrammingLanguage(), true, false, false, true, false,
List.of(ProjectType.PLAIN_MAVEN), false);
when(programmingLanguageFeatureService.getProgrammingLanguageFeatures(any())).thenReturn(trivialProgrammingLanguageFeatures);
// mock gitService et all.
when(versionControlService.checkIfProjectExists(eq(exercise.getProjectKey()), eq(exercise.getProjectName()))).thenReturn(false);
when(continuousIntegrationService.checkIfProjectExists(eq(exercise.getProjectKey()), eq(exercise.getProjectName()))).thenReturn(null);
// when(continuousIntegrationService.createProjectForExercise(any()));
when(continuousIntegrationService.copyBuildPlan(any(), anyString(), any(), anyString(), anyString(), anyBoolean())).thenReturn("");
// when(continuousIntegrationService.givePlanPermissions(any(), anyString()))
// when(continuousIntegrationService.enablePlan(any(), any()));

when(versionControlService.getDefaultBranchOfArtemis()).thenReturn("main");
when(versionControlService.getCloneRepositoryUri(eq(exercise.getProjectKey()), any())).thenReturn(new VcsRepositoryUri("http://some.cloneurl"));

doAnswer(invocation -> {
VcsRepositoryUri uri = invocation.getArgument(0, VcsRepositoryUri.class);
Repository mockedRepository = Mockito.mock(Repository.class);
Mockito.when(mockedRepository.getLocalPath()).thenReturn(Files.createTempDirectory("sharingImportTest" + uri.hashCode()));
return mockedRepository;
}).when(gitService).getOrCheckoutRepository(any(), anyBoolean());

doNothing().when(gitService).stageAllChanges(any());
doNothing().when(gitService).commitAndPush(any(), anyString(), anyBoolean(), any());
doReturn(List.of()).when(gitService).getFiles(any());
doAnswer(invocation -> ObjectId.fromString("419a93c002688aeb7a5fd3badada7f263c1926ba")).when(gitService).getLastCommitHash(any());
// doNothing().when(continuousIntegrationTriggerService).triggerBuild(any());

String setupInfoJsonString = objectMapper.writeValueAsString(setupInfo);
MvcResult resultPE = restMockMvc
.perform(post("/api/sharing/setup-import").contentType(MediaType.APPLICATION_JSON).content(setupInfoJsonString).accept(MediaType.APPLICATION_JSON))
.andExpect(content().contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()).andReturn();

// finally cleanup the cache
exerciseSharingService.getRepositoryCache().asMap().forEach((key, value) -> {
exerciseSharingService.getRepositoryCache().invalidate(key);
});
exerciseSharingService.getRepositoryCache().cleanUp();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor large test method for better maintainability.

The importExerciseCompleteProcess method is quite long (93 lines) and covers multiple responsibilities, violating the single responsibility principle mentioned in coding guidelines.

Consider breaking this test into smaller, focused test methods:

  1. shouldImportBasketToken() - for basket import verification
  2. shouldFetchExerciseDetails() - for exercise details validation
  3. shouldFetchProblemStatement() - for problem statement validation
  4. shouldCompleteExerciseSetup() - for final setup import

Each method should focus on a specific aspect of the import process, making tests easier to understand and maintain.

🤖 Prompt for AI Agents
In
src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java
around lines 261 to 353, the importExerciseCompleteProcess test method is too
long and handles multiple responsibilities. Refactor by splitting this method
into smaller, focused test methods: one for verifying basket token import, one
for validating exercise details fetching, one for checking problem statement
retrieval, and one for completing the exercise setup import. Each new test
should isolate and test a specific part of the process to improve readability
and maintainability.

Copy link

github-actions bot commented Jun 6, 2025

End-to-End (E2E) Test Results Summary

2 similar comments
Copy link

github-actions bot commented Jun 6, 2025

End-to-End (E2E) Test Results Summary

Copy link

github-actions bot commented Jun 6, 2025

End-to-End (E2E) Test Results Summary

@Wallenstein61 Wallenstein61 force-pushed the feature/connecting-to-sharing-platform branch from 562e101 to 8f6ada8 Compare June 6, 2025 15:35
Copy link

github-actions bot commented Jun 6, 2025

End-to-End (E2E) Test Results Summary

Copy link
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: 3

♻️ Duplicate comments (11)
src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java (6)

259-259: Remove server-side HTML escaping for Markdown content.

Server-side escaping of problemStatement will break Markdown formatting when rendered by the client-side Markdown renderer. The client should handle sanitization.

- return Objects.requireNonNullElse(org.springframework.web.util.HtmlUtils.htmlEscape(problemStatement), "No Problem Statement found!");
+ return Objects.requireNonNullElse(problemStatement, "No Problem Statement found!");

370-370: Use proper URL-safe Base64 encoding instead of manual padding removal.

Manually removing Base64 padding can invalidate the encoding. Use URL-safe Base64 encoder without padding for clean, valid encoding.

- String tokenInB64 = Base64.getEncoder().encodeToString(token.getBytes()).replaceAll("=+$", "");
+ String tokenInB64 = Base64.getUrlEncoder().withoutPadding().encodeToString(token.getBytes());

415-416: Don't return empty array on cryptographic failures.

Returning an empty byte array on HMAC generation failure is a security vulnerability as it produces predictable output.

catch (NoSuchAlgorithmException | InvalidKeyException e) {
-    return Base64.getEncoder().encodeToString(new byte[] {});
+    throw new SharingException("Failed to generate HMAC", e);
}

404-406: Add null check for pre-shared key.

The PSK retrieved from getSharingApiKeyOrNull() could be null if not configured, causing a NullPointerException.

String psk = sharingConnectorService.getSharingApiKeyOrNull();
+ if (psk == null) {
+     throw new IllegalStateException("Sharing API key is not configured");
+ }
SecretKeySpec secretKeySpec = new SecretKeySpec(psk.getBytes(StandardCharsets.UTF_8), algorithm);

429-432: Use proper URL decoding instead of manual character replacement.

Manual space-to-plus conversion indicates improper URL decoding. This should be handled at the REST endpoint level with proper @RequestParam configuration.


443-449: Strengthen token validation against directory traversal.

The decoded token is used directly in path construction, which could allow directory traversal attacks if the token contains path separators.

String decodedToken = new String(Base64.getDecoder().decode(b64Token));
+ // Additional validation after decoding
+ if (decodedToken.contains("..") || decodedToken.contains("/") || decodedToken.contains("\\")) {
+     log.warn("Token contains invalid path characters: {}", b64Token);
+     return null;
+ }
Path parent = Paths.get(repoDownloadClonePath, decodedToken + ".zip");
src/main/webapp/app/sharing/sharing.component.spec.ts (2)

133-133: Fix typo in error message.

"Bakset" should be "Basket".

- { message: 'Bakset not found' }, // error body
+ { message: 'Basket not found' }, // error body

147-147: Missing toBeBetween() custom matcher definition.

The .toBeBetween() matcher used here isn't defined anywhere in the codebase, causing test failures.

Create a custom matcher in your test helpers:

beforeAll(() => {
  jasmine.addMatchers({
    toBeBetween: () => ({
      compare(actual: Date, floor: Date, ceiling: Date) {
        const pass = actual >= floor && actual <= ceiling;
        return {
          pass,
          message: pass
            ? `Expected ${actual} not to be between ${floor} and ${ceiling}`
            : `Expected ${actual} to be between ${floor} and ${ceiling}`,
        };
      },
    }),
  });
});
src/main/webapp/app/sharing/sharing.component.ts (2)

80-84: Add null safety check for token expiry calculation.

The method should handle the case where tokenValidUntil might be undefined or invalid.

getTokenExpiryDate(): Date {
-    if (this.shoppingBasket) {
+    if (this.shoppingBasket?.tokenValidUntil) {
        return new Date(this.shoppingBasket.tokenValidUntil);
    }
    return new Date();
}

93-93: Avoid exposing internal error messages to users.

Directly concatenating res.message in error alerts can expose sensitive internal information to users.

- error: (res: HttpErrorResponse) => this.alertService.error('Cannot load courses: ' + res.message),
+ error: (res: HttpErrorResponse) => this.alertService.error('artemisApp.sharing.error.loadingCourses'),
src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts (1)

23-30: Fix import path errors that prevent compilation.

Multiple imports have incorrect paths causing compilation failures. Based on the relevant code snippets, here are the correct paths:

-import { TemplateProgrammingExerciseParticipation } from 'app/entities/participation/template-programming-exercise-participation.model';
-import { SolutionProgrammingExerciseParticipation } from 'app/entities/participation/solution-programming-exercise-participation.model';
+import { TemplateProgrammingExerciseParticipation } from 'app/exercise/shared/entities/participation/template-programming-exercise-participation.model';
+import { SolutionProgrammingExerciseParticipation } from 'app/exercise/shared/entities/participation/solution-programming-exercise-participation.model';
-import { ProfileInfo } from 'app/shared/layouts/profiles/profile-info.model';
-import { ProgrammingLanguageFeature, ProgrammingLanguageFeatureService } from 'app/programming/shared/services/programming-language-feature/programming-language-feature.service';
+import { ProfileInfo, ProgrammingLanguageFeature } from 'app/core/layouts/profiles/profile-info.model';
+import { ProgrammingLanguageFeatureService } from 'app/programming/shared/services/programming-language-feature/programming-language-feature.service';
-import { BuildLogStatisticsDTO } from 'app/entities/programming/build-log-statistics-dto';
+// Remove this unused import as BuildLogStatisticsDTO is not used in the test
🧰 Tools
🪛 GitHub Check: client-tests

[failure] 30-30:
Cannot find module 'app/entities/programming/build-log-statistics-dto' or its corresponding type declarations.


[failure] 30-30:
'BuildLogStatisticsDTO' is declared but its value is never read.


[failure] 27-27:
'"app/programming/shared/services/programming-language-feature/programming-language-feature.service"' has no exported member named 'ProgrammingLanguageFeature'. Did you mean 'ProgrammingLanguageFeatureService'?


[failure] 26-26:
Cannot find module 'app/shared/layouts/profiles/profile-info.model' or its corresponding type declarations.


[failure] 24-24:
Cannot find module 'app/entities/participation/solution-programming-exercise-participation.model' or its corresponding type declarations.


[failure] 23-23:
Cannot find module 'app/entities/participation/template-programming-exercise-participation.model' or its corresponding type declarations.

🪛 GitHub Check: client-tests-selected

[failure] 30-30:
Cannot find module 'app/entities/programming/build-log-statistics-dto' or its corresponding type declarations.


[failure] 30-30:
'BuildLogStatisticsDTO' is declared but its value is never read.


[failure] 27-27:
'"app/programming/shared/services/programming-language-feature/programming-language-feature.service"' has no exported member named 'ProgrammingLanguageFeature'. Did you mean 'ProgrammingLanguageFeatureService'?


[failure] 26-26:
Cannot find module 'app/shared/layouts/profiles/profile-info.model' or its corresponding type declarations.


[failure] 24-24:
Cannot find module 'app/entities/participation/solution-programming-exercise-participation.model' or its corresponding type declarations.


[failure] 23-23:
Cannot find module 'app/entities/participation/template-programming-exercise-participation.model' or its corresponding type declarations.

🧹 Nitpick comments (2)
src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts (2)

126-129: Remove duplicate service mock assignment.

The profileService.getProfileInfo method is mocked twice, creating redundant assignments.

         jest.spyOn(exerciseService, 'getDiffReport').mockReturnValue(of(gitDiffReport));
         jest.spyOn(profileService, 'getProfileInfo').mockReturnValue(of(profileInfo));
         jest.spyOn(submissionPolicyService, 'getSubmissionPolicyOfProgrammingExercise').mockReturnValue(of(undefined));
-
-        jest.spyOn(profileService, 'getProfileInfo').mockReturnValue(of(profileInfo));
🧰 Tools
🪛 GitHub Check: client-tests

[failure] 129-129:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.


[failure] 126-126:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.

🪛 GitHub Check: client-tests-selected

[failure] 129-129:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.


[failure] 126-126:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.


46-58: Fix type assertion issue in mock object.

The mock object doesn't fully match the ProgrammingExercise type structure, causing compilation warnings.

-    const mockProgrammingExercise = {
+    const mockProgrammingExercise: Partial<ProgrammingExercise> = {
         id: 1,
         categories: [{ category: 'Important' }],
         templateParticipation: {
             id: 1,
         } as TemplateProgrammingExerciseParticipation,
         solutionParticipation: {
             id: 2,
         } as SolutionProgrammingExerciseParticipation,
         buildConfig: {
             buildTool: 'GRADLE',
         },
-    } as ProgrammingExercise;
+    };
🧰 Tools
🪛 GitHub Check: client-tests

[failure] 46-46:
Conversion of type '{ id: number; categories: { category: string; }[]; templateParticipation: TemplateProgrammingExerciseParticipation; solutionParticipation: SolutionProgrammingExerciseParticipation; buildConfig: { ...; }; }' to type 'ProgrammingExercise' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

🪛 GitHub Check: client-tests-selected

[failure] 46-46:
Conversion of type '{ id: number; categories: { category: string; }[]; templateParticipation: TemplateProgrammingExerciseParticipation; solutionParticipation: SolutionProgrammingExerciseParticipation; buildConfig: { ...; }; }' to type 'ProgrammingExercise' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 562e101 and 8f6ada8.

⛔ Files ignored due to path filters (16)
  • .github/workflows/build.yml is excluded by !**/*.yml
  • docs/admin/setup/sharing/sharingButtonArtemis.png is excluded by !**/*.png, !**/*.png
  • docs/admin/setup/sharing/sharingButtonSharing.png is excluded by !**/*.png, !**/*.png
  • docs/admin/setup/sharing/sharing_health1.png is excluded by !**/*.png, !**/*.png
  • docs/admin/setup/sharing/sharing_health2.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/artemis_import.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/artemis_import2.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_export.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_metadata.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_metadata2.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_namespace.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_search.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_success.png is excluded by !**/*.png, !**/*.png
  • src/main/resources/config/application-dev.yml is excluded by !**/*.yml
  • src/main/resources/config/application-sharing.yml is excluded by !**/*.yml
  • src/test/resources/de/tum/cit/aet/artemis/programming/service/sharing/basket/sampleExercise.zip is excluded by !**/*.zip, !**/*.zip
📒 Files selected for processing (54)
  • build.gradle (2 hunks)
  • docker/artemis/config/dev-local-vc-local-ci.env (1 hunks)
  • docs/admin/extension-services.rst (1 hunks)
  • docs/admin/setup/sharing.rst (1 hunks)
  • docs/index.rst (1 hunks)
  • docs/user/sharing.rst (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/config/Constants.java (3 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/config/RestTemplateConfiguration.java (2 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/config/SecurityConfiguration.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/dto/SharingInfoDTO.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/service/ProfileService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/web/SharingSupportResource.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportFromFileService.java (4 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ProgrammingExerciseImportFromSharingService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingException.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingHealthIndicator.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingMultipartZipFile.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSetupInfo.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java (1 hunks)
  • src/main/webapp/app/app.routes.ts (1 hunks)
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.html (1 hunks)
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.ts (7 hunks)
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts (1 hunks)
  • src/main/webapp/app/programming/manage/exercise/programming-exercise.component.ts (1 hunks)
  • src/main/webapp/app/programming/manage/programming-exercise-management.route.ts (1 hunks)
  • src/main/webapp/app/programming/manage/services/programming-exercise-sharing.service.ts (1 hunks)
  • src/main/webapp/app/programming/manage/update/programming-exercise-creation-config.ts (1 hunks)
  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.html (3 hunks)
  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.ts (17 hunks)
  • src/main/webapp/app/programming/shared/actions/programming-exercise-instructor-exercise-sharing.component.ts (1 hunks)
  • src/main/webapp/app/sharing/search-result-dto.model.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.html (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.spec.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.model.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.route.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.scss (1 hunks)
  • src/main/webapp/i18n/de/programmingExercise.json (1 hunks)
  • src/main/webapp/i18n/de/sharing.json (1 hunks)
  • src/main/webapp/i18n/en/programmingExercise.json (1 hunks)
  • src/main/webapp/i18n/en/sharing.json (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceExportTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingServiceTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorHealthCheckRegistryTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorServiceTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingInfoDTOTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingPlatformMockProvider.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSupportResourceTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationIndependentTest.java (2 hunks)
  • src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationLocalCILocalVCTest.java (3 hunks)
  • src/test/resources/de/tum/cit/aet/artemis/programming/service/sharing/basket/sampleBasket.json (1 hunks)
✅ Files skipped from review due to trivial changes (8)
  • docs/admin/extension-services.rst
  • src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationIndependentTest.java
  • build.gradle
  • src/main/webapp/i18n/en/programmingExercise.json
  • src/main/webapp/app/programming/manage/services/programming-exercise-sharing.service.ts
  • src/main/webapp/app/sharing/sharing.component.html
  • docs/admin/setup/sharing.rst
  • src/main/webapp/app/sharing/search-result-dto.model.ts
🚧 Files skipped from review as they are similar to previous changes (39)
  • docs/index.rst
  • src/main/webapp/app/programming/manage/exercise/programming-exercise.component.ts
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.html
  • src/main/webapp/app/programming/manage/update/programming-exercise-creation-config.ts
  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.html
  • src/main/webapp/i18n/en/sharing.json
  • src/main/webapp/app/app.routes.ts
  • src/main/java/de/tum/cit/aet/artemis/core/service/ProfileService.java
  • src/main/webapp/app/programming/manage/programming-exercise-management.route.ts
  • src/main/java/de/tum/cit/aet/artemis/core/config/SecurityConfiguration.java
  • src/test/resources/de/tum/cit/aet/artemis/programming/service/sharing/basket/sampleBasket.json
  • src/main/webapp/i18n/de/programmingExercise.json
  • src/main/webapp/i18n/de/sharing.json
  • src/main/java/de/tum/cit/aet/artemis/core/config/RestTemplateConfiguration.java
  • src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationLocalCILocalVCTest.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSetupInfo.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingServiceTest.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingPlatformMockProvider.java
  • src/main/webapp/app/sharing/sharing.model.ts
  • src/main/webapp/app/sharing/sharing.scss
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorHealthCheckRegistryTest.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingException.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSupportResourceTest.java
  • src/main/webapp/app/sharing/sharing.route.ts
  • src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportFromFileService.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingHealthIndicator.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ProgrammingExerciseImportFromSharingService.java
  • docs/user/sharing.rst
  • src/main/java/de/tum/cit/aet/artemis/core/config/Constants.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingMultipartZipFile.java
  • src/main/webapp/app/programming/shared/actions/programming-exercise-instructor-exercise-sharing.component.ts
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorServiceTest.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.ts
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorService.java
  • src/main/java/de/tum/cit/aet/artemis/core/dto/SharingInfoDTO.java
  • src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceExportTest.java
  • src/main/java/de/tum/cit/aet/artemis/core/web/SharingSupportResource.java
🧰 Additional context used
📓 Path-based instructions (3)
`src/test/java/**/*.java`: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_...

src/test/java/**/*.java: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_use: enforce_package_rules; db_query_count_tests: track_performance; util_service_factory_pattern: true; avoid_db_access: true; mock_strategy: static_mocks; context_restart_minimize: true

  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingInfoDTOTest.java
`src/main/webapp/**/*.ts`: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalC...

src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

  • src/main/webapp/app/sharing/sharing.component.ts
  • src/main/webapp/app/sharing/sharing.component.spec.ts
  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.ts
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts
`src/main/java/**/*.java`: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,de...

src/main/java/**/*.java: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java
🧠 Learnings (2)
📓 Common learnings
Learnt from: Wallenstein61
PR: ls1intum/Artemis#10989
File: src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java:122-135
Timestamp: 2025-06-06T14:47:54.274Z
Learning: In Artemis sharing platform integration (under "sharing" profile), endpoints in ExerciseSharingResource use checksum-based authentication with shared secrets instead of @PreAuthorize annotations. These endpoints (like getProblemStatement, getExerciseDetails, loadShoppingBasket) validate security through sharingInfo.checkChecksum() calls rather than role-based access control, as they serve as API interfaces for external sharing platform communication.
src/main/webapp/app/programming/manage/update/programming-exercise-update.component.ts (1)
Learnt from: Wallenstein61
PR: ls1intum/Artemis#10989
File: src/main/webapp/app/programming/manage/update/programming-exercise-update.component.ts:1283-1303
Timestamp: 2025-06-06T14:12:04.548Z
Learning: Wallenstein61 prefers to minimize interference with original code when implementing new features, even if it means not following optimal patterns like avoiding nested subscriptions. This approach prioritizes compatibility and reduced risk over strict adherence to best practices in large established codebases.
🧬 Code Graph Analysis (3)
src/main/webapp/app/sharing/sharing.component.spec.ts (4)
src/test/javascript/spec/helpers/mocks/service/mock-alert.service.ts (1)
  • MockAlertService (3-8)
src/test/javascript/spec/helpers/mocks/mock-router.ts (1)
  • MockRouter (5-30)
src/main/webapp/app/sharing/sharing.model.ts (1)
  • ShoppingBasket (50-54)
src/main/webapp/app/core/course/shared/entities/course.model.ts (1)
  • Course (59-180)
src/main/webapp/app/programming/manage/update/programming-exercise-update.component.ts (1)
src/main/webapp/app/programming/manage/update/programming-exercise-creation-config.ts (1)
  • ProgrammingExerciseCreationConfig (7-56)
src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts (4)
src/main/webapp/app/core/layouts/profiles/profile-info.model.ts (1)
  • ProgrammingLanguageFeature (115-124)
src/test/javascript/spec/helpers/mocks/service/mock-ngb-modal.service.ts (1)
  • MockNgbModalService (1-5)
src/test/javascript/spec/helpers/mocks/mock-router.ts (1)
  • MockRouter (5-30)
src/main/webapp/app/core/course/shared/entities/course.model.ts (1)
  • Course (59-180)
🪛 GitHub Check: client-tests
src/main/webapp/app/sharing/sharing.component.spec.ts

[failure] 2-2:
Cannot find module '../../helpers/mocks/service/mock-translate.service' or its corresponding type declarations.

src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts

[failure] 129-129:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.


[failure] 126-126:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.


[failure] 46-46:
Conversion of type '{ id: number; categories: { category: string; }[]; templateParticipation: TemplateProgrammingExerciseParticipation; solutionParticipation: SolutionProgrammingExerciseParticipation; buildConfig: { ...; }; }' to type 'ProgrammingExercise' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.


[failure] 30-30:
Cannot find module 'app/entities/programming/build-log-statistics-dto' or its corresponding type declarations.


[failure] 30-30:
'BuildLogStatisticsDTO' is declared but its value is never read.


[failure] 27-27:
'"app/programming/shared/services/programming-language-feature/programming-language-feature.service"' has no exported member named 'ProgrammingLanguageFeature'. Did you mean 'ProgrammingLanguageFeatureService'?


[failure] 26-26:
Cannot find module 'app/shared/layouts/profiles/profile-info.model' or its corresponding type declarations.


[failure] 24-24:
Cannot find module 'app/entities/participation/solution-programming-exercise-participation.model' or its corresponding type declarations.


[failure] 23-23:
Cannot find module 'app/entities/participation/template-programming-exercise-participation.model' or its corresponding type declarations.

🪛 GitHub Check: client-tests-selected
src/main/webapp/app/sharing/sharing.component.spec.ts

[failure] 2-2:
Cannot find module '../../helpers/mocks/service/mock-translate.service' or its corresponding type declarations.

src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts

[failure] 129-129:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.


[failure] 126-126:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.


[failure] 46-46:
Conversion of type '{ id: number; categories: { category: string; }[]; templateParticipation: TemplateProgrammingExerciseParticipation; solutionParticipation: SolutionProgrammingExerciseParticipation; buildConfig: { ...; }; }' to type 'ProgrammingExercise' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.


[failure] 30-30:
Cannot find module 'app/entities/programming/build-log-statistics-dto' or its corresponding type declarations.


[failure] 30-30:
'BuildLogStatisticsDTO' is declared but its value is never read.


[failure] 27-27:
'"app/programming/shared/services/programming-language-feature/programming-language-feature.service"' has no exported member named 'ProgrammingLanguageFeature'. Did you mean 'ProgrammingLanguageFeatureService'?


[failure] 26-26:
Cannot find module 'app/shared/layouts/profiles/profile-info.model' or its corresponding type declarations.


[failure] 24-24:
Cannot find module 'app/entities/participation/solution-programming-exercise-participation.model' or its corresponding type declarations.


[failure] 23-23:
Cannot find module 'app/entities/participation/template-programming-exercise-participation.model' or its corresponding type declarations.

🪛 ast-grep (0.38.1)
src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java

[warning] 408-408: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: Mac.getInstance(algorithm)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html

(use-of-aes-ecb-java)


[warning] 408-408: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Mac.getInstance(algorithm)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA

(desede-is-deprecated-java)

🪛 dotenv-linter (3.3.0)
docker/artemis/config/dev-local-vc-local-ci.env

[warning] 5-5: [IncorrectDelimiter] The SPRING_PROFILES_ACTIVE: artemis,scheduling,localci,localvc,buildagent,core,dev,docker,sharing key has incorrect delimiter


[warning] 5-5: [KeyWithoutValue] The SPRING_PROFILES_ACTIVE: artemis,scheduling,localci,localvc,buildagent,core,dev,docker,sharing key should be with a value or have an equal sign


[warning] 5-5: [LowercaseKey] The SPRING_PROFILES_ACTIVE: artemis,scheduling,localci,localvc,buildagent,core,dev,docker,sharing key should be in uppercase

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build .war artifact
  • GitHub Check: client-style
  • GitHub Check: server-tests
  • GitHub Check: server-style
  • GitHub Check: Analyse
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build .war artifact
🔇 Additional comments (9)
docker/artemis/config/dev-local-vc-local-ci.env (1)

5-5: LGTM: Sharing profile correctly added.

The addition of the "sharing" profile to SPRING_PROFILES_ACTIVE properly enables the sharing platform integration feature as documented.

🧰 Tools
🪛 dotenv-linter (3.3.0)

[warning] 5-5: [IncorrectDelimiter] The SPRING_PROFILES_ACTIVE: artemis,scheduling,localci,localvc,buildagent,core,dev,docker,sharing key has incorrect delimiter


[warning] 5-5: [KeyWithoutValue] The SPRING_PROFILES_ACTIVE: artemis,scheduling,localci,localvc,buildagent,core,dev,docker,sharing key should be with a value or have an equal sign


[warning] 5-5: [LowercaseKey] The SPRING_PROFILES_ACTIVE: artemis,scheduling,localci,localvc,buildagent,core,dev,docker,sharing key should be in uppercase

src/main/webapp/app/sharing/sharing.component.ts (1)

170-209: LGTM: Initialization and error handling improvements.

The refactoring of ngOnInit() with parameter validation, separate loadSharedExercises() method, and improved navigation error handling addresses the previous concerns well.

src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingInfoDTOTest.java (1)

41-64: Well-structured test methods following best practices.

The test methods properly validate the SharingMultipartZipFile functionality with appropriate resource management using try-with-resources and comprehensive assertions for file properties and exception handling.

src/main/webapp/app/programming/manage/update/programming-exercise-update.component.ts (6)

10-10: Well-integrated sharing service and properties.

The integration of ProgrammingExerciseSharingService and SharingInfo follows established patterns in the component with proper service injection and property initialization.

Also applies to: 43-43, 82-96, 152-152, 207-207


471-471: Proper route segment detection for sharing import.

The route parsing correctly identifies the import-from-sharing segment and triggers the appropriate setup method, following the same pattern used for other import types.

Also applies to: 480-482


132-134: Appropriate UI field visibility control for sharing.

The conditional display of the SHORT_NAME field when sharing info is present ensures proper user experience during sharing import workflows.


352-352: Consistent conditional checks for sharing import.

The isImportFromSharing flag is properly integrated into existing conditional logic throughout the component, maintaining consistency with other import types.

Also applies to: 373-373, 520-520, 645-645, 722-722


771-780: Effective sharing import save implementation.

The save logic for sharing import properly calls the sharing service, handles success/error scenarios, and integrates well with the existing save flow.


1308-1308: Complete configuration integration.

The isImportFromSharing flag is properly included in the programming exercise creation configuration, ensuring consistent state throughout the component tree.

@@ -0,0 +1,203 @@
import { TranslateDirective } from 'app/shared/language/translate.directive';
import { MockTranslateService, TranslatePipeMock } from '../../helpers/mocks/service/mock-translate.service';
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Fix import path for mock translate service.

The import path '../../helpers/mocks/service/mock-translate.service' is causing build failures.


🏁 Script executed:

#!/bin/bash
# Find the correct path for mock-translate.service
fd "mock-translate.service" --type f --extension ts

Length of output: 124


Fix import path in sharing.component.spec.ts

The mock-translate service has been moved under the test helpers directory. Update the import in src/main/webapp/app/sharing/sharing.component.spec.ts to point to the correct location:

File: src/main/webapp/app/sharing/sharing.component.spec.ts

- import { MockTranslateService, TranslatePipeMock } from '../../helpers/mocks/service/mock-translate.service';
+ import { MockTranslateService, TranslatePipeMock } from '../../../../test/javascript/spec/helpers/mocks/service/mock-translate.service';
📝 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
import { MockTranslateService, TranslatePipeMock } from '../../helpers/mocks/service/mock-translate.service';
import { MockTranslateService, TranslatePipeMock } from '../../../../test/javascript/spec/helpers/mocks/service/mock-translate.service';
🧰 Tools
🪛 GitHub Check: client-tests

[failure] 2-2:
Cannot find module '../../helpers/mocks/service/mock-translate.service' or its corresponding type declarations.

🪛 GitHub Check: client-tests-selected

[failure] 2-2:
Cannot find module '../../helpers/mocks/service/mock-translate.service' or its corresponding type declarations.

🤖 Prompt for AI Agents
In src/main/webapp/app/sharing/sharing.component.spec.ts at line 2, the import
path for MockTranslateService and TranslatePipeMock is incorrect and causes
build failures. Update the import statement to use the new correct path under
the test helpers directory where the mock-translate.service.ts file now resides.
Replace the old relative path
'../../helpers/mocks/service/mock-translate.service' with the accurate path
reflecting the new location of the mock-translate service.

Copy link

github-actions bot commented Jun 6, 2025

End-to-End (E2E) Test Results Summary

1 similar comment
Copy link

github-actions bot commented Jun 6, 2025

End-to-End (E2E) Test Results Summary

Copy link
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: 0

♻️ Duplicate comments (3)
src/main/webapp/app/sharing/sharing.component.spec.ts (3)

2-2: Fix import path for mock translate service.

The import path appears to be incorrect based on the project structure. The current relative path may not resolve properly.

Based on previous review feedback, this import path needs to be corrected to point to the actual location of the mock translate service file.


133-133: Fix typo in error message.

"Bakset" should be "Basket".

-            { message: 'Bakset not found' }, // error body
+            { message: 'Basket not found' }, // error body

146-146: Missing custom matcher definition.

The .toBeBetween() matcher used here is not a standard Jasmine matcher and needs to be defined.

This custom matcher must be implemented and registered before it can be used in tests. Consider using standard Jasmine matchers instead:

-        expect(fixture.componentInstance.getTokenExpiryDate()).toBeBetween(new Date(Date.now() - 1000), new Date(Date.now() + 1000));
+        const tokenExpiryDate = fixture.componentInstance.getTokenExpiryDate();
+        expect(tokenExpiryDate.getTime()).toBeGreaterThanOrEqual(Date.now() - 1000);
+        expect(tokenExpiryDate.getTime()).toBeLessThanOrEqual(Date.now() + 1000);
🧹 Nitpick comments (4)
src/main/webapp/app/sharing/sharing.component.spec.ts (2)

71-72: Fix typo in course titles.

"testCouse" should be "testCourse" in both course objects.

-        { id: 1, title: 'testCouse 1' },
-        { id: 2, title: 'testCouse 2' },
+        { id: 1, title: 'testCourse 1' },
+        { id: 2, title: 'testCourse 2' },

186-192: Simplify redundant error testing.

The try-catch block from lines 186-192 tests the same validation error as lines 180-185. This duplication is unnecessary.

-        try {
-            sharingInfo.validate();
-            throw new Error('Error expected, got none');
-        } catch (err) {
-            expect(err).toBeInstanceOf(Error);
-            expect(err.message).toBe('Basket token is required');
-        }
src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts (2)

45-57: Fix type assertion for mock programming exercise.

The static analysis correctly identifies a type mismatch. Use proper type assertion to resolve this.

-    const mockProgrammingExercise = {
+    const mockProgrammingExercise = {
         id: 1,
         categories: [{ category: 'Important' }],
         templateParticipation: {
             id: 1,
         } as TemplateProgrammingExerciseParticipation,
         solutionParticipation: {
             id: 2,
         } as SolutionProgrammingExerciseParticipation,
         buildConfig: {
             buildTool: 'GRADLE',
         },
-    } as ProgrammingExercise;
+    } as Partial<ProgrammingExercise>;
🧰 Tools
🪛 GitHub Check: client-tests

[failure] 45-45:
Conversion of type '{ id: number; categories: { category: string; }[]; templateParticipation: TemplateProgrammingExerciseParticipation; solutionParticipation: SolutionProgrammingExerciseParticipation; buildConfig: { ...; }; }' to type 'ProgrammingExercise' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

🪛 GitHub Check: client-tests-selected

[failure] 45-45:
Conversion of type '{ id: number; categories: { category: string; }[]; templateParticipation: TemplateProgrammingExerciseParticipation; solutionParticipation: SolutionProgrammingExerciseParticipation; buildConfig: { ...; }; }' to type 'ProgrammingExercise' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.


125-128: Remove duplicate mock setup.

The profileService.getProfileInfo is mocked twice with identical configuration, creating unnecessary duplication.

         jest.spyOn(exerciseService, 'getDiffReport').mockReturnValue(of(gitDiffReport));
         jest.spyOn(profileService, 'getProfileInfo').mockReturnValue(of(profileInfo));
         jest.spyOn(submissionPolicyService, 'getSubmissionPolicyOfProgrammingExercise').mockReturnValue(of(undefined));

-        jest.spyOn(profileService, 'getProfileInfo').mockReturnValue(of(profileInfo));
         jest.spyOn(programmingLanguageFeatureService, 'getProgrammingLanguageFeature').mockReturnValue({
             plagiarismCheckSupported: true,
         } as ProgrammingLanguageFeature);
🧰 Tools
🪛 GitHub Check: client-tests

[failure] 128-128:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.


[failure] 125-125:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.

🪛 GitHub Check: client-tests-selected

[failure] 128-128:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.


[failure] 125-125:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f6ada8 and cb68711.

📒 Files selected for processing (4)
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.spec.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.ts (1 hunks)
  • src/test/javascript/spec/helpers/mocks/programming-exercise-creation-config-mock.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/test/javascript/spec/helpers/mocks/programming-exercise-creation-config-mock.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/webapp/app/sharing/sharing.component.ts
🧰 Additional context used
📓 Path-based instructions (1)
`src/main/webapp/**/*.ts`: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalC...

src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

  • src/main/webapp/app/sharing/sharing.component.spec.ts
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: Wallenstein61
PR: ls1intum/Artemis#10989
File: src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java:122-135
Timestamp: 2025-06-06T14:47:54.274Z
Learning: In Artemis sharing platform integration (under "sharing" profile), endpoints in ExerciseSharingResource use checksum-based authentication with shared secrets instead of @PreAuthorize annotations. These endpoints (like getProblemStatement, getExerciseDetails, loadShoppingBasket) validate security through sharingInfo.checkChecksum() calls rather than role-based access control, as they serve as API interfaces for external sharing platform communication.
🧬 Code Graph Analysis (2)
src/main/webapp/app/sharing/sharing.component.spec.ts (4)
src/test/javascript/spec/helpers/mocks/service/mock-alert.service.ts (1)
  • MockAlertService (3-8)
src/test/javascript/spec/helpers/mocks/mock-router.ts (1)
  • MockRouter (5-30)
src/main/webapp/app/sharing/sharing.model.ts (1)
  • ShoppingBasket (50-54)
src/main/webapp/app/core/course/shared/entities/course.model.ts (1)
  • Course (59-180)
src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts (4)
src/main/webapp/app/core/layouts/profiles/profile-info.model.ts (1)
  • ProgrammingLanguageFeature (115-124)
src/test/javascript/spec/helpers/mocks/service/mock-ngb-modal.service.ts (1)
  • MockNgbModalService (1-5)
src/test/javascript/spec/helpers/mocks/mock-router.ts (1)
  • MockRouter (5-30)
src/main/webapp/app/core/course/shared/entities/course.model.ts (1)
  • Course (59-180)
🪛 GitHub Check: client-tests
src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts

[failure] 128-128:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.


[failure] 125-125:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.


[failure] 45-45:
Conversion of type '{ id: number; categories: { category: string; }[]; templateParticipation: TemplateProgrammingExerciseParticipation; solutionParticipation: SolutionProgrammingExerciseParticipation; buildConfig: { ...; }; }' to type 'ProgrammingExercise' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

🪛 GitHub Check: client-tests-selected
src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts

[failure] 128-128:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.


[failure] 125-125:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.


[failure] 45-45:
Conversion of type '{ id: number; categories: { category: string; }[]; templateParticipation: TemplateProgrammingExerciseParticipation; solutionParticipation: SolutionProgrammingExerciseParticipation; buildConfig: { ...; }; }' to type 'ProgrammingExercise' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: client-style
  • GitHub Check: server-tests
  • GitHub Check: Build .war artifact
  • GitHub Check: server-style
  • GitHub Check: Analyse
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build .war artifact
🔇 Additional comments (8)
src/main/webapp/app/sharing/sharing.component.spec.ts (2)

75-110: LGTM! Comprehensive integration test.

This test effectively covers the complete user flow from loading data to navigation, with proper mocking and assertions.


167-202: Excellent validation test coverage.

The test thoroughly covers both positive and negative validation scenarios for the SharingInfo model, ensuring proper error handling and validation logic.

src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts (6)

1-32: Import structure looks good.

The imports are well-organized and include all necessary dependencies for testing the sharing functionality.


92-132: TestBed configuration follows best practices.

The test module setup correctly provides all necessary services and mocks for testing the component.

🧰 Tools
🪛 GitHub Check: client-tests

[failure] 128-128:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.


[failure] 125-125:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.

🪛 GitHub Check: client-tests-selected

[failure] 128-128:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.


[failure] 125-125:
Argument of type 'Observable' is not assignable to parameter of type 'ProfileInfo'.


138-151: Test structure follows Angular testing best practices.

The test setup correctly uses HttpTestingController for HTTP mocking and includes proper teardown with httpMock.verify().


152-163: Test case correctly verifies sharing enabled scenario.

The test properly mocks the HTTP response and validates that the component sets isExportToSharingEnabled to true when the API returns success.


165-182: Test case correctly verifies sharing disabled scenario.

The test properly simulates a 404 error response and validates that the component sets isExportToSharingEnabled to false when the sharing API is not available.


33-35: Good decision to separate sharing tests.

Creating a dedicated test file for sharing functionality helps maintain clear test organization and can be easily integrated with the main test file when the feature stabilizes.

Copy link

End-to-End (E2E) Test Results Summary

4 similar comments
Copy link

End-to-End (E2E) Test Results Summary

Copy link

End-to-End (E2E) Test Results Summary

Copy link

End-to-End (E2E) Test Results Summary

Copy link

End-to-End (E2E) Test Results Summary

Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ✅Skipped ⚠️FailedTime ⏱
End-to-End (E2E) Test Report201 ran198 passed3 skipped0 failed1h 43s 877ms
TestResultTime ⏱
No test annotations available

Copy link

End-to-End (E2E) Test Results Summary

Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ✅Skipped ⚠️FailedTime ⏱
End-to-End (E2E) Test Report201 ran198 passed3 skipped0 failed52m 20s 21ms
TestResultTime ⏱
No test annotations available

Copy link
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

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd9ab1f and df8b9ea.

📒 Files selected for processing (1)
  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.spec.ts (5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`src/main/webapp/**/*.ts`: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalC...

src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.spec.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: Wallenstein61
PR: ls1intum/Artemis#10989
File: src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java:122-135
Timestamp: 2025-06-06T14:47:54.300Z
Learning: In Artemis sharing platform integration (under "sharing" profile), endpoints in ExerciseSharingResource use checksum-based authentication with shared secrets instead of @PreAuthorize annotations. These endpoints (like getProblemStatement, getExerciseDetails, loadShoppingBasket) validate security through sharingInfo.checkChecksum() calls rather than role-based access control, as they serve as API interfaces for external sharing platform communication.
🪛 GitHub Check: client-tests
src/main/webapp/app/programming/manage/update/programming-exercise-update.component.spec.ts

[failure] 759-759:
Type 'ProgrammingExercise' has no properties in common with type '{ body?: unknown; headers?: HttpHeaders | undefined; status?: number | undefined; statusText?: string | undefined; url?: string | undefined; }'.


[failure] 759-759:
Argument of type 'Observable<HttpResponse>' is not assignable to parameter of type 'Observable'.

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: server-tests
  • GitHub Check: client-tests-selected
  • GitHub Check: submit-gradle
  • GitHub Check: Build .war artifact

Copy link

End-to-End (E2E) Test Results Summary

@Wallenstein61 Wallenstein61 force-pushed the feature/connecting-to-sharing-platform branch from df8b9ea to d7e0690 Compare June 11, 2025 11:10
Copy link
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: 4

♻️ Duplicate comments (5)
src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (3)

273-274: Typo in inline comment

unfortunatellyunfortunately


232-241: ⚠️ Potential issue

Input-validation still missing in parseParamsToMap

The method still trusts its var-arg input. A null array or odd parameter count will trigger NPE / assertion errors outside AssertJ’s scope in production code that re-uses the utility.

     private Map<String, String> parseParamsToMap(String... params) {
-        Map<String, String> paramsMap = new HashMap<>();
-        assertThat(params.length % 2).isEqualTo(0).describedAs("paramList must contain even elements");
+        if (params == null) {
+            throw new IllegalArgumentException("params cannot be null");
+        }
+        assertThat(params.length % 2).isEqualTo(0).describedAs("paramList must contain even elements");
+        Map<String, String> paramsMap = new HashMap<>(params.length / 2);
         for (int i = 0; i < params.length; i = i + 2) {
             String paramName = params[i];
             String paramValue = params[i + 1];
             paramsMap.put(paramName, paramValue);
         }
         return paramsMap;
     }

270-334: 🛠️ Refactor suggestion

importExerciseCompleteProcess test is still 90+ lines

The test continues to mix basket import, REST calls, Git & CI stubbing, and cache cleanup. Splitting into focused, smaller tests (e.g. shouldImportBasket , shouldRetrieveExerciseDetails, shouldSetupExercise) would improve readability and pinpoint failures faster.

src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts (1)

130-131: ⚠️ Potential issue

getProfileInfo mock must return an Observable, not a raw object

ProfileService.getProfileInfo() returns an Observable<ProfileInfo>.
Returning the plain profileInfo object breaks the method’s contract and was already flagged earlier — this will re-introduce the same compilation error.

-        jest.spyOn(profileService, 'getProfileInfo').mockReturnValue(profileInfo);
+        jest.spyOn(profileService, 'getProfileInfo').mockReturnValue(of(profileInfo));
src/main/webapp/app/sharing/sharing.component.spec.ts (1)

144-147: toBeBetween() matcher still undefined – tests will fail
Previous review already raised this; the custom Jasmine matcher is still missing.

Replace with built-in assertions or register the matcher in the global test setup.

🧹 Nitpick comments (4)
src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (2)

243-257: DRY: duplicate checksum calculation logic

calculateCorrectChecksum already encapsulates checksum generation. addCorrectChecksum re-implements it inline. Re-use the helper to avoid divergent logic.

-        String checkSum = SecretChecksumCalculator.calculateChecksum(paramsToCheckSum, sharingApiKey);
+        String checkSum = calculateCorrectChecksum(params);

170-173: Remove unused local variable

sampleBasket is read but never used in testImportBasketFail; delete the dead code to keep the test focused.

-        String sampleBasket = IOUtils.toString(Objects.requireNonNull(this.getClass().getResource("./basket/sampleBasket.json")), StandardCharsets.UTF_8);
src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts (1)

156-166: Redundant manual assignment of programmingExercise inside the tests

After ngOnInit() the component already receives the exercise via the ActivatedRoute data; the extra lines

comp.programmingExercise = mockProgrammingExercise;
comp.programmingExerciseBuildConfig = mockProgrammingExercise.buildConfig;

overwrite that state in every test, adding noise without changing the outcome.
Consider removing those two statements in each test case to keep the spec concise.

Also applies to: 170-186, 188-205

src/main/webapp/app/sharing/sharing.component.html (1)

113-115: Spelling / translation nit – “Platform”

“Plattform” is German spelling.
For the English translation key use “Platform” to avoid inconsistency.

-<span class="hidden-sm-down" jhiTranslate="artemisApp.programmingExercise.sharing.import">Import Programming Exercise from Sharing Plattform</span>
+<span class="hidden-sm-down" jhiTranslate="artemisApp.programmingExercise.sharing.import">Import Programming Exercise from Sharing Platform</span>
🧰 Tools
🪛 HTMLHint (1.5.0)

[error] 114-114: The attribute name of [ jhiTranslate ] must be in lowercase.

(attr-lowercase)

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df8b9ea and d7e0690.

📒 Files selected for processing (24)
  • build.gradle (2 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/web/SharingSupportResource.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java (2 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportFromFileService.java (4 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java (1 hunks)
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts (1 hunks)
  • src/main/webapp/app/programming/manage/services/programming-exercise-sharing.service.spec.ts (1 hunks)
  • src/main/webapp/app/programming/manage/services/programming-exercise-sharing.service.ts (1 hunks)
  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.spec.ts (5 hunks)
  • src/main/webapp/app/programming/shared/actions/programming-exercise-instructor-exercise-sharing.component.spec.ts (1 hunks)
  • src/main/webapp/app/programming/shared/actions/programming-exercise-instructor-exercise-sharing.component.ts (1 hunks)
  • src/main/webapp/app/sharing/search-result-dto.model.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.html (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.spec.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.scss (1 hunks)
  • src/main/webapp/i18n/de/sharing.json (1 hunks)
  • src/main/webapp/i18n/en/sharing.json (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceExportTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingInfoDTOTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSupportResourceTest.java (1 hunks)
  • src/test/resources/de/tum/cit/aet/artemis/programming/service/sharing/basket/sampleBasket.json (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • build.gradle
  • src/main/webapp/app/sharing/search-result-dto.model.ts
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceExportTest.java
🚧 Files skipped from review as they are similar to previous changes (16)
  • src/test/resources/de/tum/cit/aet/artemis/programming/service/sharing/basket/sampleBasket.json
  • src/main/webapp/i18n/en/sharing.json
  • src/main/webapp/app/programming/shared/actions/programming-exercise-instructor-exercise-sharing.component.spec.ts
  • src/main/webapp/i18n/de/sharing.json
  • src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java
  • src/main/webapp/app/programming/manage/services/programming-exercise-sharing.service.spec.ts
  • src/main/webapp/app/programming/shared/actions/programming-exercise-instructor-exercise-sharing.component.ts
  • src/main/webapp/app/sharing/sharing.scss
  • src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportFromFileService.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSupportResourceTest.java
  • src/main/java/de/tum/cit/aet/artemis/core/web/SharingSupportResource.java
  • src/main/webapp/app/sharing/sharing.component.ts
  • src/main/webapp/app/programming/manage/services/programming-exercise-sharing.service.ts
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingInfoDTOTest.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorService.java
  • src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java
🧰 Additional context used
📓 Path-based instructions (3)
`src/main/webapp/**/*.ts`: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalC...

src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

  • src/main/webapp/app/sharing/sharing.component.spec.ts
  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.spec.ts
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts
`src/test/java/**/*.java`: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_...

src/test/java/**/*.java: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_use: enforce_package_rules; db_query_count_tests: track_performance; util_service_factory_pattern: true; avoid_db_access: true; mock_strategy: static_mocks; context_restart_minimize: true

  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java
`src/main/webapp/**/*.html`: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

  • src/main/webapp/app/sharing/sharing.component.html
🧠 Learnings (2)
📓 Common learnings
Learnt from: Wallenstein61
PR: ls1intum/Artemis#10989
File: src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java:122-135
Timestamp: 2025-06-06T14:47:54.300Z
Learning: In Artemis sharing platform integration (under "sharing" profile), endpoints in ExerciseSharingResource use checksum-based authentication with shared secrets instead of @PreAuthorize annotations. These endpoints (like getProblemStatement, getExerciseDetails, loadShoppingBasket) validate security through sharingInfo.checkChecksum() calls rather than role-based access control, as they serve as API interfaces for external sharing platform communication.
src/main/webapp/app/sharing/sharing.component.html (3)
Learnt from: florian-glombik
PR: ls1intum/Artemis#8858
File: src/main/webapp/i18n/de/student-dashboard.json:0-0
Timestamp: 2024-10-08T15:35:52.595Z
Learning: Avoid suggesting the phrase "Keine Aufgaben passen zu den gewählten Filtern" for the translation key `noExercisesFoundWithAppliedFilter` in the PR ls1intum/Artemis#8858.
Learnt from: florian-glombik
PR: ls1intum/Artemis#8858
File: src/main/webapp/i18n/de/student-dashboard.json:36-37
Timestamp: 2024-10-14T10:27:58.500Z
Learning: Avoid suggesting the translation change for "noExercisesFoundWithAppliedFilter" in the PR ls1intum/Artemis#8858. The preferred translation is "Keine Aufgaben passen zu den gewählten Filtern."
Learnt from: florian-glombik
PR: ls1intum/Artemis#8858
File: src/main/webapp/app/shared/sidebar/sidebar.component.html:18-22
Timestamp: 2024-07-09T10:08:14.161Z
Learning: Translation keys `artemisApp.courseOverview.general.noExercisesFoundWithAppliedFilter` and `artemisApp.courseOverview.general.noDataFound` should be added to the relevant translation files to ensure consistent translations across the application.
🧬 Code Graph Analysis (1)
src/main/webapp/app/sharing/sharing.component.spec.ts (4)
src/test/javascript/spec/helpers/mocks/service/mock-alert.service.ts (1)
  • MockAlertService (3-8)
src/test/javascript/spec/helpers/mocks/mock-router.ts (1)
  • MockRouter (5-30)
src/main/webapp/app/sharing/sharing.model.ts (1)
  • ShoppingBasket (50-54)
src/main/webapp/app/core/course/shared/entities/course.model.ts (1)
  • Course (59-180)
🪛 HTMLHint (1.5.0)
src/main/webapp/app/sharing/sharing.component.html

[error] 1-1: Doctype must be declared before any non-comment content.

(doctype-first)


[error] 5-5: The attribute name of [ jhiTranslate ] must be in lowercase.

(attr-lowercase)


[error] 6-6: The attribute name of [ jhiTranslate ] must be in lowercase.

(attr-lowercase)


[error] 15-15: The attribute name of [ jhiTranslate ] must be in lowercase.

(attr-lowercase)


[error] 21-21: The attribute name of [ jhiTranslate ] must be in lowercase.

(attr-lowercase)


[error] 31-31: The attribute name of [ [(ngModel)] ] must be in lowercase.

(attr-lowercase)


[error] 50-50: The attribute name of [ jhiTranslate ] must be in lowercase.

(attr-lowercase)


[error] 50-50: The attribute name of [ [translateValues] ] must be in lowercase.

(attr-lowercase)


[error] 54-54: The attribute name of [ jhiTranslate ] must be in lowercase.

(attr-lowercase)


[error] 63-63: The attribute name of [ jhiSort ] must be in lowercase.

(attr-lowercase)


[error] 63-63: The attribute name of [ (sortChange) ] must be in lowercase.

(attr-lowercase)


[error] 66-66: The attribute name of [ jhiSortBy ] must be in lowercase.

(attr-lowercase)


[error] 66-66: The attribute name of [ jhiTranslate ] must be in lowercase.

(attr-lowercase)


[error] 67-67: The attribute name of [ jhiSortBy ] must be in lowercase.

(attr-lowercase)


[error] 67-67: The attribute name of [ jhiTranslate ] must be in lowercase.

(attr-lowercase)


[error] 82-82: The attribute name of [ ngModel ] must be in lowercase.

(attr-lowercase)


[error] 87-87: The attribute name of [ [ngStyle] ] must be in lowercase.

(attr-lowercase)


[error] 90-90: The attribute name of [ [routerLink] ] must be in lowercase.

(attr-lowercase)


[error] 93-93: The attribute name of [ [routerLink] ] must be in lowercase.

(attr-lowercase)


[error] 96-96: The attribute name of [ jhiTranslate ] must be in lowercase.

(attr-lowercase)


[error] 114-114: The attribute name of [ jhiTranslate ] must be in lowercase.

(attr-lowercase)

⏰ Context from checks skipped due to timeout of 90000ms (13)
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build .war artifact
  • GitHub Check: client-style
  • GitHub Check: client-tests
  • GitHub Check: client-tests-selected
  • GitHub Check: server-tests
  • GitHub Check: server-style
  • GitHub Check: Analyse
  • GitHub Check: Mend Security Check
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build .war artifact
🔇 Additional comments (2)
src/main/webapp/app/programming/manage/update/programming-exercise-update.component.spec.ts (2)

692-700: Inconsistent query-param naming (returnUrl vs returnURL)

Other parts of the codebase & tests use returnURL (capital “URL”).
Using a different casing here risks the component not picking up the value and breaking the import flow.

Please align the key name across tests and production code.


706-709: Return type of loadDetailsForExercises stub may be wrong

The stub returns a single ProgrammingExercise, but the real service likely returns an array or a wrapper DTO (judging from the method name).
A mismatch will let the test pass yet hide runtime type errors.

Double-check the actual signature and adjust:

-const sharingServiceStub = jest.spyOn(programmingExerciseSharingService, 'loadDetailsForExercises');
-sharingServiceStub.mockReturnValue(of(programmingExercise));
+jest.spyOn(programmingExerciseSharingService, 'loadDetailsForExercises')
+    .mockReturnValue(of([programmingExercise])); // or appropriate DTO

Comment on lines +404 to +417
restMockMvc.perform(post("/api/programming/sharing/setup-import").contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(emptySetupInfo))
.accept(MediaType.APPLICATION_JSON)).andDo(print()).andExpect(status().is5xxServerError());

}

@Test
@WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
void importExerciseInfosWrongChecksum() throws Exception {

SharingInfoDTO sharingInfo = new SharingInfoDTO("Some Basket Token", TEST_RETURN_URL, SharingPlatformMockProvider.SHARING_BASEURL_PLUGIN, "Invalid Checksum", 0);

restMockMvc.perform(post("/api/programming/sharing/import/basket/exercise-details").contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(sharingInfo)).accept(MediaType.APPLICATION_JSON)).andExpect(status().isBadRequest());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Endpoint path drift – tests hit a non-existing route

The failing-checksum and missing-exercise tests use /api/programming/sharing/..., while all production endpoints (and the rest of the tests) use /api/sharing/....

These tests will pass for the wrong reason (Spring returns 404 → 5xx/400) instead of exercising the real controller.

-        restMockMvc.perform(post("/api/programming/sharing/setup-import")
+        restMockMvc.perform(post("/api/sharing/setup-import")

Apply the same correction to the two checksum tests below.

📝 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
restMockMvc.perform(post("/api/programming/sharing/setup-import").contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(emptySetupInfo))
.accept(MediaType.APPLICATION_JSON)).andDo(print()).andExpect(status().is5xxServerError());
}
@Test
@WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
void importExerciseInfosWrongChecksum() throws Exception {
SharingInfoDTO sharingInfo = new SharingInfoDTO("Some Basket Token", TEST_RETURN_URL, SharingPlatformMockProvider.SHARING_BASEURL_PLUGIN, "Invalid Checksum", 0);
restMockMvc.perform(post("/api/programming/sharing/import/basket/exercise-details").contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(sharingInfo)).accept(MediaType.APPLICATION_JSON)).andExpect(status().isBadRequest());
}
restMockMvc.perform(post("/api/sharing/setup-import")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(emptySetupInfo)))
.andDo(print())
.andExpect(status().is5xxServerError());
🤖 Prompt for AI Agents
In
src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java
around lines 404 to 417, the test methods use the incorrect endpoint path
starting with /api/programming/sharing/ instead of the correct /api/sharing/.
Update the endpoint paths in these tests to match the production endpoints by
replacing /api/programming/sharing/ with /api/sharing/ to ensure the tests hit
the real controller and validate the intended logic.

Comment on lines +1 to +3
<body>
<div class="content">
<table style="border-spacing: 50px 0px; border-collapse: separate">
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid embedding a second <body> tag inside a component template

Angular inserts component HTML into the existing document body. Having another <body> element results in invalid DOM and unpredictable styling. Simply drop the outer <body> wrapper.

-<body>
+<div class="sharing-component">
 ...
-</body>
+</div>

Also applies to: 120-122

🧰 Tools
🪛 HTMLHint (1.5.0)

[error] 1-1: Doctype must be declared before any non-comment content.

(doctype-first)

🤖 Prompt for AI Agents
In src/main/webapp/app/sharing/sharing.component.html at lines 1 to 3 and also
lines 120 to 122, remove the outer <body> tags from the component template
because Angular inserts component HTML into the existing document body, and
having a nested <body> tag creates invalid DOM and styling issues. Keep only the
inner content inside the <div class="content"> and below, without wrapping it in
another <body> element.

Comment on lines +4 to +8
<thead>
<th jhiTranslate="artemisApp.sharing.importedExercise">Imported Exercise</th>
<th jhiTranslate="artemisApp.sharing.courseToImport">Course to import</th>
<th></th>
</thead>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

<thead> must contain a <tr> wrapper

HTML spec requires header cells to be children of a <tr> element. Rendering engines will auto-fix, but Angular’s HTML validators (and some screen readers) will flag this as invalid.

-<thead>
-    <th jhiTranslate="artemisApp.sharing.importedExercise">Imported Exercise</th>
-    <th jhiTranslate="artemisApp.sharing.courseToImport">Course to import</th>
-    <th></th>
-</thead>
+<thead>
+    <tr>
+        <th jhiTranslate="artemisApp.sharing.importedExercise">Imported Exercise</th>
+        <th jhiTranslate="artemisApp.sharing.courseToImport">Course to import</th>
+        <th></th>
+    </tr>
+</thead>
📝 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
<thead>
<th jhiTranslate="artemisApp.sharing.importedExercise">Imported Exercise</th>
<th jhiTranslate="artemisApp.sharing.courseToImport">Course to import</th>
<th></th>
</thead>
<thead>
<tr>
<th jhiTranslate="artemisApp.sharing.importedExercise">Imported Exercise</th>
<th jhiTranslate="artemisApp.sharing.courseToImport">Course to import</th>
<th></th>
</tr>
</thead>
🧰 Tools
🪛 HTMLHint (1.5.0)

[error] 5-5: The attribute name of [ jhiTranslate ] must be in lowercase.

(attr-lowercase)


[error] 6-6: The attribute name of [ jhiTranslate ] must be in lowercase.

(attr-lowercase)

🤖 Prompt for AI Agents
In src/main/webapp/app/sharing/sharing.component.html between lines 4 and 8, the
<thead> element directly contains <th> elements without a <tr> wrapper, which
violates HTML specifications. Wrap all the <th> elements inside a <tr> element
within the <thead> to ensure valid HTML structure and proper accessibility.

Copy link

End-to-End (E2E) Test Results Summary

@Wallenstein61 Wallenstein61 force-pushed the feature/connecting-to-sharing-platform branch from d7e0690 to 5e2d7af Compare June 12, 2025 10:56
Copy link
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: 4

♻️ Duplicate comments (13)
src/main/webapp/app/sharing/sharing.component.html (2)

1-3: ⚠️ Potential issue

Remove the nested <body> element – still violates DOM rules
The outer <body> wrapper makes the template an invalid fragment (Angular inserts component HTML inside the existing document body). The same remark was raised previously but the tag is still present.

-<body>
+<div class="sharing-component">
 ...
-</body>
+</div>
🧰 Tools
🪛 HTMLHint (1.5.0)

[error] 1-1: Doctype must be declared before any non-comment content.

(doctype-first)


4-8: ⚠️ Potential issue

<thead> must wrap its cells in a <tr> – spec violation remains
Header cells are still direct children of <thead>, breaking HTML validity and some accessibility tooling.

-<thead>
-    <th jhiTranslate="artemisApp.sharing.importedExercise">Imported Exercise</th>
-    <th jhiTranslate="artemisApp.sharing.courseToImport">Course to import</th>
-    <th></th>
-</thead>
+<thead>
+    <tr>
+        <th jhiTranslate="artemisApp.sharing.importedExercise">Imported Exercise</th>
+        <th jhiTranslate="artemisApp.sharing.courseToImport">Course to import</th>
+        <th></th>
+    </tr>
+</thead>
src/main/webapp/app/sharing/sharing.component.spec.ts (1)

145-147: ⚠️ Potential issue

Undeclared custom matcher toBeBetween still used
The earlier review flagged this. The matcher is not defined in the repo and tests will fail at runtime. Replace with standard assertions or add the matcher.

-expect(fixture.componentInstance.getTokenExpiryDate()).toBeBetween(new Date(Date.now() - 1000), new Date(Date.now() + 1000));
+const expiry = fixture.componentInstance.getTokenExpiryDate().getTime();
+expect(expiry).toBeGreaterThanOrEqual(Date.now() - 1000);
+expect(expiry).toBeLessThanOrEqual(Date.now() + 1000);
src/main/webapp/app/programming/manage/update/programming-exercise-update.component.ts (1)

1282-1303: Nested subscriptions introduce memory-leak risk

createProgrammingExerciseForImportFromSharing() subscribes to queryParams and then nests another subscription to loadDetailsForExercises.
Neither subscription is disposed, so every route change (incl. param updates) adds additional active subscriptions.

At minimum, chain the calls with switchMap and tie the outer subscription to destroy$:

this.activatedRoute.queryParams.pipe(
    takeUntil(this.destroy$),
    switchMap((qp: Params) => {
        Object.assign(this.sharingInfo, { ... });
        return this.programmingExerciseSharingService.loadDetailsForExercises(this.sharingInfo);
    })
).subscribe({
    next: (exerciseDetails) => { ... },
    error: (err) => this.alertService.error(`Failed to load exercise details from the sharing platform: ${err}`),
});

This follows the same feedback given in previous reviews and avoids growth of unseen subscriptions.

src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (3)

232-241: parseParamsToMap still lacks basic null / length validation
The previous review already covered this; the comment is still applicable.


273-273: Typo: “unfortunatelly” → “unfortunately”
Same remark as in the earlier review.


270-334: Monolithic test violates single-responsibility & readability
importExerciseCompleteProcess is ~60 lines, mocks half a dozen services and verifies several behaviours. Consider splitting as suggested earlier.

src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java (4)

246-258: Server-side HTML escaping breaks Markdown formatting

Escaping here mangles legitimate Markdown that will later be rendered client-side. The earlier feedback still applies: return the raw statement and leave sanitisation to the frontend Markdown renderer.


366-370: Manual = stripping – switch to URL-safe Base64 encoder

Use Base64.getUrlEncoder().withoutPadding() instead of regex replacement; fewer bugs, more intent.

-            String tokenInB64 = Base64.getEncoder().encodeToString(token.getBytes()).replaceAll("=+$", "");
+            String tokenInB64 = Base64.getUrlEncoder().withoutPadding().encodeToString(token.getBytes());

456-459: Token validation too permissive

Only character class check; length & path traversals still possible. Strengthen validation as suggested earlier.


400-415: ⚠️ Potential issue

createHMAC returns empty hash and ignores null PSK – security risk

  1. If the pre-shared key is absent, an NPE will occur.
  2. Swallowing NoSuchAlgorithmException / InvalidKeyException and returning an empty hash defeats integrity checks.
-        String psk = sharingConnectorService.getSharingApiKeyOrNull();
-        SecretKeySpec secretKeySpec = new SecretKeySpec(psk.getBytes(StandardCharsets.UTF_8), algorithm);
+        String psk = sharingConnectorService.getSharingApiKeyOrNull();
+        if (psk == null || psk.isBlank()) {
+            throw new IllegalStateException("Sharing API key is not configured");
+        }
+        SecretKeySpec secretKeySpec = new SecretKeySpec(psk.getBytes(StandardCharsets.UTF_8), algorithm);
...
-        catch (NoSuchAlgorithmException | InvalidKeyException e) {
-            return Base64.getEncoder().encodeToString(new byte[] {});
+        catch (NoSuchAlgorithmException | InvalidKeyException e) {
+            throw new SharingException("Failed to create HMAC for export token", e);
         }
🧰 Tools
🪛 ast-grep (0.38.1)

[warning] 406-406: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: Mac.getInstance(algorithm)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html

(use-of-aes-ecb-java)


[warning] 406-406: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Mac.getInstance(algorithm)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA

(desede-is-deprecated-java)

src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java (2)

54-68: Inline inner class violates SRP & hides error handling

NewFileInputStream should live in a utility class and use Files.deleteIfExists, emitting a warning on failure. Same comment was provided previously.


155-160: Null body on error response

badRequest().body(null) sends a JSON null which confuses clients; prefer .build() or a structured error DTO.

🧹 Nitpick comments (8)
src/main/webapp/app/sharing/sharing.component.html (1)

108-115: Fallback text is untranslated and contains a typo
The button already references a translation key. Including hard-coded fallback text (“Import Programming Exercise from Sharing Plattform”) defeats i18n and “Plattform” is misspelled.

-    <span class="hidden-sm-down" jhiTranslate="artemisApp.programmingExercise.sharing.import">Import Programming Exercise from Sharing Plattform</span>
+    <span class="hidden-sm-down" jhiTranslate="artemisApp.programmingExercise.sharing.import"></span>
src/main/webapp/app/sharing/sharing.component.spec.ts (1)

140-141: toHaveBeenCalledOnce is not part of standard Jest matchers
Unless jest-extended (or similar) is configured, this will throw. Prefer toHaveBeenCalledTimes(1).

-expect(errorSpy).toHaveBeenCalledOnce();
+expect(errorSpy).toHaveBeenCalledTimes(1);

Also applies to: 162-165

src/main/webapp/app/programming/manage/update/programming-exercise-update.component.ts (3)

10-10: Use application-wide path aliases instead of relative imports

All other service/model imports in this file use the app/... alias.
ProgrammingExerciseSharingService is pulled in via a relative '../services/... path while SharingInfo correctly uses app/sharing/....

Mixing import styles hampers refactoring and breaks when the file is moved. Stick to the alias that is configured in tsconfig.json.

-import { ProgrammingExerciseSharingService } from '../services/programming-exercise-sharing.service';
+import { ProgrammingExerciseSharingService } from 'app/programming/manage/services/programming-exercise-sharing.service';

Also applies to: 43-43


947-970: Duplicated template-loading branches

The two branches differ only in the service call. Consider extracting the common success/error handling into a helper, or use a ternary to pick the observable:

const src$ = this.isImportFromSharing
    ? this.programmingExerciseSharingService.loadProblemStatementForExercises(this.sharingInfo)
    : this.fileService.getTemplateFile(this.programmingExercise.programmingLanguage, this.programmingExercise.projectType);

src$.pipe(takeUntil(this.destroy$)).subscribe({ ... });

This keeps the method concise and avoids future divergence.


965-966: Hard-coded English error message breaks i18n

'Failed to load problem statement from the sharing platform.' bypasses the translation pipeline. Replace with a translate key and let the template handle localisation, e.g.:

this.alertService.error('artemisApp.programmingExercise.sharing.problemStatementLoadFailed');
docker/artemis/config/dev-local-vc-local-ci.env (1)

5-5: Prefer = delimiter for .env variables

The dotenv-linter warnings are correct – using : instead of = breaks standard .env parsing in many tools (docker compose, Spring Dotenv, Github Actions, etc.).
Replace the colon with an equals sign.

-SPRING_PROFILES_ACTIVE: artemis,scheduling,localci,localvc,buildagent,core,dev,docker,sharing
+SPRING_PROFILES_ACTIVE=artemis,scheduling,localci,localvc,buildagent,core,dev,docker,sharing
🧰 Tools
🪛 dotenv-linter (3.3.0)

[warning] 5-5: [IncorrectDelimiter] The SPRING_PROFILES_ACTIVE: artemis,scheduling,localci,localvc,buildagent,core,dev,docker,sharing key has incorrect delimiter


[warning] 5-5: [KeyWithoutValue] The SPRING_PROFILES_ACTIVE: artemis,scheduling,localci,localvc,buildagent,core,dev,docker,sharing key should be with a value or have an equal sign


[warning] 5-5: [LowercaseKey] The SPRING_PROFILES_ACTIVE: artemis,scheduling,localci,localvc,buildagent,core,dev,docker,sharing key should be in uppercase

src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java (1)

174-183: Safer temp-file cleanup for cache eviction

File.delete() silently ignores failures. Use Files.deleteIfExists(path) to handle cross-platform issues and log failures explicitly.

-                File f = (File) notification.getValue();
-                if (f != null) {
-                    boolean deleted = f.delete();
-                    if (!deleted) {
-                        log.info("Cannot delete {}", f.getName());
-                    }
-                }
+                File f = (File) notification.getValue();
+                if (f != null) {
+                    try {
+                        Files.deleteIfExists(f.toPath());
+                    } catch (IOException ex) {
+                        log.warn("Temp file {} could not be deleted", f.getAbsolutePath(), ex);
+                    }
+                }
src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java (1)

106-116: Bad-request response lacks error context

When checksum validation fails the client receives a bare 400. Consider returning a Problem JSON with a reason, or at least 401/403 to distinguish auth failure.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7e0690 and 5e2d7af.

⛔ Files ignored due to path filters (16)
  • .github/workflows/build.yml is excluded by !**/*.yml
  • docs/admin/setup/sharing/sharingButtonArtemis.png is excluded by !**/*.png, !**/*.png
  • docs/admin/setup/sharing/sharingButtonSharing.png is excluded by !**/*.png, !**/*.png
  • docs/admin/setup/sharing/sharing_health1.png is excluded by !**/*.png, !**/*.png
  • docs/admin/setup/sharing/sharing_health2.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/artemis_import.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/artemis_import2.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_export.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_metadata.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_metadata2.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_namespace.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_search.png is excluded by !**/*.png, !**/*.png
  • docs/user/sharing/sharing_success.png is excluded by !**/*.png, !**/*.png
  • src/main/resources/config/application-dev.yml is excluded by !**/*.yml
  • src/main/resources/config/application-sharing.yml is excluded by !**/*.yml
  • src/test/resources/de/tum/cit/aet/artemis/programming/service/sharing/basket/sampleExercise.zip is excluded by !**/*.zip, !**/*.zip
📒 Files selected for processing (61)
  • build.gradle (2 hunks)
  • docker/artemis/config/dev-local-vc-local-ci.env (1 hunks)
  • docs/admin/extension-services.rst (1 hunks)
  • docs/admin/setup/sharing.rst (1 hunks)
  • docs/index.rst (1 hunks)
  • docs/user/sharing.rst (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/config/Constants.java (2 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/config/RestTemplateConfiguration.java (2 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/config/SecurityConfiguration.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/dto/SharingInfoDTO.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/service/ProfileService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/web/SharingSupportResource.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java (2 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportFromFileService.java (4 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ProgrammingExerciseImportFromSharingService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingException.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingHealthIndicator.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingMultipartZipFile.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSetupInfo.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java (1 hunks)
  • src/main/webapp/app/app.routes.ts (1 hunks)
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.html (1 hunks)
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.ts (7 hunks)
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts (1 hunks)
  • src/main/webapp/app/programming/manage/exercise/programming-exercise.component.ts (1 hunks)
  • src/main/webapp/app/programming/manage/programming-exercise-management.route.ts (1 hunks)
  • src/main/webapp/app/programming/manage/services/programming-exercise-sharing.service.spec.ts (1 hunks)
  • src/main/webapp/app/programming/manage/services/programming-exercise-sharing.service.ts (1 hunks)
  • src/main/webapp/app/programming/manage/update/programming-exercise-creation-config.ts (1 hunks)
  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.html (3 hunks)
  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.spec.ts (5 hunks)
  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.ts (17 hunks)
  • src/main/webapp/app/programming/shared/actions/programming-exercise-instructor-exercise-sharing.component.spec.ts (1 hunks)
  • src/main/webapp/app/programming/shared/actions/programming-exercise-instructor-exercise-sharing.component.ts (1 hunks)
  • src/main/webapp/app/sharing/search-result-dto.model.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.html (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.spec.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.model.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.route.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.scss (1 hunks)
  • src/main/webapp/i18n/de/health.json (1 hunks)
  • src/main/webapp/i18n/de/programmingExercise.json (1 hunks)
  • src/main/webapp/i18n/de/sharing.json (1 hunks)
  • src/main/webapp/i18n/en/health.json (1 hunks)
  • src/main/webapp/i18n/en/programmingExercise.json (1 hunks)
  • src/main/webapp/i18n/en/sharing.json (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceExportTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingServiceTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorHealthCheckRegistryTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorServiceTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingInfoDTOTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingPlatformMockProvider.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSupportResourceTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationIndependentTest.java (2 hunks)
  • src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationLocalCILocalVCTest.java (3 hunks)
  • src/test/javascript/spec/helpers/mocks/programming-exercise-creation-config-mock.ts (1 hunks)
  • src/test/resources/de/tum/cit/aet/artemis/programming/service/sharing/basket/sampleBasket.json (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • src/main/webapp/i18n/de/health.json
  • src/main/webapp/i18n/en/health.json
  • docs/admin/setup/sharing.rst
🚧 Files skipped from review as they are similar to previous changes (51)
  • src/main/webapp/app/programming/manage/exercise/programming-exercise.component.ts
  • docs/admin/extension-services.rst
  • build.gradle
  • docs/index.rst
  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.html
  • src/main/java/de/tum/cit/aet/artemis/core/service/ProfileService.java
  • src/main/java/de/tum/cit/aet/artemis/core/config/SecurityConfiguration.java
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.html
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingException.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorServiceTest.java
  • src/main/webapp/app/programming/manage/update/programming-exercise-creation-config.ts
  • src/main/webapp/i18n/en/sharing.json
  • src/main/webapp/app/sharing/sharing.scss
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorHealthCheckRegistryTest.java
  • src/main/webapp/app/programming/shared/actions/programming-exercise-instructor-exercise-sharing.component.spec.ts
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSetupInfo.java
  • src/main/webapp/app/sharing/sharing.route.ts
  • src/main/webapp/i18n/de/sharing.json
  • src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java
  • src/test/resources/de/tum/cit/aet/artemis/programming/service/sharing/basket/sampleBasket.json
  • src/main/webapp/app/programming/manage/programming-exercise-management.route.ts
  • src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationLocalCILocalVCTest.java
  • src/main/java/de/tum/cit/aet/artemis/core/dto/SharingInfoDTO.java
  • src/main/java/de/tum/cit/aet/artemis/core/web/SharingSupportResource.java
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.with-sharing.spec.ts
  • src/main/webapp/app/app.routes.ts
  • src/main/webapp/i18n/de/programmingExercise.json
  • src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationIndependentTest.java
  • src/main/java/de/tum/cit/aet/artemis/core/config/RestTemplateConfiguration.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingServiceTest.java
  • src/main/webapp/i18n/en/programmingExercise.json
  • src/test/javascript/spec/helpers/mocks/programming-exercise-creation-config-mock.ts
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingPlatformMockProvider.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportFromFileService.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingSupportResourceTest.java
  • src/main/java/de/tum/cit/aet/artemis/core/config/Constants.java
  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.spec.ts
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ProgrammingExerciseImportFromSharingService.java
  • src/main/webapp/app/programming/manage/services/programming-exercise-sharing.service.ts
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingMultipartZipFile.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingHealthIndicator.java
  • src/main/webapp/app/sharing/search-result-dto.model.ts
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingInfoDTOTest.java
  • src/main/webapp/app/sharing/sharing.model.ts
  • src/main/webapp/app/programming/manage/services/programming-exercise-sharing.service.spec.ts
  • src/main/webapp/app/programming/shared/actions/programming-exercise-instructor-exercise-sharing.component.ts
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceExportTest.java
  • src/main/webapp/app/sharing/sharing.component.ts
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorService.java
  • docs/user/sharing.rst
  • src/main/webapp/app/programming/manage/detail/programming-exercise-detail.component.ts
🧰 Additional context used
📓 Path-based instructions (4)
`src/main/webapp/**/*.ts`: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalC...

src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

  • src/main/webapp/app/sharing/sharing.component.spec.ts
  • src/main/webapp/app/programming/manage/update/programming-exercise-update.component.ts
`src/test/java/**/*.java`: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_...

src/test/java/**/*.java: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_use: enforce_package_rules; db_query_count_tests: track_performance; util_service_factory_pattern: true; avoid_db_access: true; mock_strategy: static_mocks; context_restart_minimize: true

  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java
`src/main/java/**/*.java`: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,de...

src/main/java/**/*.java: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java
  • src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java
`src/main/webapp/**/*.html`: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

  • src/main/webapp/app/sharing/sharing.component.html
🧬 Code Graph Analysis (1)
src/main/webapp/app/sharing/sharing.component.spec.ts (4)
src/test/javascript/spec/helpers/mocks/service/mock-alert.service.ts (1)
  • MockAlertService (3-8)
src/test/javascript/spec/helpers/mocks/mock-router.ts (1)
  • MockRouter (5-30)
src/main/webapp/app/sharing/sharing.model.ts (1)
  • ShoppingBasket (50-54)
src/main/webapp/app/core/course/shared/entities/course.model.ts (1)
  • Course (59-180)
🪛 ast-grep (0.38.1)
src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java

[warning] 406-406: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: Mac.getInstance(algorithm)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html

(use-of-aes-ecb-java)


[warning] 406-406: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Mac.getInstance(algorithm)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA

(desede-is-deprecated-java)

🪛 dotenv-linter (3.3.0)
docker/artemis/config/dev-local-vc-local-ci.env

[warning] 5-5: [IncorrectDelimiter] The SPRING_PROFILES_ACTIVE: artemis,scheduling,localci,localvc,buildagent,core,dev,docker,sharing key has incorrect delimiter


[warning] 5-5: [KeyWithoutValue] The SPRING_PROFILES_ACTIVE: artemis,scheduling,localci,localvc,buildagent,core,dev,docker,sharing key should be with a value or have an equal sign


[warning] 5-5: [LowercaseKey] The SPRING_PROFILES_ACTIVE: artemis,scheduling,localci,localvc,buildagent,core,dev,docker,sharing key should be in uppercase

🪛 HTMLHint (1.5.0)
src/main/webapp/app/sharing/sharing.component.html

[error] 1-1: Doctype must be declared before any non-comment content.

(doctype-first)

⏰ Context from checks skipped due to timeout of 90000ms (13)
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build .war artifact
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: client-tests-selected
  • GitHub Check: client-style
  • GitHub Check: client-tests
  • GitHub Check: server-tests
  • GitHub Check: server-style
  • GitHub Check: Analyse
  • GitHub Check: Mend Security Check
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build .war artifact
🔇 Additional comments (1)
src/main/webapp/app/programming/manage/update/programming-exercise-update.component.ts (1)

722-727: Potential mismatch between build-plan serialization and import flags

buildPlanConfiguration is serialised when any of
customizeBuildPlanWithAeolus || isImportFromFile || isImportFromSharing is true.
For an import-from-sharing scenario that does not customise the build plan the Windfile is nevertheless serialised although the backend will recreate the plan on its own (setUpFromSharingImport). Double-check that this does not overwrite configuration coming from the sharing service.

Comment on lines +1 to +3
import { TranslateDirective } from 'app/shared/language/translate.directive';
import { MockTranslateService, TranslatePipeMock } from 'test/helpers/mocks/service/mock-translate.service';
import { ProfileService } from 'app/core/layouts/profiles/shared/profile.service';
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Import path likely unresolved – verify TypeScript path mapping
'test/helpers/…' assumes a path alias. Unless tsconfig contains "paths": { "test/*": ["src/test/javascript/spec/*"] }, the module won’t be found during compilation.


🏁 Script executed:

#!/bin/bash
# Verify that the import actually resolves
fd mock-translate.service | head

Length of output: 107


🏁 Script executed:

#!/bin/bash
# Locate and inspect tsconfig files for path mappings
rg -n '"paths":' -n tsconfig*.json
rg -n '"test/*":' -n tsconfig*.json

Length of output: 222


Missing TypeScript test/* path mapping

The import

import { MockTranslateService, TranslatePipeMock } from 'test/helpers/mocks/service/mock-translate.service';

resolves to

src/test/javascript/spec/helpers/mocks/service/mock-translate.service.ts

but neither tsconfig.json nor tsconfig.spec.json defines a "test/*" alias under compilerOptions.paths. Without that mapping, the module won’t compile.

Please update your tsconfig to include:

// in tsconfig.json (or extend in tsconfig.spec.json if it’s test-only)
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "test/*": ["src/test/javascript/spec/*"]
    }
  }
}

Alternatively, convert the import to a relative path:

import { MockTranslateService, TranslatePipeMock }
  from '../../../test/javascript/spec/helpers/mocks/service/mock-translate.service';
🤖 Prompt for AI Agents
In src/main/webapp/app/sharing/sharing.component.spec.ts at lines 1 to 3, the
import path 'test/helpers/mocks/service/mock-translate.service' is unresolved
because the TypeScript configuration lacks a "test/*" path mapping. Fix this by
adding the "test/*" alias to the "paths" section in tsconfig.json or
tsconfig.spec.json, mapping it to "src/test/javascript/spec/*". Alternatively,
replace the import with a correct relative path to the mock-translate.service
file.

Comment on lines +132 to +135
if (this.sharingInfo) {
isEditFieldDisplayedMapping[ProgrammingExerciseInputField.SHORT_NAME] = true;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

SHORT_NAME field forced visible for every scenario

isEditFieldDisplayedRecord now checks if (this.sharingInfo) – but sharingInfo is always instantiated (new SharingInfo()), so the condition is perpetually true and the SHORT_NAME input is shown even when the user is not importing from the sharing platform.

Use the dedicated flag that already exists:

-        if (this.sharingInfo) {
+        if (this.isImportFromSharing) {
             isEditFieldDisplayedMapping[ProgrammingExerciseInputField.SHORT_NAME] = true;
         }
📝 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
if (this.sharingInfo) {
isEditFieldDisplayedMapping[ProgrammingExerciseInputField.SHORT_NAME] = true;
}
// Only show the SHORT_NAME field when importing from the sharing platform
if (this.isImportFromSharing) {
isEditFieldDisplayedMapping[ProgrammingExerciseInputField.SHORT_NAME] = true;
}
🤖 Prompt for AI Agents
In
src/main/webapp/app/programming/manage/update/programming-exercise-update.component.ts
around lines 132 to 135, the condition checking this.sharingInfo is always true
because sharingInfo is always instantiated, causing the SHORT_NAME field to be
shown incorrectly. Replace the condition with the dedicated flag that indicates
whether the user is importing from the sharing platform to control the
visibility of the SHORT_NAME input correctly.

Comment on lines 770 to 781
this.subscribeToSaveResponse(this.programmingExerciseService.importFromFile(this.programmingExercise, this.courseId));
} else if (this.isImportFromSharing) {
this.courseService.find(this.courseId).subscribe((res) => {
this.programmingExerciseSharingService.setUpFromSharingImport(this.programmingExercise, res.body!, this.sharingInfo).subscribe({
next: (response: HttpResponse<ProgrammingExercise>) => {
this.alertService.success('artemisApp.programmingExercise.created', { param: this.programmingExercise.title });
this.onSaveSuccess(response.body!);
},
error: (err) => this.onSaveError(err),
});
});
} else if (this.isImportFromExistingExercise) {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

No error handling for failed course lookup before sharing import

If courseService.find(this.courseId) fails (e.g. invalid courseId or network error) the component silently stalls with isSaving = true. Add error handling to surface the problem and unblock the UI.

-        } else if (this.isImportFromSharing) {
-            this.courseService.find(this.courseId).subscribe((res) => {
+        } else if (this.isImportFromSharing) {
+            this.courseService.find(this.courseId).subscribe({
+                next: (res) => {
                     this.programmingExerciseSharingService.setUpFromSharingImport(this.programmingExercise, res.body!, this.sharingInfo).subscribe({
                         next: ...
                         error: (err) => this.onSaveError(err),
                     });
-            });
+                },
+                error: (err) => this.onSaveError(err),
+            });
📝 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
this.subscribeToSaveResponse(this.programmingExerciseService.importFromFile(this.programmingExercise, this.courseId));
} else if (this.isImportFromSharing) {
this.courseService.find(this.courseId).subscribe((res) => {
this.programmingExerciseSharingService.setUpFromSharingImport(this.programmingExercise, res.body!, this.sharingInfo).subscribe({
next: (response: HttpResponse<ProgrammingExercise>) => {
this.alertService.success('artemisApp.programmingExercise.created', { param: this.programmingExercise.title });
this.onSaveSuccess(response.body!);
},
error: (err) => this.onSaveError(err),
});
});
} else if (this.isImportFromExistingExercise) {
this.subscribeToSaveResponse(this.programmingExerciseService.importFromFile(this.programmingExercise, this.courseId));
} else if (this.isImportFromSharing) {
this.courseService.find(this.courseId).subscribe({
next: (res) => {
this.programmingExerciseSharingService.setUpFromSharingImport(this.programmingExercise, res.body!, this.sharingInfo).subscribe({
next: (response: HttpResponse<ProgrammingExercise>) => {
this.alertService.success('artemisApp.programmingExercise.created', { param: this.programmingExercise.title });
this.onSaveSuccess(response.body!);
},
error: (err) => this.onSaveError(err),
});
},
error: (err) => this.onSaveError(err),
});
} else if (this.isImportFromExistingExercise) {
🤖 Prompt for AI Agents
In
src/main/webapp/app/programming/manage/update/programming-exercise-update.component.ts
around lines 770 to 781, the call to courseService.find(this.courseId) lacks
error handling, causing the UI to stall with isSaving = true if the course
lookup fails. Add an error callback to the subscribe method that calls
this.onSaveError with the error to handle failures properly and unblock the UI.

Comment on lines +172 to +178
@PostMapping(SHARINGEXPORT_RESOURCE_PATH + "/{exerciseId}")
public ResponseEntity<String> exportExerciseToSharing(@RequestBody String callBackUrl, @PathVariable("exerciseId") Long exerciseId) {
try {
URI uriRedirect = exerciseSharingService.exportExerciseToSharing(exerciseId).toURI();
uriRedirect = UriBuilder.fromUri(uriRedirect).queryParam("callBack", callBackUrl).build();
return ResponseEntity.ok().body("\"" + uriRedirect.toString() + "\"");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Manual JSON string building – return structured DTO

Wrapping the URL in quotes creates invalid JSON unless double-encoded by consumer. Return a DTO or Map.of("url", uriRedirect.toString()) and let Spring serialise it.

-            return ResponseEntity.ok().body("\"" + uriRedirect.toString() + "\"");
+            return ResponseEntity.ok(Map.of("sharingUrl", uriRedirect.toString()));

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java
around lines 172 to 178, the method manually builds a JSON string by wrapping
the URL in quotes, which results in invalid JSON unless double-encoded by the
consumer. To fix this, change the return type to return a structured DTO or use
Map.of("url", uriRedirect.toString()) so that Spring can automatically serialize
it into valid JSON.

Copy link

End-to-End (E2E) Test Results Summary

Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
End-to-End (E2E) Test Report201 ran197 passed3 skipped1 failed1h 982ms
TestResultTime ⏱
End-to-End (E2E) Test Report
e2e/exercise/programming/ProgrammingExerciseStaticCodeAnalysis.spec.ts
ts.Static code analysis tests › Configures SCA grading and makes a successful submission with SCA errors❌ failure2m 17s 533ms

Copy link
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: 0

♻️ Duplicate comments (4)
src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (4)

273-273: Typo in comment

unfortunatellyunfortunately


232-241: ⚠️ Potential issue

Add missing null‐checks in helper to avoid NPE

parseParamsToMap still trusts the caller completely. If params is null or contains null elements you hit an NPE before the AssertJ check fires.
The validation proposed in the previous review is still applicable.

private Map<String, String> parseParamsToMap(String... params) {
-    Map<String, String> paramsMap = new HashMap<>();
-    assertThat(params.length % 2).isEqualTo(0).describedAs("paramList must contain even elements");
+    Objects.requireNonNull(params, "params cannot be null");
+    assertThat(params.length % 2).isEqualTo(0).describedAs("paramList must contain an even number of elements");
+    Map<String, String> paramsMap = new HashMap<>();
     for (int i = 0; i < params.length; i = i + 2) {
         String paramName = params[i];
         String paramValue = params[i + 1];
+        if (paramName == null || paramValue == null) {
+            throw new IllegalArgumentException("Parameter names and values cannot be null");
+        }
         paramsMap.put(paramName, paramValue);
     }
     return paramsMap;
}

265-267: ⚠️ Potential issue

Endpoint path still incorrect – tests call a non-existing route

Earlier feedback pointed out that the correct production route is /api/sharing/*, not /api/programming/sharing/*.
The three occurrences below are still wrong and therefore do not test the real controller:

importBasketWrongChecksum – lines 265-267
importExerciseCompleteProcess – lines 326-327
setUpWithMissingExercise – lines 404-405

-"/api/programming/sharing/
+"/api/sharing/

Also applies to: 326-327, 404-405


270-334: 🛠️ Refactor suggestion

Monolithic test violates SRP – refactor into focused tests

importExerciseCompleteProcess spans ~60 lines and covers basket import, detail retrieval, problem-statement validation and setup-import. Splitting this into 3-4 small tests (e.g. shouldImportBasket, shouldRetrieveExerciseDetails, shouldRetrieveProblemStatement, shouldCompleteSetupImport) improves readability and failure diagnostics.

🧹 Nitpick comments (1)
src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (1)

172-173: Remove unused variable

sampleBasket is read but never used in testImportBasketFail, which may hide accidental leftovers and adds I/O overhead.

-String sampleBasket = IOUtils.toString(...);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e2d7af and e44f1a8.

📒 Files selected for processing (1)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`src/test/java/**/*.java`: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_...

src/test/java/**/*.java: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_use: enforce_package_rules; db_query_count_tests: track_performance; util_service_factory_pattern: true; avoid_db_access: true; mock_strategy: static_mocks; context_restart_minimize: true

  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java
🧠 Learnings (1)
📓 Common learnings
Learnt from: Wallenstein61
PR: ls1intum/Artemis#10989
File: src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java:122-135
Timestamp: 2025-06-06T14:47:54.300Z
Learning: In Artemis sharing platform integration (under "sharing" profile), endpoints in ExerciseSharingResource use checksum-based authentication with shared secrets instead of @PreAuthorize annotations. These endpoints (like getProblemStatement, getExerciseDetails, loadShoppingBasket) validate security through sharingInfo.checkChecksum() calls rather than role-based access control, as they serve as API interfaces for external sharing platform communication.
🧬 Code Graph Analysis (1)
src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (4)
src/main/webapp/app/sharing/sharing.model.ts (1)
  • ShoppingBasket (50-54)
src/main/webapp/app/core/course/shared/entities/course.model.ts (1)
  • Course (59-180)
src/main/java/de/tum/cit/aet/artemis/programming/domain/Repository.java (1)
  • Repository (18-95)
src/main/webapp/app/core/layouts/profiles/profile-info.model.ts (1)
  • ProgrammingLanguageFeature (115-124)
⏰ Context from checks skipped due to timeout of 90000ms (12)
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build .war artifact
  • GitHub Check: client-tests-selected
  • GitHub Check: client-style
  • GitHub Check: client-tests
  • GitHub Check: Analyse
  • GitHub Check: server-style
  • GitHub Check: server-tests
  • GitHub Check: Build .war artifact
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis

Copy link

End-to-End (E2E) Test Results Summary

1 similar comment
Copy link

End-to-End (E2E) Test Results Summary

Copy link
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: 1

♻️ Duplicate comments (4)
src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (4)

232-241: Input validation still missing – risk of NPE & assertion failures

The helper does not guard against null or an odd number of arguments. This is identical to the issue raised in a previous review.


265-267: Wrong endpoint prefix (/api/programming/sharing/...) – tests target a non-existing route

The production controller is exposed under /api/sharing/**. Using the outdated prefix lets the test pass for the wrong reason (Spring returns 404 → 400/500).


404-405: Same endpoint drift here – adjust to /api/sharing/setup-import


270-334: 93-line test violates “small & specific” guideline – split into focused tests

importExerciseCompleteProcess mixes basket import, problem-statement retrieval, exercise-details parsing and final setup. Break it up into four atomic tests as already suggested to improve readability and maintenance.

🧹 Nitpick comments (1)
src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseServiceTest.java (1)

91-100: Clean up provisional test and align with test guidelines

  • Method name should convey the behaviour under test (e.g. shouldReturnEmptyListWhenCourseDoesNotExist).
  • Add an explicit @WithMockUser to satisfy RBAC expectations of the coding guidelines.
  • Remove emoticon & PR URL from Javadoc, replace with a TODO that points to the tracked issue so the test can be located and deleted later.

Suggested change:

-    /**
-     * This method is solely added to circumvent problem from open pull request
-     * https://github.com/ls1intum/Artemis/pull/10997 :-(.
-     * It can be deleted after resolving this issue
-     */
-    @Test
-    void findAllByCourseId() {
-        assertThat(programmingExerciseService.findAllByCourseId(9999L)).describedAs("course with id 9999 should not exist").isEmpty();
-    }
+    /**
+     * TODO Remove once issue #10997 is fixed – ensures the workaround service
+     * method returns an empty list for a non-existing course.
+     */
+    @Test
+    @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
+    void shouldReturnEmptyListWhenCourseDoesNotExist() {
+        assertThat(programmingExerciseService.findAllByCourseId(9999L))
+                .describedAs("Service should return empty list for a non-existing course id")
+                .isEmpty();
+    }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e44f1a8 and 2cc1238.

📒 Files selected for processing (3)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseServiceTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
`src/test/java/**/*.java`: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_...

src/test/java/**/*.java: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_use: enforce_package_rules; db_query_count_tests: track_performance; util_service_factory_pattern: true; avoid_db_access: true; mock_strategy: static_mocks; context_restart_minimize: true

  • src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseServiceTest.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java
`src/main/java/**/*.java`: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,de...

src/main/java/**/*.java: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

  • src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java
🧠 Learnings (1)
📓 Common learnings
Learnt from: Wallenstein61
PR: ls1intum/Artemis#10989
File: src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java:122-135
Timestamp: 2025-06-06T14:47:54.300Z
Learning: In Artemis sharing platform integration (under "sharing" profile), endpoints in ExerciseSharingResource use checksum-based authentication with shared secrets instead of @PreAuthorize annotations. These endpoints (like getProblemStatement, getExerciseDetails, loadShoppingBasket) validate security through sharingInfo.checkChecksum() calls rather than role-based access control, as they serve as API interfaces for external sharing platform communication.
⏰ Context from checks skipped due to timeout of 90000ms (13)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build .war artifact
  • GitHub Check: client-style
  • GitHub Check: client-tests
  • GitHub Check: client-tests-selected
  • GitHub Check: server-style
  • GitHub Check: server-tests
  • GitHub Check: Analyse
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build .war artifact
🔇 Additional comments (1)
src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (1)

243-246: sharingApiKey may be null – checksum calculation can blow up

@Value("${artemis.sharing.apikey:#{null}}") allows a null to be injected. If that happens, SecretChecksumCalculator.calculateChecksum is very likely to throw or silently produce a wrong hash, breaking every test that relies on it.
Add a fail-fast guard or provide a default key via test properties.

Comment on lines 481 to 501
/**
* Not for public use!
* This method is solely added to circumvent a problem from open pull request
* https://github.com/ls1intum/Artemis/pull/10997 :-(.
*
* <pre>
* method de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository.findAllByCourseId(java.lang.Long) is not used in production code
* </pre>
*
* It can be deleted after resolving the related issue
*
* @param id the course id
*/
public List<ProgrammingExercise> findAllByCourseId(Long id) {
return programmingExerciseRepository.findAllByCourseId(id);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Leaking non-public helper as a public API – downgrade visibility & mark for removal

The method is intentionally a stop-gap for test compilation, yet making it public silently broadens the service’s contract and invites unintended production use.
Recommend:

  1. Reduce visibility to package-private and/or add @VisibleForTesting (or at least @Deprecated) so IDEs warn callers.
  2. Rename the parameter to courseId for clarity.
  3. Add @Transactional(readOnly = true) or @Transactional(propagation = Propagation.SUPPORTS, readOnly = true) if you keep it, to align with other read-only accessors.
  4. Replace the emoticon and PR URL in the Javadoc with a TODO referencing the issue tracker to keep the codebase professional.

Example patch:

-    public List<ProgrammingExercise> findAllByCourseId(Long id) {
-        return programmingExerciseRepository.findAllByCourseId(id);
+    /**
+     * TEMPORARY workaround for <issue-id>. Remove once resolved.
+     */
+    @VisibleForTesting
+    /* package */ List<ProgrammingExercise> findAllByCourseId(Long courseId) {
+        return programmingExerciseRepository.findAllByCourseId(courseId);
+    }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java
around lines 481 to 496, the method findAllByCourseId is unnecessarily public
and lacks proper annotations and documentation. Change its visibility to
package-private, rename the parameter from id to courseId for clarity, add
@VisibleForTesting or at least @Deprecated annotation to signal limited use, and
annotate the method with @Transactional(readOnly = true) or
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true) to match
other read-only methods. Also, update the Javadoc to remove the emoticon and PR
URL, replacing them with a professional TODO referencing the issue tracker.

Copy link

End-to-End (E2E) Test Results Summary

@Wallenstein61 Wallenstein61 force-pushed the feature/connecting-to-sharing-platform branch from 62e2d26 to 6cb84af Compare June 12, 2025 14:31
Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ✅Skipped ⚠️FailedTime ⏱
End-to-End (E2E) Test Report201 ran198 passed3 skipped0 failed57m 40s 5ms
TestResultTime ⏱
No test annotations available

Copy link
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

♻️ Duplicate comments (10)
src/main/webapp/app/sharing/sharing.component.html (1)

4-8: 🛠️ Refactor suggestion

Wrap <th> elements inside a <tr> within <thead>
HTML requires <thead> to contain one or more <tr> rows, not direct <th> children. Encapsulate the header cells in a <tr>.

-            <thead>
-                <th jhiTranslate="artemisApp.sharing.importedExercise">Imported Exercise</th>
-                <th jhiTranslate="artemisApp.sharing.courseToImport">Course to import</th>
-                <th></th>
-            </thead>
+            <thead>
+                <tr>
+                    <th jhiTranslate="artemisApp.sharing.importedExercise">Imported Exercise</th>
+                    <th jhiTranslate="artemisApp.sharing.courseToImport">Course to import</th>
+                    <th></th>
+                </tr>
+            </thead>
src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java (6)

257-257: Remove server-side HTML escaping of Markdown
See earlier review – escaping breaks Markdown rendering.


368-369: Stop stripping Base64 padding; use URL-encoder instead


402-405: psk can be null – add a defensive check


413-415: Don’t return predictable value on crypto error


427-430: Avoid manual “space→+” sanitisation – fix URL decoding at controller level


456-459: Strengthen token validation (length, path separators, …)

src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (3)

232-241: Add null / even-length validation to parseParamsToMap


265-267: Tests hit non-existing route /api/programming/sharing/**

Endpoints use /api/sharing/**. Update paths.


270-334: importExerciseCompleteProcess is 90+ lines – split into focussed tests

🧹 Nitpick comments (1)
src/main/webapp/app/sharing/sharing.component.html (1)

3-3: Avoid inline styles for layout
Consider moving hard-coded border-spacing and border-collapse into a CSS class (e.g., .sharing-table) to improve maintainability and theming.

-<table style="border-spacing: 50px 0px; border-collapse: separate">
+<table class="sharing-table">
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62e2d26 and 6cb84af.

📒 Files selected for processing (13)
  • src/main/java/de/tum/cit/aet/artemis/core/config/SecurityConfiguration.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/service/ProfileService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/web/SharingSupportResource.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java (2 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorService.java (1 hunks)
  • src/main/webapp/app/programming/manage/services/programming-exercise-sharing.service.ts (1 hunks)
  • src/main/webapp/app/sharing/sharing.component.html (1 hunks)
  • src/main/webapp/i18n/de/health.json (1 hunks)
  • src/main/webapp/i18n/en/health.json (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseServiceTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingPlatformMockProvider.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • src/main/webapp/i18n/de/health.json
  • src/main/webapp/i18n/en/health.json
  • src/main/java/de/tum/cit/aet/artemis/core/config/SecurityConfiguration.java
  • src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseServiceTest.java
  • src/main/java/de/tum/cit/aet/artemis/core/service/ProfileService.java
  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingPlatformMockProvider.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java
  • src/main/webapp/app/programming/manage/services/programming-exercise-sharing.service.ts
  • src/main/java/de/tum/cit/aet/artemis/core/web/SharingSupportResource.java
  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/SharingConnectorService.java
🧰 Additional context used
📓 Path-based instructions (3)
`src/test/java/**/*.java`: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_...

src/test/java/**/*.java: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_use: enforce_package_rules; db_query_count_tests: track_performance; util_service_factory_pattern: true; avoid_db_access: true; mock_strategy: static_mocks; context_restart_minimize: true

  • src/test/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingResourceImportTest.java
`src/main/java/**/*.java`: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,de...

src/main/java/**/*.java: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

  • src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java
`src/main/webapp/**/*.html`: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

  • src/main/webapp/app/sharing/sharing.component.html
🧠 Learnings (2)
📓 Common learnings
Learnt from: Wallenstein61
PR: ls1intum/Artemis#10989
File: src/main/java/de/tum/cit/aet/artemis/programming/web/ExerciseSharingResource.java:122-135
Timestamp: 2025-06-06T14:47:54.300Z
Learning: In Artemis sharing platform integration (under "sharing" profile), endpoints in ExerciseSharingResource use checksum-based authentication with shared secrets instead of @PreAuthorize annotations. These endpoints (like getProblemStatement, getExerciseDetails, loadShoppingBasket) validate security through sharingInfo.checkChecksum() calls rather than role-based access control, as they serve as API interfaces for external sharing platform communication.
src/main/webapp/app/sharing/sharing.component.html (3)
Learnt from: florian-glombik
PR: ls1intum/Artemis#8858
File: src/main/webapp/i18n/de/student-dashboard.json:0-0
Timestamp: 2024-10-08T15:35:52.595Z
Learning: Avoid suggesting the phrase "Keine Aufgaben passen zu den gewählten Filtern" for the translation key `noExercisesFoundWithAppliedFilter` in the PR ls1intum/Artemis#8858.
Learnt from: florian-glombik
PR: ls1intum/Artemis#8858
File: src/main/webapp/i18n/de/student-dashboard.json:36-37
Timestamp: 2024-10-14T10:27:58.500Z
Learning: Avoid suggesting the translation change for "noExercisesFoundWithAppliedFilter" in the PR ls1intum/Artemis#8858. The preferred translation is "Keine Aufgaben passen zu den gewählten Filtern."
Learnt from: florian-glombik
PR: ls1intum/Artemis#8858
File: src/main/webapp/app/shared/sidebar/sidebar.component.html:18-22
Timestamp: 2024-07-09T10:08:14.161Z
Learning: Translation keys `artemisApp.courseOverview.general.noExercisesFoundWithAppliedFilter` and `artemisApp.courseOverview.general.noDataFound` should be added to the relevant translation files to ensure consistent translations across the application.
🪛 ast-grep (0.38.1)
src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java

[warning] 406-406: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: Mac.getInstance(algorithm)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html

(use-of-aes-ecb-java)


[warning] 406-406: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Mac.getInstance(algorithm)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA

(desede-is-deprecated-java)

🪛 HTMLHint (1.5.0)
src/main/webapp/app/sharing/sharing.component.html

[error] 1-1: Doctype must be declared before any non-comment content.

(doctype-first)

⏰ Context from checks skipped due to timeout of 90000ms (14)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Build .war artifact
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: client-tests
  • GitHub Check: client-style
  • GitHub Check: client-tests-selected
  • GitHub Check: server-tests
  • GitHub Check: server-style
  • GitHub Check: Analyse
  • GitHub Check: Mend Security Check
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
  • GitHub Check: Build .war artifact
🔇 Additional comments (4)
src/main/webapp/app/sharing/sharing.component.html (4)

15-15: Verify translation key for “No importable exercises”
Ensure that artemisApp.sharing.error.noImportedExercises exists in your i18n/en/sharing.json and i18n/de/sharing.json under the error namespace and matches the intended message.


14-44: Exercise list: correct use of new Angular template syntax and accessibility
The @if and @for constructs adhere to the updated Angular syntax guidelines, and the radio inputs include aria-label and title attributes for screen readers. Great job on this integration!


62-102: Course list: correct sorting and selection implementation
The jhiSort directives on <tr> headers, the use of @for for iteration, and the labeled radio inputs for course selection follow best practices. All looks solid here.


107-116: Import button implementation looks correct
The disabled state is tied to the model, the id is unique, and the click handler directs to the import flow with appropriate iconography and translation key usage.

Comment on lines +1 to +2
<body>
<div class="content">
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove <body> wrapper from component template
Angular component templates should not include their own <body> tags; this leads to invalid DOM nesting. Replace the outer <body>/</body> with a wrapping <div> (e.g., <div class="sharing-component">).

-<body>
+<div class="sharing-component">
 ...
-</body>
+</div>

Also applies to: 121-122

🧰 Tools
🪛 HTMLHint (1.5.0)

[error] 1-1: Doctype must be declared before any non-comment content.

(doctype-first)

🤖 Prompt for AI Agents
In src/main/webapp/app/sharing/sharing.component.html at lines 1-2 and 121-122,
remove the <body> and </body> tags from the component template as Angular
components should not include their own body tags. Replace these with a wrapping
<div> element, such as <div class="sharing-component">, to ensure valid DOM
structure and proper encapsulation.

Comment on lines +190 to +201
String exercisesZipUrl = correctLocalHostInDocker(sharingInfo.apiBaseURL()) + "/basket/{basketToken}/repository/" + itemPosition + "?format={format}";
Resource zipInputResource = restTemplate.getForObject(exercisesZipUrl, Resource.class,
Map.of("basketToken", sharingInfo.basketToken(), "format", "artemis"));
if (zipInputResource == null) {
throw new SharingException("Could not retrieve basket item resource");
}
InputStream zipInput = zipInputResource.getInputStream();

File basketFile = Files.createTempFile("basketStore", ".zip").toFile();
FileCopyUtils.copy(zipInput, new FileOutputStream(basketFile));
return basketFile;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Close streams in CacheLoader.load to avoid FD leaks

zipInput and the FileOutputStream are not closed, leaking file descriptors. Wrap both in try-with-resources.

-InputStream zipInput = zipInputResource.getInputStream();
-
-File basketFile = Files.createTempFile("basketStore", ".zip").toFile();
-FileCopyUtils.copy(zipInput, new FileOutputStream(basketFile));
-
-return basketFile;
+try (InputStream zipInput = zipInputResource.getInputStream();
+     FileOutputStream fos = new FileOutputStream(Files.createTempFile("basketStore", ".zip").toFile())) {
+    FileCopyUtils.copy(zipInput, fos);
+    return new File(fos.getFD().sync();  // ensure data is flushed
+}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
src/main/java/de/tum/cit/aet/artemis/programming/service/sharing/ExerciseSharingService.java
around lines 190 to 201, the InputStream zipInput and the FileOutputStream used
to copy data to basketFile are not closed, causing file descriptor leaks. Fix
this by wrapping both the InputStream and the FileOutputStream in
try-with-resources blocks to ensure they are automatically closed after use.

Copy link

End-to-End (E2E) Test Results Summary

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
client Pull requests that update TypeScript code. (Added Automatically!) config-change Pull requests that change the config in a way that they require a deployment via Ansible. core Pull requests that affect the corresponding module docker documentation programming Pull requests that affect the corresponding module ready for review server Pull requests that update Java code. (Added Automatically!) tests
Projects
Status: Ready For Review
Development

Successfully merging this pull request may close these issues.

1 participant