diff --git a/angular.json b/angular.json index e5543ff2ce60..008ac75d13bf 100644 --- a/angular.json +++ b/angular.json @@ -113,7 +113,7 @@ }, { "glob": "**/*", - "input": "./node_modules/monaco-editor/min/vs", + "input": "./node_modules/monaco-editor/bundles/vs", "output": "vs" } ], diff --git a/build.gradle b/build.gradle index 29da90bf3674..cdad264bac58 100644 --- a/build.gradle +++ b/build.gradle @@ -246,14 +246,15 @@ dependencies { implementation "org.gitlab4j:gitlab4j-api:6.0.0-rc.5" implementation "de.jplag:jplag:${jplag_version}" - implementation "de.jplag:java:${jplag_version}" - implementation "de.jplag:kotlin:${jplag_version}" + implementation "de.jplag:c:${jplag_version}" - implementation "de.jplag:swift:${jplag_version}" implementation "de.jplag:java:${jplag_version}" + implementation "de.jplag:javascript:${jplag_version}" + implementation "de.jplag:kotlin:${jplag_version}" implementation "de.jplag:python-3:${jplag_version}" + implementation "de.jplag:rlang:${jplag_version}" implementation "de.jplag:rust:${jplag_version}" - implementation "de.jplag:javascript:${jplag_version}" + implementation "de.jplag:swift:${jplag_version}" implementation "de.jplag:text:${jplag_version}" // those are transitive dependencies of JPlag Text --> Stanford NLP diff --git a/docs/index.rst b/docs/index.rst index 32c130434e4f..f4a7bc4cf449 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -37,6 +37,7 @@ All these exercises are supposed to be run either live in the lecture with insta user/grading user/courses/customizable user/markdown-support + user/integrated-code-lifecycle user/exports user/mobile-applications user/lti diff --git a/docs/user/exercises/programming-exercise-features.inc b/docs/user/exercises/programming-exercise-features.inc index 7bccf1596315..660e2bd4bf02 100644 --- a/docs/user/exercises/programming-exercise-features.inc +++ b/docs/user/exercises/programming-exercise-features.inc @@ -37,6 +37,8 @@ Instructors can still use those templates to generate programming exercises and +----------------------+----------+---------+ | JavaScript | yes | yes | +----------------------+----------+---------+ + | R | yes | yes | + +----------------------+----------+---------+ - Not all ``templates`` support the same feature set and supported features can also change depending on the continuous integration system setup. Depending on the feature set, some options might not be available during the creation of the programming exercise. @@ -71,6 +73,8 @@ Instructors can still use those templates to generate programming exercises and +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ | JavaScript | no | no | yes | no | n/a | no | no | L: yes, J: no | +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ + | R | no | no | yes | no | n/a | no | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - *Sequential Test Runs*: ``Artemis`` can generate a build plan which first executes structural and then behavioral tests. This feature can help students to better concentrate on the immediate challenge at hand. - *Static Code Analysis*: ``Artemis`` can generate a build plan which additionally executes static code analysis tools. diff --git a/docs/user/exercises/programming-repository-access.inc b/docs/user/exercises/programming-repository-access.inc index 45323fced7dd..c4e201d0f4b6 100644 --- a/docs/user/exercises/programming-repository-access.inc +++ b/docs/user/exercises/programming-repository-access.inc @@ -1,3 +1,5 @@ +If you are a student, and want to know how to use the Artemis Version Control for checking out repositories locally, :ref:`checkout this guide.` + The following tables lists the different types of repositories and gives an overview of the access rights different users have. To gain these access rights, a user must assume the indicated role in the course the repository belongs to. diff --git a/docs/user/exercises/programming.rst b/docs/user/exercises/programming.rst index 6f8a8e00782c..61f1731c4dff 100644 --- a/docs/user/exercises/programming.rst +++ b/docs/user/exercises/programming.rst @@ -224,19 +224,5 @@ Each one represents a single test case feedback and should have the format: Integrated Code Lifecycle ------------------------- -The following sections describe programming exercise features that are part of the integrated code lifecycle system. - -Repository View -^^^^^^^^^^^^^^^ - -.. include:: programming-integrated-code-lifecycle-repository-view.inc - -Build Agent View -^^^^^^^^^^^^^^^^ - -.. include:: programming-integrated-code-lifecycle-build-agent-view.inc - -Build Overview View -^^^^^^^^^^^^^^^^^^^ - -.. include:: programming-integrated-code-lifecycle-build-queue-view.inc +The Artemis Integrated Code Lifecycle system allows you to use programming exercises fully integrated within Artemis, without the need of any external tools. +Find more information on it :ref:`here`. diff --git a/docs/user/exercises/programming/build-agent-details.png b/docs/user/exercises/programming/build-agent-details.png deleted file mode 100644 index ae9d8f2561f4..000000000000 Binary files a/docs/user/exercises/programming/build-agent-details.png and /dev/null differ diff --git a/docs/user/exercises/programming/build-agent-summary.png b/docs/user/exercises/programming/build-agent-summary.png deleted file mode 100644 index 414451407f71..000000000000 Binary files a/docs/user/exercises/programming/build-agent-summary.png and /dev/null differ diff --git a/docs/user/exercises/programming/buildQueueCourseManagement.png b/docs/user/exercises/programming/buildQueueCourseManagement.png deleted file mode 100644 index dc16f6dd6a32..000000000000 Binary files a/docs/user/exercises/programming/buildQueueCourseManagement.png and /dev/null differ diff --git a/docs/user/exercises/programming/buildQueueSystemAdministration.png b/docs/user/exercises/programming/buildQueueSystemAdministration.png deleted file mode 100644 index 1a18244e443a..000000000000 Binary files a/docs/user/exercises/programming/buildQueueSystemAdministration.png and /dev/null differ diff --git a/docs/user/exercises/programming/commit-diff-view.png b/docs/user/exercises/programming/commit-diff-view.png deleted file mode 100644 index cfd5cda349cc..000000000000 Binary files a/docs/user/exercises/programming/commit-diff-view.png and /dev/null differ diff --git a/docs/user/exercises/programming/commit-history-view.png b/docs/user/exercises/programming/commit-history-view.png deleted file mode 100644 index ac691c9c3a60..000000000000 Binary files a/docs/user/exercises/programming/commit-history-view.png and /dev/null differ diff --git a/docs/user/exercises/programming/course-management-repositories.png b/docs/user/exercises/programming/course-management-repositories.png deleted file mode 100644 index 47e842da25a5..000000000000 Binary files a/docs/user/exercises/programming/course-management-repositories.png and /dev/null differ diff --git a/docs/user/exercises/programming/current-repository-content-view.png b/docs/user/exercises/programming/current-repository-content-view.png deleted file mode 100644 index 041b6411f6a1..000000000000 Binary files a/docs/user/exercises/programming/current-repository-content-view.png and /dev/null differ diff --git a/docs/user/exercises/programming/finished-build-jobs.png b/docs/user/exercises/programming/finished-build-jobs.png deleted file mode 100644 index 149a7f98819b..000000000000 Binary files a/docs/user/exercises/programming/finished-build-jobs.png and /dev/null differ diff --git a/docs/user/exercises/programming/open-repository-button.png b/docs/user/exercises/programming/open-repository-button.png deleted file mode 100644 index a758454dead7..000000000000 Binary files a/docs/user/exercises/programming/open-repository-button.png and /dev/null differ diff --git a/docs/user/exercises/programming/open-repository-instructor-participations.png b/docs/user/exercises/programming/open-repository-instructor-participations.png deleted file mode 100644 index 03e9e66e3910..000000000000 Binary files a/docs/user/exercises/programming/open-repository-instructor-participations.png and /dev/null differ diff --git a/docs/user/exercises/programming/open-repository-student.png b/docs/user/exercises/programming/open-repository-student.png deleted file mode 100644 index 5685e134d177..000000000000 Binary files a/docs/user/exercises/programming/open-repository-student.png and /dev/null differ diff --git a/docs/user/exercises/programming/queued-build-jobs.png b/docs/user/exercises/programming/queued-build-jobs.png deleted file mode 100644 index eb7165c01436..000000000000 Binary files a/docs/user/exercises/programming/queued-build-jobs.png and /dev/null differ diff --git a/docs/user/exercises/programming/running-build-jobs.png b/docs/user/exercises/programming/running-build-jobs.png deleted file mode 100644 index 08ccfa8856fd..000000000000 Binary files a/docs/user/exercises/programming/running-build-jobs.png and /dev/null differ diff --git a/docs/user/icl/general.rst b/docs/user/icl/general.rst new file mode 100644 index 000000000000..bf0132e6d20e --- /dev/null +++ b/docs/user/icl/general.rst @@ -0,0 +1,12 @@ +.. _icl-general-information: + +General Information +=================== + +Artemis offers the Integrated Code Lifecycle (ICL), a comprehensive feature designed to streamline the development process for programming exercises. +ICL combines version control, secure communication, and continuous integration to provide a seamless experience for students and instructors. +Understanding these components is crucial for effectively using Artemis, especially if you're new to concepts like SSH and Git. The key components of ICL are: + +- :ref:`Local Version Control`: Use the Local Version Control to interact with the repositories of programming exercises. +- :ref:`SSH`: Use SSH to perform Git operations on repositories. +- :ref:`Local Continuous Integration`: Exercise submissions are built and tested by the Local CI system. diff --git a/docs/user/exercises/programming-integrated-code-lifecycle-build-agent-view.inc b/docs/user/icl/local-ci-build-agent-view.inc similarity index 95% rename from docs/user/exercises/programming-integrated-code-lifecycle-build-agent-view.inc rename to docs/user/icl/local-ci-build-agent-view.inc index a59068be8589..b211e879b252 100644 --- a/docs/user/exercises/programming-integrated-code-lifecycle-build-agent-view.inc +++ b/docs/user/icl/local-ci-build-agent-view.inc @@ -5,7 +5,7 @@ The build agent view consists of two parts: the *Build Agent Summary View* and t The **Build Agent Summary View** shows a list of all build agents in the system, along with their status (idle and running), maximum number of concurrent builds, and running builds. The running builds can be canceled individually by clicking the red cancellation button next to the build. An Administrator can also cancel all running builds on a build agent by clicking the '*Cancel All*' button. -.. figure:: programming/build-agent-summary.png +.. figure:: local-ci/build-agent-summary.png :alt: Build Agent Summary View :align: center @@ -20,7 +20,7 @@ the submission time, start time, end time, the duration of the build job, the co The administrator can click on the participation ID to navigate to the participation's submission page. The administrator can also click on the commit hash to navigate to the commits details page and the course ID to navigate to the course management page. -.. figure:: programming/build-agent-details.png +.. figure:: local-ci/build-agent-details.png :alt: Build Agent Details :align: center diff --git a/docs/user/exercises/programming-integrated-code-lifecycle-build-queue-view.inc b/docs/user/icl/local-ci-build-queue-view.inc similarity index 90% rename from docs/user/exercises/programming-integrated-code-lifecycle-build-queue-view.inc rename to docs/user/icl/local-ci-build-queue-view.inc index 2c2470d9561a..7c865758f98c 100644 --- a/docs/user/exercises/programming-integrated-code-lifecycle-build-queue-view.inc +++ b/docs/user/icl/local-ci-build-queue-view.inc @@ -1,7 +1,7 @@ Artemis provides a build overview view that displays all queued, running, and finished build jobs. Access to the system-wide build overview is exclusively available to **Administrators** through the *System Administration* menu. -.. figure:: programming/buildQueueSystemAdministration.png +.. figure:: local-ci/buildQueueSystemAdministration.png :alt: Build Overview System Administration :align: center @@ -10,7 +10,7 @@ exclusively available to **Administrators** through the *System Administration* **Instructors** can access the build overview for a specific course through the *Build Overview* button located in *Course Management*. This view displays only the queued, running, and finished build jobs associated with the selected course. -.. figure:: programming/buildQueueCourseManagement.png +.. figure:: local-ci/buildQueueCourseManagement.png :alt: Build Overview Course Management :align: center @@ -27,7 +27,7 @@ Jobs are dynamically added to and removed from the queue in real-time. Users have the ability to cancel any job in progress. The table provides the following information: -.. figure:: programming/running-build-jobs.png +.. figure:: local-ci/running-build-jobs.png :alt: Running Build Jobs :align: center @@ -43,7 +43,7 @@ Users also have the option to cancel any job that is queued. The table below displays the following information: -.. figure:: programming/queued-build-jobs.png +.. figure:: local-ci/queued-build-jobs.png :alt: Queued Build Jobs :align: center @@ -59,7 +59,7 @@ Instructors can also access build logs with detailed information about the build The table provides the following information: -.. figure:: programming/finished-build-jobs.png +.. figure:: local-ci/finished-build-jobs.png :alt: Finished Build Jobs :align: center diff --git a/docs/user/icl/local-ci/build-agent-details.png b/docs/user/icl/local-ci/build-agent-details.png new file mode 100644 index 000000000000..4b5b8474781a Binary files /dev/null and b/docs/user/icl/local-ci/build-agent-details.png differ diff --git a/docs/user/icl/local-ci/build-agent-summary.png b/docs/user/icl/local-ci/build-agent-summary.png new file mode 100644 index 000000000000..ec11e28a0d1d Binary files /dev/null and b/docs/user/icl/local-ci/build-agent-summary.png differ diff --git a/docs/user/icl/local-ci/buildQueueCourseManagement.png b/docs/user/icl/local-ci/buildQueueCourseManagement.png new file mode 100644 index 000000000000..d4e255f39988 Binary files /dev/null and b/docs/user/icl/local-ci/buildQueueCourseManagement.png differ diff --git a/docs/user/icl/local-ci/buildQueueSystemAdministration.png b/docs/user/icl/local-ci/buildQueueSystemAdministration.png new file mode 100644 index 000000000000..9a857a317ce8 Binary files /dev/null and b/docs/user/icl/local-ci/buildQueueSystemAdministration.png differ diff --git a/docs/user/icl/local-ci/finished-build-jobs.png b/docs/user/icl/local-ci/finished-build-jobs.png new file mode 100644 index 000000000000..eb7b3dda190d Binary files /dev/null and b/docs/user/icl/local-ci/finished-build-jobs.png differ diff --git a/docs/user/icl/local-ci/queued-build-jobs.png b/docs/user/icl/local-ci/queued-build-jobs.png new file mode 100644 index 000000000000..acd382dfe1f8 Binary files /dev/null and b/docs/user/icl/local-ci/queued-build-jobs.png differ diff --git a/docs/user/icl/local-ci/running-build-jobs.png b/docs/user/icl/local-ci/running-build-jobs.png new file mode 100644 index 000000000000..8cc66a2e5c63 Binary files /dev/null and b/docs/user/icl/local-ci/running-build-jobs.png differ diff --git a/docs/user/icl/local-continuous-integration.rst b/docs/user/icl/local-continuous-integration.rst new file mode 100644 index 000000000000..6c3cdd9b8e65 --- /dev/null +++ b/docs/user/icl/local-continuous-integration.rst @@ -0,0 +1,27 @@ +.. _local-ci: + +Continuous Integration +====================== + +Continuous Integration (CI) is a software development practice where developers frequently merge their code changes into a shared repository. Each change is automatically built and tested, which helps to: + +- Detect and address integration issues early +- Ensure code quality and consistency +- Streamline the development process + +Artemis Local CI is our implementation of these CI principles, tailored to support the Artemis learning platform. +It provides tools and views to help users build and test in programming exercises effectively. + +This document will introduce you to the key components of Artemis Local CI: the Build Agent View and the Build Overview View. +These tools will assist you throughout your development workflow on the Artemis platform. + +Build Agent View +^^^^^^^^^^^^^^^^ + +.. include:: local-ci-build-agent-view.inc + + +Build Overview View +^^^^^^^^^^^^^^^^^^^ + +.. include:: local-ci-build-queue-view.inc diff --git a/docs/user/icl/local-vc-authentication.inc b/docs/user/icl/local-vc-authentication.inc new file mode 100644 index 000000000000..6735cd2743df --- /dev/null +++ b/docs/user/icl/local-vc-authentication.inc @@ -0,0 +1,67 @@ +Cloning a repository +^^^^^^^^^^^^^^^^^^^^ + +You can use Sourcetree, git from the terminal, or any client you like to clone your Git repository. +These instructions show you how to clone your repository using Git from the terminal. + +From the exercise view, click the code button to display the Clone dialog. +Copy the clone URL (either the SSH format or the HTTPS, with or without token). +If you are using the SSH protocol, ensure your public key is stored in your Artemis account settings and loaded on the local system to which you are cloning. +From a terminal window, change to the local directory where you want to clone your repository. + +Paste the command you copied from Bitbucket, for example: + +Clone over HTTPS: + +.. code-block:: bash + + git clone https://username@artemis.cit.tum.de/course/documentation-tests.git + + +Clone over HTTPS with access token: + +.. code-block:: bash + + git clone https://username:accessToken@artemis.cit.tum.de/course/documentation-tests.git + + +Clone over SSH: + +.. code-block:: bash + + git clone ssh://git@artemis.cit.tum.de/course/documentation-tests.git + + +If the clone was successful, a new sub-directory appears on your local drive. +This directory has the same name as the repository that you cloned. +The clone contains the files and metadata that Git requires to maintain the changes you make to the source files. + +Choosing between HTTPS and SSH: + +- HTTPS: Easier to set up initially, works through firewalls, but requires entering credentials more frequently. +- SSH: More secure, doesn't require entering passwords for each operation once set up, but initial setup can be more complex. + +Choose HTTPS if you're new to Git or working in an environment with strict firewall rules. +Choose SSH for enhanced security and convenience in long-term development. + +HTTPS access tokens +^^^^^^^^^^^^^^^^^^^ + +Instructors can create HTTP access tokens for repository access in Artemis. +They are created in the account settings and are used in place of passwords for Git over HTTPS. +For every student's exercise, Artemis automatically generates an access token, only associated with the repository of this particular exercise. +You can use these to authenticate to the Artemis Local Version Control. + +Token Creation +"""""""""""""" + +1. Go to Profile > Settings > VCS token. +2. Create a new token + +Using SSH keys to secure Git operations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Artemis provides a simple way for users to connect securely to repositories, using SSH to perform Git operations. +Next is a :ref:`small and basic introduction to SSH`, and if you already know it, :ref:`here is a guide on how to create SSH keys`. +If you already have an SSH key, :ref:`learn how to add it to your Artemis account here`. + diff --git a/docs/user/exercises/programming-integrated-code-lifecycle-repository-view.inc b/docs/user/icl/local-vc-repository-view.inc similarity index 88% rename from docs/user/exercises/programming-integrated-code-lifecycle-repository-view.inc rename to docs/user/icl/local-vc-repository-view.inc index 5273befacd08..aef46936e99a 100644 --- a/docs/user/exercises/programming-integrated-code-lifecycle-repository-view.inc +++ b/docs/user/icl/local-vc-repository-view.inc @@ -3,13 +3,13 @@ These changes are stored in a repository. A repository is a collection of files How to navigate through the repository and view the code is explained in the following sections. This guide is divided into two parts: one for students and one for instructors. It is demonstrated using course programming exercises, but the same principles apply to exams as well. -For Students -"""""""""""" +Access Repositories as a Student +"""""""""""""""""""""""""""""""" As a student, you can use the repository to view your course submissions. To access your submissions, you need to navigate to the exercise, press the '*Code*' button, and click on |open-repository-button|. -.. figure:: programming/open-repository-student.png +.. figure:: local-vc/open-repository-student.png :alt: Opening the repository in the student view :align: center @@ -22,7 +22,7 @@ To the right side of the screen, above the problem statement, you can see the re the '*Code*' button to clone the repository to your local machine and see the full commit history by pressing the '*Open Commit History*' button. Finally, you can download the repository as a ZIP file by pressing the '*Download Repository*' button. -.. figure:: programming/current-repository-content-view.png +.. figure:: local-vc/current-repository-content-view.png :alt: Current Repository Content :align: center @@ -33,7 +33,7 @@ You can see the commit message, the author of the commit, the date of the commit If you created a submission with multiple commits, you can see all of them here but only the last commit has the result of the submission. Lastly, you can also view the code of the commit by pressing on the commit hash. -.. figure:: programming/commit-history-view.png +.. figure:: local-vc/commit-history-view.png :alt: Commit History View :align: center @@ -44,20 +44,20 @@ You can see the changes in the code, the files that have been added, the files t Green color indicates the lines that have been added and red color indicates the lines that have been deleted. You can also see the commit message, the author of the commit, the date of the commit and the commit hash. -.. figure:: programming/commit-diff-view.png +.. figure:: local-vc/commit-diff-view.png :alt: Commit Diff View :align: center Commit Diff View -For Instructors -""""""""""""""" +Access Repositories as an Instructor +"""""""""""""""""""""""""""""""""""" As an instructor, you can use the repository to view the submissions of your students. For this purpose, you need to navigate to the exercise participations page. Here you can see all the students’ participations for the exercise and navigate to the repository of a student by pressing the '*Code*' button and clicking on |open-repository-button|. This will show the repository of the student just like it is shown to the student. -.. figure:: programming/open-repository-instructor-participations.png +.. figure:: local-vc/open-repository-instructor-participations.png :alt: Open Repository Button in Participations Page :align: center @@ -66,11 +66,11 @@ This will show the repository of the student just like it is shown to the studen You can also see the repositories for an exercises **solution**, **template** and **test** repositories on the exercise management page. You can navigate to these repositories by pressing the '*Code*' button and clicking |open-repository-button| as described above. -.. figure:: programming/course-management-repositories.png +.. figure:: local-vc/course-management-repositories.png :alt: Solution, Template and Test Repositories in Exercise Management Page :align: center Solution, Template and Test Repositories in Exercise Management Page -.. |open-repository-button| image:: programming/open-repository-button.png +.. |open-repository-button| image:: local-vc/open-repository-button.png :scale: 50% diff --git a/docs/user/icl/local-vc/commit-diff-view.png b/docs/user/icl/local-vc/commit-diff-view.png new file mode 100644 index 000000000000..19ebb58be3ab Binary files /dev/null and b/docs/user/icl/local-vc/commit-diff-view.png differ diff --git a/docs/user/icl/local-vc/commit-history-view.png b/docs/user/icl/local-vc/commit-history-view.png new file mode 100644 index 000000000000..2dd208d0de16 Binary files /dev/null and b/docs/user/icl/local-vc/commit-history-view.png differ diff --git a/docs/user/icl/local-vc/course-management-repositories.png b/docs/user/icl/local-vc/course-management-repositories.png new file mode 100644 index 000000000000..ab0af10e9103 Binary files /dev/null and b/docs/user/icl/local-vc/course-management-repositories.png differ diff --git a/docs/user/icl/local-vc/current-repository-content-view.png b/docs/user/icl/local-vc/current-repository-content-view.png new file mode 100644 index 000000000000..961e9f17889b Binary files /dev/null and b/docs/user/icl/local-vc/current-repository-content-view.png differ diff --git a/docs/user/icl/local-vc/open-repository-button.png b/docs/user/icl/local-vc/open-repository-button.png new file mode 100644 index 000000000000..b1b4d60873cd Binary files /dev/null and b/docs/user/icl/local-vc/open-repository-button.png differ diff --git a/docs/user/icl/local-vc/open-repository-instructor-participations.png b/docs/user/icl/local-vc/open-repository-instructor-participations.png new file mode 100644 index 000000000000..bf051e6d644d Binary files /dev/null and b/docs/user/icl/local-vc/open-repository-instructor-participations.png differ diff --git a/docs/user/icl/local-vc/open-repository-student.png b/docs/user/icl/local-vc/open-repository-student.png new file mode 100644 index 000000000000..93312d1cab27 Binary files /dev/null and b/docs/user/icl/local-vc/open-repository-student.png differ diff --git a/docs/user/icl/local-vc/open-settings.png b/docs/user/icl/local-vc/open-settings.png new file mode 100644 index 000000000000..84c13407ab74 Binary files /dev/null and b/docs/user/icl/local-vc/open-settings.png differ diff --git a/docs/user/icl/local-vc/ssh-add-public-key.png b/docs/user/icl/local-vc/ssh-add-public-key.png new file mode 100644 index 000000000000..3e4683c09b7e Binary files /dev/null and b/docs/user/icl/local-vc/ssh-add-public-key.png differ diff --git a/docs/user/icl/local-version-control.rst b/docs/user/icl/local-version-control.rst new file mode 100644 index 000000000000..1ca1c1c2c31c --- /dev/null +++ b/docs/user/icl/local-version-control.rst @@ -0,0 +1,16 @@ +.. _local-vc: + +Local Version Control +===================== + +.. contents:: Content of this document + :local: + :depth: 2 + + +Repository View +^^^^^^^^^^^^^^^ + +.. include:: local-vc-repository-view.inc + +.. include:: local-vc-authentication.inc diff --git a/docs/user/icl/ssh-add-key-to-artemis.rst b/docs/user/icl/ssh-add-key-to-artemis.rst new file mode 100644 index 000000000000..0e3425faa501 --- /dev/null +++ b/docs/user/icl/ssh-add-key-to-artemis.rst @@ -0,0 +1,68 @@ +.. _use ssh key: + +Using SSH with Artemis +^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Content of this document + :local: + :depth: 1 + +You can use SSH keys to establish a secure connection between your computer and Artemis when you are performing Git operations (pull, clone, push) from your local machine. +Personal keys are linked to your Artemis account, inheriting its permissions and operating under its unique identity. +To use your generated SSH keys with Artemis, you need to add it in the account settings. + + +Add an SSH key to your Artemis account +"""""""""""""""""""""""""""""""""""""" + +**1. Copy your public key** + +On Windows in your command prompt, change directory to your `.ssh` directory, and copy the public key file to your clipboard by running: + +.. code-block:: bash + + cd %userprofile%/.ssh + clip < id_ed25519.pub + +On macOS or Linux simply run the following in a terminal: + +.. _xclip: https://wiki.ubuntuusers.de/xclip/ + +.. code-block:: bash + + pbcopy < ~/.ssh/id_ed25519.pub + +If `pbcopy` isn't working, locate the hidden `.ssh` folder, open the file in a text editor, and copy it to your clipboard. +Note that on Linux, you may need to download and install `xclip`_, then use that, as shown in this code snippet: + +.. code-block:: bash + + sudo apt-get install xclip + xclip -sel clip < ~/.ssh/id_ed25519.pub + +Note that the key's name is not necessarily **id_ed25519.pub**, but can be arbitrary, and depends on how you saved it. + + +**2. Add the key to your Artemis account** + +Open the settings, go to the SSH tab, and select 'New Key'. +Then paste the copied SSH key into the text box. + ++---------------------------------------------------+--------------------------------------------------------------+ +|.. figure:: local-vc/open-settings.png | .. figure:: local-vc/ssh-add-public-key.png | +| :alt: Open account settings | :alt: Add public SSH key to account | +| :align: center | :align: center | +| | | +| Open you Artemis account settings | Add public SSH key to account in account settings | ++---------------------------------------------------+--------------------------------------------------------------+ + +**3. Save the key. You're done!** + +Use SSH to connect to Artemis repositories +"""""""""""""""""""""""""""""""""""""""""" + +After everything is set up, you can go to a programming exercise, and use the SSH clone URL with git to access the repository locally, like this, for example: + +.. code-block:: bash + + git clone ssh://git@artemis.cit.tum.de:7921/git/COURSE/exercise-user_1.git diff --git a/docs/user/icl/ssh-intro.rst b/docs/user/icl/ssh-intro.rst new file mode 100644 index 000000000000..fd41034ebe5d --- /dev/null +++ b/docs/user/icl/ssh-intro.rst @@ -0,0 +1,67 @@ +.. _basic SSH introduction: + +SSH +^^^ + +.. contents:: Content of this document + :local: + :depth: 2 + +Artemis uses SSH as a simple way for users to connect securely to repositories to perform Git operations. + +What is SSH? +"""""""""""" + +.. _SSH (Secure Shell): https://en.wikipedia.org/wiki/Secure_Shell + +`SSH (Secure Shell)`_ is a protocol that allows you to securely connect to another computer over a network. +It’s mostly used by system administrators, developers, and IT professionals to remotely manage servers or computers. +SSH provides a secure and encrypted communication channel between your computer and a remote machine, so any data passed (like passwords or commands) is protected from eavesdropping. +In Artemis you an use SSH to access your repositories with Git. + +Why use SSH? +"""""""""""" + +The main advantage of SSH is security. +When you connect to a remote machine using SSH, all the data exchanged between your computer and the server is encrypted. +This means if someone tries to intercept the communication, they can't read it. It's like sending messages through a locked box that only you and the server can open. + +How does SSH work? +"""""""""""""""""" + +SSH works by using two components: + +- Client: The computer you are using to connect. +- Server: The machine you want to connect to. + +When you want to connect, your SSH client sends a request to the server. +If the connection is successful, you can log in to the server and start working as if you were sitting in front of it. +The connection uses SSH keys for authentication. Although it is also possible to use username and password to connect over SSH, this is discouraged. + +What are SSH Keys? +"""""""""""""""""" + +.. _public-key cryptography: https://en.wikipedia.org/wiki/Public-key_cryptography + + +SSH keys are a more secure alternative to passwords for logging into a server. +They are based on `public-key cryptography`_ and come in pairs: a public key and a private key. + +- Public Key: This key is stored on the server. Think of it like a lock that only you can open. +- Private Key: This key stays on your local machine (never shared!). It’s like the key to that lock. + +When you try to connect to the server, your computer proves it has the private key that matches the server's public key, granting you access. +You can add a personal SSH key to your user account to easily authenticate when performing read operations from your local machine. +An Artemis user can currently add one key to their account. +For instructions on how to add your SSH key to your Artemis account, please refer to :ref:`the relevant documentation`. + +Before you can use SSH keys to secure a connection with Artemis the following must have already been done: + +- SSH is enabled on your university's Artemis instance +- You need an SSH key! See :ref:`Creating SSH keys`. + +.. note:: + + - You can use the same SSH key for multiple repositories or projects. + - An Artemis user can currently only add one key to their account. + - Artemis supports ECDSA, RSA2, and Ed25519 key types. diff --git a/docs/user/icl/ssh-key-creation.rst b/docs/user/icl/ssh-key-creation.rst new file mode 100644 index 000000000000..58e7b01f5a4d --- /dev/null +++ b/docs/user/icl/ssh-key-creation.rst @@ -0,0 +1,161 @@ +.. _create ssh key: + +Creating SSH keys +^^^^^^^^^^^^^^^^^ + +.. contents:: Content of this document + :local: + :depth: 2 + +SSH keys can be used to establish a secure connection with the Artemis Local Version Control, where you are performing Git operations from your local machine. +The SSH key needs to be added to Artemis before you can make use of the key. + +Creating an SSH key on Windows +"""""""""""""""""""""""""""""" + +**1. Check for existing keys** + +You should check for existing SSH keys on your local computer. Open a command prompt, and run: + +.. code-block:: bash + + cd %userprofile%/.ssh + +- If you see "No such file or directory", then there aren't any existing keys: go to step 3. + +- Check to see if you have a key already: + +.. code-block:: bash + + dir id_* + +If there are existing keys, you may want to use those: :ref:`Add your key to Artemis`. + +**2. Back up old SSH keys** + +If you have existing SSH keys, but you don't want to use them when connecting to Bitbucket, you should back those up. +In a command prompt on your local computer, run: + +.. code-block:: bash + + mkdir key_backup + copy * key_backup + +**3. Generate a new SSH key** + +If you don't have an existing SSH key that you wish to use, generate one as follows: +1. Log in to your local computer as an administrator. +2. In a command prompt, run: + +.. _Git (with Git Bash): https://gitforwindows.org/ + + +.. code-block:: bash + + ssh-keygen -t ed25519 -C "your_email@example.com" + +Associating the key with your email address helps you to identify the key later on. +Note that the `ssh-keygen` command is only available if you have already installed `Git (with Git Bash)`_. +You'll see a response similar to this: + +.. code-block:: bash + + C:\Users\artemis>ssh-keygen -t ed25519 -C "your_email@example.com" + Generating public/private ed25519 key pair. + Enter file in which to save the key (/c/Users/artemis/.ssh/id_ed25519): + +3. Just press to accept the default location and file name. If the .ssh directory doesn't exist, the system creates one for you. +4. Enter, and re-enter, a passphrase when prompted. The whole interaction will look similar to this: + +.. code-block:: bash + + C:\Users\artemis>ssh-keygen -t ed25519 -C "your_email@example.com" + Generating public/private ed25519 key pair. + Enter file in which to save the key (/c/Users/artemis/.ssh/id_ed25519): + Created directory '/c/Users/artemis/.ssh'. + Enter passphrase (empty for no passphrase): + Enter same passphrase again: + Your identification has been saved in c/Users/artemis/.ssh/id_ed25519. + Your public key has been saved in c/Users/artemis/.ssh/id_ed25519.pub. + The key fingerprint is: + SHA256:wvaHYeLtY6+DlvV5sFZgDi3abcdefghijklmnopqrstuvw your_email@example.com + +5. You're done and you can now :ref:`add your key to Artemis`. + +Creating an SSH key on Linux & macOS +"""""""""""""""""""""""""""""""""""" + +**1. Check for existing SSH keys** + +You should check for existing SSH keys on your local computer. Open a terminal and run: + +.. code-block:: bash + + cd ~/.ssh + +If you see "No such file or directory, then there aren't any existing keys: go to step 3. +Check to see if you have a key already: + +.. code-block:: bash + + ls id_* + +If there are existing keys, you may want to use those: :ref:`Add your key to Artemis`. + +**2. Back up old SSH keys** + +If you have existing SSH keys, but you don't want to use them when connecting to Bitbucket, you should back those up. +In a command prompt on your local computer, run: + +.. code-block:: bash + + mkdir key_backup + cp * key_backup + +**3. Generate a new SSH key** + +If you don't have an existing SSH key that you wish to use, generate one as follows: + +1. Open a terminal on your local computer and enter the following: + +.. code-block:: bash + + ssh-keygen -t ed25519 -C "your_email@example.com" + +Associating the key with your email address helps you to identify the key later on. You'll see a response similar to this: + +.. code-block:: bash + + artemis@homemac ~ % ssh-keygen -t ed25519 -C artemis@email.com + Generating public/private ed25519 key pair. + Enter file in which to save the key (/Users/artemis/.ssh/id_ed25519): + +2. Just press to accept the default location and file name. If the .ssh directory doesn't exist, the system creates one for you. +3. Enter, and re-enter, a passphrase when prompted. The whole interaction will look similar to this: + +.. code-block:: bash + + artemis@homemac ~ % ssh-keygen -t ed25519 -C artemis@email.com + Generating public/private ed25519 key pair. + Enter file in which to save the key (/Users/artemis/.ssh/id_ed25519): + Enter passphrase (empty for no passphrase): + Enter same passphrase again: + Your identification has been saved in /Users/artemis/.ssh/id_ed25519. + Your public key has been saved in /Users/artemis/.ssh/id_ed25519.pub. + The key fingerprint is: + SHA256:gTVWKbn41z6JgBNu3wYjLC4abcdefghijklmnopqrstuvwxy artemis@email.com + The keys randomart image is: + +--[ED25519 256]--+ + |==+. +o.. | + |.oE. +o.. | + | . ...o | + | .o... | + | oo+S . | + | + ..B = . . | + |.+.+.oo+ * o . | + |o++.o+ . + + | + |B+ o. . . | + +----[SHA256]-----+ + artemis@homemac ~ % + +4. You're done and you can now :ref:`add your key to Artemis`. diff --git a/docs/user/integrated-code-lifecycle.rst b/docs/user/integrated-code-lifecycle.rst new file mode 100644 index 000000000000..9e5ab5231ef0 --- /dev/null +++ b/docs/user/integrated-code-lifecycle.rst @@ -0,0 +1,15 @@ +.. _integrated code lifecycle: + +Integrated Code Lifecycle +========================= + +Artemis' Integrated Code Lifecycle consists of two main components: Local Version Control and Local Continuous Integration. + +.. toctree:: + + icl/general + icl/local-version-control + icl/ssh-intro + icl/ssh-key-creation + icl/ssh-add-key-to-artemis + icl/local-continuous-integration diff --git a/package-lock.json b/package-lock.json index c72840ee901d..4b7bc1437ba6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,7 +55,7 @@ "jszip": "3.10.1", "lodash-es": "4.17.21", "mobile-drag-drop": "3.0.0-rc.0", - "monaco-editor": "0.51.0", + "monaco-editor": "0.52.0", "ngx-infinite-scroll": "18.0.0", "ngx-webstorage": "18.0.0", "papaparse": "5.4.1", @@ -16958,10 +16958,9 @@ "license": "MIT" }, "node_modules/monaco-editor": { - "version": "0.51.0", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.51.0.tgz", - "integrity": "sha512-xaGwVV1fq343cM7aOYB6lVE4Ugf0UyimdD/x5PWcWBMKENwectaEu77FAN7c5sFiyumqeJdX1RPTh1ocioyDjw==", - "license": "MIT" + "version": "0.52.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.0.tgz", + "integrity": "sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw==" }, "node_modules/moo-color": { "version": "1.0.3", diff --git a/package.json b/package.json index dfe192b8d21b..b1063f9a90e0 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "jszip": "3.10.1", "lodash-es": "4.17.21", "mobile-drag-drop": "3.0.0-rc.0", - "monaco-editor": "0.51.0", + "monaco-editor": "0.52.0", "ngx-infinite-scroll": "18.0.0", "ngx-webstorage": "18.0.0", "papaparse": "5.4.1", diff --git a/prebuild.mjs b/prebuild.mjs index 7ef783be432c..5f362babe3fc 100644 --- a/prebuild.mjs +++ b/prebuild.mjs @@ -5,10 +5,11 @@ * - webpack.DefinePlugin and * - MergeJsonWebpackPlugin */ -import fs from "fs"; -import path from "path"; -import { hashElement } from "folder-hash"; -import { fileURLToPath } from "url"; +import fs from 'fs'; +import path from 'path'; +import { hashElement } from 'folder-hash'; +import { fileURLToPath } from 'url'; +import * as esbuild from 'esbuild'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -111,4 +112,25 @@ for (const group of groups) { } } +/* + * The workers of the monaco editor must be bundled separately. + * Specialized workers are available in the vs/esm/language/ directory. + * Be sure to modify the MonacoConfig if you choose to add a worker here. + * For more details, refer to https://github.com/microsoft/monaco-editor/blob/main/samples/browser-esm-esbuild/build.js + */ +const workerEntryPoints = [ + 'vs/language/json/json.worker.js', + 'vs/language/css/css.worker.js', + 'vs/language/html/html.worker.js', + 'vs/language/typescript/ts.worker.js', + 'vs/editor/editor.worker.js' +]; +await esbuild.build({ + entryPoints: workerEntryPoints.map((entry) => `node_modules/monaco-editor/esm/${entry}`), + bundle: true, + format: 'esm', + outbase: 'node_modules/monaco-editor/esm', + outdir: 'node_modules/monaco-editor/bundles' +}); + console.log("Pre-Build complete!"); diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java b/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java index cc14d7a35e34..77c01c6fae19 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java @@ -629,7 +629,7 @@ public boolean isAutomatic() { * @return true if the result is an automatic AI Athena result */ @JsonIgnore - public boolean isAthenaAutomatic() { + public boolean isAthenaBased() { return AssessmentType.AUTOMATIC_ATHENA == assessmentType; } diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/dto/metrics/CompetencyInformationDTO.java b/src/main/java/de/tum/cit/aet/artemis/atlas/dto/metrics/CompetencyInformationDTO.java index 02e0fc7edeb2..f361bc402718 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/dto/metrics/CompetencyInformationDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/dto/metrics/CompetencyInformationDTO.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; +import de.tum.cit.aet.artemis.atlas.domain.competency.Competency; import de.tum.cit.aet.artemis.atlas.domain.competency.CompetencyTaxonomy; /** @@ -19,4 +20,15 @@ */ @JsonInclude(JsonInclude.Include.NON_EMPTY) public record CompetencyInformationDTO(long id, String title, String description, CompetencyTaxonomy taxonomy, ZonedDateTime softDueDate, boolean optional, int masteryThreshold) { + + /** + * Creates a CompetencyInformationDTO from a Competency. + * + * @param competency the Competency to create the DTO from + * @return the created DTO + */ + public static CompetencyInformationDTO of(C competency) { + return new CompetencyInformationDTO(competency.getId(), competency.getTitle(), competency.getDescription(), competency.getTaxonomy(), competency.getSoftDueDate(), + competency.isOptional(), competency.getMasteryThreshold()); + } } diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/dto/metrics/LectureUnitInformationDTO.java b/src/main/java/de/tum/cit/aet/artemis/atlas/dto/metrics/LectureUnitInformationDTO.java index 3a5d9cc8bcb4..8f4016a5f346 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/dto/metrics/LectureUnitInformationDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/dto/metrics/LectureUnitInformationDTO.java @@ -18,4 +18,15 @@ */ @JsonInclude(JsonInclude.Include.NON_EMPTY) public record LectureUnitInformationDTO(long id, long lectureId, String lectureTitle, String name, ZonedDateTime releaseDate, Class type) { + + /** + * Creates a LectureUnitInformationDTO from a LectureUnit. + * + * @param lectureUnit the LectureUnit to create the DTO from + * @return the created DTO + */ + public static LectureUnitInformationDTO of(L lectureUnit) { + return new LectureUnitInformationDTO(lectureUnit.getId(), lectureUnit.getLecture().getId(), lectureUnit.getLecture().getTitle(), lectureUnit.getName(), + lectureUnit.getReleaseDate(), lectureUnit.getClass()); + } } diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/domain/push_notification/PushNotificationDeviceConfiguration.java b/src/main/java/de/tum/cit/aet/artemis/communication/domain/push_notification/PushNotificationDeviceConfiguration.java index c6bfd3384110..b9a911ce6194 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/domain/push_notification/PushNotificationDeviceConfiguration.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/domain/push_notification/PushNotificationDeviceConfiguration.java @@ -106,7 +106,9 @@ public boolean equals(Object object) { return false; } PushNotificationDeviceConfiguration that = (PushNotificationDeviceConfiguration) object; - return token.equals(that.token) && deviceType == that.deviceType && expirationDate.equals(that.expirationDate) && Arrays.equals(secretKey, that.secretKey) + // Use compareTo rather than equals for dates to ensure timestamps and dates with the same time are considered equal + // This is caused by Java internal design having different classes for Date (java.util) and Timestamp (java.sql) + return token.equals(that.token) && deviceType == that.deviceType && expirationDate.compareTo(that.expirationDate) == 0 && Arrays.equals(secretKey, that.secretKey) && owner.equals(that.owner); } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/config/websocket/WebsocketConfiguration.java b/src/main/java/de/tum/cit/aet/artemis/core/config/websocket/WebsocketConfiguration.java index 9163cfb7d7f1..d0c6941cc698 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/config/websocket/WebsocketConfiguration.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/config/websocket/WebsocketConfiguration.java @@ -64,7 +64,6 @@ import de.tum.cit.aet.artemis.core.security.jwt.TokenProvider; import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService; import de.tum.cit.aet.artemis.exam.repository.ExamRepository; -import de.tum.cit.aet.artemis.exercise.domain.Exercise; import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.exercise.repository.StudentParticipationRepository; @@ -309,8 +308,7 @@ private boolean allowSubscription(@Nullable Principal principal, String destinat // TODO: Is it right that TAs are not allowed to subscribe to exam exercises? if (exerciseRepository.isExamExercise(exerciseId)) { - Exercise exercise = exerciseRepository.findByIdElseThrow(exerciseId); - return authorizationCheckService.isAtLeastInstructorInCourse(login, exercise.getCourseViaExerciseGroupOrCourseMember().getId()); + return authorizationCheckService.isAtLeastInstructorInExercise(login, exerciseId); } else { return authorizationCheckService.isAtLeastTeachingAssistantInExercise(login, exerciseId); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/dto/UserPublicInfoDTO.java b/src/main/java/de/tum/cit/aet/artemis/core/dto/UserPublicInfoDTO.java index a6da8966dfc5..f84bf9e0819a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/dto/UserPublicInfoDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/dto/UserPublicInfoDTO.java @@ -25,6 +25,8 @@ public class UserPublicInfoDTO { private String lastName; + private String imageUrl; + private Boolean isInstructor; private Boolean isEditor; @@ -43,6 +45,7 @@ public UserPublicInfoDTO(User user) { this.name = user.getName(); this.firstName = user.getFirstName(); this.lastName = user.getLastName(); + this.imageUrl = user.getImageUrl(); } /** @@ -101,6 +104,14 @@ public void setLastName(String lastName) { this.lastName = lastName; } + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + public Boolean getIsInstructor() { return isInstructor; } @@ -152,6 +163,7 @@ public int hashCode() { @Override public String toString() { return "UserPublicInfoDTO{" + "id=" + id + ", login='" + login + '\'' + ", name='" + name + '\'' + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' - + ", isInstructor=" + isInstructor + ", isEditor=" + isEditor + ", isTeachingAssistant=" + isTeachingAssistant + ", isStudent=" + isStudent + '}'; + + ", imageUrl='" + imageUrl + '\'' + ", isInstructor=" + isInstructor + ", isEditor=" + isEditor + ", isTeachingAssistant=" + isTeachingAssistant + ", isStudent=" + + isStudent + '}'; } } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/ZipFileService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/ZipFileService.java index 4d40473c4eb9..5871cd7ed7d4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/ZipFileService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/ZipFileService.java @@ -6,6 +6,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Stream; import java.util.zip.ZipEntry; @@ -33,6 +34,12 @@ public class ZipFileService { private final FileService fileService; + /** + * Set of file names that should be ignored when zipping. + * This currently only includes the gc.log.lock (garbage collector) file created by JGit in programming repositories. + */ + private static final Set IGNORED_ZIP_FILE_NAMES = Set.of(Path.of("gc.log.lock")); + public ZipFileService(FileService fileService) { this.fileService = fileService; } @@ -113,7 +120,7 @@ private void createZipFileFromPathStream(Path zipFilePath, Stream paths, P if (extraFilter != null) { filteredPaths = filteredPaths.filter(extraFilter); } - filteredPaths.forEach(path -> { + filteredPaths.filter(path -> !IGNORED_ZIP_FILE_NAMES.contains(path)).forEach(path -> { ZipEntry zipEntry = new ZipEntry(pathsRoot.relativize(path).toString()); copyToZipFile(zipOutputStream, path, zipEntry); }); diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/domain/Exercise.java b/src/main/java/de/tum/cit/aet/artemis/exercise/domain/Exercise.java index 7503427a81fc..b25eb7ab154d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/domain/Exercise.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/domain/Exercise.java @@ -562,8 +562,9 @@ public Submission findLatestSubmissionWithRatedResultWithCompletionDate(Particip boolean ratedOrPractice = Boolean.TRUE.equals(result.isRated()) || participation.isPracticeMode(); boolean noProgrammingAndAssessmentOver = !isProgrammingExercise && isAssessmentOver; // For programming exercises we check that the assessment due date has passed (if set) for manual results otherwise we always show the automatic result - boolean programmingAfterAssessmentOrAutomatic = isProgrammingExercise && ((result.isManual() && isAssessmentOver) || result.isAutomatic()); - if (ratedOrPractice && (noProgrammingAndAssessmentOver || programmingAfterAssessmentOrAutomatic)) { + boolean programmingAfterAssessmentOrAutomaticOrAthena = isProgrammingExercise + && ((result.isManual() && isAssessmentOver) || result.isAutomatic() || result.isAthenaBased()); + if (ratedOrPractice && (noProgrammingAndAssessmentOver || programmingAfterAssessmentOrAutomaticOrAthena)) { // take the first found result that fulfills the above requirements // or // take newer results and thus disregard older ones diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/domain/Submission.java b/src/main/java/de/tum/cit/aet/artemis/exercise/domain/Submission.java index 326507d47dd4..304c206938b8 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/domain/Submission.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/domain/Submission.java @@ -162,7 +162,7 @@ public Result getResultForCorrectionRound(int correctionRound) { */ @NotNull private List filterNonAutomaticResults() { - return results.stream().filter(result -> result == null || !(result.isAutomatic() || result.isAthenaAutomatic())).toList(); + return results.stream().filter(result -> result == null || !(result.isAutomatic() || result.isAthenaBased())).toList(); } /** @@ -188,8 +188,7 @@ public boolean hasResultForCorrectionRound(int correctionRound) { */ @JsonIgnore public void removeAutomaticResults() { - this.results = this.results.stream().filter(result -> result == null || !(result.isAutomatic() || result.isAthenaAutomatic())) - .collect(Collectors.toCollection(ArrayList::new)); + this.results = this.results.stream().filter(result -> result == null || !(result.isAutomatic() || result.isAthenaBased())).collect(Collectors.toCollection(ArrayList::new)); } /** @@ -214,7 +213,7 @@ public List getResults() { @JsonIgnore public List getManualResults() { - return results.stream().filter(result -> result != null && !result.isAutomatic() && !result.isAthenaAutomatic()).collect(Collectors.toCollection(ArrayList::new)); + return results.stream().filter(result -> result != null && !result.isAutomatic() && !result.isAthenaBased()).collect(Collectors.toCollection(ArrayList::new)); } /** @@ -224,7 +223,7 @@ public List getManualResults() { */ @JsonIgnore public List getNonAthenaResults() { - return results.stream().filter(result -> result != null && !result.isAthenaAutomatic()).collect(Collectors.toCollection(ArrayList::new)); + return results.stream().filter(result -> result != null && !result.isAthenaBased()).collect(Collectors.toCollection(ArrayList::new)); } /** diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/web/ParticipationResource.java b/src/main/java/de/tum/cit/aet/artemis/exercise/web/ParticipationResource.java index c6cdc6ee1730..898b4456de07 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/web/ParticipationResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/web/ParticipationResource.java @@ -20,7 +20,6 @@ import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotNull; -import org.apache.velocity.exception.ResourceNotFoundException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -382,7 +381,7 @@ private ResponseEntity handleExerciseFeedbackRequest(Exerc throw new BadRequestAlertException("Not intended for the use in exams", "participation", "preconditions not met"); } if (exercise.getDueDate() != null && now().isAfter(exercise.getDueDate())) { - throw new BadRequestAlertException("The due date is over", "participation", "preconditions not met"); + throw new BadRequestAlertException("The due date is over", "participation", "feedbackRequestAfterDueDate", true); } if (exercise instanceof ProgrammingExercise) { ((ProgrammingExercise) exercise).validateSettingsForFeedbackRequest(); @@ -393,7 +392,7 @@ private ResponseEntity handleExerciseFeedbackRequest(Exerc StudentParticipation participation = (exercise instanceof ProgrammingExercise) ? programmingExerciseParticipationService.findStudentParticipationByExerciseAndStudentId(exercise, principal.getName()) : studentParticipationRepository.findByExerciseIdAndStudentLogin(exercise.getId(), principal.getName()) - .orElseThrow(() -> new ResourceNotFoundException("Participation not found")); + .orElseThrow(() -> new BadRequestAlertException("Submission not found", "participation", "noSubmissionExists", true)); checkAccessPermissionOwner(participation, user); participation = studentParticipationRepository.findByIdWithResultsElseThrow(participation.getId()); @@ -406,15 +405,14 @@ private ResponseEntity handleExerciseFeedbackRequest(Exerc } else if (exercise instanceof ProgrammingExercise) { if (participation.findLatestLegalResult() == null) { - throw new BadRequestAlertException("User has not reached the conditions to submit a feedback request", "participation", "preconditions not met"); + throw new BadRequestAlertException("You need to submit at least once and have the build results", "participation", "noSubmissionExists", true); } } // Check if feedback has already been requested - var currentDate = now(); - var participationIndividualDueDate = participation.getIndividualDueDate(); - if (participationIndividualDueDate != null && currentDate.isAfter(participationIndividualDueDate)) { - throw new BadRequestAlertException("Request has already been sent", "participation", "already sent"); + var latestResult = participation.findLatestResult(); + if (latestResult != null && latestResult.getAssessmentType() == AssessmentType.AUTOMATIC_ATHENA && latestResult.getCompletionDate().isAfter(now())) { + throw new BadRequestAlertException("Request has already been sent", "participation", "feedbackRequestAlreadySent", true); } // Process feedback request diff --git a/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/ProgrammingPlagiarismDetectionService.java b/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/ProgrammingPlagiarismDetectionService.java index 9fe9b1cc0f8c..ea6ebfdf3b81 100644 --- a/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/ProgrammingPlagiarismDetectionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/ProgrammingPlagiarismDetectionService.java @@ -37,6 +37,7 @@ import de.jplag.options.JPlagOptions; import de.jplag.python3.PythonLanguage; import de.jplag.reporting.reportobject.ReportObjectFactory; +import de.jplag.rlang.RLanguage; import de.jplag.rust.RustLanguage; import de.jplag.swift.SwiftLanguage; import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException; @@ -310,14 +311,15 @@ public void deleteTempLocalRepository(Repository repository) { private Language getJPlagProgrammingLanguage(ProgrammingExercise programmingExercise) { return switch (programmingExercise.getProgrammingLanguage()) { - case JAVA -> new JavaLanguage(); case C -> new CLanguage(); - case PYTHON -> new PythonLanguage(); - case SWIFT -> new SwiftLanguage(); + case JAVA -> new JavaLanguage(); + case JAVASCRIPT -> new JavaScriptLanguage(); case KOTLIN -> new KotlinLanguage(); + case PYTHON -> new PythonLanguage(); + case R -> new RLanguage(); case RUST -> new RustLanguage(); - case JAVASCRIPT -> new JavaScriptLanguage(); - case EMPTY, PHP, DART, HASKELL, ASSEMBLER, OCAML, C_SHARP, C_PLUS_PLUS, SQL, R, TYPESCRIPT, GO, MATLAB, BASH, VHDL, RUBY, POWERSHELL, ADA -> + case SWIFT -> new SwiftLanguage(); + case EMPTY, PHP, DART, HASKELL, ASSEMBLER, OCAML, C_SHARP, C_PLUS_PLUS, SQL, TYPESCRIPT, GO, MATLAB, BASH, VHDL, RUBY, POWERSHELL, ADA -> throw new BadRequestAlertException("Programming language " + programmingExercise.getProgrammingLanguage() + " not supported for plagiarism check.", "ProgrammingExercise", "notSupported"); }; diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExercise.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExercise.java index df7911670a22..c2a4666c7c1b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExercise.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExercise.java @@ -712,8 +712,7 @@ private boolean checkForRatedAndAssessedResult(Result result) { * @return true if the result is manual and the assessment is over, or it is an automatic result, false otherwise */ private boolean checkForAssessedResult(Result result) { - return result.getCompletionDate() != null - && ((result.isManual() && ExerciseDateService.isAfterAssessmentDueDate(this)) || result.isAutomatic() || result.isAthenaAutomatic()); + return result.getCompletionDate() != null && ((result.isManual() && ExerciseDateService.isAfterAssessmentDueDate(this)) || result.isAutomatic() || result.isAthenaBased()); } @Override diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingLanguage.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingLanguage.java index 4206bfe15dbc..781ad04f98c6 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingLanguage.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingLanguage.java @@ -38,18 +38,19 @@ public enum ProgrammingLanguage { PHP("php"); private static final Set ENABLED_LANGUAGES = Set.of( - EMPTY, - JAVA, - PYTHON, + ASSEMBLER, C, HASKELL, + JAVA, + JAVASCRIPT, KOTLIN, - VHDL, - ASSEMBLER, - SWIFT, OCAML, + PYTHON, + R, RUST, - JAVASCRIPT + SWIFT, + VHDL, + EMPTY ); // @formatter:on diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseCodeReviewFeedbackService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseCodeReviewFeedbackService.java index 8c92446d22d0..935a3412b10e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseCodeReviewFeedbackService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseCodeReviewFeedbackService.java @@ -4,6 +4,7 @@ import static java.time.ZonedDateTime.now; import java.time.ZonedDateTime; +import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -11,6 +12,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; @@ -59,6 +61,9 @@ public class ProgrammingExerciseCodeReviewFeedbackService { private final ProgrammingMessagingService programmingMessagingService; + @Value("${artemis.athena.allowed-feedback-attempts:20}") + private int allowedFeedbackAttempts; + public ProgrammingExerciseCodeReviewFeedbackService(GroupNotificationService groupNotificationService, Optional athenaFeedbackSuggestionsService, SubmissionService submissionService, ResultService resultService, ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository, ResultRepository resultRepository, @@ -111,14 +116,14 @@ public void generateAutomaticNonGradedFeedback(ProgrammingExerciseStudentPartici var submissionOptional = programmingExerciseParticipationService.findProgrammingExerciseParticipationWithLatestSubmissionAndResult(participation.getId()) .findLatestSubmission(); if (submissionOptional.isEmpty()) { - throw new BadRequestAlertException("No legal submissions found", "submission", "noSubmission"); + throw new BadRequestAlertException("No legal submissions found", "submission", "noSubmissionExists"); } var submission = submissionOptional.get(); // save result and transmit it over websockets to notify the client about the status var automaticResult = this.submissionService.saveNewEmptyResult(submission); automaticResult.setAssessmentType(AssessmentType.AUTOMATIC_ATHENA); - automaticResult.setRated(false); + automaticResult.setRated(true); // we want to use this feedback to give the grade in the future automaticResult.setScore(100.0); automaticResult.setSuccessful(null); automaticResult.setCompletionDate(ZonedDateTime.now().plusMinutes(5)); // we do not want to show dates without a completion date, but we want the students to know their @@ -127,7 +132,6 @@ public void generateAutomaticNonGradedFeedback(ProgrammingExerciseStudentPartici try { - setIndividualDueDateAndLockRepository(participation, programmingExercise, false); this.programmingMessagingService.notifyUserAboutNewResult(automaticResult, participation); // now the client should be able to see new result @@ -158,9 +162,10 @@ public void generateAutomaticNonGradedFeedback(ProgrammingExerciseStudentPartici feedback.setDetailText(individualFeedbackItem.description()); feedback.setHasLongFeedbackText(false); feedback.setType(FeedbackType.AUTOMATIC); - feedback.setCredits(0.0); + feedback.setCredits(individualFeedbackItem.credits()); return feedback; - }).toList(); + }).sorted(Comparator.comparing(Feedback::getCredits, Comparator.nullsLast(Comparator.naturalOrder()))).toList(); + ; automaticResult.setSuccessful(true); automaticResult.setCompletionDate(ZonedDateTime.now()); @@ -176,9 +181,6 @@ public void generateAutomaticNonGradedFeedback(ProgrammingExerciseStudentPartici this.resultRepository.save(automaticResult); this.programmingMessagingService.notifyUserAboutNewResult(automaticResult, participation); } - finally { - unlockRepository(participation, programmingExercise); - } } /** @@ -225,15 +227,10 @@ private void checkRateLimitOrThrow(ProgrammingExerciseStudentParticipation parti List athenaResults = participation.getResults().stream().filter(result -> result.getAssessmentType() == AssessmentType.AUTOMATIC_ATHENA).toList(); - long countOfAthenaResultsInProcessOrSuccessful = athenaResults.stream().filter(result -> result.isSuccessful() == null || result.isSuccessful() == Boolean.TRUE).count(); - long countOfSuccessfulRequests = athenaResults.stream().filter(result -> result.isSuccessful() == Boolean.TRUE).count(); - if (countOfAthenaResultsInProcessOrSuccessful >= 3) { - throw new BadRequestAlertException("Cannot send additional AI feedback requests now. Try again later!", "participation", "preconditions not met"); - } - if (countOfSuccessfulRequests >= 20) { - throw new BadRequestAlertException("Maximum number of AI feedback requests reached.", "participation", "preconditions not met"); + if (countOfSuccessfulRequests >= this.allowedFeedbackAttempts) { + throw new BadRequestAlertException("Maximum number of AI feedback requests reached.", "participation", "maxAthenaResultsReached", true); } } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/TemplateUpgradePolicyService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/TemplateUpgradePolicyService.java index 16285e6a0695..4c73046b1ab0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/TemplateUpgradePolicyService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/TemplateUpgradePolicyService.java @@ -32,8 +32,8 @@ public TemplateUpgradePolicyService(JavaTemplateUpgradeService javaRepositoryUpg public TemplateUpgradeService getUpgradeService(ProgrammingLanguage programmingLanguage) { return switch (programmingLanguage) { case JAVA -> javaRepositoryUpgradeService; - case KOTLIN, PYTHON, C, HASKELL, VHDL, ASSEMBLER, SWIFT, OCAML, EMPTY, RUST, JAVASCRIPT -> defaultRepositoryUpgradeService; - case C_SHARP, C_PLUS_PLUS, SQL, R, TYPESCRIPT, GO, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> + case KOTLIN, PYTHON, C, HASKELL, VHDL, ASSEMBLER, SWIFT, OCAML, EMPTY, RUST, JAVASCRIPT, R -> defaultRepositoryUpgradeService; + case C_SHARP, C_PLUS_PLUS, SQL, TYPESCRIPT, GO, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + programmingLanguage); }; } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationService.java index b4f67794c073..b9050501c67a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationService.java @@ -219,8 +219,8 @@ enum RepositoryCheckoutPath implements CustomizableCheckoutPath { @Override public String forProgrammingLanguage(ProgrammingLanguage language) { return switch (language) { - case JAVA, PYTHON, C, HASKELL, KOTLIN, VHDL, ASSEMBLER, SWIFT, OCAML, EMPTY, RUST, JAVASCRIPT -> "assignment"; - case C_SHARP, C_PLUS_PLUS, SQL, R, TYPESCRIPT, GO, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> + case JAVA, PYTHON, C, HASKELL, KOTLIN, VHDL, ASSEMBLER, SWIFT, OCAML, EMPTY, RUST, JAVASCRIPT, R -> "assignment"; + case C_SHARP, C_PLUS_PLUS, SQL, TYPESCRIPT, GO, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + language); }; } @@ -230,9 +230,9 @@ public String forProgrammingLanguage(ProgrammingLanguage language) { @Override public String forProgrammingLanguage(ProgrammingLanguage language) { return switch (language) { - case JAVA, PYTHON, HASKELL, KOTLIN, SWIFT, EMPTY, RUST, JAVASCRIPT -> ""; + case JAVA, PYTHON, HASKELL, KOTLIN, SWIFT, EMPTY, RUST, JAVASCRIPT, R -> ""; case C, VHDL, ASSEMBLER, OCAML -> "tests"; - case C_SHARP, C_PLUS_PLUS, SQL, R, TYPESCRIPT, GO, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> + case C_SHARP, C_PLUS_PLUS, SQL, TYPESCRIPT, GO, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + language); }; } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIProgrammingLanguageFeatureService.java index a92bcdd26cb5..0c71114e13bb 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIProgrammingLanguageFeatureService.java @@ -25,7 +25,7 @@ public class GitLabCIProgrammingLanguageFeatureService extends ProgrammingLangua public GitLabCIProgrammingLanguageFeatureService() { programmingLanguageFeatures.put(EMPTY, new ProgrammingLanguageFeature(EMPTY, false, false, false, false, false, List.of(), false, false)); programmingLanguageFeatures.put(JAVA, new ProgrammingLanguageFeature(JAVA, false, false, false, true, false, List.of(PLAIN_MAVEN, MAVEN_MAVEN), false, false)); - programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false, false)); programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), false, false)); + programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false, false)); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java index 38893ea41093..45a473da9148 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java @@ -7,6 +7,7 @@ import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.JAVASCRIPT; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.KOTLIN; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.PYTHON; +import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.R; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.RUST; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.SWIFT; import static de.tum.cit.aet.artemis.programming.domain.ProjectType.FACT; @@ -33,15 +34,16 @@ public class JenkinsProgrammingLanguageFeatureService extends ProgrammingLanguag public JenkinsProgrammingLanguageFeatureService() { // Must be extended once a new programming language is added programmingLanguageFeatures.put(EMPTY, new ProgrammingLanguageFeature(EMPTY, false, false, false, false, false, List.of(), false, false)); + programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, false, true, false, false, List.of(FACT, GCC), false, false)); + programmingLanguageFeatures.put(HASKELL, new ProgrammingLanguageFeature(HASKELL, false, false, false, false, true, List.of(), false, false)); programmingLanguageFeatures.put(JAVA, new ProgrammingLanguageFeature(JAVA, true, true, true, true, false, List.of(PLAIN_GRADLE, GRADLE_GRADLE, PLAIN_MAVEN, MAVEN_MAVEN, MAVEN_BLACKBOX), true, false)); + programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), false, false)); programmingLanguageFeatures.put(KOTLIN, new ProgrammingLanguageFeature(KOTLIN, true, false, true, true, false, List.of(), true, false)); programmingLanguageFeatures.put(PYTHON, new ProgrammingLanguageFeature(PYTHON, false, false, true, false, false, List.of(), false, false)); + programmingLanguageFeatures.put(R, new ProgrammingLanguageFeature(R, false, false, true, false, false, List.of(), false, false)); + programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false, false)); // Jenkins is not supporting XCODE at the moment programmingLanguageFeatures.put(SWIFT, new ProgrammingLanguageFeature(SWIFT, false, true, true, true, false, List.of(PLAIN), false, false)); - programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, false, true, false, false, List.of(FACT, GCC), false, false)); - programmingLanguageFeatures.put(HASKELL, new ProgrammingLanguageFeature(HASKELL, false, false, false, false, true, List.of(), false, false)); - programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false, false)); - programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), false, false)); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java index 6e904910ca57..f900cc0f6dd1 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java @@ -184,8 +184,8 @@ private JenkinsXmlConfigBuilder builderFor(ProgrammingLanguage programmingLangua throw new UnsupportedOperationException("Xcode templates are not available for Jenkins."); } return switch (programmingLanguage) { - case JAVA, KOTLIN, PYTHON, C, HASKELL, SWIFT, EMPTY, RUST, JAVASCRIPT -> jenkinsBuildPlanCreator; - case VHDL, ASSEMBLER, OCAML, C_SHARP, C_PLUS_PLUS, SQL, R, TYPESCRIPT, GO, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> + case JAVA, KOTLIN, PYTHON, C, HASKELL, SWIFT, EMPTY, RUST, JAVASCRIPT, R -> jenkinsBuildPlanCreator; + case VHDL, ASSEMBLER, OCAML, C_SHARP, C_PLUS_PLUS, SQL, TYPESCRIPT, GO, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException(programmingLanguage + " templates are not available for Jenkins."); }; } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java index 525170cca334..bc8292d407bb 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java @@ -10,6 +10,7 @@ import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.KOTLIN; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.OCAML; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.PYTHON; +import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.R; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.RUST; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.SWIFT; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.VHDL; @@ -39,17 +40,18 @@ public class LocalCIProgrammingLanguageFeatureService extends ProgrammingLanguag public LocalCIProgrammingLanguageFeatureService() { // Must be extended once a new programming language is added programmingLanguageFeatures.put(EMPTY, new ProgrammingLanguageFeature(EMPTY, false, false, false, false, false, List.of(), false, true)); + programmingLanguageFeatures.put(ASSEMBLER, new ProgrammingLanguageFeature(ASSEMBLER, false, false, false, false, false, List.of(), false, true)); + programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, true, true, false, false, List.of(FACT, GCC), false, true)); + programmingLanguageFeatures.put(HASKELL, new ProgrammingLanguageFeature(HASKELL, true, false, false, false, true, List.of(), false, true)); programmingLanguageFeatures.put(JAVA, new ProgrammingLanguageFeature(JAVA, true, true, true, true, false, List.of(PLAIN_GRADLE, GRADLE_GRADLE, PLAIN_MAVEN, MAVEN_MAVEN), false, true)); - programmingLanguageFeatures.put(PYTHON, new ProgrammingLanguageFeature(PYTHON, false, false, true, false, false, List.of(), false, true)); - programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, true, true, false, false, List.of(FACT, GCC), false, true)); - programmingLanguageFeatures.put(ASSEMBLER, new ProgrammingLanguageFeature(ASSEMBLER, false, false, false, false, false, List.of(), false, true)); + programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), false, true)); programmingLanguageFeatures.put(KOTLIN, new ProgrammingLanguageFeature(KOTLIN, false, false, true, true, false, List.of(), false, true)); - programmingLanguageFeatures.put(VHDL, new ProgrammingLanguageFeature(VHDL, false, false, false, false, false, List.of(), false, true)); - programmingLanguageFeatures.put(HASKELL, new ProgrammingLanguageFeature(HASKELL, true, false, false, false, true, List.of(), false, true)); programmingLanguageFeatures.put(OCAML, new ProgrammingLanguageFeature(OCAML, false, false, false, false, true, List.of(), false, true)); - programmingLanguageFeatures.put(SWIFT, new ProgrammingLanguageFeature(SWIFT, false, false, true, true, false, List.of(PLAIN), false, true)); + programmingLanguageFeatures.put(PYTHON, new ProgrammingLanguageFeature(PYTHON, false, false, true, false, false, List.of(), false, true)); + programmingLanguageFeatures.put(R, new ProgrammingLanguageFeature(R, false, false, true, false, false, List.of(), false, true)); programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false, true)); - programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), false, true)); + programmingLanguageFeatures.put(SWIFT, new ProgrammingLanguageFeature(SWIFT, false, false, true, true, false, List.of(PLAIN), false, true)); + programmingLanguageFeatures.put(VHDL, new ProgrammingLanguageFeature(VHDL, false, false, false, false, false, List.of(), false, true)); } } diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml index 924d087ec8f2..3924e2d804f9 100644 --- a/src/main/resources/config/application.yml +++ b/src/main/resources/config/application.yml @@ -91,6 +91,8 @@ artemis: default: "ghcr.io/ls1intum/artemis-rust-docker:v0.9.70" javascript: default: "ghcr.io/ls1intum/artemis-javascript-docker:v1.0.0" + r: + default: "ghcr.io/ls1intum/artemis-r-docker:v1.0.0" management: endpoints: diff --git a/src/main/resources/templates/aeolus/r/default.sh b/src/main/resources/templates/aeolus/r/default.sh new file mode 100644 index 000000000000..1d0b32e87105 --- /dev/null +++ b/src/main/resources/templates/aeolus/r/default.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -e +export AEOLUS_INITIAL_DIRECTORY=${PWD} +install () { + echo '⚙️ executing install' + R CMD INSTALL assignment +} + +run_all_tests () { + echo '⚙️ executing run_all_tests' + Rscript -e 'library("testthat"); options(testthat.output_file = "junit.xml"); test_local(".", reporter = "junit")' +} + +main () { + if [[ "${1}" == "aeolus_sourcing" ]]; then + return 0 # just source to use the methods in the subshell, no execution + fi + local _script_name + _script_name=${BASH_SOURCE[0]:-$0} + cd "${AEOLUS_INITIAL_DIRECTORY}" + bash -c "source ${_script_name} aeolus_sourcing; install" + cd "${AEOLUS_INITIAL_DIRECTORY}" + bash -c "source ${_script_name} aeolus_sourcing; run_all_tests" +} + +main "${@}" diff --git a/src/main/resources/templates/aeolus/r/default.yaml b/src/main/resources/templates/aeolus/r/default.yaml new file mode 100644 index 000000000000..a41d23c6f012 --- /dev/null +++ b/src/main/resources/templates/aeolus/r/default.yaml @@ -0,0 +1,14 @@ +api: v0.0.1 +metadata: + name: R + id: r + description: Test package using testthat +actions: + - name: install + script: R CMD INSTALL assignment + - name: run_all_tests + script: Rscript -e 'library("testthat"); options(testthat.output_file = "junit.xml"); test_local(".", reporter = "junit")' + results: + - name: junit + path: tests/testthat/junit.xml + type: junit diff --git a/src/main/resources/templates/jenkins/r/regularRuns/pipeline.groovy b/src/main/resources/templates/jenkins/r/regularRuns/pipeline.groovy new file mode 100644 index 000000000000..9a2ec97b5843 --- /dev/null +++ b/src/main/resources/templates/jenkins/r/regularRuns/pipeline.groovy @@ -0,0 +1,59 @@ +/* + * This file configures the actual build steps for the automatic grading. + * + * !!! + * For regular exercises, there is no need to make changes to this file. + * Only this base configuration is actively supported by the Artemis maintainers + * and/or your Artemis instance administrators. + * !!! + */ + +dockerImage = '#dockerImage' +dockerFlags = '#dockerArgs' + +/** + * Main function called by Jenkins. + */ +void testRunner() { + docker.image(dockerImage).inside(dockerFlags) { c -> + runTestSteps() + } +} + +private void runTestSteps() { + test() +} + +/** + * Run unit tests + */ +private void test() { + stage('Test') { + sh ''' + R CMD INSTALL assignment + Rscript -e 'library("testthat"); options(testthat.output_file = "junit.xml"); test_local(".", reporter = "junit")' + ''' + } +} + +/** + * Script of the post build tasks aggregating all JUnit files in $WORKSPACE/results. + * + * Called by Jenkins. + */ +void postBuildTasks() { + sh ''' + rm -rf results + mkdir results + if [ -e tests/testthat/junit.xml ] + then + sed -i 's/]*>//g ; s/<\\/testsuites>/<\\/testsuite>/g' tests/testthat/junit.xml + fi + cp tests/testthat/junit.xml $WORKSPACE/results/ || true + sed -i 's/[^[:print:]\t]/�/g' $WORKSPACE/results/*.xml || true + ''' +} + +// very important, do not remove +// required so that Jenkins finds the methods defined in this script +return this diff --git a/src/main/resources/templates/r/exercise/DESCRIPTION b/src/main/resources/templates/r/exercise/DESCRIPTION new file mode 100644 index 000000000000..2933cb767621 --- /dev/null +++ b/src/main/resources/templates/r/exercise/DESCRIPTION @@ -0,0 +1,7 @@ +Package: assignment +Title: Artemis R Student Assignment +Version: 0.0.0.9000 +Author: Artemis +Description: This is an assignment to be solved by students. +License: MIT +Encoding: UTF-8 diff --git a/src/main/resources/templates/r/exercise/NAMESPACE b/src/main/resources/templates/r/exercise/NAMESPACE new file mode 100644 index 000000000000..9c9f9ac2d917 --- /dev/null +++ b/src/main/resources/templates/r/exercise/NAMESPACE @@ -0,0 +1 @@ +exportPattern("^[^\\.]") diff --git a/src/main/resources/templates/r/exercise/R/convert.R b/src/main/resources/templates/r/exercise/R/convert.R new file mode 100644 index 000000000000..28e787cf2967 --- /dev/null +++ b/src/main/resources/templates/r/exercise/R/convert.R @@ -0,0 +1,3 @@ +matrix_to_column_list <- function(mat) { + # TODO: implement +} diff --git a/src/main/resources/templates/r/readme b/src/main/resources/templates/r/readme new file mode 100644 index 000000000000..73377139d293 --- /dev/null +++ b/src/main/resources/templates/r/readme @@ -0,0 +1,6 @@ +# Matrix Columns + +Write a function `matrix_to_column_list` in R that takes a matrix of any shape and converts it into a list of +column-vectors. Each element of the list should represent a column of the matrix. + +1. [task][Convert to column-vectors](converts_3x3_matrix_to_vectors,converts_4x2_matrix_to_vectors,converts_1x5_matrix_to_scalars,converts_5x1_matrix_to_vector) diff --git a/src/main/resources/templates/r/solution/DESCRIPTION b/src/main/resources/templates/r/solution/DESCRIPTION new file mode 100644 index 000000000000..2933cb767621 --- /dev/null +++ b/src/main/resources/templates/r/solution/DESCRIPTION @@ -0,0 +1,7 @@ +Package: assignment +Title: Artemis R Student Assignment +Version: 0.0.0.9000 +Author: Artemis +Description: This is an assignment to be solved by students. +License: MIT +Encoding: UTF-8 diff --git a/src/main/resources/templates/r/solution/NAMESPACE b/src/main/resources/templates/r/solution/NAMESPACE new file mode 100644 index 000000000000..9c9f9ac2d917 --- /dev/null +++ b/src/main/resources/templates/r/solution/NAMESPACE @@ -0,0 +1 @@ +exportPattern("^[^\\.]") diff --git a/src/main/resources/templates/r/solution/R/convert.R b/src/main/resources/templates/r/solution/R/convert.R new file mode 100644 index 000000000000..7d701772ab7b --- /dev/null +++ b/src/main/resources/templates/r/solution/R/convert.R @@ -0,0 +1,17 @@ +matrix_to_column_list <- function(mat) { + if (!is.matrix(mat)) { + stop("Input must be a matrix") + } + + n_cols <- ncol(mat) + + # Initialize an empty list to store column-vectors + column_list <- vector("list", length = n_cols) + + # Loop through each column and store it in the list + for (i in 1:n_cols) { + column_list[[i]] <- mat[, i] + } + + return(column_list) +} diff --git a/src/main/resources/templates/r/test/DESCRIPTION b/src/main/resources/templates/r/test/DESCRIPTION new file mode 100644 index 000000000000..e19a2b735419 --- /dev/null +++ b/src/main/resources/templates/r/test/DESCRIPTION @@ -0,0 +1,14 @@ +Package: test +Title: Artemis R Tests +Version: 0.0.0.9000 +Author: Artemis +Description: This package tests the student assignment. +License: MIT +Encoding: UTF-8 +Imports: + assignment +Remotes: + local::./assignment +Suggests: + testthat (>= 3.0.0) +Config/testthat/edition: 3 diff --git a/src/main/resources/templates/r/test/tests/testthat.R b/src/main/resources/templates/r/test/tests/testthat.R new file mode 100644 index 000000000000..388438828173 --- /dev/null +++ b/src/main/resources/templates/r/test/tests/testthat.R @@ -0,0 +1,12 @@ +# This file is part of the standard setup for testthat. +# It is recommended that you do not modify it. +# +# Where should you do additional test configuration? +# Learn more about the roles of various files in: +# * https://r-pkgs.org/testing-design.html#sec-tests-files-overview +# * https://testthat.r-lib.org/articles/special-files.html + +library(testthat) +library(tests) + +test_check("tests") diff --git a/src/main/resources/templates/r/test/tests/testthat/test-convert.R b/src/main/resources/templates/r/test/tests/testthat/test-convert.R new file mode 100644 index 000000000000..a84a0e879711 --- /dev/null +++ b/src/main/resources/templates/r/test/tests/testthat/test-convert.R @@ -0,0 +1,47 @@ +test_that("converts_3x3_matrix_to_vectors", { + mat <- matrix(c(5, 8, 11, 6, 9, 12, 7, 10, 13), nrow = 3, ncol = 3) + + result <- assignment::matrix_to_column_list(mat) + + # Make sure to only use exactly one "expect_" function per test + expect_equal(result, list( + c(5, 8, 11), + c(6, 9, 12), + c(7, 10, 13) + )) +}) + +test_that("converts_4x2_matrix_to_vectors", { + mat <- matrix(c(13, 13, 5, 18, 11, 4, 7, 10), nrow = 4, ncol = 2) + + result <- assignment::matrix_to_column_list(mat) + + expect_equal(result, list( + c(13, 13, 5, 18), + c(11, 4, 7, 10) + )) +}) + +test_that("converts_1x5_matrix_to_scalars", { + mat <- matrix(c(16, 10, 15, 8, 7), nrow = 1, ncol = 5) + + result <- assignment::matrix_to_column_list(mat) + + expect_equal(result, list( + 16, + 10, + 15, + 8, + 7 + )) +}) + +test_that("converts_5x1_matrix_to_vector", { + mat <- matrix(c(14, 9, 1, 3, 4), nrow = 5, ncol = 1) + + result <- assignment::matrix_to_column_list(mat) + + expect_equal(result, list( + c(14, 9, 1, 3, 4) + )) +}) diff --git a/src/main/webapp/app/core/config/monaco.config.ts b/src/main/webapp/app/core/config/monaco.config.ts index aa40e47c177c..f37dfe5a4069 100644 --- a/src/main/webapp/app/core/config/monaco.config.ts +++ b/src/main/webapp/app/core/config/monaco.config.ts @@ -1,19 +1,23 @@ /** * Sets up the MonacoEnvironment for the monaco editor's service worker. + * See https://github.com/microsoft/monaco-editor/blob/main/samples/browser-esm-esbuild/index.js */ export function MonacoConfig() { self.MonacoEnvironment = { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getWorkerUrl: function (workerId: string, label: string) { - /* - * This is the AMD-based service worker, which comes bundled with a few special workers for selected languages. - * (e.g.: javascript, typescript, html, css) - * - * It is also possible to use an ESM-based approach, which requires a little more setup and case distinctions in this method. - * At the moment, it seems that the ESM-based approaches are incompatible with the Artemis client, as they would require custom builders. - * Support for custom builders was removed in #6546. - */ - return 'vs/base/worker/workerMain.js'; + getWorkerUrl: (_moduleId: string, label: string): string => { + if (label === 'json') { + return './vs/language/json/json.worker.js'; + } + if (label === 'css' || label === 'scss' || label === 'less') { + return './vs/language/css/css.worker.js'; + } + if (label === 'html' || label === 'handlebars' || label === 'razor') { + return './vs/language/html/html.worker.js'; + } + if (label === 'typescript' || label === 'javascript') { + return './vs/language/typescript/ts.worker.js'; + } + return './vs/editor/editor.worker.js'; }, }; } diff --git a/src/main/webapp/app/core/user/user.model.ts b/src/main/webapp/app/core/user/user.model.ts index 816cf4fc9a9c..b55ff839fd54 100644 --- a/src/main/webapp/app/core/user/user.model.ts +++ b/src/main/webapp/app/core/user/user.model.ts @@ -66,6 +66,7 @@ export class UserPublicInfoDTO { public firstName?: string; public lastName?: string; public email?: string; + public imageUrl?: string; public isInstructor?: boolean; public isEditor?: boolean; public isTeachingAssistant?: boolean; diff --git a/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.ts b/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.ts index 8366f6d25e22..7857288daba1 100644 --- a/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.ts +++ b/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.ts @@ -10,13 +10,14 @@ import { LearningPathExerciseComponent } from 'app/course/learning-paths/compone import { LearningPathApiService } from 'app/course/learning-paths/services/learning-path-api.service'; import { LearningPathNavigationService } from 'app/course/learning-paths/services/learning-path-navigation.service'; import { onError } from 'app/shared/util/global.utils'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; @Component({ selector: 'jhi-learning-path-student-page', templateUrl: './learning-path-student-page.component.html', styleUrl: './learning-path-student-page.component.scss', standalone: true, - imports: [LearningPathNavComponent, LearningPathLectureUnitComponent, LearningPathExerciseComponent], + imports: [LearningPathNavComponent, LearningPathLectureUnitComponent, LearningPathExerciseComponent, TranslateDirective], }) export class LearningPathStudentPageComponent { protected readonly LearningObjectType = LearningObjectType; diff --git a/src/main/webapp/app/entities/programming/programming-exercise.model.ts b/src/main/webapp/app/entities/programming/programming-exercise.model.ts index 9b1b392e54b7..8dde59469c03 100644 --- a/src/main/webapp/app/entities/programming/programming-exercise.model.ts +++ b/src/main/webapp/app/entities/programming/programming-exercise.model.ts @@ -13,18 +13,19 @@ import { SubmissionPolicy } from 'app/entities/submission-policy.model'; import dayjs from 'dayjs/esm'; export enum ProgrammingLanguage { - JAVA = 'JAVA', - PYTHON = 'PYTHON', + EMPTY = 'EMPTY', + ASSEMBLER = 'ASSEMBLER', C = 'C', HASKELL = 'HASKELL', + JAVA = 'JAVA', + JAVASCRIPT = 'JAVASCRIPT', KOTLIN = 'KOTLIN', - VHDL = 'VHDL', - ASSEMBLER = 'ASSEMBLER', - SWIFT = 'SWIFT', OCAML = 'OCAML', - EMPTY = 'EMPTY', + PYTHON = 'PYTHON', + R = 'R', RUST = 'RUST', - JAVASCRIPT = 'JAVASCRIPT', + SWIFT = 'SWIFT', + VHDL = 'VHDL', } export enum ProjectType { diff --git a/src/main/webapp/app/entities/result.model.ts b/src/main/webapp/app/entities/result.model.ts index d6c2f96adaaa..47fff80fda31 100644 --- a/src/main/webapp/app/entities/result.model.ts +++ b/src/main/webapp/app/entities/result.model.ts @@ -39,24 +39,6 @@ export class Result implements BaseEntity { this.successful = false; // default value } - /** - * Checks whether the result is a manual result. A manual result can be from type MANUAL or SEMI_AUTOMATIC - * - * @return true if the result is a manual result - */ - public static isManualResult(that: Result): boolean { - return that.assessmentType === AssessmentType.MANUAL || that.assessmentType === AssessmentType.SEMI_AUTOMATIC; - } - - /** - * Checks whether the result is generated by Athena AI. - * - * @return true if the result is an automatic Athena AI result - */ - public static isAthenaAIResult(that: Result): boolean { - return that.assessmentType === AssessmentType.AUTOMATIC_ATHENA; - } - /** * Checks whether the given result has an assessment note that is not empty. * @param that the result of which the presence of an assessment note is being checked diff --git a/src/main/webapp/app/exam/participate/exercises/file-upload/file-upload-exam-submission.component.html b/src/main/webapp/app/exam/participate/exercises/file-upload/file-upload-exam-submission.component.html index be5dce5a54a3..be860bc79852 100644 --- a/src/main/webapp/app/exam/participate/exercises/file-upload/file-upload-exam-submission.component.html +++ b/src/main/webapp/app/exam/participate/exercises/file-upload/file-upload-exam-submission.component.html @@ -15,7 +15,7 @@


- {{ examTimeline ? exercise.title : (exercise?.exerciseGroup?.title ?? '-') }} + {{ examTimeline ? exercise.title : ('artemisApp.exam.yourSolution' | artemisTranslate) }}
@if (isActive && !result && exercise && studentSubmission && !readonly) { diff --git a/src/main/webapp/app/exam/participate/exercises/modeling/modeling-exam-submission.component.html b/src/main/webapp/app/exam/participate/exercises/modeling/modeling-exam-submission.component.html index 273a1acbb8e5..aa3306277c53 100644 --- a/src/main/webapp/app/exam/participate/exercises/modeling/modeling-exam-submission.component.html +++ b/src/main/webapp/app/exam/participate/exercises/modeling/modeling-exam-submission.component.html @@ -16,7 +16,7 @@

- {{ examTimeline ? exercise.title : (exercise?.exerciseGroup?.title ?? '-') }} + {{ examTimeline ? exercise.title : ('artemisApp.exam.yourSolution' | artemisTranslate) }}
diff --git a/src/main/webapp/app/exam/participate/exercises/text/text-exam-submission.component.html b/src/main/webapp/app/exam/participate/exercises/text/text-exam-submission.component.html index 653d0f6b0ad5..cc0a6fba438f 100644 --- a/src/main/webapp/app/exam/participate/exercises/text/text-exam-submission.component.html +++ b/src/main/webapp/app/exam/participate/exercises/text/text-exam-submission.component.html @@ -16,7 +16,7 @@

- {{ examTimeline ? exercise.title : (exercise?.exerciseGroup?.title ?? '-') }} + {{ examTimeline ? exercise.title : ('artemisApp.exam.yourSolution' | artemisTranslate) }}
diff --git a/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.html b/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.html index f1017919bfb7..44eea193c092 100644 --- a/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.html +++ b/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.html @@ -190,6 +190,7 @@

[resultsPublished]="resultsArePublished" [isPrinting]="isPrinting" [isAfterResultsArePublished]="resultsArePublished" + [instructorView]="instructorView" /> } } diff --git a/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.html b/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.html index c483a168cd8a..2a0df39f1353 100644 --- a/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.html +++ b/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.html @@ -48,7 +48,7 @@
@if (exercise.problemStatement) { - + }
diff --git a/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.ts b/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.ts index 2243c413644a..5ffdffc38018 100644 --- a/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.ts +++ b/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.ts @@ -39,6 +39,8 @@ export class ProgrammingExamSummaryComponent implements OnInit { @Input() isAfterResultsArePublished?: boolean = false; + @Input() instructorView?: boolean = false; + readonly PROGRAMMING: ExerciseType = ExerciseType.PROGRAMMING; protected readonly AssessmentType = AssessmentType; diff --git a/src/main/webapp/app/exercises/programming/participate/code-editor-student-container.component.ts b/src/main/webapp/app/exercises/programming/participate/code-editor-student-container.component.ts index 34aa3e0b0d5a..ad6ed37728d2 100644 --- a/src/main/webapp/app/exercises/programming/participate/code-editor-student-container.component.ts +++ b/src/main/webapp/app/exercises/programming/participate/code-editor-student-container.component.ts @@ -26,6 +26,7 @@ import { ExerciseHint } from 'app/entities/hestia/exercise-hint.model'; import { ExerciseHintService } from 'app/exercises/shared/exercise-hint/shared/exercise-hint.service'; import { HttpResponse } from '@angular/common/http'; import { AlertService } from 'app/core/util/alert.service'; +import { isManualResult as isManualResultFunction } from 'app/exercises/shared/result/result.utils'; @Component({ selector: 'jhi-code-editor-student', @@ -148,7 +149,7 @@ export class CodeEditorStudentContainerComponent implements OnInit, OnDestroy { let hasTutorFeedback = false; if (this.latestResult) { // latest result is the first element of results, see loadParticipationWithLatestResult - isManualResult = Result.isManualResult(this.latestResult); + isManualResult = isManualResultFunction(this.latestResult); if (isManualResult) { hasTutorFeedback = this.latestResult.feedbacks!.some((feedback) => feedback.type === FeedbackType.MANUAL); } diff --git a/src/main/webapp/app/exercises/programming/shared/actions/programming-exercise-trigger-build-button.component.ts b/src/main/webapp/app/exercises/programming/shared/actions/programming-exercise-trigger-build-button.component.ts index 52089b28c991..1431ad0bfe38 100644 --- a/src/main/webapp/app/exercises/programming/shared/actions/programming-exercise-trigger-build-button.component.ts +++ b/src/main/webapp/app/exercises/programming/shared/actions/programming-exercise-trigger-build-button.component.ts @@ -13,6 +13,7 @@ import { SubmissionType } from 'app/entities/submission.model'; import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model'; import { AlertService } from 'app/core/util/alert.service'; import { hasParticipationChanged } from 'app/exercises/shared/participation/participation.utils'; +import { isManualResult } from 'app/exercises/shared/result/result.utils'; /** * Component for triggering a build for the CURRENT submission of the student (does not create a new commit!). @@ -60,7 +61,7 @@ export abstract class ProgrammingExerciseTriggerBuildButtonComponent implements if (hasDueDatePassed(this.exercise)) { // If the last result was manual, the instructor might not want to override it with a new automatic result. const newestResult = !!this.participation.results && head(orderBy(this.participation.results, ['id'], ['desc'])); - this.lastResultIsManual = !!newestResult && Result.isManualResult(newestResult); + this.lastResultIsManual = !!newestResult && isManualResult(newestResult); } // We can trigger the build only if the participation is active (has build plan), if the build plan was archived (new build plan will be created) // or the due date is over. @@ -126,7 +127,7 @@ export abstract class ProgrammingExerciseTriggerBuildButtonComponent implements .pipe( filter((result) => !!result), tap((result: Result) => { - this.lastResultIsManual = !!result && Result.isManualResult(result); + this.lastResultIsManual = !!result && isManualResult(result); }), ) .subscribe(); diff --git a/src/main/webapp/app/exercises/programming/shared/code-editor/actions/code-editor-actions.component.html b/src/main/webapp/app/exercises/programming/shared/code-editor/actions/code-editor-actions.component.html index 29e3ded8363c..d0186a25665e 100644 --- a/src/main/webapp/app/exercises/programming/shared/code-editor/actions/code-editor-actions.component.html +++ b/src/main/webapp/app/exercises/programming/shared/code-editor/actions/code-editor-actions.component.html @@ -1,3 +1,6 @@ +@if (!!participation()?.exercise) { + +} @if (commitState === CommitState.CONFLICT) {
diff --git a/src/main/webapp/app/exercises/programming/shared/code-editor/file-browser/supported-file-extensions.ts b/src/main/webapp/app/exercises/programming/shared/code-editor/file-browser/supported-file-extensions.ts index 73ec34bebb00..89664e7f5963 100644 --- a/src/main/webapp/app/exercises/programming/shared/code-editor/file-browser/supported-file-extensions.ts +++ b/src/main/webapp/app/exercises/programming/shared/code-editor/file-browser/supported-file-extensions.ts @@ -1,5 +1,6 @@ export const supportedTextFileExtensions = [ 'Makefile', + 'R', 'Rakefile', 'ada', 'adb', diff --git a/src/main/webapp/app/exercises/programming/shared/utils/programming-exercise.utils.ts b/src/main/webapp/app/exercises/programming/shared/utils/programming-exercise.utils.ts index 5e95bb66dbac..262fcb9e30a7 100644 --- a/src/main/webapp/app/exercises/programming/shared/utils/programming-exercise.utils.ts +++ b/src/main/webapp/app/exercises/programming/shared/utils/programming-exercise.utils.ts @@ -7,6 +7,7 @@ import { SubmissionType } from 'app/entities/submission.model'; import { ProgrammingExerciseStudentParticipation } from 'app/entities/participation/programming-exercise-student-participation.model'; import { AssessmentType } from 'app/entities/assessment-type.model'; import { isPracticeMode } from 'app/entities/participation/student-participation.model'; +import { isAIResultAndFailed, isAIResultAndIsBeingProcessed, isAIResultAndProcessed, isAIResultAndTimedOut } from 'app/exercises/shared/result/result.utils'; export const createBuildPlanUrl = (template: string, projectKey: string, buildPlanId: string): string | undefined => { if (template && projectKey && buildPlanId) { @@ -59,7 +60,10 @@ export const isResultPreliminary = (latestResult: Result, programmingExercise?: if (!programmingExercise) { return false; } - if (latestResult.assessmentType === AssessmentType.AUTOMATIC_ATHENA) { + if (isAIResultAndProcessed(latestResult)) { + return true; + } + if (isAIResultAndIsBeingProcessed(latestResult) || isAIResultAndTimedOut(latestResult) || isAIResultAndFailed(latestResult)) { return false; } if (latestResult.participation?.type === ParticipationType.PROGRAMMING && isPracticeMode(latestResult.participation)) { diff --git a/src/main/webapp/app/exercises/shared/assessment-progress-label/assessment-progress-label.component.ts b/src/main/webapp/app/exercises/shared/assessment-progress-label/assessment-progress-label.component.ts index 75f9243432c3..254a3b2a5f82 100644 --- a/src/main/webapp/app/exercises/shared/assessment-progress-label/assessment-progress-label.component.ts +++ b/src/main/webapp/app/exercises/shared/assessment-progress-label/assessment-progress-label.component.ts @@ -1,6 +1,6 @@ import { Component, Input, OnChanges } from '@angular/core'; import { Submission, getLatestSubmissionResult } from 'app/entities/submission.model'; -import { Result } from 'app/entities/result.model'; +import { isManualResult } from 'app/exercises/shared/result/result.utils'; @Component({ selector: 'jhi-assessment-progress-label', @@ -14,7 +14,7 @@ export class AssessmentProgressLabelComponent implements OnChanges { ngOnChanges() { this.numberAssessedSubmissions = this.submissions.filter((submission) => { const result = getLatestSubmissionResult(submission); - return result?.rated && Result.isManualResult(result) && result?.completionDate; + return result?.rated && isManualResult(result) && result?.completionDate; }).length; } } diff --git a/src/main/webapp/app/exercises/shared/dashboards/tutor/exercise-assessment-dashboard.component.ts b/src/main/webapp/app/exercises/shared/dashboards/tutor/exercise-assessment-dashboard.component.ts index a5bffcbe5575..63a23747c1f9 100644 --- a/src/main/webapp/app/exercises/shared/dashboards/tutor/exercise-assessment-dashboard.component.ts +++ b/src/main/webapp/app/exercises/shared/dashboards/tutor/exercise-assessment-dashboard.component.ts @@ -42,12 +42,12 @@ import { ArtemisNavigationUtilService, getLinkToSubmissionAssessment } from 'app import { AssessmentType } from 'app/entities/assessment-type.model'; import { LegendPosition } from '@swimlane/ngx-charts'; import { AssessmentDashboardInformationEntry } from 'app/course/dashboards/assessment-dashboard/assessment-dashboard-information.component'; -import { Result } from 'app/entities/result.model'; import dayjs from 'dayjs/esm'; import { faCheckCircle, faExclamationTriangle, faFolderOpen, faListAlt, faQuestionCircle, faSort, faSpinner } from '@fortawesome/free-solid-svg-icons'; import { GraphColors } from 'app/entities/statistics.model'; import { PROFILE_LOCALVC } from 'app/app.constants'; import { ProfileService } from 'app/shared/layouts/profiles/profile.service'; +import { isManualResult } from 'app/exercises/shared/result/result.utils'; export interface ExampleSubmissionQueryParams { readOnly?: boolean; @@ -640,7 +640,7 @@ export class ExerciseAssessmentDashboardComponent implements OnInit { */ calculateSubmissionStatusIsDraft(submission: Submission, correctionRound = 0): boolean { const tmpResult = submission.results?.[correctionRound]; - return !(tmpResult?.completionDate && Result.isManualResult(tmpResult)); + return !(tmpResult?.completionDate && isManualResult(tmpResult)); } /** diff --git a/src/main/webapp/app/exercises/shared/exercise-scores/exercise-scores.component.ts b/src/main/webapp/app/exercises/shared/exercise-scores/exercise-scores.component.ts index c2642528bc00..dbc3b28d2e83 100644 --- a/src/main/webapp/app/exercises/shared/exercise-scores/exercise-scores.component.ts +++ b/src/main/webapp/app/exercises/shared/exercise-scores/exercise-scores.component.ts @@ -13,7 +13,6 @@ import { areManualResultsAllowed } from 'app/exercises/shared/exercise/exercise. import { ResultService } from 'app/exercises/shared/result/result.service'; import { Exercise, ExerciseType } from 'app/entities/exercise.model'; import { StudentParticipation } from 'app/entities/participation/student-participation.model'; -import { Result } from 'app/entities/result.model'; import { ProgrammingSubmission } from 'app/entities/programming/programming-submission.model'; import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service'; import { AssessmentType } from 'app/entities/assessment-type.model'; @@ -27,6 +26,7 @@ import dayjs from 'dayjs/esm'; import { ExerciseCacheService } from 'app/exercises/shared/exercise/exercise-cache.service'; import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; import { PROFILE_LOCALVC } from 'app/app.constants'; +import { isManualResult } from 'app/exercises/shared/result/result.utils'; /** * Filter properties for a result @@ -229,7 +229,7 @@ export class ExerciseScoresComponent implements OnInit, OnDestroy { case FilterProp.BUILD_FAILED: return !!(participation.submissions?.[0] && (participation.submissions?.[0] as ProgrammingSubmission).buildFailed); case FilterProp.MANUAL: - return !!latestResult && Result.isManualResult(latestResult); + return !!latestResult && isManualResult(latestResult); case FilterProp.AUTOMATIC: return latestResult?.assessmentType === AssessmentType.AUTOMATIC; case FilterProp.LOCKED: diff --git a/src/main/webapp/app/exercises/shared/feedback/feedback.component.html b/src/main/webapp/app/exercises/shared/feedback/feedback.component.html index a0de0676f7dc..23ecac9676b2 100644 --- a/src/main/webapp/app/exercises/shared/feedback/feedback.component.html +++ b/src/main/webapp/app/exercises/shared/feedback/feedback.component.html @@ -119,11 +119,15 @@

{{ 'artemisApp.result.preliminary' | artemisTranslate | uppercase }}
- @if (exercise?.assessmentType !== AssessmentType.AUTOMATIC) { -

- } - @if (exercise?.assessmentType === AssessmentType.AUTOMATIC) { -

+ @if (result?.assessmentType === AssessmentType.AUTOMATIC_ATHENA) { +

+ } @else { + @if (exercise?.assessmentType !== AssessmentType.AUTOMATIC) { +

+ } + @if (exercise?.assessmentType === AssessmentType.AUTOMATIC) { +

+ } }

} diff --git a/src/main/webapp/app/exercises/shared/participation/participation.utils.ts b/src/main/webapp/app/exercises/shared/participation/participation.utils.ts index 5fc349f22b27..d931734c6407 100644 --- a/src/main/webapp/app/exercises/shared/participation/participation.utils.ts +++ b/src/main/webapp/app/exercises/shared/participation/participation.utils.ts @@ -6,6 +6,7 @@ import dayjs from 'dayjs/esm'; import { StudentParticipation } from 'app/entities/participation/student-participation.model'; import { Result } from 'app/entities/result.model'; import { orderBy as _orderBy } from 'lodash-es'; +import { isAIResultAndIsBeingProcessed } from 'app/exercises/shared/result/result.utils'; /** * Check if the participation has changed. @@ -102,7 +103,11 @@ export const isParticipationInDueTime = (participation: Participation, exercise: * @param participation * @param showUngradedResults */ -export function getLatestResultOfStudentParticipation(participation: StudentParticipation | undefined, showUngradedResults: boolean): Result | undefined { +export function getLatestResultOfStudentParticipation( + participation: StudentParticipation | undefined, + showUngradedResults: boolean, + showAthenaPreliminaryFeedback: boolean = false, +): Result | undefined { if (!participation) { return undefined; } @@ -111,8 +116,11 @@ export function getLatestResultOfStudentParticipation(participation: StudentPart if (participation.results) { participation.results = _orderBy(participation.results, 'completionDate', 'desc'); } + // The latest result is the first rated result in the sorted array (=newest) or any result if the option is active to show ungraded results. - const latestResult = participation.results?.find(({ rated }) => showUngradedResults || rated === true); + const latestResult = participation.results?.find( + (result) => showUngradedResults || result.rated === true || (showAthenaPreliminaryFeedback && isAIResultAndIsBeingProcessed(result)), + ); // Make sure that the participation result is connected to the newest result. return latestResult ? { ...latestResult, participation: participation } : undefined; } diff --git a/src/main/webapp/app/exercises/shared/result/result.component.html b/src/main/webapp/app/exercises/shared/result/result.component.html index 2dfd17685054..911320c183df 100644 --- a/src/main/webapp/app/exercises/shared/result/result.component.html +++ b/src/main/webapp/app/exercises/shared/result/result.component.html @@ -12,6 +12,9 @@ } @case (ResultTemplateStatus.FEEDBACK_GENERATION_FAILED) { @if (result) { + @if (showIcon) { + + } {{ resultString }} @@ -20,17 +23,16 @@ } } @case (ResultTemplateStatus.IS_GENERATING_FEEDBACK) { - @if (result) { - - - - {{ resultString }} - - - } + + + + } @case (ResultTemplateStatus.FEEDBACK_GENERATION_TIMED_OUT) { @if (result) { + @if (showIcon) { + + } {{ resultString }} @@ -59,7 +61,7 @@ } @if (!isInSidebarCard) { - ({{ result!.completionDate | artemisTimeAgo }}) + ({{ result!.completionDate | artemisTimeAgo }} ) } @if (hasBuildArtifact() && participation.type === ParticipationType.PROGRAMMING) { diff --git a/src/main/webapp/app/exercises/shared/result/result.component.ts b/src/main/webapp/app/exercises/shared/result/result.component.ts index 3415021e11c7..f9edf80994d9 100644 --- a/src/main/webapp/app/exercises/shared/result/result.component.ts +++ b/src/main/webapp/app/exercises/shared/result/result.component.ts @@ -1,6 +1,13 @@ import { Component, Input, OnChanges, OnDestroy, OnInit, Optional, SimpleChanges } from '@angular/core'; import { ParticipationService } from 'app/exercises/shared/participation/participation.service'; -import { MissingResultInformation, ResultTemplateStatus, evaluateTemplateStatus, getResultIconClass, getTextColorClass } from 'app/exercises/shared/result/result.utils'; +import { + MissingResultInformation, + ResultTemplateStatus, + evaluateTemplateStatus, + getResultIconClass, + getTextColorClass, + isAthenaAIResult, +} from 'app/exercises/shared/result/result.utils'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { TranslateService } from '@ngx-translate/core'; import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model'; @@ -190,7 +197,7 @@ export class ResultComponent implements OnInit, OnChanges, OnDestroy { this.resultString = this.resultService.getResultString(this.result, this.exercise, this.short); } else if ( this.result && - ((this.result.score !== undefined && (this.result.rated || this.result.rated == undefined || this.showUngradedResults)) || Result.isAthenaAIResult(this.result)) + ((this.result.score !== undefined && (this.result.rated || this.result.rated == undefined || this.showUngradedResults)) || isAthenaAIResult(this.result)) ) { this.textColorClass = getTextColorClass(this.result, this.templateStatus); this.resultIconClass = getResultIconClass(this.result, this.templateStatus); @@ -230,7 +237,7 @@ export class ResultComponent implements OnInit, OnChanges, OnDestroy { return 'artemisApp.result.resultString.automaticAIFeedbackTimedOutTooltip'; } else if (this.templateStatus === ResultTemplateStatus.IS_GENERATING_FEEDBACK) { return 'artemisApp.result.resultString.automaticAIFeedbackInProgressTooltip'; - } else if (this.templateStatus === ResultTemplateStatus.HAS_RESULT && Result.isAthenaAIResult(this.result)) { + } else if (this.templateStatus === ResultTemplateStatus.HAS_RESULT && isAthenaAIResult(this.result)) { return 'artemisApp.result.resultString.automaticAIFeedbackSuccessfulTooltip'; } } diff --git a/src/main/webapp/app/exercises/shared/result/result.service.ts b/src/main/webapp/app/exercises/shared/result/result.service.ts index 2fd62aa574b7..ea4c9eaaca4c 100644 --- a/src/main/webapp/app/exercises/shared/result/result.service.ts +++ b/src/main/webapp/app/exercises/shared/result/result.service.ts @@ -24,6 +24,7 @@ import { isAIResultAndIsBeingProcessed, isAIResultAndProcessed, isAIResultAndTimedOut, + isAthenaAIResult, isStudentParticipation, } from 'app/exercises/shared/result/result.utils'; import { CsvDownloadService } from 'app/shared/util/CsvDownloadService'; @@ -94,7 +95,7 @@ export class ResultService implements IResultService { const relativeScore = roundValueSpecifiedByCourseSettings(result.score!, getCourseFromExercise(exercise)); const points = roundValueSpecifiedByCourseSettings((result.score! * exercise.maxPoints!) / 100, getCourseFromExercise(exercise)); if (exercise.type !== ExerciseType.PROGRAMMING) { - if (Result.isAthenaAIResult(result)) { + if (isAthenaAIResult(result)) { return this.getResultStringNonProgrammingExerciseWithAIFeedback(result, relativeScore, points, short); } return this.getResultStringNonProgrammingExercise(relativeScore, points, short); @@ -112,7 +113,7 @@ export class ResultService implements IResultService { */ private getResultStringNonProgrammingExerciseWithAIFeedback(result: Result, relativeScore: number, points: number, short: boolean | undefined): string { let aiFeedbackMessage: string = ''; - if (result && Result.isAthenaAIResult(result) && result.successful === undefined) { + if (result && isAthenaAIResult(result) && result.successful === undefined) { return this.translateService.instant('artemisApp.result.resultString.automaticAIFeedbackInProgress'); } aiFeedbackMessage = this.getResultStringNonProgrammingExercise(relativeScore, points, short); @@ -149,9 +150,7 @@ export class ResultService implements IResultService { */ private getResultStringProgrammingExercise(result: Result, exercise: ProgrammingExercise, relativeScore: number, points: number, short: boolean | undefined): string { let buildAndTestMessage: string; - if (result.submission && (result.submission as ProgrammingSubmission).buildFailed) { - buildAndTestMessage = this.translateService.instant('artemisApp.result.resultString.buildFailed'); - } else if (isAIResultAndFailed(result)) { + if (isAIResultAndFailed(result)) { buildAndTestMessage = this.translateService.instant('artemisApp.result.resultString.automaticAIFeedbackFailed'); } else if (isAIResultAndIsBeingProcessed(result)) { buildAndTestMessage = this.translateService.instant('artemisApp.result.resultString.automaticAIFeedbackInProgress'); @@ -159,6 +158,8 @@ export class ResultService implements IResultService { buildAndTestMessage = this.translateService.instant('artemisApp.result.resultString.automaticAIFeedbackTimedOut'); } else if (isAIResultAndProcessed(result)) { buildAndTestMessage = this.translateService.instant('artemisApp.result.resultString.automaticAIFeedbackSuccessful'); + } else if (result.submission && (result.submission as ProgrammingSubmission).buildFailed) { + buildAndTestMessage = this.translateService.instant('artemisApp.result.resultString.buildFailed'); } else if (!result.testCaseCount) { buildAndTestMessage = this.translateService.instant('artemisApp.result.resultString.buildSuccessfulNoTests'); } else { @@ -187,7 +188,7 @@ export class ResultService implements IResultService { * @param short flag that indicates if the resultString should use the short format */ private getBaseResultStringProgrammingExercise(result: Result, relativeScore: number, points: number, buildAndTestMessage: string, short: boolean | undefined): string { - if (Result.isAthenaAIResult(result)) { + if (isAthenaAIResult(result)) { return buildAndTestMessage; } if (short) { diff --git a/src/main/webapp/app/exercises/shared/result/result.utils.ts b/src/main/webapp/app/exercises/shared/result/result.utils.ts index be0126c54db9..cee9c6dbb07f 100644 --- a/src/main/webapp/app/exercises/shared/result/result.utils.ts +++ b/src/main/webapp/app/exercises/shared/result/result.utils.ts @@ -119,20 +119,29 @@ export const getUnreferencedFeedback = (feedbacks: Feedback[] | undefined): Feed return feedbacks ? feedbacks.filter((feedbackElement) => !feedbackElement.reference && feedbackElement.type === FeedbackType.MANUAL_UNREFERENCED) : undefined; }; -export function isAIResultAndFailed(result: Result | undefined) { - return result && Result.isAthenaAIResult(result) && result.successful === false; +export function isAIResultAndFailed(result: Result | undefined): boolean { + return (result && isAthenaAIResult(result) && result.successful === false) ?? false; } -export function isAIResultAndTimedOut(result: Result | undefined) { - return result && Result.isAthenaAIResult(result) && result.successful === undefined && result.completionDate && dayjs().isAfter(result.completionDate); +export function isAIResultAndTimedOut(result: Result | undefined): boolean { + return (result && isAthenaAIResult(result) && result.successful === undefined && result.completionDate && dayjs().isAfter(result.completionDate)) ?? false; } -export function isAIResultAndProcessed(result: Result | undefined) { - return result && Result.isAthenaAIResult(result) && result.successful === true; +export function isAIResultAndProcessed(result: Result | undefined): boolean { + return (result && isAthenaAIResult(result) && result.successful === true) ?? false; } -export function isAIResultAndIsBeingProcessed(result: Result | undefined) { - return result && Result.isAthenaAIResult(result) && result.successful === undefined && result.completionDate && dayjs().isSameOrBefore(result.completionDate); +export function isAIResultAndIsBeingProcessed(result: Result | undefined): boolean { + return (result && isAthenaAIResult(result) && result.successful === undefined && result.completionDate && dayjs().isSameOrBefore(result.completionDate)) ?? false; +} + +/** + * Checks whether the result is generated by Athena AI. + * + * @return true if the result is an automatic Athena AI result + */ +export function isAthenaAIResult(result: Result): boolean { + return result.assessmentType === AssessmentType.AUTOMATIC_ATHENA; } export const evaluateTemplateStatus = ( @@ -248,9 +257,12 @@ export const getTextColorClass = (result: Result | undefined, templateStatus: Re } if (result.assessmentType === AssessmentType.AUTOMATIC_ATHENA) { - if (result.successful == undefined) { + if (isAIResultAndIsBeingProcessed(result)) { return 'text-primary'; } + if (isAIResultAndFailed(result)) { + return 'text-danger'; + } return 'text-secondary'; } @@ -258,11 +270,11 @@ export const getTextColorClass = (result: Result | undefined, templateStatus: Re return 'result-late'; } - if (isBuildFailedAndResultIsAutomatic(result) || isAIResultAndFailed(result)) { + if (isBuildFailedAndResultIsAutomatic(result)) { return 'text-danger'; } - if (resultIsPreliminary(result) || isAIResultAndIsBeingProcessed(result) || isAIResultAndTimedOut(result)) { + if (resultIsPreliminary(result)) { return 'text-secondary'; } @@ -294,18 +306,19 @@ export const getResultIconClass = (result: Result | undefined, templateStatus: R return faQuestionCircle; } - if (result.assessmentType === AssessmentType.AUTOMATIC_ATHENA) { - if (result.successful === undefined) { - return faCircleNotch; - } - return faQuestionCircle; + if (isAIResultAndProcessed(result)) { + return faCheckCircle; } if (isBuildFailedAndResultIsAutomatic(result) || isAIResultAndFailed(result)) { return faTimesCircle; } - if (resultIsPreliminary(result) || isAIResultAndTimedOut(result) || isAIResultAndIsBeingProcessed(result)) { + if (isAIResultAndIsBeingProcessed(result)) { + return faCircleNotch; + } + + if (resultIsPreliminary(result) || isAIResultAndTimedOut(result)) { return faQuestionCircle; } diff --git a/src/main/webapp/app/exercises/shared/result/updating-result.component.ts b/src/main/webapp/app/exercises/shared/result/updating-result.component.ts index 55cde780b0ec..640b02f38bff 100644 --- a/src/main/webapp/app/exercises/shared/result/updating-result.component.ts +++ b/src/main/webapp/app/exercises/shared/result/updating-result.component.ts @@ -13,7 +13,7 @@ import { StudentParticipation } from 'app/entities/participation/student-partici import { Result } from 'app/entities/result.model'; import { getExerciseDueDate } from 'app/exercises/shared/exercise/exercise.utils'; import { getLatestResultOfStudentParticipation, hasParticipationChanged } from 'app/exercises/shared/participation/participation.utils'; -import { MissingResultInformation } from 'app/exercises/shared/result/result.utils'; +import { MissingResultInformation, isAIResultAndIsBeingProcessed, isAthenaAIResult } from 'app/exercises/shared/result/result.utils'; import { convertDateFromServer } from 'app/utils/date.utils'; /** @@ -59,7 +59,7 @@ export class UpdatingResultComponent implements OnChanges, OnDestroy { */ ngOnChanges(changes: SimpleChanges) { if (hasParticipationChanged(changes)) { - this.result = getLatestResultOfStudentParticipation(this.participation, this.showUngradedResults); + this.result = getLatestResultOfStudentParticipation(this.participation, this.showUngradedResults, true); this.missingResultInfo = MissingResultInformation.NONE; this.subscribeForNewResults(); @@ -101,10 +101,17 @@ export class UpdatingResultComponent implements OnChanges, OnDestroy { // Ignore initial null result of subscription filter((result) => !!result), // Ignore ungraded results if ungraded results are supposed to be ignored. - filter((result: Result) => this.showUngradedResults || result.rated === true), + // If the result is a preliminary feedback(being generated), show it + filter((result: Result) => this.showUngradedResults || result.rated === true || isAthenaAIResult(result)), map((result) => ({ ...result, completionDate: convertDateFromServer(result.completionDate), participation: this.participation })), tap((result) => { - this.result = result; + if ((isAthenaAIResult(result) && isAIResultAndIsBeingProcessed(result)) || result.rated) { + this.result = result; + } else if (result.rated === false && this.showUngradedResults) { + this.result = result; + } else { + this.result = getLatestResultOfStudentParticipation(this.participation, this.showUngradedResults, false); + } this.onParticipationChange.emit(); if (result) { this.showResult.emit(); diff --git a/src/main/webapp/app/overview/course-conversations/course-wide-search/course-wide-search.component.html b/src/main/webapp/app/overview/course-conversations/course-wide-search/course-wide-search.component.html index f4a255b49422..aa3b35159b88 100644 --- a/src/main/webapp/app/overview/course-conversations/course-wide-search/course-wide-search.component.html +++ b/src/main/webapp/app/overview/course-conversations/course-wide-search/course-wide-search.component.html @@ -3,9 +3,9 @@

@if (!courseWideSearchConfig.searchTerm) { - All Messages + } @else { - Search Results for "{{ courseWideSearchConfig.searchTerm }}" + }

diff --git a/src/main/webapp/app/overview/course-conversations/dialogs/conversation-detail-dialog/tabs/conversation-members/conversation-member-row/conversation-member-row.component.html b/src/main/webapp/app/overview/course-conversations/dialogs/conversation-detail-dialog/tabs/conversation-members/conversation-member-row/conversation-member-row.component.html index a481df162d51..47eff1257e65 100644 --- a/src/main/webapp/app/overview/course-conversations/dialogs/conversation-detail-dialog/tabs/conversation-members/conversation-member-row/conversation-member-row.component.html +++ b/src/main/webapp/app/overview/course-conversations/dialogs/conversation-detail-dialog/tabs/conversation-members/conversation-member-row/conversation-member-row.component.html @@ -1,11 +1,23 @@ @if (activeConversation && course) {
- + @if (userImageUrl) { + + } @else { + {{ userInitials }} + } @if (isChannel(activeConversation) && conversationMember?.isChannelModerator) { } {{ userLabel }} + @if (!conversationMember.isStudent) { + + }
@if (canBeRemovedFromConversation || canBeGrantedChannelModeratorRole || canBeRevokedChannelModeratorRole) { diff --git a/src/main/webapp/app/overview/course-conversations/dialogs/conversation-detail-dialog/tabs/conversation-members/conversation-member-row/conversation-member-row.component.scss b/src/main/webapp/app/overview/course-conversations/dialogs/conversation-detail-dialog/tabs/conversation-members/conversation-member-row/conversation-member-row.component.scss index 28814b8391f5..a2f66745bed6 100644 --- a/src/main/webapp/app/overview/course-conversations/dialogs/conversation-detail-dialog/tabs/conversation-members/conversation-member-row/conversation-member-row.component.scss +++ b/src/main/webapp/app/overview/course-conversations/dialogs/conversation-detail-dialog/tabs/conversation-members/conversation-member-row/conversation-member-row.component.scss @@ -1,3 +1,5 @@ +$profile-picture-height: 2rem; + .conversation-member-row { min-height: 3rem; @@ -14,4 +16,21 @@ .dropdown-toggle::after { content: none; } + + .conversation-member-row-default-profile-picture { + font-size: 0.8rem; + display: inline-flex; + align-items: center; + justify-content: center; + } + + .conversation-member-row-profile-picture, + .conversation-member-row-default-profile-picture { + width: $profile-picture-height; + height: $profile-picture-height; + max-width: $profile-picture-height; + max-height: $profile-picture-height; + background-color: var(--gray-400); + color: var(--white); + } } diff --git a/src/main/webapp/app/overview/course-conversations/dialogs/conversation-detail-dialog/tabs/conversation-members/conversation-member-row/conversation-member-row.component.ts b/src/main/webapp/app/overview/course-conversations/dialogs/conversation-detail-dialog/tabs/conversation-members/conversation-member-row/conversation-member-row.component.ts index 5044543d4878..39a712c64424 100644 --- a/src/main/webapp/app/overview/course-conversations/dialogs/conversation-detail-dialog/tabs/conversation-members/conversation-member-row/conversation-member-row.component.ts +++ b/src/main/webapp/app/overview/course-conversations/dialogs/conversation-detail-dialog/tabs/conversation-members/conversation-member-row/conversation-member-row.component.ts @@ -1,5 +1,5 @@ import { Component, EventEmitter, HostBinding, Input, OnDestroy, OnInit, Output } from '@angular/core'; -import { faChalkboardTeacher, faEllipsis, faUser, faUserCheck, faUserGear } from '@fortawesome/free-solid-svg-icons'; +import { faEllipsis, faUser, faUserCheck, faUserGear, faUserGraduate } from '@fortawesome/free-solid-svg-icons'; import { User } from 'app/core/user/user.model'; import { ConversationDTO } from 'app/entities/metis/conversation/conversation.model'; import { AccountService } from 'app/core/auth/account.service'; @@ -20,6 +20,8 @@ import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; import { getAsGroupChatDTO, isGroupChatDTO } from 'app/entities/metis/conversation/group-chat.model'; import { GroupChatService } from 'app/shared/metis/conversations/group-chat.service'; import { catchError } from 'rxjs/operators'; +import { getBackgroundColorHue } from 'app/utils/color.utils'; +import { getInitialsFromString } from 'app/utils/text.utils'; @Component({ // eslint-disable-next-line @angular-eslint/component-selector @@ -56,6 +58,9 @@ export class ConversationMemberRowComponent implements OnInit, OnDestroy { canBeRevokedChannelModeratorRole = false; userLabel: string; + userImageUrl: string | undefined; + userDefaultPictureHue: string; + userInitials: string; // icons userIcon: IconProp = faUser; userTooltip = ''; @@ -88,7 +93,10 @@ export class ConversationMemberRowComponent implements OnInit, OnDestroy { this.isCreator = true; } + this.userImageUrl = this.conversationMember.imageUrl; this.userLabel = getUserLabel(this.conversationMember); + this.userInitials = getInitialsFromString(this.conversationMember.name ?? 'NA'); + this.userDefaultPictureHue = getBackgroundColorHue(this.conversationMember.id ? this.conversationMember.id.toString() : 'default'); this.setUserAuthorityIconAndTooltip(); // the creator of a channel can not be removed from the channel this.canBeRemovedFromConversation = !this.isCurrentUser && this.canRemoveUsersFromConversation(this.activeConversation); @@ -242,7 +250,7 @@ export class ConversationMemberRowComponent implements OnInit, OnDestroy { const toolTipTranslationPath = 'artemisApp.metis.userAuthorityTooltips.'; // highest authority is displayed if (this.conversationMember.isInstructor) { - this.userIcon = faChalkboardTeacher; + this.userIcon = faUserGraduate; this.userTooltip = this.translateService.instant(toolTipTranslationPath + 'instructor'); } else if (this.conversationMember.isEditor || this.conversationMember.isTeachingAssistant) { this.userIcon = faUserCheck; diff --git a/src/main/webapp/app/overview/courses.component.html b/src/main/webapp/app/overview/courses.component.html index a9d1655aba7c..5836c04911c3 100644 --- a/src/main/webapp/app/overview/courses.component.html +++ b/src/main/webapp/app/overview/courses.component.html @@ -19,28 +19,32 @@

{{ nextRelevantExam.title }}

} -
-
-

- @if (regularCourses.length) { -
- +
+
+

{{ 'artemisApp.studentDashboard.title' | artemisTranslate }} ({{ regularCourses.length + recentlyAccessedCourses.length }})

+
+ - } + + @if (regularCourses.length) { + + } +
@if (recentlyAccessedCourses.length) {

-
-
- @for (course of recentlyAccessedCourses; track course) { -
- -
- } -
-
+ @if (regularCourses.length) {

@@ -55,18 +59,26 @@

+ } +

+ + + @if ((courses | searchFilter: ['title'] : searchCourseText).length > 0) {
-
- @for (course of regularCourses; track course) { +
+ @for (course of courses | searchFilter: ['title'] : searchCourseText; track course) {
- +
}
+ } @else { + @if (coursesLoaded) { +
+ +
+ } } -
- - - diff --git a/src/main/webapp/app/overview/courses.component.ts b/src/main/webapp/app/overview/courses.component.ts index fad63ba437cb..2ad16cae7fcd 100644 --- a/src/main/webapp/app/overview/courses.component.ts +++ b/src/main/webapp/app/overview/courses.component.ts @@ -10,7 +10,7 @@ import { JhiWebsocketService } from 'app/core/websocket/websocket.service'; import dayjs from 'dayjs/esm'; import { Exam } from 'app/entities/exam/exam.model'; import { Router } from '@angular/router'; -import { faPenAlt } from '@fortawesome/free-solid-svg-icons'; +import { faArrowDownAZ, faArrowUpAZ, faDoorOpen, faPenAlt } from '@fortawesome/free-solid-svg-icons'; import { CourseAccessStorageService } from 'app/course/course-access-storage.service'; import { CourseForDashboardDTO } from 'app/course/manage/course-for-dashboard-dto'; import { sortCourses } from 'app/shared/util/course.util'; @@ -21,6 +21,11 @@ import { sortCourses } from 'app/shared/util/course.util'; styleUrls: ['./courses.component.scss'], }) export class CoursesComponent implements OnInit, OnDestroy { + protected readonly faPenAlt = faPenAlt; + protected readonly faArrowDownAZ = faArrowDownAZ; + protected readonly faArrowUpAZ = faArrowUpAZ; + protected readonly faDoorOpen = faDoorOpen; + courses: Course[]; public nextRelevantCourse?: Course; nextRelevantCourseForExam?: Course; @@ -31,11 +36,10 @@ export class CoursesComponent implements OnInit, OnDestroy { courseForGuidedTour?: Course; quizExercisesChannels: string[] = []; - - // Icons - faPenAlt = faPenAlt; + searchCourseText = ''; coursesLoaded = false; + isSortAscending = true; constructor( private courseService: CourseManagementService, @@ -125,4 +129,19 @@ export class CoursesComponent implements OnInit, OnDestroy { openExam(): void { this.router.navigate(['courses', this.nextRelevantCourseForExam?.id, 'exams', this.nextRelevantExam!.id]); } + + setSearchValue(searchValue: string): void { + this.searchCourseText = searchValue; + } + + /** + * Sorts the courses in alphabetical order + */ + onSort(): void { + if (this.courses) { + this.isSortAscending = !this.isSortAscending; + this.regularCourses = [...sortCourses(this.regularCourses, this.isSortAscending)]; + this.recentlyAccessedCourses = [...sortCourses(this.recentlyAccessedCourses, this.isSortAscending)]; + } + } } diff --git a/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.ts b/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.ts index 62a1b6fa7429..4790d0b24bf9 100644 --- a/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.ts +++ b/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.ts @@ -198,7 +198,6 @@ export class CourseExerciseDetailsComponent extends AbstractScienceComponent imp this.exerciseCategories = this.exercise.categories ?? []; this.allowComplaintsForAutomaticAssessments = false; this.plagiarismCaseInfo = newExerciseDetails.plagiarismCaseInfo; - if (this.exercise.type === ExerciseType.PROGRAMMING) { const programmingExercise = this.exercise as ProgrammingExercise; const isAfterDateForComplaint = @@ -243,7 +242,7 @@ export class CourseExerciseDetailsComponent extends AbstractScienceComponent imp private filterUnfinishedResults(participations?: StudentParticipation[]) { participations?.forEach((participation: Participation) => { if (participation.results) { - participation.results = participation.results.filter((result: Result) => result.completionDate && result.successful !== undefined); + participation.results = participation.results.filter((result: Result) => result.completionDate); } }); } @@ -254,7 +253,7 @@ export class CourseExerciseDetailsComponent extends AbstractScienceComponent imp this.sortedHistoryResults = this.studentParticipations .flatMap((participation) => participation.results ?? []) .sort(this.resultSortFunction) - .filter((result) => !(result.assessmentType === AssessmentType.AUTOMATIC_ATHENA && result.successful == undefined)); + .filter((result) => !(result.assessmentType === AssessmentType.AUTOMATIC_ATHENA && dayjs().isBefore(result.completionDate))); } } diff --git a/src/main/webapp/app/overview/exercise-details/exercise-buttons.module.ts b/src/main/webapp/app/overview/exercise-details/exercise-buttons.module.ts index 1b36ab7e6f5d..7b6aab3d5f7c 100644 --- a/src/main/webapp/app/overview/exercise-details/exercise-buttons.module.ts +++ b/src/main/webapp/app/overview/exercise-details/exercise-buttons.module.ts @@ -6,9 +6,10 @@ import { OrionExerciseDetailsStudentActionsComponent } from 'app/orion/participa import { ExerciseDetailsStudentActionsComponent } from 'app/overview/exercise-details/exercise-details-student-actions.component'; import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; import { ArtemisSharedPipesModule } from 'app/shared/pipes/shared-pipes.module'; +import { RequestFeedbackButtonComponent } from 'app/overview/exercise-details/request-feedback-button/request-feedback-button.component'; @NgModule({ - imports: [ArtemisSharedModule, ArtemisSharedComponentModule, ArtemisSharedPipesModule, OrionModule, FeatureToggleModule], + imports: [ArtemisSharedModule, ArtemisSharedComponentModule, ArtemisSharedPipesModule, OrionModule, FeatureToggleModule, RequestFeedbackButtonComponent], declarations: [ExerciseDetailsStudentActionsComponent, OrionExerciseDetailsStudentActionsComponent], exports: [ExerciseDetailsStudentActionsComponent, OrionExerciseDetailsStudentActionsComponent], }) diff --git a/src/main/webapp/app/overview/exercise-details/exercise-details-student-actions.component.html b/src/main/webapp/app/overview/exercise-details/exercise-details-student-actions.component.html index 53a99d1f440a..6e2df76cbef9 100644 --- a/src/main/webapp/app/overview/exercise-details/exercise-details-student-actions.component.html +++ b/src/main/webapp/app/overview/exercise-details/exercise-details-student-actions.component.html @@ -135,30 +135,8 @@ } - @if (exercise.allowFeedbackRequests) { - @if (athenaEnabled) { - - - Send automatic feedback request - - } @else { - - - Send manual feedback request - - } + @if (exercise.allowFeedbackRequests && gradedParticipation && exercise.type === ExerciseType.PROGRAMMING) { + } } diff --git a/src/main/webapp/app/overview/exercise-details/exercise-details-student-actions.component.ts b/src/main/webapp/app/overview/exercise-details/exercise-details-student-actions.component.ts index fcdf131a87c4..2991bac3355f 100644 --- a/src/main/webapp/app/overview/exercise-details/exercise-details-student-actions.component.ts +++ b/src/main/webapp/app/overview/exercise-details/exercise-details-student-actions.component.ts @@ -110,6 +110,7 @@ export class ExerciseDetailsStudentActionsComponent implements OnInit, OnChanges this.profileService.getProfileInfo().subscribe((profileInfo) => { this.localVCEnabled = profileInfo.activeProfiles?.includes(PROFILE_LOCALVC); this.athenaEnabled = profileInfo.activeProfiles?.includes(PROFILE_ATHENA); + // The online IDE is only available with correct SpringProfile and if it's enabled for this exercise if (profileInfo.activeProfiles?.includes(PROFILE_THEIA) && this.programmingExercise) { this.theiaEnabled = true; @@ -257,6 +258,7 @@ export class ExerciseDetailsStudentActionsComponent implements OnInit, OnChanges }); } + // TODO remove this method once support of the button component is implemented for text and modeling exercises requestFeedback() { if (!this.assureConditionsSatisfied()) return; if (this.exercise.type === ExerciseType.PROGRAMMING) { @@ -341,6 +343,7 @@ export class ExerciseDetailsStudentActionsComponent implements OnInit, OnChanges * 3. There is no already pending feedback request. * @returns {boolean} `true` if all conditions are satisfied, otherwise `false`. */ + // TODO remove this method once support of the button component is implemented for text and modeling exercises assureConditionsSatisfied(): boolean { this.updateParticipations(); if (this.exercise.type === ExerciseType.PROGRAMMING) { @@ -378,7 +381,7 @@ export class ExerciseDetailsStudentActionsComponent implements OnInit, OnChanges } } - if (this.hasAthenaResultForlatestSubmission()) { + if (this.hasAthenaResultForLatestSubmission()) { const submitFirstWarning = this.translateService.instant('artemisApp.exercise.submissionAlreadyHasAthenaResult'); this.alertService.warning(submitFirstWarning); return false; @@ -386,29 +389,14 @@ export class ExerciseDetailsStudentActionsComponent implements OnInit, OnChanges return true; } - hasAthenaResultForlatestSubmission(): boolean { + hasAthenaResultForLatestSubmission(): boolean { if (this.gradedParticipation?.submissions && this.gradedParticipation?.results) { - const sortedSubmissions = this.gradedParticipation.submissions.slice().sort((a, b) => { - const dateA = this.getDateValue(a.submissionDate) ?? -Infinity; - const dateB = this.getDateValue(b.submissionDate) ?? -Infinity; - return dateB - dateA; - }); - - return this.gradedParticipation.results.some((result) => result.submission?.id === sortedSubmissions[0]?.id); + // submissions.results is always undefined so this is necessary + return ( + this.gradedParticipation.submissions.last()?.id === + this.gradedParticipation?.results.filter((result) => result.assessmentType == AssessmentType.AUTOMATIC_ATHENA).first()?.submission?.id + ); } return false; } - - private getDateValue = (date: any): number => { - if (dayjs.isDayjs(date)) { - return date.valueOf(); - } - if (date instanceof Date) { - return date.valueOf(); - } - if (typeof date === 'string') { - return new Date(date).valueOf(); - } - return -Infinity; // fallback for null, undefined, or invalid dates - }; } diff --git a/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.html b/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.html new file mode 100644 index 000000000000..6d6addcc2b84 --- /dev/null +++ b/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.html @@ -0,0 +1,39 @@ +@if (!isExamExercise) { + @if (athenaEnabled) { + @if (exercise().type === ExerciseType.TEXT) { + + } @else { + + + + + } + } @else { + + + + + } +} diff --git a/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.ts b/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.ts new file mode 100644 index 000000000000..b9aecaec56d5 --- /dev/null +++ b/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.ts @@ -0,0 +1,117 @@ +import { Component, OnInit, inject, input, output } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { faPenSquare } from '@fortawesome/free-solid-svg-icons'; +import { ProfileService } from 'app/shared/layouts/profiles/profile.service'; +import { PROFILE_ATHENA } from 'app/app.constants'; +import { StudentParticipation } from 'app/entities/participation/student-participation.model'; +import { Exercise, ExerciseType } from 'app/entities/exercise.model'; +import { AlertService } from 'app/core/util/alert.service'; +import { CourseExerciseService } from 'app/exercises/shared/course-exercises/course-exercise.service'; +import { AssessmentType } from 'app/entities/assessment-type.model'; +import { TranslateService } from '@ngx-translate/core'; +import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; +import { isExamExercise } from 'app/shared/util/utils'; +import { ExerciseDetailsType, ExerciseService } from 'app/exercises/shared/exercise/exercise.service'; +import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; +import { ParticipationService } from 'app/exercises/shared/participation/participation.service'; + +@Component({ + selector: 'jhi-request-feedback-button', + standalone: true, + imports: [CommonModule, ArtemisSharedCommonModule, NgbTooltipModule, FontAwesomeModule], + templateUrl: './request-feedback-button.component.html', +}) +export class RequestFeedbackButtonComponent implements OnInit { + faPenSquare = faPenSquare; + athenaEnabled = false; + isExamExercise: boolean; + participation?: StudentParticipation; + + isGeneratingFeedback = input(); + smallButtons = input(false); + exercise = input.required(); + generatingFeedback = output(); + + private feedbackSent = false; + private profileService = inject(ProfileService); + private alertService = inject(AlertService); + private courseExerciseService = inject(CourseExerciseService); + private translateService = inject(TranslateService); + private exerciseService = inject(ExerciseService); + private participationService = inject(ParticipationService); + + protected readonly ExerciseType = ExerciseType; + + ngOnInit() { + this.profileService.getProfileInfo().subscribe((profileInfo) => { + this.athenaEnabled = profileInfo.activeProfiles?.includes(PROFILE_ATHENA); + }); + this.isExamExercise = isExamExercise(this.exercise()); + if (this.isExamExercise || !this.exercise().id) { + return; + } + this.updateParticipation(); + } + + private updateParticipation() { + if (this.exercise().id) { + this.exerciseService.getExerciseDetails(this.exercise().id!).subscribe({ + next: (exerciseResponse: HttpResponse) => { + this.participation = this.participationService.getSpecificStudentParticipation(exerciseResponse.body!.exercise.studentParticipations ?? [], false); + }, + error: (error: HttpErrorResponse) => { + this.alertService.error(`artemisApp.${error.error.entityName}.errors.${error.error.errorKey}`); + }, + }); + } + } + + requestFeedback() { + if (!this.assureConditionsSatisfied()) { + return; + } + + this.courseExerciseService.requestFeedback(this.exercise().id!).subscribe({ + next: (participation: StudentParticipation) => { + if (participation) { + this.generatingFeedback.emit(); + this.feedbackSent = true; + this.alertService.success('artemisApp.exercise.feedbackRequestSent'); + } + }, + error: (error: HttpErrorResponse) => { + this.alertService.error(`artemisApp.exercise.${error.error.errorKey}`); + }, + }); + } + + /** + * Checks if the conditions for requesting automatic non-graded feedback are satisfied. + * The student can request automatic non-graded feedback under the following conditions: + * 1. They have a graded submission. + * 2. The deadline for the exercise has not been exceeded. + * 3. There is no already pending feedback request. + * @returns {boolean} `true` if all conditions are satisfied, otherwise `false`. + */ + assureConditionsSatisfied(): boolean { + if (this.exercise().type === ExerciseType.PROGRAMMING || !this.hasAthenaResultForLatestSubmission()) { + return true; + } + const submitFirstWarning = this.translateService.instant('artemisApp.exercise.submissionAlreadyHasAthenaResult'); + this.alertService.warning(submitFirstWarning); + return false; + } + + hasAthenaResultForLatestSubmission(): boolean { + if (this.participation?.submissions && this.participation?.results) { + // submissions.results is always undefined so this is neccessary + return ( + this.participation.submissions?.last()?.id === + this.participation.results?.filter((result) => result.assessmentType == AssessmentType.AUTOMATIC_ATHENA).first()?.submission?.id + ); + } + return false; + } +} diff --git a/src/main/webapp/app/shared/monaco-editor/model/actions/adapter/monaco-text-editor.adapter.ts b/src/main/webapp/app/shared/monaco-editor/model/actions/adapter/monaco-text-editor.adapter.ts index 7bd630b7d479..f55bbfe3e3f1 100644 --- a/src/main/webapp/app/shared/monaco-editor/model/actions/adapter/monaco-text-editor.adapter.ts +++ b/src/main/webapp/app/shared/monaco-editor/model/actions/adapter/monaco-text-editor.adapter.ts @@ -152,8 +152,8 @@ export class MonacoTextEditorAdapter implements TextEditor { return this.editor.getDomNode() ?? undefined; } - typeText(text: string) { - this.editor.trigger('MonacoTextEditorAdapter::typeText', 'type', { text }); + triggerCompletion(): void { + this.editor.trigger('MonacoTextEditorAdapter::triggerCompletion', 'editor.action.triggerSuggest', {}); } getTextAtRange(range: TextEditorRange): string { diff --git a/src/main/webapp/app/shared/monaco-editor/model/actions/adapter/text-editor.interface.ts b/src/main/webapp/app/shared/monaco-editor/model/actions/adapter/text-editor.interface.ts index b3403c274b64..b847645a2efd 100644 --- a/src/main/webapp/app/shared/monaco-editor/model/actions/adapter/text-editor.interface.ts +++ b/src/main/webapp/app/shared/monaco-editor/model/actions/adapter/text-editor.interface.ts @@ -42,10 +42,9 @@ export interface TextEditor { getDomNode(): HTMLElement | undefined; /** - * Types the given text into the editor as if the user had typed it, e.g. to trigger a completer registered in the editor. - * @param text The text to type into the editor. + * Triggers the completion in the editor, e.g. by showing a widget. */ - typeText(text: string): void; + triggerCompletion(): void; /** * Retrieves the text at the given range in the editor. diff --git a/src/main/webapp/app/shared/monaco-editor/model/actions/communication/channel-reference.action.ts b/src/main/webapp/app/shared/monaco-editor/model/actions/communication/channel-reference.action.ts index f3bc053b43da..5afefaf5275d 100644 --- a/src/main/webapp/app/shared/monaco-editor/model/actions/communication/channel-reference.action.ts +++ b/src/main/webapp/app/shared/monaco-editor/model/actions/communication/channel-reference.action.ts @@ -44,11 +44,12 @@ export class ChannelReferenceAction extends TextEditorAction { } /** - * Types the text '#' into the editor and focuses it. This will trigger the completion provider to show the available channels. + * Inserts the text '#' into the editor and focuses it. This method will trigger the completion provider to show the available channels. * @param editor The editor to type the text into. */ run(editor: TextEditor) { - this.typeText(editor, ChannelReferenceAction.DEFAULT_INSERT_TEXT); + this.replaceTextAtCurrentSelection(editor, ChannelReferenceAction.DEFAULT_INSERT_TEXT); + editor.triggerCompletion(); editor.focus(); } diff --git a/src/main/webapp/app/shared/monaco-editor/model/actions/communication/exercise-reference.action.ts b/src/main/webapp/app/shared/monaco-editor/model/actions/communication/exercise-reference.action.ts index 84fe668e955e..44b72e0f8724 100644 --- a/src/main/webapp/app/shared/monaco-editor/model/actions/communication/exercise-reference.action.ts +++ b/src/main/webapp/app/shared/monaco-editor/model/actions/communication/exercise-reference.action.ts @@ -52,11 +52,12 @@ export class ExerciseReferenceAction extends TextEditorDomainActionWithOptions { } /** - * Types the text '/exercise' into the editor and focuses it. This will trigger the completion provider to show the available exercises. - * @param editor The editor to type the text into. + * Inserts the text '/exercise' into the editor and focuses it. This method will trigger the completion provider to show the available exercises. + * @param editor The editor to insert the text into. */ run(editor: TextEditor): void { - this.typeText(editor, ExerciseReferenceAction.DEFAULT_INSERT_TEXT); + this.replaceTextAtCurrentSelection(editor, ExerciseReferenceAction.DEFAULT_INSERT_TEXT); + editor.triggerCompletion(); editor.focus(); } diff --git a/src/main/webapp/app/shared/monaco-editor/model/actions/communication/user-mention.action.ts b/src/main/webapp/app/shared/monaco-editor/model/actions/communication/user-mention.action.ts index e6fa9b208397..240805b4adda 100644 --- a/src/main/webapp/app/shared/monaco-editor/model/actions/communication/user-mention.action.ts +++ b/src/main/webapp/app/shared/monaco-editor/model/actions/communication/user-mention.action.ts @@ -45,11 +45,12 @@ export class UserMentionAction extends TextEditorAction { } /** - * Types the text '@' into the editor and focuses it. This will trigger the completion provider to show the available users. - * @param editor The editor to type the text into. + * Inserts the text '@' into the editor and focuses it. This method will trigger the completion provider to show the available users. + * @param editor The editor to insert the text into. */ run(editor: TextEditor) { - this.typeText(editor, UserMentionAction.DEFAULT_INSERT_TEXT); + this.replaceTextAtCurrentSelection(editor, UserMentionAction.DEFAULT_INSERT_TEXT); + editor.triggerCompletion(); editor.focus(); } diff --git a/src/main/webapp/app/shared/monaco-editor/model/actions/text-editor-action.model.ts b/src/main/webapp/app/shared/monaco-editor/model/actions/text-editor-action.model.ts index 96a8e6549b9c..62e037868ce0 100644 --- a/src/main/webapp/app/shared/monaco-editor/model/actions/text-editor-action.model.ts +++ b/src/main/webapp/app/shared/monaco-editor/model/actions/text-editor-action.model.ts @@ -148,15 +148,6 @@ export abstract class TextEditorAction implements Disposable { return text.startsWith(openDelimiter) && text.endsWith(closeDelimiter) && text.length >= openDelimiter.length + closeDelimiter.length; } - /** - * Types the given text in the editor at the current cursor position. You can use this e.g. to trigger a suggestion. - * @param editor The editor to type the text in. - * @param text The text to type. - */ - typeText(editor: TextEditor, text: string): void { - editor.typeText(text); - } - /** * Replaces the text at the current selection with the given text. If there is no selection, the text is inserted at the current cursor position. * @param editor The editor to replace the text in. diff --git a/src/main/webapp/app/shared/util/course.util.ts b/src/main/webapp/app/shared/util/course.util.ts index ae49c6318b8c..ce1d2f00c2af 100644 --- a/src/main/webapp/app/shared/util/course.util.ts +++ b/src/main/webapp/app/shared/util/course.util.ts @@ -4,8 +4,12 @@ import { Course } from 'app/entities/course.model'; * Sorts an array of Course objects alphabetically by their title. * * @param {Course[]} courses - The array of Course objects to be sorted. + * @param {boolean} isSortAscending - Sort direction, it is ascending by default. * @returns {Course[]} The sorted array of Course objects. */ -export function sortCourses(courses: Course[]): Course[] { - return courses.sort((courseA, courseB) => (courseA.title ?? '').localeCompare(courseB.title ?? '')); +export function sortCourses(courses: Course[], isSortAscending: boolean = true): Course[] { + return courses.sort((courseA, courseB) => { + const sortOrder = (courseA.title ?? '').localeCompare(courseB.title ?? ''); + return isSortAscending ? sortOrder : -sortOrder; + }); } diff --git a/src/main/webapp/i18n/de/exam.json b/src/main/webapp/i18n/de/exam.json index a528405b3451..ff6b4685e0f5 100644 --- a/src/main/webapp/i18n/de/exam.json +++ b/src/main/webapp/i18n/de/exam.json @@ -150,6 +150,7 @@ "falseName": "Der angegebene Name ist nicht korrekt. Bitte versuche es erneut!", "notSet": "Nicht gesetzt", "startExamToolTip": "Button wird 5 Minuten vor der Klausur aktiviert.", + "yourSolution": "Deine Lösung", "cleanup": { "title": "Aufräumen", "question": "Möchtest du die Prüfung {{title}} wirklich bereinigen? Dadurch werden alle Studierenden-Repositories in der Prüfung gelöscht. Diese Aktion kann NICHT rückgängig gemacht werden!" diff --git a/src/main/webapp/i18n/de/exercise.json b/src/main/webapp/i18n/de/exercise.json index fb698572ee34..3aee88e253c5 100644 --- a/src/main/webapp/i18n/de/exercise.json +++ b/src/main/webapp/i18n/de/exercise.json @@ -168,8 +168,8 @@ "resumeProgrammingExercise": "Die Aufgabe wurde wieder aufgenommen. Du kannst nun weiterarbeiten!", "feedbackRequestSent": "Deine Feedbackanfrage wurde gesendet.", "feedbackRequestAlreadySent": "Deine Feedbackanfrage wurde bereits gesendet.", - "notEnoughPoints": "Um eine Feedbackanfrage zu senden, brauchst du mindestens eine Abgabe.", - "lockRepositoryWarning": "Dein Repository wird gesperrt. Du kannst erst weiterarbeiten, wenn deine Feedbackanfrage beantwortet wird.", + "noSubmissionExists": "Um eine Feedbackanfrage zu senden, brauchst du mindestens eine Abgabe.", + "lockRepositoryWarning": "Dein Repository wird gesperrt. Du kannst erst weiterarbeiten wenn deine Feedbackanfrage beantwortet wird.", "feedbackRequestAfterDueDate": "Du kannst nach der Abgabefrist keine weiteren Anfragen einreichen.", "maxAthenaResultsReached": "Du hast die maximale Anzahl an KI-Feedbackanfragen erreicht.", "athenaFeedbackSuccessful": "AI-Feedback erfolgreich generiert. Klicke auf das Ergebnis, um Details zu sehen.", diff --git a/src/main/webapp/i18n/de/metis.json b/src/main/webapp/i18n/de/metis.json index 5100a48cd2e4..8a669a9c8c8c 100644 --- a/src/main/webapp/i18n/de/metis.json +++ b/src/main/webapp/i18n/de/metis.json @@ -90,7 +90,9 @@ "TECH_SUPPORT": "Technische Hilfe", "ORGANIZATION": "Organisation", "RANDOM": "Sonstiges", - "ANNOUNCEMENT": "Ankündigung" + "ANNOUNCEMENT": "Ankündigung", + "allPublicMessages": "Alle öffentlichen Nachrichten", + "searchResults": "Suchergebnisse für {{ search }}" }, "post": { "context": "Kontext", diff --git a/src/main/webapp/i18n/de/result.json b/src/main/webapp/i18n/de/result.json index c97ee41d3d33..933db7e69593 100644 --- a/src/main/webapp/i18n/de/result.json +++ b/src/main/webapp/i18n/de/result.json @@ -92,7 +92,8 @@ "preliminary": "vorläufig", "preliminaryTooltip": "Dein Ergebnis ist noch nicht endgültig, weil weitere Tests nach der Einreichungsfrist ausgeführt werden.", "preliminaryTooltipSemiAutomatic": "Dein Ergebnis ist noch nicht endgültig, weil weitere Tests nach der Einreichungsfrist ausgeführt werden oder eine manuelle Bewertung aussteht.", - "codeIssuesTooltip": "Die automatische Codeanalyse hat Code-Issues gefunden.", + "preliminaryTooltipAthena": "Dies ist eine KI-Bewertung. Das tatsächliche Ergebnis kann abweichen.", + "codeIssuesTooltip": "Die automatische Codeanalyse hat Codeissues gefunden.", "noResultDetails": "Keine weiteren Informationen verfügbar für dieses Ergebnis.", "onlyCompilationTested": "Dein Code kompiliert erfolgreich. Derzeit sind keine Testfälle sichtbar.", "chart": { diff --git a/src/main/webapp/i18n/de/student-dashboard.json b/src/main/webapp/i18n/de/student-dashboard.json index cb10f2199d96..5906220b4d8a 100644 --- a/src/main/webapp/i18n/de/student-dashboard.json +++ b/src/main/webapp/i18n/de/student-dashboard.json @@ -4,6 +4,7 @@ "title": "Deine aktuellen Kurse", "recentlyAccessed": "Zuletzt besuchte Kurse", "otherCourses": "Andere Kurse", + "sort": "Sortieren", "noCourses": "Du bist in keinem Kurs eingeschrieben. Schreibe dich manuell ein oder kontaktiere die Lehrenden.", "exerciseTitle": "Aktuelle aktive Übung von \"{{ course }}\"", "exerciseTitleWithoutDueDate": "Aktuelle Übung von \"{{ course }}\":", @@ -15,6 +16,7 @@ "points": "{{ totalAbsoluteScore }} / {{ totalReachableScore }} Punkte", "cardScore": "Punktzahl", "cardManageCourse": "Kurs Verwalten", + "noCoursesFound": "Keine Kurse gefunden", "enroll": { "title": "Kurseinschreibung", "enrollSuccessful": "Einschreibung erfolgreich", diff --git a/src/main/webapp/i18n/en/exam.json b/src/main/webapp/i18n/en/exam.json index 4ed4737897a0..713a9a92fd1a 100644 --- a/src/main/webapp/i18n/en/exam.json +++ b/src/main/webapp/i18n/en/exam.json @@ -150,6 +150,7 @@ "falseName": "Entered name is incorrect. Please try again!", "notSet": "Not set", "startExamToolTip": "Button will be activated 5 minutes before the exam start.", + "yourSolution": "Your Solution", "cleanup": { "title": "Cleanup", "question": "Are you sure you want to clean up the exam {{ title }}? This will delete all student programming exercise repositories in the exam. This action can NOT be undone!" diff --git a/src/main/webapp/i18n/en/exercise.json b/src/main/webapp/i18n/en/exercise.json index 6a06631fc343..f50e61efcbb8 100644 --- a/src/main/webapp/i18n/en/exercise.json +++ b/src/main/webapp/i18n/en/exercise.json @@ -168,7 +168,7 @@ "resumeProgrammingExercise": "The exercise has been resumed. You can now continue working on the exercise!", "feedbackRequestSent": "Your feedback request has been sent.", "feedbackRequestAlreadySent": "Your feedback request has already been sent.", - "notEnoughPoints": "You have to submit your work at least once.", + "noSubmissionExists": "You have to submit your work at least once.", "lockRepositoryWarning": "Your repository will be locked. You can only continue working after you receive an answer.", "feedbackRequestAfterDueDate": "You cannot submit feedback requests after the due date.", "maxAthenaResultsReached": "You have reached the maximum number of AI feedback requests.", diff --git a/src/main/webapp/i18n/en/metis.json b/src/main/webapp/i18n/en/metis.json index bd0cc952c805..9a3ff0977f2b 100644 --- a/src/main/webapp/i18n/en/metis.json +++ b/src/main/webapp/i18n/en/metis.json @@ -90,7 +90,9 @@ "TECH_SUPPORT": "Tech Support", "ORGANIZATION": "Organization", "RANDOM": "Random", - "ANNOUNCEMENT": "Announcement" + "ANNOUNCEMENT": "Announcement", + "allPublicMessages": "All Public Messages", + "searchResults": "Search Results for '{{ search }}'" }, "post": { "context": "Context", diff --git a/src/main/webapp/i18n/en/result.json b/src/main/webapp/i18n/en/result.json index 67bdc0711adb..f8178c0cde1e 100644 --- a/src/main/webapp/i18n/en/result.json +++ b/src/main/webapp/i18n/en/result.json @@ -92,6 +92,7 @@ "preliminary": "preliminary", "preliminaryTooltip": "Your result is not final yet, because more tests will be executed after the due date", "preliminaryTooltipSemiAutomatic": "Your result is not final yet, because more tests will be executed after the due date or a manual assessment will be done.", + "preliminaryTooltipAthena": "This is an AI grading. The actual result may differ", "codeIssuesTooltip": "The automatic code analysis generated some warnings for your code.", "noResultDetails": "No result details available.", "onlyCompilationTested": "Your code compiled successfully. There are currently no tests visible.", diff --git a/src/main/webapp/i18n/en/student-dashboard.json b/src/main/webapp/i18n/en/student-dashboard.json index 8660888fb761..b9eddb4ecfa3 100644 --- a/src/main/webapp/i18n/en/student-dashboard.json +++ b/src/main/webapp/i18n/en/student-dashboard.json @@ -5,6 +5,7 @@ "recentlyAccessed": "Recently accessed courses", "otherCourses": "Other courses", "noCourses": "You are not enrolled in any courses. Enroll manually or contact your course instructor.", + "sort": "Sort", "exerciseTitle": "Current active exercise in \"{{ course }}\"", "exerciseTitleWithoutDueDate": "Current exercise in \"{{ course }}\":", "examTitle": "Current exam in \"{{ course }}\":", @@ -15,6 +16,7 @@ "points": "{{ totalAbsoluteScore }} / {{ totalReachableScore }} Points", "cardScore": "Score", "cardManageCourse": "Manage Course", + "noCoursesFound": "No Courses Found", "enroll": { "title": "Course Enrollment", "enrollSuccessful": "Enrollment successful", diff --git a/src/test/java/de/tum/cit/aet/artemis/assessment/ExerciseScoresChartIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/assessment/ExerciseScoresChartIntegrationTest.java index fd48d4a5ace5..8782f11f8593 100644 --- a/src/test/java/de/tum/cit/aet/artemis/assessment/ExerciseScoresChartIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/assessment/ExerciseScoresChartIntegrationTest.java @@ -3,10 +3,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -import java.time.Instant; import java.time.ZonedDateTime; import java.util.List; -import java.util.Optional; import java.util.Set; import org.junit.jupiter.api.AfterEach; @@ -15,7 +13,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.util.ReflectionTestUtils; import de.tum.cit.aet.artemis.assessment.repository.ParticipantScoreRepository; import de.tum.cit.aet.artemis.assessment.service.ParticipantScoreScheduleService; @@ -60,11 +57,7 @@ class ExerciseScoresChartIntegrationTest extends AbstractSpringIntegrationIndepe @BeforeEach void setupTestScenario() { - // Prevents the ParticipantScoreScheduleService from scheduling tasks related to prior results - ReflectionTestUtils.setField(participantScoreScheduleService, "lastScheduledRun", Optional.of(Instant.now())); - ParticipantScoreScheduleService.DEFAULT_WAITING_TIME_FOR_SCHEDULED_TASKS = 50; - participantScoreScheduleService.activate(); ZonedDateTime pastTimestamp = ZonedDateTime.now().minusDays(5); userUtilService.addUsers(TEST_PREFIX, 3, 2, 0, 0); Course course = courseUtilService.createCourse(); diff --git a/src/test/java/de/tum/cit/aet/artemis/assessment/ParticipantScoreIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/assessment/ParticipantScoreIntegrationTest.java index 85f521f00be5..1313f5147150 100644 --- a/src/test/java/de/tum/cit/aet/artemis/assessment/ParticipantScoreIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/assessment/ParticipantScoreIntegrationTest.java @@ -3,10 +3,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -import java.time.Instant; import java.time.ZonedDateTime; import java.util.List; -import java.util.Optional; import java.util.Set; import org.junit.jupiter.api.AfterEach; @@ -15,7 +13,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.util.ReflectionTestUtils; import de.tum.cit.aet.artemis.assessment.domain.GradingScale; import de.tum.cit.aet.artemis.assessment.dto.score.ScoreDTO; @@ -98,11 +95,7 @@ class ParticipantScoreIntegrationTest extends AbstractSpringIntegrationLocalCILo @BeforeEach void setupTestScenario() { - // Prevents the ParticipantScoreScheduleService from scheduling tasks related to prior results - ReflectionTestUtils.setField(participantScoreScheduleService, "lastScheduledRun", Optional.of(Instant.now())); - ParticipantScoreScheduleService.DEFAULT_WAITING_TIME_FOR_SCHEDULED_TASKS = 200; - participantScoreScheduleService.activate(); ZonedDateTime pastTimestamp = ZonedDateTime.now().minusDays(5); // creating the users student1, tutor1 and instructors1 userUtilService.addUsers(TEST_PREFIX, 1, 1, 0, 1); diff --git a/src/test/java/de/tum/cit/aet/artemis/assessment/ResultListenerIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/assessment/ResultListenerIntegrationTest.java index 4877e18e8c99..dffe1451edf7 100644 --- a/src/test/java/de/tum/cit/aet/artemis/assessment/ResultListenerIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/assessment/ResultListenerIntegrationTest.java @@ -4,10 +4,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -import java.time.Instant; import java.time.ZonedDateTime; import java.util.List; -import java.util.Optional; import java.util.Set; import org.junit.jupiter.api.AfterEach; @@ -17,7 +15,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.util.ReflectionTestUtils; import de.tum.cit.aet.artemis.assessment.domain.ParticipantScore; import de.tum.cit.aet.artemis.assessment.domain.Result; @@ -84,11 +81,7 @@ void cleanup() { @BeforeEach void setupTestScenario() { - // Prevents the ParticipantScoreScheduleService from scheduling tasks related to prior results - ReflectionTestUtils.setField(participantScoreScheduleService, "lastScheduledRun", Optional.of(Instant.now())); - ParticipantScoreScheduleService.DEFAULT_WAITING_TIME_FOR_SCHEDULED_TASKS = 100; - participantScoreScheduleService.activate(); ZonedDateTime pastReleaseDate = ZonedDateTime.now().minusDays(5); ZonedDateTime pastDueDate = ZonedDateTime.now().minusDays(3); ZonedDateTime pastAssessmentDueDate = ZonedDateTime.now().minusDays(2); diff --git a/src/test/java/de/tum/cit/aet/artemis/assessment/service/CourseScoreCalculationServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/assessment/service/CourseScoreCalculationServiceTest.java index 89d594d99982..f6c323c8e798 100644 --- a/src/test/java/de/tum/cit/aet/artemis/assessment/service/CourseScoreCalculationServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/assessment/service/CourseScoreCalculationServiceTest.java @@ -35,7 +35,6 @@ import de.tum.cit.aet.artemis.exercise.domain.IncludedInOverallScore; import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.exercise.test_repository.StudentParticipationTestRepository; import de.tum.cit.aet.artemis.plagiarism.domain.PlagiarismVerdict; import de.tum.cit.aet.artemis.quiz.domain.QuizExercise; @@ -45,9 +44,6 @@ class CourseScoreCalculationServiceTest extends AbstractSpringIntegrationIndepen private static final String TEST_PREFIX = "cscservicetest"; - @Autowired - private ExerciseRepository exerciseRepository; - @Autowired private StudentParticipationTestRepository studentParticipationRepository; diff --git a/src/test/java/de/tum/cit/aet/artemis/assessment/util/StudentScoreUtilService.java b/src/test/java/de/tum/cit/aet/artemis/assessment/util/StudentScoreUtilService.java index 409c18fdc45d..a2e9aeee3da1 100644 --- a/src/test/java/de/tum/cit/aet/artemis/assessment/util/StudentScoreUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/assessment/util/StudentScoreUtilService.java @@ -34,6 +34,23 @@ public void createStudentScore(Exercise exercise, User user, double score) { studentScoreRepository.save(studentScore); } + /** + * Creates rated and normal score (which are set equal) for given exercise and user. + * + * @param exercise the exercise to link the student score to + * @param user the user that is linked to the score + * @param score the score and rated score that the specified user has reached for the given exercise + */ + public void createRatedStudentScore(Exercise exercise, User user, double score) { + final var studentScore = new StudentScore(); + studentScore.setExercise(exercise); + studentScore.setUser(user); + studentScore.setLastScore(score); + studentScore.setLastPoints(exercise.getMaxPoints() * score / 100); + studentScore.setLastRatedScore(score); + studentScoreRepository.save(studentScore); + } + /** * Creates student score for given exercise and user. * diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CourseCompetencyIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CourseCompetencyIntegrationTest.java index 748a1f4d5b0f..df2c561bda7e 100644 --- a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CourseCompetencyIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CourseCompetencyIntegrationTest.java @@ -50,8 +50,6 @@ class CourseCompetencyIntegrationTest extends AbstractCompetencyPrerequisiteInte @BeforeEach void setupTestScenario() { super.setupTestScenario(TEST_PREFIX, course -> competencyUtilService.createCompetency(course, "penguin")); - - participantScoreScheduleService.activate(); } private Result createExerciseParticipationSubmissionAndResult(Exercise exercise, StudentParticipation studentParticipation, double pointsOfExercise, diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/util/CompetencyUtilService.java b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/util/CompetencyUtilService.java index 411228bd332a..bca2072b19d0 100644 --- a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/util/CompetencyUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/util/CompetencyUtilService.java @@ -18,7 +18,7 @@ import de.tum.cit.aet.artemis.core.domain.Course; import de.tum.cit.aet.artemis.core.domain.User; import de.tum.cit.aet.artemis.exercise.domain.Exercise; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; +import de.tum.cit.aet.artemis.exercise.repository.ExerciseTestRepository; import de.tum.cit.aet.artemis.lecture.domain.LectureUnit; import de.tum.cit.aet.artemis.lecture.repository.LectureUnitRepository; @@ -35,7 +35,7 @@ public class CompetencyUtilService { private LectureUnitRepository lectureUnitRepository; @Autowired - private ExerciseRepository exerciseRepository; + private ExerciseTestRepository exerciseRepository; @Autowired private CompetencyRelationRepository competencyRelationRepository; diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/service/LearningPathServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/service/LearningPathServiceTest.java index 6717bbd6b508..65143ebdf769 100644 --- a/src/test/java/de/tum/cit/aet/artemis/atlas/service/LearningPathServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/service/LearningPathServiceTest.java @@ -41,7 +41,6 @@ import de.tum.cit.aet.artemis.core.util.CourseUtilService; import de.tum.cit.aet.artemis.exercise.domain.DifficultyLevel; import de.tum.cit.aet.artemis.exercise.domain.Exercise; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.lecture.domain.Lecture; import de.tum.cit.aet.artemis.lecture.domain.LectureUnit; import de.tum.cit.aet.artemis.lecture.repository.LectureUnitRepository; @@ -80,9 +79,6 @@ class LearningPathServiceTest extends AbstractSpringIntegrationIndependentTest { @Autowired private CompetencyProgressUtilService competencyProgressUtilService; - @Autowired - private ExerciseRepository exerciseRepository; - @Autowired private StudentScoreUtilService studentScoreUtilService; diff --git a/src/test/java/de/tum/cit/aet/artemis/communication/notification/GroupNotificationServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/communication/notification/GroupNotificationServiceTest.java index 88dbd05d6ccb..4ccf0116d463 100644 --- a/src/test/java/de/tum/cit/aet/artemis/communication/notification/GroupNotificationServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/communication/notification/GroupNotificationServiceTest.java @@ -61,7 +61,6 @@ import de.tum.cit.aet.artemis.exam.repository.ExamRepository; import de.tum.cit.aet.artemis.exam.util.ExamUtilService; import de.tum.cit.aet.artemis.exercise.domain.Exercise; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.lecture.domain.Attachment; import de.tum.cit.aet.artemis.lecture.domain.Lecture; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; @@ -82,9 +81,6 @@ class GroupNotificationServiceTest extends AbstractSpringIntegrationIndependentT @Autowired private NotificationSettingRepository notificationSettingRepository; - @Autowired - private ExerciseRepository exerciseRepository; - @Autowired private ExamRepository examRepository; diff --git a/src/test/java/de/tum/cit/aet/artemis/communication/notification/NotificationScheduleServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/communication/notification/NotificationScheduleServiceTest.java index f3dc650315c9..c49163742ba4 100644 --- a/src/test/java/de/tum/cit/aet/artemis/communication/notification/NotificationScheduleServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/communication/notification/NotificationScheduleServiceTest.java @@ -6,7 +6,6 @@ import static org.awaitility.Awaitility.await; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anySet; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; @@ -32,7 +31,6 @@ import de.tum.cit.aet.artemis.core.util.CourseUtilService; import de.tum.cit.aet.artemis.exercise.domain.Exercise; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationLocalCILocalVCTest; import de.tum.cit.aet.artemis.text.domain.TextSubmission; import de.tum.cit.aet.artemis.text.util.TextExerciseFactory; @@ -44,9 +42,6 @@ class NotificationScheduleServiceTest extends AbstractSpringIntegrationLocalCILo @Autowired private InstanceMessageReceiveService instanceMessageReceiveService; - @Autowired - private ExerciseRepository exerciseRepository; - @Autowired private NotificationRepository notificationRepository; @@ -85,7 +80,6 @@ void init() { exercise.setMaxPoints(5.0); exerciseRepository.saveAndFlush(exercise); - doNothing().when(javaMailSender).send(any(MimeMessage.class)); sizeBefore = notificationRepository.count(); } diff --git a/src/test/java/de/tum/cit/aet/artemis/communication/notification/SingleUserNotificationServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/communication/notification/SingleUserNotificationServiceTest.java index 9faaf7d03bc3..347438c89a97 100644 --- a/src/test/java/de/tum/cit/aet/artemis/communication/notification/SingleUserNotificationServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/communication/notification/SingleUserNotificationServiceTest.java @@ -22,12 +22,14 @@ import static de.tum.cit.aet.artemis.communication.domain.notification.NotificationConstants.MESSAGE_REPLY_IN_CONVERSATION_TITLE; import static de.tum.cit.aet.artemis.communication.domain.notification.NotificationConstants.NEW_PLAGIARISM_CASE_STUDENT_TITLE; import static de.tum.cit.aet.artemis.communication.domain.notification.NotificationConstants.PLAGIARISM_CASE_VERDICT_STUDENT_TITLE; +import static de.tum.cit.aet.artemis.communication.domain.notification.NotificationConstants.TUTORIAL_GROUP_ASSIGNED_TEXT; import static de.tum.cit.aet.artemis.communication.domain.notification.NotificationConstants.TUTORIAL_GROUP_ASSIGNED_TITLE; import static de.tum.cit.aet.artemis.communication.domain.notification.NotificationConstants.TUTORIAL_GROUP_DEREGISTRATION_STUDENT_TITLE; import static de.tum.cit.aet.artemis.communication.domain.notification.NotificationConstants.TUTORIAL_GROUP_DEREGISTRATION_TUTOR_TITLE; import static de.tum.cit.aet.artemis.communication.domain.notification.NotificationConstants.TUTORIAL_GROUP_REGISTRATION_MULTIPLE_TUTOR_TITLE; import static de.tum.cit.aet.artemis.communication.domain.notification.NotificationConstants.TUTORIAL_GROUP_REGISTRATION_STUDENT_TITLE; import static de.tum.cit.aet.artemis.communication.domain.notification.NotificationConstants.TUTORIAL_GROUP_REGISTRATION_TUTOR_TITLE; +import static de.tum.cit.aet.artemis.communication.domain.notification.NotificationConstants.TUTORIAL_GROUP_UNASSIGNED_TEXT; import static de.tum.cit.aet.artemis.communication.domain.notification.NotificationConstants.TUTORIAL_GROUP_UNASSIGNED_TITLE; import static de.tum.cit.aet.artemis.communication.service.notifications.NotificationSettingsService.NOTIFICATION_USER_NOTIFICATION_DATA_EXPORT_CREATED; import static de.tum.cit.aet.artemis.communication.service.notifications.NotificationSettingsService.NOTIFICATION_USER_NOTIFICATION_DATA_EXPORT_FAILED; @@ -40,7 +42,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anySet; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; @@ -63,6 +64,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.springframework.beans.factory.annotation.Autowired; import de.tum.cit.aet.artemis.assessment.domain.AssessmentType; @@ -90,7 +92,6 @@ import de.tum.cit.aet.artemis.core.util.CourseUtilService; import de.tum.cit.aet.artemis.exercise.domain.Exercise; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.fileupload.domain.FileUploadExercise; import de.tum.cit.aet.artemis.fileupload.util.FileUploadExerciseUtilService; import de.tum.cit.aet.artemis.lecture.domain.Lecture; @@ -118,9 +119,6 @@ class SingleUserNotificationServiceTest extends AbstractSpringIntegrationIndepen @Autowired private NotificationSettingRepository notificationSettingRepository; - @Autowired - private ExerciseRepository exerciseRepository; - @Autowired private ResultTestRepository resultRepository; @@ -136,6 +134,12 @@ class SingleUserNotificationServiceTest extends AbstractSpringIntegrationIndepen @Autowired private ParticipationUtilService participationUtilService; + @Captor + private ArgumentCaptor appleNotificationCaptor; + + @Captor + private ArgumentCaptor firebaseNotificationCaptor; + private User user; private User userTwo; @@ -267,8 +271,6 @@ void setUp() { dataExport = new DataExport(); dataExport.setUser(user); - - doNothing().when(javaMailSender).send(any(MimeMessage.class)); } /** @@ -277,8 +279,10 @@ void setUp() { * @param expectedNotificationTitle is the title (NotificationTitleTypeConstants) of the expected notification */ private void verifyRepositoryCallWithCorrectNotification(String expectedNotificationTitle) { - Notification capturedNotification = notificationRepository.findAll().getFirst(); - assertThat(capturedNotification.getTitle()).as("Title of the captured notification should be equal to the expected one").isEqualTo(expectedNotificationTitle); + List capturedNotifications = notificationRepository.findAll(); + assertThat(capturedNotifications).isNotEmpty(); + List relevantNotifications = capturedNotifications.stream().filter(e -> e.getTitle().equals(expectedNotificationTitle)).toList(); + assertThat(relevantNotifications).as("Title of the captured notification should be equal to the expected one").hasSize(1); } /// General notify Tests @@ -535,24 +539,24 @@ void testTutorialGroupNotifications_tutorDeregistration() { @Test void testTutorialGroupNotifications_groupAssigned() { notificationSettingRepository.deleteAll(); - notificationSettingRepository - .save(new NotificationSetting(tutorialGroup.getTeachingAssistant(), true, true, true, NOTIFICATION__TUTOR_NOTIFICATION__TUTORIAL_GROUP_ASSIGN_UNASSIGN)); + User teachingAssistant = tutorialGroup.getTeachingAssistant(); + notificationSettingRepository.save(new NotificationSetting(teachingAssistant, true, true, true, NOTIFICATION__TUTOR_NOTIFICATION__TUTORIAL_GROUP_ASSIGN_UNASSIGN)); singleUserNotificationService.notifyTutorAboutAssignmentToTutorialGroup(tutorialGroup, tutorialGroup.getTeachingAssistant(), userThree); verifyRepositoryCallWithCorrectNotification(TUTORIAL_GROUP_ASSIGNED_TITLE); verifyEmail(); - verifyPush(1); + verifyPush(1, TUTORIAL_GROUP_ASSIGNED_TEXT, teachingAssistant); } @Test void testTutorialGroupNotifications_groupUnassigned() { notificationSettingRepository.deleteAll(); - notificationSettingRepository - .save(new NotificationSetting(tutorialGroup.getTeachingAssistant(), true, true, true, NOTIFICATION__TUTOR_NOTIFICATION__TUTORIAL_GROUP_ASSIGN_UNASSIGN)); + User teachingAssistant = tutorialGroup.getTeachingAssistant(); + notificationSettingRepository.save(new NotificationSetting(teachingAssistant, true, true, true, NOTIFICATION__TUTOR_NOTIFICATION__TUTORIAL_GROUP_ASSIGN_UNASSIGN)); singleUserNotificationService.notifyTutorAboutUnassignmentFromTutorialGroup(tutorialGroup, tutorialGroup.getTeachingAssistant(), userThree); verifyRepositoryCallWithCorrectNotification(TUTORIAL_GROUP_UNASSIGNED_TITLE); verifyEmail(); - verifyPush(1); + verifyPush(1, TUTORIAL_GROUP_UNASSIGNED_TEXT, teachingAssistant); } @Test @@ -583,9 +587,20 @@ private void verifyEmail() { * * @param times how often the email should have been sent */ - private void verifyPush(int times) { - verify(applePushNotificationService, timeout(1500).times(times)).sendNotification(any(Notification.class), anySet(), any(Object.class)); - verify(firebasePushNotificationService, timeout(1500).times(times)).sendNotification(any(Notification.class), anySet(), any(Object.class)); + private void verifyPush(int times, String text, User recipient) { + verify(applePushNotificationService, timeout(1500).atLeast(times)).sendNotification(appleNotificationCaptor.capture(), anySet(), any(Object.class)); + verify(firebasePushNotificationService, timeout(1500).atLeast(times)).sendNotification(firebaseNotificationCaptor.capture(), anySet(), any(Object.class)); + + List appleNotifications = filterRelevantNotifications(appleNotificationCaptor.getAllValues(), text, recipient); + assertThat(appleNotifications).as(times + " Apple notifications should have been sent").hasSize(times); + + List firebaseNotifications = filterRelevantNotifications(firebaseNotificationCaptor.getAllValues(), text, recipient); + assertThat(firebaseNotifications).as(times + " Firebase notifications should have been sent").hasSize(times); + } + + private List filterRelevantNotifications(List notifications, String title, User recipient) { + return notifications.stream().filter(notification -> notification instanceof SingleUserNotification).map(notification -> (SingleUserNotification) notification) + .filter(notification -> title.equals(notification.getText()) && recipient.getId().equals(notification.getRecipient().getId())).toList(); } private static Stream getNotificationTypesAndTitlesParametersForGroupChat() { diff --git a/src/test/java/de/tum/cit/aet/artemis/communication/notifications/service/TutorialGroupNotificationServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/communication/notifications/service/TutorialGroupNotificationServiceTest.java index 23ad94f5a00f..26816a87c239 100644 --- a/src/test/java/de/tum/cit/aet/artemis/communication/notifications/service/TutorialGroupNotificationServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/communication/notifications/service/TutorialGroupNotificationServiceTest.java @@ -4,8 +4,6 @@ import static de.tum.cit.aet.artemis.communication.domain.notification.NotificationConstants.TUTORIAL_GROUP_UPDATED_TITLE; import static de.tum.cit.aet.artemis.communication.service.notifications.NotificationSettingsService.NOTIFICATION__TUTORIAL_GROUP_NOTIFICATION__TUTORIAL_GROUP_DELETE_UPDATE; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; @@ -15,8 +13,6 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -import jakarta.mail.internet.MimeMessage; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -86,7 +82,6 @@ void setUp() { userRepository.findOneByLogin(TEST_PREFIX + "tutor1").orElseThrow(), IntStream.range(1, STUDENT_COUNT + 1) .mapToObj((studentId) -> userRepository.findOneByLogin(TEST_PREFIX + "student" + studentId).orElseThrow()).collect(Collectors.toSet())); - doNothing().when(javaMailSender).send(any(MimeMessage.class)); tutorialGroupNotificationRepository.deleteAll(); notificationSettingRepository.deleteAll(); } diff --git a/src/test/java/de/tum/cit/aet/artemis/communication/service/EmailSummaryServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/communication/service/EmailSummaryServiceTest.java index 3e83e09a225c..a54be369017e 100644 --- a/src/test/java/de/tum/cit/aet/artemis/communication/service/EmailSummaryServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/communication/service/EmailSummaryServiceTest.java @@ -3,7 +3,6 @@ import static de.tum.cit.aet.artemis.communication.service.notifications.NotificationSettingsService.NOTIFICATION__WEEKLY_SUMMARY__BASIC_WEEKLY_SUMMARY; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; @@ -29,7 +28,6 @@ import de.tum.cit.aet.artemis.core.util.CourseUtilService; import de.tum.cit.aet.artemis.exercise.domain.DifficultyLevel; import de.tum.cit.aet.artemis.exercise.domain.Exercise; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest; import de.tum.cit.aet.artemis.text.util.TextExerciseFactory; @@ -40,9 +38,6 @@ class EmailSummaryServiceTest extends AbstractSpringIntegrationIndependentTest { @Autowired private EmailSummaryService weeklyEmailSummaryService; - @Autowired - private ExerciseRepository exerciseRepository; - @Autowired private NotificationSettingRepository notificationSettingRepository; @@ -122,8 +117,6 @@ void setUp() { exerciseRepository.saveAll(allTestExercises); weeklyEmailSummaryService.setScheduleInterval(Duration.ofDays(7)); - - doNothing().when(javaMailSender).send(any(MimeMessage.class)); } /** diff --git a/src/test/java/de/tum/cit/aet/artemis/communication/service/PushNotificationDeviceConfigurationCleanupServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/communication/service/PushNotificationDeviceConfigurationCleanupServiceTest.java index dc59407ff76b..c8b142c4dc59 100644 --- a/src/test/java/de/tum/cit/aet/artemis/communication/service/PushNotificationDeviceConfigurationCleanupServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/communication/service/PushNotificationDeviceConfigurationCleanupServiceTest.java @@ -1,10 +1,9 @@ package de.tum.cit.aet.artemis.communication.service; -import static org.springframework.test.util.AssertionErrors.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; import java.time.Instant; import java.time.temporal.ChronoUnit; -import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Set; @@ -54,6 +53,7 @@ void cleanupTest() { List result = deviceConfigurationRepository.findByUserIn(Set.of(user), PushNotificationDeviceType.FIREBASE); - assertEquals("The result is not correct", Collections.singletonList(valid), result); + assertThat(result).contains(valid); + assertThat(result).doesNotContain(expired); } } diff --git a/src/test/java/de/tum/cit/aet/artemis/communication/util/ConversationUtilService.java b/src/test/java/de/tum/cit/aet/artemis/communication/util/ConversationUtilService.java index 46a2adbe005e..1d581ffc7c66 100644 --- a/src/test/java/de/tum/cit/aet/artemis/communication/util/ConversationUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/communication/util/ConversationUtilService.java @@ -37,7 +37,7 @@ import de.tum.cit.aet.artemis.core.user.util.UserUtilService; import de.tum.cit.aet.artemis.core.util.CourseFactory; import de.tum.cit.aet.artemis.core.util.CourseUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; +import de.tum.cit.aet.artemis.exercise.repository.ExerciseTestRepository; import de.tum.cit.aet.artemis.exercise.util.ExerciseUtilService; import de.tum.cit.aet.artemis.lecture.domain.Lecture; import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; @@ -64,7 +64,7 @@ public class ConversationUtilService { private CourseTestRepository courseRepo; @Autowired - private ExerciseRepository exerciseRepo; + private ExerciseTestRepository exerciseRepo; @Autowired private LectureRepository lectureRepo; diff --git a/src/test/java/de/tum/cit/aet/artemis/connectors/Lti13ServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/connectors/Lti13ServiceTest.java index b1c21105fbb9..5fc135207c5f 100644 --- a/src/test/java/de/tum/cit/aet/artemis/connectors/Lti13ServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/connectors/Lti13ServiceTest.java @@ -55,7 +55,7 @@ import de.tum.cit.aet.artemis.core.test_repository.UserTestRepository; import de.tum.cit.aet.artemis.exercise.domain.Exercise; import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; +import de.tum.cit.aet.artemis.exercise.repository.ExerciseTestRepository; import de.tum.cit.aet.artemis.lti.config.Lti13TokenRetriever; import de.tum.cit.aet.artemis.lti.domain.LtiPlatformConfiguration; import de.tum.cit.aet.artemis.lti.domain.LtiResourceLaunch; @@ -78,7 +78,7 @@ class Lti13ServiceTest { private UserTestRepository userRepository; @Mock - private ExerciseRepository exerciseRepository; + private ExerciseTestRepository exerciseRepository; @Mock private CourseTestRepository courseRepository; diff --git a/src/test/java/de/tum/cit/aet/artemis/core/MetricsIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/core/MetricsIntegrationTest.java index aa126be537b7..4aefa856aef2 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/MetricsIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/MetricsIntegrationTest.java @@ -1,36 +1,44 @@ package de.tum.cit.aet.artemis.core; +import static de.tum.cit.aet.artemis.core.config.Constants.MIN_SCORE_GREEN; import static de.tum.cit.aet.artemis.core.util.TimeUtil.toRelativeTime; import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; -import java.time.Instant; import java.util.Comparator; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.util.ReflectionTestUtils; -import de.tum.cit.aet.artemis.assessment.domain.Result; +import de.tum.cit.aet.artemis.assessment.domain.ParticipantScore; +import de.tum.cit.aet.artemis.assessment.repository.StudentScoreRepository; import de.tum.cit.aet.artemis.assessment.service.ParticipantScoreScheduleService; +import de.tum.cit.aet.artemis.assessment.util.StudentScoreUtilService; +import de.tum.cit.aet.artemis.atlas.competency.util.CompetencyUtilService; +import de.tum.cit.aet.artemis.atlas.dto.metrics.CompetencyInformationDTO; +import de.tum.cit.aet.artemis.atlas.dto.metrics.LectureUnitInformationDTO; import de.tum.cit.aet.artemis.atlas.dto.metrics.ResourceTimestampDTO; import de.tum.cit.aet.artemis.atlas.dto.metrics.StudentMetricsDTO; +import de.tum.cit.aet.artemis.atlas.repository.CompetencyRepository; import de.tum.cit.aet.artemis.core.domain.Course; import de.tum.cit.aet.artemis.exercise.domain.Exercise; import de.tum.cit.aet.artemis.exercise.domain.Submission; import de.tum.cit.aet.artemis.exercise.dto.ExerciseInformationDTO; import de.tum.cit.aet.artemis.exercise.repository.ExerciseMetricsRepository; +import de.tum.cit.aet.artemis.exercise.repository.ExerciseTestRepository; +import de.tum.cit.aet.artemis.lecture.repository.LectureUnitRepository; +import de.tum.cit.aet.artemis.lecture.service.LectureUnitService; +import de.tum.cit.aet.artemis.lecture.util.LectureUtilService; import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest; class MetricsIntegrationTest extends AbstractSpringIntegrationIndependentTest { @@ -40,18 +48,41 @@ class MetricsIntegrationTest extends AbstractSpringIntegrationIndependentTest { @Autowired private ExerciseMetricsRepository exerciseMetricsRepository; + @Autowired + private CompetencyRepository competencyRepository; + + @Autowired + private ExerciseTestRepository exerciseTestRepository; + + @Autowired + private LectureUnitRepository lectureUnitRepository; + + @Autowired + private StudentScoreRepository studentScoreRepository; + + @Autowired + protected StudentScoreUtilService studentScoreUtilService; + + @Autowired + protected LectureUtilService lectureUtilService; + + @Autowired + protected CompetencyUtilService competencyUtilService; + + @Autowired + protected LectureUnitService lectureUnitService; + private Course course; private Course courseWithTestRuns; + private long userID; + private static final String STUDENT_OF_COURSE = TEST_PREFIX + "student1"; @BeforeEach - void setupTestScenario() { - // Prevents the ParticipantScoreScheduleService from scheduling tasks related to prior results - ReflectionTestUtils.setField(participantScoreScheduleService, "lastScheduledRun", Optional.of(Instant.now())); + void setupTestScenario() throws Exception { ParticipantScoreScheduleService.DEFAULT_WAITING_TIME_FOR_SCHEDULED_TASKS = 100; - participantScoreScheduleService.activate(); userUtilService.addUsers(TEST_PREFIX, 3, 1, 1, 1); @@ -59,6 +90,7 @@ void setupTestScenario() { courseWithTestRuns = courseUtilService.createCourseWithAllExerciseTypesAndParticipationsAndSubmissionsAndResultsAndTestRunsAndTwoUsers(TEST_PREFIX, true); userUtilService.createAndSaveUser(TEST_PREFIX + "user1337"); + userID = userUtilService.getUserByLogin(TEST_PREFIX + "student1").getId(); } @AfterEach @@ -94,25 +126,56 @@ void shouldReturnExerciseInformation() throws Exception { assertThat(exerciseInformation).allSatisfy((id, dto) -> assertThat(id).isEqualTo(dto.id())); } - @Disabled // TODO: reduce jacoco missing by one after enabled @Test @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") - void shouldReturnAverageScores() throws Exception { - // Wait for the scheduler to execute its task - participantScoreScheduleService.executeScheduledTasks(); - await().until(() -> participantScoreScheduleService.isIdle()); + void shouldReturnCategories() throws Exception { + final var result = request.get("/api/metrics/course/" + course.getId() + "/student", HttpStatus.OK, StudentMetricsDTO.class); + assertThat(result).isNotNull(); + assertThat(result.exerciseMetrics()).isNotNull(); + final var categories = result.exerciseMetrics().categories(); + + final var expectedCategories = exerciseTestRepository.findAllWithCategoriesByCourseId(course.getId()).stream() + .collect(Collectors.toMap(Exercise::getId, Exercise::getCategories)); + + assertThat(categories).isEqualTo(expectedCategories); + } + @Test + @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + void shouldReturnAverageScores() throws Exception { + final var exercises = exerciseTestRepository.findAllExercisesByCourseIdWithEagerParticipation(course.getId()); + exercises.forEach(exercise -> studentScoreUtilService.createRatedStudentScore(exercise, userUtilService.getUserByLogin(STUDENT_OF_COURSE), 5)); final var result = request.get("/api/metrics/course/" + course.getId() + "/student", HttpStatus.OK, StudentMetricsDTO.class); assertThat(result).isNotNull(); assertThat(result.exerciseMetrics()).isNotNull(); final var averageScores = result.exerciseMetrics().averageScore(); + final var expectedAverageScores = exercises.stream().collect(Collectors.toMap(Exercise::getId, + exercise -> exercise.getStudentParticipations().stream().flatMap(participation -> participation.getStudents().stream()).mapToDouble( + student -> studentScoreRepository.findByExercise_IdAndUser_Id(exercise.getId(), student.getId()).map(ParticipantScore::getLastRatedScore).orElse(0.0)) + .average().orElse(0.0))); + + assertThat(averageScores).isEqualTo(expectedAverageScores); + } + + @Test + @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + void shouldReturnScore() throws Exception { final var exercises = exerciseRepository.findAllExercisesByCourseId(course.getId()); - final var expectedMap = exercises.stream().map(Exercise::getId).collect( - Collectors.toMap(Function.identity(), id -> resultRepository.findAllByParticipationExerciseId(id).stream().mapToDouble(Result::getScore).average().orElse(0))); + exercises.forEach(exercise -> studentScoreUtilService.createRatedStudentScore(exercise, userUtilService.getUserByLogin(STUDENT_OF_COURSE), 0.5)); - assertThat(averageScores).isEqualTo(expectedMap); + final var result = request.get("/api/metrics/course/" + course.getId() + "/student", HttpStatus.OK, StudentMetricsDTO.class); + assertThat(result).isNotNull(); + assertThat(result.exerciseMetrics()).isNotNull(); + final var score = result.exerciseMetrics().score(); + + var expectedScores = exercises.stream() + .map(exercise -> studentScoreRepository.findByExercise_IdAndUser_Id(exercise.getId(), userID) + .map(studentScore -> Map.entry(exercise.getId(), studentScore.getLastRatedScore()))) + .filter(Optional::isPresent).map(Optional::get).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + assertThat(score).isEqualTo(expectedScores); } @Test @@ -196,5 +259,72 @@ void shouldFindLatestSubmissionDatesByUser() throws Exception { Set result = exerciseMetricsRepository.findLatestSubmissionDatesForUser(exerciseIds, userID); assertThat(result).isEqualTo(expectedSet); } + + @Test + @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + void shouldReturnCompleted() throws Exception { + final var exercises = exerciseRepository.findAllExercisesByCourseId(course.getId()); + exercises.forEach(exercise -> studentScoreUtilService.createRatedStudentScore(exercise, userUtilService.getUserByLogin(STUDENT_OF_COURSE), MIN_SCORE_GREEN)); + + final var result = request.get("/api/metrics/course/" + course.getId() + "/student", HttpStatus.OK, StudentMetricsDTO.class); + assertThat(result).isNotNull(); + assertThat(result.exerciseMetrics()).isNotNull(); + final var completed = result.exerciseMetrics().completed(); + + final var expectedCompleted = exercises.stream().map(Exercise::getId).filter( + id -> studentScoreRepository.findByExercise_IdAndUser_Id(id, userID).map(studentScore -> studentScore.getLastRatedScore() >= MIN_SCORE_GREEN).orElse(false)) + .collect(Collectors.toSet()); + + assertThat(completed).isEqualTo(expectedCompleted); + } + } + + @Nested + class CompetencyMetrics { + + @Test + @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + void shouldReturnCompetencyInformation() throws Exception { + course.setCompetencies(Set.of(competencyUtilService.createCompetency(course))); + + final var result = request.get("/api/metrics/course/" + course.getId() + "/student", HttpStatus.OK, StudentMetricsDTO.class); + assertThat(result).isNotNull(); + assertThat(result.competencyMetrics()).isNotNull(); + + final var competencyInformation = result.competencyMetrics().competencyInformation(); + + final var competencies = competencyRepository.findAllForCourseWithExercisesAndLectureUnitsAndLecturesAndAttachments(course.getId()); + final var expectedDTOs = competencies.stream().map(CompetencyInformationDTO::of).collect(Collectors.toSet()); + + assertThat(competencyInformation.values()).containsExactlyInAnyOrderElementsOf(expectedDTOs); + assertThat(competencyInformation).allSatisfy((id, dto) -> assertThat(id).isEqualTo(dto.id())); + } + } + + @Nested + class LectureMetrics { + + @Test + @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + void shouldReturnLectureUnitInformation() throws Exception { + + final var lectureUnit = lectureUtilService.createTextUnit(); + lectureUnitService.linkLectureUnitsToCompetency(competencyUtilService.createCompetency(course), Set.of(lectureUnit), Set.of()); + + final var testLecture = lectureUtilService.createLecture(course, null); + lectureUtilService.addLectureUnitsToLecture(testLecture, List.of(lectureUnit)); + course.addLectures(testLecture); + + final var result = request.get("/api/metrics/course/" + course.getId() + "/student", HttpStatus.OK, StudentMetricsDTO.class); + assertThat(result).isNotNull(); + assertThat(result.lectureUnitStudentMetricsDTO()).isNotNull(); + final var lectureUnitInformation = result.lectureUnitStudentMetricsDTO().lectureUnitInformation(); + + final var lectureUnits = lectureUnitRepository.findAllById(Set.of(lectureUnit.getId())); + final var expectedDTOs = lectureUnits.stream().map(LectureUnitInformationDTO::of).collect(Collectors.toSet()); + + assertThat(lectureUnitInformation.values()).containsExactlyInAnyOrderElementsOf(expectedDTOs); + assertThat(lectureUnitInformation).allSatisfy((id, dto) -> assertThat(id).isEqualTo(dto.id())); + } } } diff --git a/src/test/java/de/tum/cit/aet/artemis/core/StatisticsIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/core/StatisticsIntegrationTest.java index bcd5a8d5c8ae..3a483ba5b736 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/StatisticsIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/StatisticsIntegrationTest.java @@ -95,7 +95,6 @@ class StatisticsIntegrationTest extends AbstractSpringIntegrationIndependentTest @BeforeEach void initTestCase() { - participantScoreScheduleService.activate(); userUtilService.addUsers(TEST_PREFIX, NUMBER_OF_STUDENTS, 1, 0, 1); course = modelingExerciseUtilService.addCourseWithOneModelingExercise(); diff --git a/src/test/java/de/tum/cit/aet/artemis/core/config/MetricsBeanTest.java b/src/test/java/de/tum/cit/aet/artemis/core/config/MetricsBeanTest.java index 969dcba1d7f8..f2f97b9ffe74 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/config/MetricsBeanTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/config/MetricsBeanTest.java @@ -24,7 +24,6 @@ import de.tum.cit.aet.artemis.exercise.domain.ExerciseType; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationFactory; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.exercise.test_repository.SubmissionTestRepository; import de.tum.cit.aet.artemis.exercise.util.ExerciseUtilService; import de.tum.cit.aet.artemis.quiz.domain.QuizExercise; @@ -70,9 +69,6 @@ class MetricsBeanTest extends AbstractSpringIntegrationIndependentTest { @Autowired private UserTestRepository userRepository; - @Autowired - private ExerciseRepository exerciseRepository; - @Autowired private ExamUserRepository examUserRepository; diff --git a/src/test/java/de/tum/cit/aet/artemis/core/service/AssessmentServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/core/service/AssessmentServiceTest.java index 673f3c13f07c..07be65439250 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/service/AssessmentServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/service/AssessmentServiceTest.java @@ -30,7 +30,6 @@ import de.tum.cit.aet.artemis.exercise.domain.participation.Participation; import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.exercise.test_repository.ParticipationTestRepository; import de.tum.cit.aet.artemis.exercise.util.ExerciseUtilService; import de.tum.cit.aet.artemis.fileupload.domain.FileUploadExercise; @@ -49,9 +48,6 @@ class AssessmentServiceTest extends AbstractSpringIntegrationIndependentTest { private static final String TEST_PREFIX = "assessmentservice"; - @Autowired - private ExerciseRepository exerciseRepository; - @Autowired private ResultTestRepository resultRepository; diff --git a/src/test/java/de/tum/cit/aet/artemis/core/service/CourseExamExportServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/core/service/CourseExamExportServiceTest.java index 719f1b47a0d9..77915abba5d3 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/service/CourseExamExportServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/service/CourseExamExportServiceTest.java @@ -20,7 +20,6 @@ import de.tum.cit.aet.artemis.core.user.util.UserUtilService; import de.tum.cit.aet.artemis.core.util.CourseUtilService; import de.tum.cit.aet.artemis.exam.repository.ExamRepository; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest; class CourseExamExportServiceTest extends AbstractSpringIntegrationIndependentTest { @@ -39,9 +38,6 @@ class CourseExamExportServiceTest extends AbstractSpringIntegrationIndependentTe @Autowired private UserUtilService userUtilService; - @Autowired - private ExerciseRepository exerciseRepository; - @Autowired private UserTestRepository userRepository; diff --git a/src/test/java/de/tum/cit/aet/artemis/core/service/CourseServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/core/service/CourseServiceTest.java index b6483580a761..65e9c93d3cec 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/service/CourseServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/service/CourseServiceTest.java @@ -29,7 +29,6 @@ import de.tum.cit.aet.artemis.core.user.util.UserUtilService; import de.tum.cit.aet.artemis.core.util.CourseUtilService; import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.exercise.test_repository.StudentParticipationTestRepository; import de.tum.cit.aet.artemis.exercise.test_repository.SubmissionTestRepository; import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationLocalCILocalVCTest; @@ -52,9 +51,6 @@ class CourseServiceTest extends AbstractSpringIntegrationLocalCILocalVCTest { @Autowired private StudentParticipationTestRepository studentParticipationRepo; - @Autowired - private ExerciseRepository exerciseRepo; - @Autowired private UserUtilService userUtilService; @@ -79,7 +75,7 @@ void testGetActiveStudents(long weeks) { var course = courseUtilService.addEmptyCourse(); var exercise = TextExerciseFactory.generateTextExercise(date, date, date, course); course.addExercises(exercise); - exercise = exerciseRepo.save(exercise); + exercise = exerciseRepository.save(exercise); var student1 = userUtilService.getUserByLogin(TEST_PREFIX + "student1"); var participation1 = new StudentParticipation(); @@ -140,7 +136,7 @@ void testGetActiveStudents_UTCConversion() { var course = courseUtilService.addEmptyCourse(); var exercise = TextExerciseFactory.generateTextExercise(date, date, date, course); course.addExercises(exercise); - exercise = exerciseRepo.save(exercise); + exercise = exerciseRepository.save(exercise); var student1 = userUtilService.getUserByLogin(TEST_PREFIX + "student1"); var participation1 = new StudentParticipation(); diff --git a/src/test/java/de/tum/cit/aet/artemis/core/service/DataExportCreationServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/core/service/DataExportCreationServiceTest.java index d4f16c93604a..73382100b910 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/service/DataExportCreationServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/service/DataExportCreationServiceTest.java @@ -67,7 +67,6 @@ import de.tum.cit.aet.artemis.exam.util.ExamUtilService; import de.tum.cit.aet.artemis.exercise.domain.Exercise; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.exercise.util.ExerciseUtilService; import de.tum.cit.aet.artemis.fileupload.util.ZipFileTestUtilService; import de.tum.cit.aet.artemis.modeling.domain.ModelingExercise; @@ -118,9 +117,6 @@ class DataExportCreationServiceTest extends AbstractSpringIntegrationJenkinsGitl @Autowired private StudentExamTestRepository studentExamRepository; - @Autowired - private ExerciseRepository exerciseRepository; - @Autowired private DataExportTestRepository dataExportRepository; diff --git a/src/test/java/de/tum/cit/aet/artemis/core/service/TitleCacheEvictionServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/core/service/TitleCacheEvictionServiceTest.java index f9991fa36fc4..30f880543785 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/service/TitleCacheEvictionServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/service/TitleCacheEvictionServiceTest.java @@ -15,7 +15,6 @@ import de.tum.cit.aet.artemis.core.util.Tuple; import de.tum.cit.aet.artemis.exam.repository.ExamRepository; import de.tum.cit.aet.artemis.exam.util.ExamUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; import de.tum.cit.aet.artemis.lecture.util.LectureUtilService; import de.tum.cit.aet.artemis.modeling.domain.DiagramType; @@ -39,9 +38,6 @@ class TitleCacheEvictionServiceTest extends AbstractSpringIntegrationIndependent @Autowired private CacheManager cacheManager; - @Autowired - private ExerciseRepository exerciseRepository; - @Autowired private LectureRepository lectureRepository; diff --git a/src/test/java/de/tum/cit/aet/artemis/core/util/CourseTestService.java b/src/test/java/de/tum/cit/aet/artemis/core/util/CourseTestService.java index c3c68e44b5e5..3b8ce269954b 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/util/CourseTestService.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/util/CourseTestService.java @@ -126,7 +126,7 @@ import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationFactory; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; +import de.tum.cit.aet.artemis.exercise.repository.ExerciseTestRepository; import de.tum.cit.aet.artemis.exercise.service.ParticipationService; import de.tum.cit.aet.artemis.exercise.team.TeamUtilService; import de.tum.cit.aet.artemis.exercise.test_repository.ParticipationTestRepository; @@ -175,7 +175,7 @@ public class CourseTestService { private CourseTestRepository courseRepo; @Autowired - private ExerciseRepository exerciseRepo; + private ExerciseTestRepository exerciseRepo; @Autowired private LectureRepository lectureRepo; diff --git a/src/test/java/de/tum/cit/aet/artemis/core/util/CourseUtilService.java b/src/test/java/de/tum/cit/aet/artemis/core/util/CourseUtilService.java index 02d8d55a6231..93e7856af38e 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/util/CourseUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/util/CourseUtilService.java @@ -49,7 +49,7 @@ import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationFactory; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; +import de.tum.cit.aet.artemis.exercise.repository.ExerciseTestRepository; import de.tum.cit.aet.artemis.exercise.test_repository.StudentParticipationTestRepository; import de.tum.cit.aet.artemis.exercise.test_repository.SubmissionTestRepository; import de.tum.cit.aet.artemis.exercise.util.ExerciseUtilService; @@ -110,7 +110,7 @@ public class CourseUtilService { private AttachmentRepository attachmentRepo; @Autowired - private ExerciseRepository exerciseRepo; + private ExerciseTestRepository exerciseRepository; @Autowired private TutorParticipationTestRepository tutorParticipationRepo; @@ -390,12 +390,12 @@ public List createCoursesWithExercisesAndLectures(String userPrefix, boo attachmentRepo.save(attachment1); attachmentRepo.save(attachment2); - modelingExercise = exerciseRepo.save(modelingExercise); - textExercise = exerciseRepo.save(textExercise); - exerciseRepo.save(fileUploadExercise); + modelingExercise = exerciseRepository.save(modelingExercise); + textExercise = exerciseRepository.save(textExercise); + exerciseRepository.save(fileUploadExercise); programmingExercise.setBuildConfig(programmingExerciseBuildConfigRepository.save(programmingExercise.getBuildConfig())); - exerciseRepo.save(programmingExercise); - exerciseRepo.save(quizExercise); + exerciseRepository.save(programmingExercise); + exerciseRepository.save(quizExercise); if (withParticipations) { @@ -521,12 +521,12 @@ public Course createCourseWithAllExerciseTypesAndParticipationsAndSubmissionsAnd // Save course and exercises to database Course courseSaved = courseRepo.save(course); - modelingExercise = exerciseRepo.save(modelingExercise); - textExercise = exerciseRepo.save(textExercise); - fileUploadExercise = exerciseRepo.save(fileUploadExercise); + modelingExercise = exerciseRepository.save(modelingExercise); + textExercise = exerciseRepository.save(textExercise); + fileUploadExercise = exerciseRepository.save(fileUploadExercise); programmingExercise.setBuildConfig(programmingExerciseBuildConfigRepository.save(programmingExercise.getBuildConfig())); - programmingExercise = exerciseRepo.save(programmingExercise); - quizExercise = exerciseRepo.save(quizExercise); + programmingExercise = exerciseRepository.save(programmingExercise); + quizExercise = exerciseRepository.save(quizExercise); // Get user and setup participations User user = (userRepo.findOneByLogin(userPrefix + "student1")).orElseThrow(); @@ -630,11 +630,11 @@ public Course createCourseWithAllExerciseTypesAndParticipationsAndSubmissionsAnd programmingSubmission = submissionRepository.save(programmingSubmission); // Save exercises - exerciseRepo.save(modelingExercise); - exerciseRepo.save(textExercise); - exerciseRepo.save(fileUploadExercise); - exerciseRepo.save(programmingExercise); - exerciseRepo.save(quizExercise); + exerciseRepository.save(modelingExercise); + exerciseRepository.save(textExercise); + exerciseRepository.save(fileUploadExercise); + exerciseRepository.save(programmingExercise); + exerciseRepository.save(quizExercise); // Connect participations with submissions participationModeling.setSubmissions(Set.of(modelingSubmission)); @@ -687,12 +687,12 @@ public Course createCourseWithAllExerciseTypesAndParticipationsAndSubmissionsAnd // Save course and exercises to database Course courseSaved = courseRepo.save(course); - modelingExercise = exerciseRepo.save(modelingExercise); - textExercise = exerciseRepo.save(textExercise); - fileUploadExercise = exerciseRepo.save(fileUploadExercise); + modelingExercise = exerciseRepository.save(modelingExercise); + textExercise = exerciseRepository.save(textExercise); + fileUploadExercise = exerciseRepository.save(fileUploadExercise); programmingExercise.setBuildConfig(programmingExerciseBuildConfigRepository.save(programmingExercise.getBuildConfig())); - programmingExercise = exerciseRepo.save(programmingExercise); - quizExercise = exerciseRepo.save(quizExercise); + programmingExercise = exerciseRepository.save(programmingExercise); + quizExercise = exerciseRepository.save(quizExercise); // Get user and setup participations User user = (userRepo.findOneByLogin(userPrefix + "student1")).orElseThrow(); @@ -811,11 +811,11 @@ public Course createCourseWithAllExerciseTypesAndParticipationsAndSubmissionsAnd programmingSubmission = submissionRepository.save(programmingSubmission); // Save exercises - exerciseRepo.save(modelingExercise); - exerciseRepo.save(textExercise); - exerciseRepo.save(fileUploadExercise); - exerciseRepo.save(programmingExercise); - exerciseRepo.save(quizExercise); + exerciseRepository.save(modelingExercise); + exerciseRepository.save(textExercise); + exerciseRepository.save(fileUploadExercise); + exerciseRepository.save(programmingExercise); + exerciseRepository.save(quizExercise); // Connect participations with submissions participationModeling.setSubmissions(Set.of(modelingSubmission)); @@ -898,7 +898,7 @@ else if ("Text".equals(title)) { textExercise.setTitle("Text"); course.addExercises(textExercise); courseRepo.save(course); - exerciseRepo.save(textExercise); + exerciseRepository.save(textExercise); } else if (title.startsWith("ClassDiagram")) { ModelingExercise modelingExercise = ModelingExerciseFactory.generateModelingExercise(PAST_TIMESTAMP, FUTURE_TIMESTAMP, FUTURE_FUTURE_TIMESTAMP, @@ -906,7 +906,7 @@ else if (title.startsWith("ClassDiagram")) { modelingExercise.setTitle(title); course.addExercises(modelingExercise); courseRepo.save(course); - exerciseRepo.save(modelingExercise); + exerciseRepository.save(modelingExercise); } return course; @@ -927,8 +927,8 @@ public Course addCourseWithModelingAndTextExercise() { textExercise.setTitle("Text"); course.addExercises(textExercise); course = courseRepo.save(course); - exerciseRepo.save(modelingExercise); - exerciseRepo.save(textExercise); + exerciseRepository.save(modelingExercise); + exerciseRepository.save(textExercise); return course; } @@ -954,9 +954,9 @@ public Course addCourseWithModelingAndTextAndFileUploadExercise() { course.addExercises(fileUploadExercise); course = courseRepo.save(course); - exerciseRepo.save(modelingExercise); - exerciseRepo.save(textExercise); - exerciseRepo.save(fileUploadExercise); + exerciseRepository.save(modelingExercise); + exerciseRepository.save(textExercise); + exerciseRepository.save(fileUploadExercise); return course; } @@ -1056,7 +1056,7 @@ public Course addCourseWithExercisesAndSubmissions(String courseShortName, Strin ModelingExercise modelingExercise = ModelingExerciseFactory.generateModelingExercise(releaseDate, dueDate, assessmentDueDate, DiagramType.ClassDiagram, course); modelingExercise.setTitle("Modeling" + i); modelingExercise.setCourse(course); - modelingExercise = exerciseRepo.save(modelingExercise); + modelingExercise = exerciseRepository.save(modelingExercise); course.addExercises(modelingExercise); for (int j = 1; j <= numberOfSubmissionPerExercise; j++) { StudentParticipation participation = participationUtilService.createAndSaveParticipationForExercise(modelingExercise, userPrefix + "student" + j); @@ -1079,7 +1079,7 @@ else if ((i % 3) == 1) { TextExercise textExercise = TextExerciseFactory.generateTextExercise(releaseDate, dueDate, assessmentDueDate, course); textExercise.setTitle("Text" + i); textExercise.setCourse(course); - textExercise = exerciseRepo.save(textExercise); + textExercise = exerciseRepository.save(textExercise); course.addExercises(textExercise); for (int j = 1; j <= numberOfSubmissionPerExercise; j++) { TextSubmission submission = ParticipationFactory.generateTextSubmission("submissionText", Language.ENGLISH, true); @@ -1097,7 +1097,7 @@ else if ((i % 3) == 1) { FileUploadExercise fileUploadExercise = FileUploadExerciseFactory.generateFileUploadExercise(releaseDate, dueDate, assessmentDueDate, "png,pdf", course); fileUploadExercise.setTitle("FileUpload" + i); fileUploadExercise.setCourse(course); - fileUploadExercise = exerciseRepo.save(fileUploadExercise); + fileUploadExercise = exerciseRepository.save(fileUploadExercise); course.addExercises(fileUploadExercise); for (int j = 1; j <= numberOfSubmissionPerExercise; j++) { FileUploadSubmission submission = ParticipationFactory.generateFileUploadSubmissionWithFile(true, null); @@ -1175,7 +1175,7 @@ public Course createCourseWithExamExercisesAndSubmissions(String userPrefix) thr // Create a file upload exercise with a dummy submission file var exerciseGroup1 = exerciseGroupRepository.save(new ExerciseGroup()); var fileUploadExercise = FileUploadExerciseFactory.generateFileUploadExerciseForExam(".png", exerciseGroup1); - fileUploadExercise = exerciseRepo.save(fileUploadExercise); + fileUploadExercise = exerciseRepository.save(fileUploadExercise); fileUploadExerciseUtilService.createFileUploadSubmissionWithFile(userPrefix, fileUploadExercise, "uploaded-file.png"); exerciseGroup1.addExercise(fileUploadExercise); exerciseGroup1 = exerciseGroupRepository.save(exerciseGroup1); @@ -1183,7 +1183,7 @@ public Course createCourseWithExamExercisesAndSubmissions(String userPrefix) thr // Create a text exercise with a dummy submission file var exerciseGroup2 = exerciseGroupRepository.save(new ExerciseGroup()); var textExercise = TextExerciseFactory.generateTextExerciseForExam(exerciseGroup2); - textExercise = exerciseRepo.save(textExercise); + textExercise = exerciseRepository.save(textExercise); var textSubmission = ParticipationFactory.generateTextSubmission("example text", Language.ENGLISH, true); textExerciseUtilService.saveTextSubmission(textExercise, textSubmission, userPrefix + "student1"); exerciseGroup2.addExercise(textExercise); @@ -1192,7 +1192,7 @@ public Course createCourseWithExamExercisesAndSubmissions(String userPrefix) thr // Create a modeling exercise with a dummy submission file var exerciseGroup3 = exerciseGroupRepository.save(new ExerciseGroup()); var modelingExercise = ModelingExerciseFactory.generateModelingExerciseForExam(DiagramType.ClassDiagram, exerciseGroup2); - modelingExercise = exerciseRepo.save(modelingExercise); + modelingExercise = exerciseRepository.save(modelingExercise); String emptyActivityModel = TestResourceUtils.loadFileFromResources("test-data/model-submission/empty-activity-diagram.json"); var modelingSubmission = ParticipationFactory.generateModelingSubmission(emptyActivityModel, true); participationUtilService.addSubmission(modelingExercise, modelingSubmission, userPrefix + "student1"); diff --git a/src/test/java/de/tum/cit/aet/artemis/exam/ExamIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/exam/ExamIntegrationTest.java index 99e6b153def6..52f93e2d6741 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exam/ExamIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exam/ExamIntegrationTest.java @@ -203,7 +203,6 @@ void setup() { userTestRepository.save(instructor10); ParticipantScoreScheduleService.DEFAULT_WAITING_TIME_FOR_SCHEDULED_TASKS = 200; - participantScoreScheduleService.activate(); } @BeforeEach diff --git a/src/test/java/de/tum/cit/aet/artemis/exam/ExamParticipationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/exam/ExamParticipationIntegrationTest.java index 0483663734ad..ec49d8ec78a7 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exam/ExamParticipationIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exam/ExamParticipationIntegrationTest.java @@ -185,7 +185,6 @@ void initTestCase() { gitlabRequestMockProvider.enableMockingOfRequests(); ParticipantScoreScheduleService.DEFAULT_WAITING_TIME_FOR_SCHEDULED_TASKS = 200; - participantScoreScheduleService.activate(); } @AfterEach diff --git a/src/test/java/de/tum/cit/aet/artemis/exam/ExamRegistrationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/exam/ExamRegistrationIntegrationTest.java index 016803accf6b..8402a7e431f3 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exam/ExamRegistrationIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exam/ExamRegistrationIntegrationTest.java @@ -88,7 +88,6 @@ void initTestCase() { examUtilService.addStudentExamForTestExam(testExam1, student1); ParticipantScoreScheduleService.DEFAULT_WAITING_TIME_FOR_SCHEDULED_TASKS = 200; - participantScoreScheduleService.activate(); } @AfterEach diff --git a/src/test/java/de/tum/cit/aet/artemis/exam/ExamStartTest.java b/src/test/java/de/tum/cit/aet/artemis/exam/ExamStartTest.java index 60aad76d09b3..ea6ca670ad01 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exam/ExamStartTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exam/ExamStartTest.java @@ -42,7 +42,6 @@ import de.tum.cit.aet.artemis.exercise.domain.Exercise; import de.tum.cit.aet.artemis.exercise.domain.participation.Participation; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.exercise.test_repository.ParticipationTestRepository; import de.tum.cit.aet.artemis.modeling.domain.DiagramType; import de.tum.cit.aet.artemis.modeling.domain.ModelingExercise; @@ -65,9 +64,6 @@ class ExamStartTest extends AbstractSpringIntegrationLocalCILocalVCTest { private static final String TEST_PREFIX = "examstarttest"; - @Autowired - private ExerciseRepository exerciseRepo; - @Autowired private ExamRepository examRepository; @@ -116,7 +112,6 @@ void initTestCase() throws GitAPIException { exam = examUtilService.addExamWithExerciseGroup(course1, true); ParticipantScoreScheduleService.DEFAULT_WAITING_TIME_FOR_SCHEDULED_TASKS = 200; - participantScoreScheduleService.activate(); doNothing().when(gitService).combineAllCommitsOfRepositoryIntoOne(any()); @@ -153,7 +148,7 @@ void testStartExercisesWithTextExercise() throws Exception { TextExercise textExercise = TextExerciseFactory.generateTextExerciseForExam(exerciseGroup); exerciseGroup.addExercise(textExercise); exerciseGroupRepository.save(exerciseGroup); - textExercise = exerciseRepo.save(textExercise); + textExercise = exerciseRepository.save(textExercise); createStudentExams(textExercise); @@ -176,7 +171,7 @@ void testStartExercisesWithModelingExercise() throws Exception { ModelingExercise modelingExercise = ModelingExerciseFactory.generateModelingExerciseForExam(DiagramType.ClassDiagram, exam.getExerciseGroups().getFirst()); exam.getExerciseGroups().getFirst().addExercise(modelingExercise); exerciseGroupRepository.save(exam.getExerciseGroups().getFirst()); - modelingExercise = exerciseRepo.save(modelingExercise); + modelingExercise = exerciseRepository.save(modelingExercise); createStudentExams(modelingExercise); @@ -271,7 +266,7 @@ private void createStudentExams(Exercise exercise) { private ProgrammingExercise createProgrammingExercise() { ProgrammingExercise programmingExercise = ProgrammingExerciseFactory.generateProgrammingExerciseForExam(exam.getExerciseGroups().getFirst()); programmingExercise.setBuildConfig(programmingExerciseBuildConfigRepository.save(programmingExercise.getBuildConfig())); - programmingExercise = exerciseRepo.save(programmingExercise); + programmingExercise = exerciseRepository.save(programmingExercise); programmingExercise = programmingExerciseUtilService.addTemplateParticipationForProgrammingExercise(programmingExercise); exam.getExerciseGroups().getFirst().addExercise(programmingExercise); exerciseGroupRepository.save(exam.getExerciseGroups().getFirst()); diff --git a/src/test/java/de/tum/cit/aet/artemis/exam/ExerciseGroupIntegrationJenkinsGitlabTest.java b/src/test/java/de/tum/cit/aet/artemis/exam/ExerciseGroupIntegrationJenkinsGitlabTest.java index 108eef0bb76f..cca6fc1591e2 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exam/ExerciseGroupIntegrationJenkinsGitlabTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exam/ExerciseGroupIntegrationJenkinsGitlabTest.java @@ -29,7 +29,6 @@ import de.tum.cit.aet.artemis.exam.util.ExamFactory; import de.tum.cit.aet.artemis.exam.util.ExamUtilService; import de.tum.cit.aet.artemis.exercise.domain.Exercise; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseBuildConfigRepository; @@ -47,9 +46,6 @@ class ExerciseGroupIntegrationJenkinsGitlabTest extends AbstractSpringIntegratio @Autowired private TextExerciseRepository textExerciseRepository; - @Autowired - private ExerciseRepository exerciseRepository; - @Autowired private ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository; diff --git a/src/test/java/de/tum/cit/aet/artemis/exam/ProgrammingExamIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/exam/ProgrammingExamIntegrationTest.java index ef813745a412..a598f6ddbb79 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exam/ProgrammingExamIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exam/ProgrammingExamIntegrationTest.java @@ -100,7 +100,6 @@ void initTestCase() { gitlabRequestMockProvider.enableMockingOfRequests(); ParticipantScoreScheduleService.DEFAULT_WAITING_TIME_FOR_SCHEDULED_TASKS = 200; - participantScoreScheduleService.activate(); } @AfterEach diff --git a/src/test/java/de/tum/cit/aet/artemis/exam/TestExamIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/exam/TestExamIntegrationTest.java index 159866350e68..0853bed96f6c 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exam/TestExamIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exam/TestExamIntegrationTest.java @@ -74,7 +74,6 @@ void initTestCase() { examUtilService.addStudentExamForTestExam(testExam1, student1); ParticipantScoreScheduleService.DEFAULT_WAITING_TIME_FOR_SCHEDULED_TASKS = 200; - participantScoreScheduleService.activate(); } @Test diff --git a/src/test/java/de/tum/cit/aet/artemis/exam/service/ExamServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/exam/service/ExamServiceTest.java index 140deeae34c1..860576490fb0 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exam/service/ExamServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exam/service/ExamServiceTest.java @@ -28,7 +28,6 @@ import de.tum.cit.aet.artemis.exam.util.ExamUtilService; import de.tum.cit.aet.artemis.exercise.domain.IncludedInOverallScore; import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.exercise.test_repository.StudentParticipationTestRepository; import de.tum.cit.aet.artemis.quiz.domain.QuizExercise; import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest; @@ -47,9 +46,6 @@ class ExamServiceTest extends AbstractSpringIntegrationIndependentTest { @Autowired private StudentParticipationTestRepository studentParticipationRepository; - @Autowired - private ExerciseRepository exerciseRepository; - @Autowired private CourseUtilService courseUtilService; diff --git a/src/test/java/de/tum/cit/aet/artemis/exam/util/ExamUtilService.java b/src/test/java/de/tum/cit/aet/artemis/exam/util/ExamUtilService.java index ba1fe0c84b8f..0d6c8a6899c1 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exam/util/ExamUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/exam/util/ExamUtilService.java @@ -44,7 +44,7 @@ import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationFactory; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; +import de.tum.cit.aet.artemis.exercise.repository.ExerciseTestRepository; import de.tum.cit.aet.artemis.exercise.test_repository.StudentParticipationTestRepository; import de.tum.cit.aet.artemis.exercise.test_repository.SubmissionTestRepository; import de.tum.cit.aet.artemis.fileupload.domain.FileUploadExercise; @@ -94,7 +94,7 @@ public class ExamUtilService { private ExamRepository examRepository; @Autowired - private ExerciseRepository exerciseRepo; + private ExerciseTestRepository exerciseRepo; @Autowired private ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository; diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java index 1162e7b3477d..04df504a5dbb 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java @@ -3,7 +3,6 @@ import static de.tum.cit.aet.artemis.core.connector.AthenaRequestMockProvider.ATHENA_MODULE_PROGRAMMING_TEST; import static de.tum.cit.aet.artemis.core.util.TestResourceUtils.HalfSecond; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.doNothing; @@ -538,7 +537,51 @@ void requestFeedbackAlreadySent() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") - void requestFeedbackSuccess_withAthenaSuccess() throws Exception { + void requestProgrammingFeedbackIfARequestAlreadySent_withAthenaSuccess() throws Exception { + + var course = programmingExercise.getCourseViaExerciseGroupOrCourseMember(); + course.setRestrictedAthenaModulesAccess(true); + this.courseRepository.save(course); + + this.programmingExercise.setFeedbackSuggestionModule(ATHENA_MODULE_PROGRAMMING_TEST); + this.exerciseRepository.save(programmingExercise); + + athenaRequestMockProvider.mockGetFeedbackSuggestionsAndExpect("programming"); + + var participation = ParticipationFactory.generateProgrammingExerciseStudentParticipation(InitializationState.INACTIVE, programmingExercise, + userUtilService.getUserByLogin(TEST_PREFIX + "student1")); + + var localRepo = new LocalRepository(defaultBranch); + localRepo.configureRepos("testLocalRepo", "testOriginRepo"); + + participation.setRepositoryUri(ParticipationFactory.getMockFileRepositoryUri(localRepo).getURI().toString()); + participationRepo.save(participation); + + gitService.getDefaultLocalPathOfRepo(participation.getVcsRepositoryUri()); + + Result result1 = participationUtilService.createSubmissionAndResult(participation, 100, false); + Result result2 = participationUtilService.addResultToParticipation(participation, result1.getSubmission()); + result2.setAssessmentType(AssessmentType.AUTOMATIC_ATHENA); + result2.setSuccessful(null); + resultRepository.save(result2); + + request.putWithResponseBody("/api/exercises/" + programmingExercise.getId() + "/request-feedback", null, ProgrammingExerciseStudentParticipation.class, HttpStatus.OK); + + verify(programmingMessagingService, timeout(2000).times(2)).notifyUserAboutNewResult(resultCaptor.capture(), any()); + + Result invokedResult = resultCaptor.getAllValues().getFirst(); + assertThat(invokedResult).isNotNull(); + assertThat(invokedResult.getId()).isNotNull(); + assertThat(invokedResult.isSuccessful()).isTrue(); + assertThat(invokedResult.isAthenaBased()).isTrue(); + assertThat(invokedResult.getFeedbacks()).hasSize(1); + + localRepo.resetLocalRepo(); + } + + @Test + @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") + void requestProgrammingFeedbackSuccess_withAthenaSuccess() throws Exception { var course = programmingExercise.getCourseViaExerciseGroupOrCourseMember(); course.setRestrictedAthenaModulesAccess(true); @@ -566,9 +609,6 @@ void requestFeedbackSuccess_withAthenaSuccess() throws Exception { result2.setCompletionDate(ZonedDateTime.now()); resultRepository.save(result2); - doNothing().when(programmingExerciseParticipationService).lockStudentRepositoryAndParticipation(eq(programmingExercise), any()); - doNothing().when(programmingExerciseParticipationService).unlockStudentRepositoryAndParticipation(any()); - request.putWithResponseBody("/api/exercises/" + programmingExercise.getId() + "/request-feedback", null, ProgrammingExerciseStudentParticipation.class, HttpStatus.OK); verify(programmingMessagingService, timeout(2000).times(2)).notifyUserAboutNewResult(resultCaptor.capture(), any()); @@ -577,7 +617,7 @@ void requestFeedbackSuccess_withAthenaSuccess() throws Exception { assertThat(invokedResult).isNotNull(); assertThat(invokedResult.getId()).isNotNull(); assertThat(invokedResult.isSuccessful()).isTrue(); - assertThat(invokedResult.isAthenaAutomatic()).isTrue(); + assertThat(invokedResult.isAthenaBased()).isTrue(); assertThat(invokedResult.getFeedbacks()).hasSize(1); localRepo.resetLocalRepo(); @@ -614,7 +654,7 @@ void requestTextFeedbackSuccess_withAthenaSuccess() throws Exception { Result invokedTextResult = resultCaptor.getAllValues().get(1); assertThat(invokedTextResult).isNotNull(); assertThat(invokedTextResult.getId()).isNotNull(); - assertThat(invokedTextResult.isAthenaAutomatic()).isTrue(); + assertThat(invokedTextResult.isAthenaBased()).isTrue(); assertThat(invokedTextResult.getFeedbacks()).hasSize(1); } @@ -649,13 +689,13 @@ void requestModelingFeedbackSuccess_withAthenaSuccess() throws Exception { Result invokedModelingResult = resultCaptor.getAllValues().get(1); assertThat(invokedModelingResult).isNotNull(); assertThat(invokedModelingResult.getId()).isNotNull(); - assertThat(invokedModelingResult.isAthenaAutomatic()).isTrue(); + assertThat(invokedModelingResult.isAthenaBased()).isTrue(); assertThat(invokedModelingResult.getFeedbacks()).hasSize(1); } @Test @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") - void requestFeedbackSuccess_withAthenaFailure() throws Exception { + void requestProgrammingFeedbackSuccess_withAthenaFailure() throws Exception { var course = programmingExercise.getCourseViaExerciseGroupOrCourseMember(); course.setRestrictedAthenaModulesAccess(true); @@ -682,9 +722,6 @@ void requestFeedbackSuccess_withAthenaFailure() throws Exception { result2.setCompletionDate(ZonedDateTime.now()); resultRepository.save(result2); - doNothing().when(programmingExerciseParticipationService).lockStudentRepositoryAndParticipation(any(), any()); - doNothing().when(programmingExerciseParticipationService).unlockStudentRepositoryAndParticipation(any()); - request.putWithResponseBody("/api/exercises/" + programmingExercise.getId() + "/request-feedback", null, ProgrammingExerciseStudentParticipation.class, HttpStatus.OK); verify(programmingMessagingService, timeout(2000).times(2)).notifyUserAboutNewResult(resultCaptor.capture(), any()); @@ -693,7 +730,7 @@ void requestFeedbackSuccess_withAthenaFailure() throws Exception { assertThat(invokedResult).isNotNull(); assertThat(invokedResult.getId()).isNotNull(); assertThat(invokedResult.isSuccessful()).isFalse(); - assertThat(invokedResult.isAthenaAutomatic()).isTrue(); + assertThat(invokedResult.isAthenaBased()).isTrue(); assertThat(invokedResult.getFeedbacks()).hasSize(0); localRepo.resetLocalRepo(); @@ -729,7 +766,7 @@ void requestTextFeedbackSuccess_withAthenaFailure() throws Exception { Result invokedTextResult = resultCaptor.getAllValues().getFirst(); assertThat(invokedTextResult).isNotNull(); - assertThat(invokedTextResult.isAthenaAutomatic()).isTrue(); + assertThat(invokedTextResult.isAthenaBased()).isTrue(); assertThat(invokedTextResult.getFeedbacks()).hasSize(0); } @@ -763,7 +800,7 @@ void requestModelingFeedbackSuccess_withAthenaFailure() throws Exception { Result invokedModelingResult = resultCaptor.getAllValues().getFirst(); assertThat(invokedModelingResult).isNotNull(); - assertThat(invokedModelingResult.isAthenaAutomatic()).isTrue(); + assertThat(invokedModelingResult.isAthenaBased()).isTrue(); assertThat(invokedModelingResult.getFeedbacks()).hasSize(0); } @@ -1615,7 +1652,7 @@ void whenFeedbackRequestedAndDeadlinePassed_thenFail() throws Exception { result.setCompletionDate(ZonedDateTime.now()); resultRepository.save(result); - request.putAndExpectError("/api/exercises/" + programmingExercise.getId() + "/request-feedback", null, HttpStatus.BAD_REQUEST, "preconditions not met"); + request.putAndExpectError("/api/exercises/" + programmingExercise.getId() + "/request-feedback", null, HttpStatus.BAD_REQUEST, "feedbackRequestAfterDueDate"); localRepo.resetLocalRepo(); } @@ -1643,50 +1680,14 @@ void whenFeedbackRequestedAndRateLimitExceeded_thenFail() throws Exception { resultRepository.save(result); // generate 5 athena results - for (int i = 0; i < 5; i++) { - var athenaResult = ParticipationFactory.generateResult(false, 100).participation(participation); - athenaResult.setCompletionDate(ZonedDateTime.now()); - athenaResult.setAssessmentType(AssessmentType.AUTOMATIC_ATHENA); - resultRepository.save(athenaResult); - } - - request.putAndExpectError("/api/exercises/" + programmingExercise.getId() + "/request-feedback", null, HttpStatus.BAD_REQUEST, "preconditions not met"); - - localRepo.resetLocalRepo(); - } - - @Test - @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") - void whenFeedbackRequestedAndRateLimitStillUnknownDueRequestsInProgress_thenFail() throws Exception { - - programmingExercise.setDueDate(ZonedDateTime.now().plusDays(100)); - programmingExercise = exerciseRepository.save(programmingExercise); - - var participation = ParticipationFactory.generateProgrammingExerciseStudentParticipation(InitializationState.INACTIVE, programmingExercise, - userUtilService.getUserByLogin(TEST_PREFIX + "student1")); - - var localRepo = new LocalRepository(defaultBranch); - localRepo.configureRepos("testLocalRepo", "testOriginRepo"); - - participation.setRepositoryUri(ParticipationFactory.getMockFileRepositoryUri(localRepo).getURI().toString()); - participationRepo.save(participation); - - gitService.getDefaultLocalPathOfRepo(participation.getVcsRepositoryUri()); - - var result = ParticipationFactory.generateResult(false, 100).participation(participation); - result.setCompletionDate(ZonedDateTime.now()); - resultRepository.save(result); - - // generate 5 athena results - for (int i = 0; i < 5; i++) { + for (int i = 0; i < 20; i++) { var athenaResult = ParticipationFactory.generateResult(false, 100).participation(participation); athenaResult.setCompletionDate(ZonedDateTime.now()); athenaResult.setAssessmentType(AssessmentType.AUTOMATIC_ATHENA); - athenaResult.setSuccessful(null); resultRepository.save(athenaResult); } - request.putAndExpectError("/api/exercises/" + programmingExercise.getId() + "/request-feedback", null, HttpStatus.BAD_REQUEST, "preconditions not met"); + request.putAndExpectError("/api/exercises/" + programmingExercise.getId() + "/request-feedback", null, HttpStatus.BAD_REQUEST, "maxAthenaResultsReached"); localRepo.resetLocalRepo(); } diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java index 13f049125c0a..ff3ab5f260e6 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java @@ -47,7 +47,7 @@ import de.tum.cit.aet.artemis.exercise.domain.participation.Participant; import de.tum.cit.aet.artemis.exercise.domain.participation.Participation; import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; +import de.tum.cit.aet.artemis.exercise.repository.ExerciseTestRepository; import de.tum.cit.aet.artemis.exercise.repository.TeamRepository; import de.tum.cit.aet.artemis.exercise.service.ParticipationService; import de.tum.cit.aet.artemis.exercise.test_repository.StudentParticipationTestRepository; @@ -91,7 +91,7 @@ public class ParticipationUtilService { private ParticipationVcsAccessTokenService participationVCSAccessTokenService; @Autowired - private ExerciseRepository exerciseRepo; + private ExerciseTestRepository exerciseRepository; @Autowired private SubmissionTestRepository submissionRepository; @@ -177,14 +177,14 @@ public Result addProgrammingParticipationWithResultForExercise(ProgrammingExerci * @return The created Result */ public Result createParticipationSubmissionAndResult(long exerciseId, Participant participant, Double points, Double bonusPoints, long scoreAwarded, boolean rated) { - Exercise exercise = exerciseRepo.findById(exerciseId).orElseThrow(); + Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(); if (!exercise.getMaxPoints().equals(points)) { exercise.setMaxPoints(points); } if (!exercise.getBonusPoints().equals(bonusPoints)) { exercise.setBonusPoints(bonusPoints); } - exercise = exerciseRepo.saveAndFlush(exercise); + exercise = exerciseRepository.saveAndFlush(exercise); StudentParticipation studentParticipation = participationService.startExercise(exercise, participant, false); return createSubmissionAndResult(studentParticipation, scoreAwarded, rated); } diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/repository/ExerciseTestRepository.java b/src/test/java/de/tum/cit/aet/artemis/exercise/repository/ExerciseTestRepository.java new file mode 100644 index 000000000000..03332b2f6fc5 --- /dev/null +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/repository/ExerciseTestRepository.java @@ -0,0 +1,31 @@ +package de.tum.cit.aet.artemis.exercise.repository; + +import java.util.List; +import java.util.Set; + +import org.springframework.context.annotation.Primary; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import de.tum.cit.aet.artemis.exercise.domain.Exercise; + +/** + * Spring Data JPA repository for the Exercise entity for Tests. + */ +@Primary +@Repository +public interface ExerciseTestRepository extends ExerciseRepository { + + @EntityGraph(attributePaths = { "studentParticipations", "studentParticipations.student", "studentParticipations.submissions" }) + @Query(""" + SELECT e + FROM Exercise e + WHERE e.course.id = :courseId + """) + Set findAllExercisesByCourseIdWithEagerParticipation(@Param("courseId") Long courseId); + + @EntityGraph(attributePaths = "categories") + List findAllWithCategoriesByCourseId(Long courseId); +} diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/service/PresentationPointsCalculationServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/exercise/service/PresentationPointsCalculationServiceTest.java index 0592824c6ad7..56aded0df2ae 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/service/PresentationPointsCalculationServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/service/PresentationPointsCalculationServiceTest.java @@ -17,7 +17,6 @@ import de.tum.cit.aet.artemis.exercise.domain.IncludedInOverallScore; import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.exercise.test_repository.StudentParticipationTestRepository; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.util.ProgrammingExerciseUtilService; @@ -27,9 +26,6 @@ class PresentationPointsCalculationServiceTest extends AbstractSpringIntegration private static final String TEST_PREFIX = "ppcservicetest"; - @Autowired - private ExerciseRepository exerciseRepository; - @Autowired private StudentParticipationTestRepository studentParticipationRepository; diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/service/TeamWebsocketServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/exercise/service/TeamWebsocketServiceTest.java index b73dc55fe396..0e9bca57e218 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/service/TeamWebsocketServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/service/TeamWebsocketServiceTest.java @@ -24,7 +24,6 @@ import de.tum.cit.aet.artemis.exercise.domain.Team; import de.tum.cit.aet.artemis.exercise.dto.TeamAssignmentPayload; import de.tum.cit.aet.artemis.exercise.dto.TeamImportStrategyType; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.exercise.repository.TeamRepository; import de.tum.cit.aet.artemis.exercise.team.TeamUtilService; import de.tum.cit.aet.artemis.modeling.domain.ModelingExercise; @@ -38,9 +37,6 @@ class TeamWebsocketServiceTest extends AbstractSpringIntegrationIndependentTest @Autowired private UserTestRepository userRepo; - @Autowired - private ExerciseRepository exerciseRepo; - @Autowired private TeamRepository teamRepository; @@ -77,11 +73,11 @@ void init() { for (Exercise exercise : course.getExercises()) { if (exercise instanceof ModelingExercise) { exercise.setMode(ExerciseMode.TEAM); - modelingExercise = (ModelingExercise) exerciseRepo.save(exercise); + modelingExercise = (ModelingExercise) exerciseRepository.save(exercise); } if (exercise instanceof TextExercise) { exercise.setMode(ExerciseMode.TEAM); - textExercise = (TextExercise) exerciseRepo.save(exercise); + textExercise = (TextExercise) exerciseRepository.save(exercise); } } assertThat(modelingExercise).isNotNull(); diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/util/ExerciseUtilService.java b/src/test/java/de/tum/cit/aet/artemis/exercise/util/ExerciseUtilService.java index fa04fd24ca0d..db92da355b2f 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/util/ExerciseUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/util/ExerciseUtilService.java @@ -38,7 +38,7 @@ import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationFactory; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; +import de.tum.cit.aet.artemis.exercise.repository.ExerciseTestRepository; import de.tum.cit.aet.artemis.exercise.test_repository.StudentParticipationTestRepository; import de.tum.cit.aet.artemis.exercise.test_repository.SubmissionTestRepository; import de.tum.cit.aet.artemis.fileupload.domain.FileUploadExercise; @@ -65,7 +65,7 @@ public class ExerciseUtilService { @Autowired - private ExerciseRepository exerciseRepo; + private ExerciseTestRepository exerciseTestRepository; @Autowired private StudentParticipationTestRepository studentParticipationRepo; @@ -122,7 +122,7 @@ public Exercise addMaxScoreAndBonusPointsToExercise(Exercise exercise) { exercise.setIncludedInOverallScore(IncludedInOverallScore.INCLUDED_COMPLETELY); exercise.setMaxPoints(100.0); exercise.setBonusPoints(10.0); - return exerciseRepo.save(exercise); + return exerciseTestRepository.save(exercise); } /** @@ -222,7 +222,7 @@ public Course addCourseWithOneExerciseAndSubmissions(String userPrefix, String e switch (exerciseType) { case "modeling" -> { course = modelingExerciseUtilService.addCourseWithOneModelingExercise(); - exercise = exerciseRepo.findAllExercisesByCourseId(course.getId()).iterator().next(); + exercise = exerciseTestRepository.findAllExercisesByCourseId(course.getId()).iterator().next(); for (int j = 1; j <= numberOfSubmissions; j++) { StudentParticipation participation = participationUtilService.createAndSaveParticipationForExercise(exercise, userPrefix + "student" + j); assertThat(modelForModelingExercise).isNotEmpty(); @@ -235,7 +235,7 @@ public Course addCourseWithOneExerciseAndSubmissions(String userPrefix, String e } case "programming" -> { course = programmingExerciseUtilService.addCourseWithOneProgrammingExercise(); - exercise = exerciseRepo.findAllExercisesByCourseId(course.getId()).iterator().next(); + exercise = exerciseTestRepository.findAllExercisesByCourseId(course.getId()).iterator().next(); for (int j = 1; j <= numberOfSubmissions; j++) { ProgrammingSubmission submission = new ProgrammingSubmission(); programmingExerciseUtilService.addProgrammingSubmission((ProgrammingExercise) exercise, submission, userPrefix + "student" + j); @@ -244,7 +244,7 @@ public Course addCourseWithOneExerciseAndSubmissions(String userPrefix, String e } case "text" -> { course = textExerciseUtilService.addCourseWithOneFinishedTextExercise(); - exercise = exerciseRepo.findAllExercisesByCourseId(course.getId()).iterator().next(); + exercise = exerciseTestRepository.findAllExercisesByCourseId(course.getId()).iterator().next(); for (int j = 1; j <= numberOfSubmissions; j++) { TextSubmission textSubmission = ParticipationFactory.generateTextSubmission("Text" + j + j, null, true); textExerciseUtilService.saveTextSubmission((TextExercise) exercise, textSubmission, userPrefix + "student" + j); @@ -253,7 +253,7 @@ public Course addCourseWithOneExerciseAndSubmissions(String userPrefix, String e } case "file-upload" -> { course = fileUploadExerciseUtilService.addCourseWithFileUploadExercise(); - exercise = exerciseRepo.findAllExercisesByCourseId(course.getId()).iterator().next(); + exercise = exerciseTestRepository.findAllExercisesByCourseId(course.getId()).iterator().next(); for (int j = 1; j <= numberOfSubmissions; j++) { FileUploadSubmission submission = ParticipationFactory.generateFileUploadSubmissionWithFile(true, "path/to/file.pdf"); fileUploadExerciseUtilService.saveFileUploadSubmission((FileUploadExercise) exercise, submission, userPrefix + "student" + j); @@ -313,12 +313,13 @@ public void addAssessmentToExercise(Exercise exercise, User assessor) { * @param newDueDate The new due date of the exercise. */ public void updateExerciseDueDate(long exerciseId, ZonedDateTime newDueDate) { - Exercise exercise = exerciseRepo.findById(exerciseId).orElseThrow(() -> new IllegalArgumentException("Exercise with given ID " + exerciseId + " could not be found")); + Exercise exercise = exerciseTestRepository.findById(exerciseId) + .orElseThrow(() -> new IllegalArgumentException("Exercise with given ID " + exerciseId + " could not be found")); exercise.setDueDate(newDueDate); if (exercise instanceof ProgrammingExercise) { ((ProgrammingExercise) exercise).setBuildAndTestStudentSubmissionsAfterDueDate(newDueDate); } - exerciseRepo.save(exercise); + exerciseTestRepository.save(exercise); } /** @@ -328,9 +329,10 @@ public void updateExerciseDueDate(long exerciseId, ZonedDateTime newDueDate) { * @param newDueDate The new assessment due date of the exercise. */ public void updateAssessmentDueDate(long exerciseId, ZonedDateTime newDueDate) { - Exercise exercise = exerciseRepo.findById(exerciseId).orElseThrow(() -> new IllegalArgumentException("Exercise with given ID " + exerciseId + " could not be found")); + Exercise exercise = exerciseTestRepository.findById(exerciseId) + .orElseThrow(() -> new IllegalArgumentException("Exercise with given ID " + exerciseId + " could not be found")); exercise.setAssessmentDueDate(newDueDate); - exerciseRepo.save(exercise); + exerciseTestRepository.save(exercise); } /** diff --git a/src/test/java/de/tum/cit/aet/artemis/fileupload/util/FileUploadExerciseUtilService.java b/src/test/java/de/tum/cit/aet/artemis/fileupload/util/FileUploadExerciseUtilService.java index 4b6084891726..c85b0c3df488 100644 --- a/src/test/java/de/tum/cit/aet/artemis/fileupload/util/FileUploadExerciseUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/fileupload/util/FileUploadExerciseUtilService.java @@ -26,7 +26,7 @@ import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationFactory; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; +import de.tum.cit.aet.artemis.exercise.repository.ExerciseTestRepository; import de.tum.cit.aet.artemis.exercise.test_repository.StudentParticipationTestRepository; import de.tum.cit.aet.artemis.exercise.test_repository.SubmissionTestRepository; import de.tum.cit.aet.artemis.fileupload.domain.FileUploadExercise; @@ -45,9 +45,6 @@ public class FileUploadExerciseUtilService { private static final ZonedDateTime FUTURE_FUTURE_TIMESTAMP = ZonedDateTime.now().plusDays(2); - @Autowired - private ExerciseRepository exerciseRepo; - @Autowired private CourseTestRepository courseRepo; @@ -72,6 +69,9 @@ public class FileUploadExerciseUtilService { @Autowired private UserUtilService userUtilService; + @Autowired + private ExerciseTestRepository exerciseRepository; + /** * Creates and saves a new Course and an Exam with one mandatory FileUploadExercise. * @@ -82,7 +82,7 @@ public class FileUploadExerciseUtilService { public FileUploadExercise addCourseExamExerciseGroupWithOneFileUploadExercise(boolean startDateBeforeCurrentTime) { ExerciseGroup exerciseGroup = examUtilService.addExerciseGroupWithExamAndCourse(true, startDateBeforeCurrentTime); FileUploadExercise fileUploadExercise = FileUploadExerciseFactory.generateFileUploadExerciseForExam("pdf", exerciseGroup); - return exerciseRepo.save(fileUploadExercise); + return exerciseRepository.save(fileUploadExercise); } /** @@ -118,10 +118,10 @@ public List createFileUploadExercisesWithCourse() { public Course addCourseWithThreeFileUploadExercise() { var fileUploadExercises = createFileUploadExercisesWithCourse(); assertThat(fileUploadExercises).as("created three exercises").hasSize(3); - exerciseRepo.saveAll(fileUploadExercises); + exerciseRepository.saveAll(fileUploadExercises); long courseId = fileUploadExercises.getFirst().getCourseViaExerciseGroupOrCourseMember().getId(); Course course = courseRepo.findByIdWithEagerExercisesElseThrow(courseId); - List exercises = exerciseRepo.findAllExercisesByCourseId(courseId).stream().toList(); + List exercises = exerciseRepository.findAllExercisesByCourseId(courseId).stream().toList(); assertThat(exercises).as("three exercises got stored").hasSize(3); assertThat(course.getExercises()).as("course contains the exercises").containsExactlyInAnyOrder(exercises.toArray(new Exercise[] {})); return course; @@ -152,7 +152,7 @@ public Course addCourseWithFourFileUploadExercise() { fileUploadExercises.add(finishedFileUploadExercise); fileUploadExercises.add(assessedFileUploadExercise); fileUploadExercises.add(noDueDateFileUploadExercise); - exerciseRepo.saveAll(fileUploadExercises); + exerciseRepository.saveAll(fileUploadExercises); return courseRepo.findByIdWithEagerExercisesElseThrow(course.getId()); } @@ -168,7 +168,7 @@ public Course addCourseWithFileUploadExercise() { assessedFileUploadExercise.setTitle("assessed"); course.addExercises(assessedFileUploadExercise); courseRepo.save(course); - exerciseRepo.save(assessedFileUploadExercise); + exerciseRepository.save(assessedFileUploadExercise); return course; } diff --git a/src/test/java/de/tum/cit/aet/artemis/lti/service/LtiDeepLinkingServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/lti/service/LtiDeepLinkingServiceTest.java index de205dc54fb1..9c597bbc1cbd 100644 --- a/src/test/java/de/tum/cit/aet/artemis/lti/service/LtiDeepLinkingServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/lti/service/LtiDeepLinkingServiceTest.java @@ -35,7 +35,7 @@ import de.tum.cit.aet.artemis.core.domain.Course; import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException; import de.tum.cit.aet.artemis.exercise.domain.Exercise; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; +import de.tum.cit.aet.artemis.exercise.repository.ExerciseTestRepository; import de.tum.cit.aet.artemis.lti.config.Lti13TokenRetriever; import de.tum.cit.aet.artemis.lti.domain.OnlineCourseConfiguration; import de.tum.cit.aet.artemis.text.domain.TextExercise; @@ -44,7 +44,7 @@ class LtiDeepLinkingServiceTest { @Mock - private ExerciseRepository exerciseRepository; + private ExerciseTestRepository exerciseRepository; @Mock private Lti13TokenRetriever tokenRetriever; diff --git a/src/test/java/de/tum/cit/aet/artemis/modeling/util/ModelingExerciseUtilService.java b/src/test/java/de/tum/cit/aet/artemis/modeling/util/ModelingExerciseUtilService.java index 8b8c4c18be85..2fe018ff8f5f 100644 --- a/src/test/java/de/tum/cit/aet/artemis/modeling/util/ModelingExerciseUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/modeling/util/ModelingExerciseUtilService.java @@ -36,7 +36,7 @@ import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationFactory; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; +import de.tum.cit.aet.artemis.exercise.repository.ExerciseTestRepository; import de.tum.cit.aet.artemis.exercise.test_repository.StudentParticipationTestRepository; import de.tum.cit.aet.artemis.modeling.domain.DiagramType; import de.tum.cit.aet.artemis.modeling.domain.ModelingExercise; @@ -62,9 +62,6 @@ public class ModelingExerciseUtilService { @Autowired private CourseTestRepository courseRepo; - @Autowired - private ExerciseRepository exerciseRepo; - @Autowired private ModelingExerciseRepository modelingExerciseRepository; @@ -98,6 +95,9 @@ public class ModelingExerciseUtilService { @Autowired private ModelingSubmissionService modelSubmissionService; + @Autowired + private ExerciseTestRepository exerciseRepository; + /** * Creates and saves a Course with a ModelingExercise. The ModelingExercise's DiagramType is set to ClassDiagram. * @@ -112,7 +112,7 @@ public Course addCourseWithOneModelingExercise(String title) { course.addExercises(modelingExercise); course.setMaxComplaintTimeDays(14); course = courseRepo.save(course); - modelingExercise = exerciseRepo.save(modelingExercise); + modelingExercise = exerciseRepository.save(modelingExercise); assertThat(course.getExercises()).as("course contains the exercise").containsExactlyInAnyOrder(modelingExercise); assertThat(modelingExercise.getPresentationScoreEnabled()).as("presentation score is enabled").isTrue(); return course; @@ -219,17 +219,17 @@ public Course addCourseWithDifferentModelingExercises() { course.addExercises(finishedExercise); course = courseRepo.save(course); - exerciseRepo.save(classExercise); - exerciseRepo.save(activityExercise); - exerciseRepo.save(objectExercise); - exerciseRepo.save(useCaseExercise); - exerciseRepo.save(communicationExercise); - exerciseRepo.save(componentExercise); - exerciseRepo.save(deploymentExercise); - exerciseRepo.save(petriNetExercise); - exerciseRepo.save(syntaxTreeExercise); - exerciseRepo.save(flowchartExercise); - exerciseRepo.save(finishedExercise); + exerciseRepository.save(classExercise); + exerciseRepository.save(activityExercise); + exerciseRepository.save(objectExercise); + exerciseRepository.save(useCaseExercise); + exerciseRepository.save(communicationExercise); + exerciseRepository.save(componentExercise); + exerciseRepository.save(deploymentExercise); + exerciseRepository.save(petriNetExercise); + exerciseRepository.save(syntaxTreeExercise); + exerciseRepository.save(flowchartExercise); + exerciseRepository.save(finishedExercise); Course storedCourse = courseRepo.findByIdWithExercisesAndExerciseDetailsAndLecturesElseThrow(course.getId()); Set exercises = storedCourse.getExercises(); assertThat(exercises).as("eleven exercises got stored").hasSize(11); diff --git a/src/test/java/de/tum/cit/aet/artemis/plagiarism/ContinuousPlagiarismControlServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/plagiarism/ContinuousPlagiarismControlServiceTest.java index bba2832d362d..1986d792856d 100644 --- a/src/test/java/de/tum/cit/aet/artemis/plagiarism/ContinuousPlagiarismControlServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/plagiarism/ContinuousPlagiarismControlServiceTest.java @@ -25,7 +25,7 @@ import de.tum.cit.aet.artemis.core.domain.User; import de.tum.cit.aet.artemis.exercise.domain.Exercise; import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; +import de.tum.cit.aet.artemis.exercise.repository.ExerciseTestRepository; import de.tum.cit.aet.artemis.fileupload.domain.FileUploadExercise; import de.tum.cit.aet.artemis.modeling.domain.ModelingExercise; import de.tum.cit.aet.artemis.plagiarism.domain.PlagiarismCase; @@ -51,7 +51,7 @@ class ContinuousPlagiarismControlServiceTest { - private final ExerciseRepository exerciseRepository = mock(); + private final ExerciseTestRepository exerciseRepository = mock(); private final PlagiarismDetectionService plagiarismChecksService = mock(); diff --git a/src/test/java/de/tum/cit/aet/artemis/plagiarism/PlagiarismUtilService.java b/src/test/java/de/tum/cit/aet/artemis/plagiarism/PlagiarismUtilService.java index 67208a1bfabe..d08bda49304e 100644 --- a/src/test/java/de/tum/cit/aet/artemis/plagiarism/PlagiarismUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/plagiarism/PlagiarismUtilService.java @@ -21,7 +21,7 @@ import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationFactory; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; +import de.tum.cit.aet.artemis.exercise.repository.ExerciseTestRepository; import de.tum.cit.aet.artemis.exercise.team.TeamUtilService; import de.tum.cit.aet.artemis.exercise.test_repository.StudentParticipationTestRepository; import de.tum.cit.aet.artemis.exercise.test_repository.SubmissionTestRepository; @@ -47,7 +47,7 @@ public class PlagiarismUtilService { private CourseTestRepository courseRepo; @Autowired - private ExerciseRepository exerciseRepo; + private ExerciseTestRepository exerciseRepository; @Autowired private StudentParticipationTestRepository studentParticipationRepo; @@ -76,7 +76,7 @@ private TextExercise createTextExercise(String userPrefix, int studentsAmount, E exercise.setMode(mode); course.addExercises(exercise); courseRepo.save(course); - return exerciseRepo.save(exercise); + return exerciseRepository.save(exercise); } private ModelingExercise createModelingExercise(String userPrefix, int studentsAmount, ExerciseMode mode) { @@ -85,7 +85,7 @@ private ModelingExercise createModelingExercise(String userPrefix, int studentsA exercise.setMode(mode); course.addExercises(exercise); courseRepo.save(course); - return exerciseRepo.save(exercise); + return exerciseRepository.save(exercise); } private StudentParticipation saveParticipationAndAddSubmission(StudentParticipation participation, Submission submission) { @@ -115,7 +115,7 @@ public long createTextExerciseAndSimilarSubmissions(String userPrefix, String si saveParticipationAndAddSubmission(participation, submission); exercise.addParticipation(participation); } - exerciseRepo.save(exercise); + exerciseRepository.save(exercise); return exercise.getId(); } @@ -137,7 +137,7 @@ public long createTeamTextExerciseAndSimilarSubmissions(String userPrefix, Strin saveParticipationAndAddSubmission(participation, submission); exercise.addParticipation(participation); } - exerciseRepo.save(exercise); + exerciseRepository.save(exercise); return exercise.getId(); } @@ -159,7 +159,7 @@ public long createModelingExerciseAndSimilarSubmissionsToTheCourse(String userPr saveParticipationAndAddSubmission(participation, submission); exercise.addParticipation(participation); } - exerciseRepo.save(exercise); + exerciseRepository.save(exercise); return exercise.getId(); } @@ -181,7 +181,7 @@ public long createTeamModelingExerciseAndSimilarSubmissionsToTheCourse(String us saveParticipationAndAddSubmission(participation, submission); exercise.addParticipation(participation); } - exerciseRepo.save(exercise); + exerciseRepository.save(exercise); return exercise.getId(); } diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/CourseGitlabJenkinsIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/CourseGitlabJenkinsIntegrationTest.java index 50e46b7e57db..f23887c80d43 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/CourseGitlabJenkinsIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/CourseGitlabJenkinsIntegrationTest.java @@ -46,7 +46,6 @@ class CourseGitlabJenkinsIntegrationTest extends AbstractSpringIntegrationJenkin @BeforeEach void setup() { - participantScoreScheduleService.activate(); courseTestService.setup(TEST_PREFIX, this); gitlabRequestMockProvider.enableMockingOfRequests(); jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseGradingServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseGradingServiceTest.java index 3b7b0bad36c8..b38a7248ae5f 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseGradingServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseGradingServiceTest.java @@ -47,7 +47,6 @@ import de.tum.cit.aet.artemis.exercise.domain.participation.Participation; import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.exercise.test_repository.StudentParticipationTestRepository; import de.tum.cit.aet.artemis.exercise.util.ExerciseUtilService; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; @@ -84,9 +83,6 @@ abstract class ProgrammingExerciseGradingServiceTest extends AbstractSpringInteg @Autowired private ProgrammingExerciseTestCaseTestRepository testCaseRepository; - @Autowired - private ExerciseRepository exerciseRepository; - @Autowired private StudentParticipationTestRepository studentParticipationRepository; diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseTestCaseServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseTestCaseServiceTest.java index 2d985741a637..cf293bf0740c 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseTestCaseServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseTestCaseServiceTest.java @@ -4,7 +4,6 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.Collections; @@ -95,7 +94,7 @@ void shouldResetExamExerciseTestCases() { private void testResetTestCases(ProgrammingExercise programmingExercise, Visibility expectedVisibility) { String dummyHash = "9b3a9bd71a0d80e5bbc42204c319ed3d1d4f0d6d"; - when(gitService.getLastCommitHash(any())).thenReturn(ObjectId.fromString(dummyHash)); + doReturn(ObjectId.fromString(dummyHash)).when(gitService).getLastCommitHash(any()); participationUtilService.addProgrammingParticipationWithResultForExercise(programmingExercise, TEST_PREFIX + "student1"); new ArrayList<>(testCaseRepository.findByExerciseId(programmingExercise.getId())).getFirst().weight(50.0); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/hestia/ProgrammingExerciseGitDiffReportIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/hestia/ProgrammingExerciseGitDiffReportIntegrationTest.java index d0144595c37b..6d5bf267139a 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/hestia/ProgrammingExerciseGitDiffReportIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/hestia/ProgrammingExerciseGitDiffReportIntegrationTest.java @@ -53,6 +53,11 @@ void initTestCase() throws Exception { exercise = ProgrammingExerciseFactory.generateProgrammingExercise(ZonedDateTime.now().minusDays(1), ZonedDateTime.now().plusDays(7), course); } + @Override + protected String getTestPrefix() { + return TEST_PREFIX; + } + @AfterEach void cleanup() throws Exception { solutionRepo.resetLocalRepo(); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/hestia/ProgrammingExerciseGitDiffReportServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/hestia/ProgrammingExerciseGitDiffReportServiceTest.java index fefa75a3cf8a..70e3c6fb8301 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/hestia/ProgrammingExerciseGitDiffReportServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/hestia/ProgrammingExerciseGitDiffReportServiceTest.java @@ -58,6 +58,11 @@ class ProgrammingExerciseGitDiffReportServiceTest extends AbstractLocalCILocalVC @Autowired private ProgrammingExerciseGitDiffReportRepository reportRepository; + @Override + protected String getTestPrefix() { + return TEST_PREFIX; + } + @BeforeEach void initTestCase() { userUtilService.addUsers(TEST_PREFIX, 1, 1, 1, 1); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/hestia/StructuralTestCaseServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/hestia/StructuralTestCaseServiceTest.java index 21e976498ece..08d6f5994ac7 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/hestia/StructuralTestCaseServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/hestia/StructuralTestCaseServiceTest.java @@ -56,6 +56,11 @@ class StructuralTestCaseServiceTest extends AbstractLocalCILocalVCIntegrationTes private ProgrammingExercise exercise; + @Override + protected String getTestPrefix() { + return TEST_PREFIX; + } + @BeforeEach void initTestCase() { Course course = courseUtilService.addEmptyCourse(); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/hestia/TestwiseCoverageReportServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/hestia/TestwiseCoverageReportServiceTest.java index fc7b8542022a..ba89b52dc804 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/hestia/TestwiseCoverageReportServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/hestia/TestwiseCoverageReportServiceTest.java @@ -69,6 +69,11 @@ class TestwiseCoverageReportServiceTest extends AbstractLocalCILocalVCIntegratio private final LocalRepository solutionRepo = new LocalRepository("main"); + @Override + protected String getTestPrefix() { + return TEST_PREFIX; + } + @BeforeEach void setup() throws Exception { userUtilService.addUsers(TEST_PREFIX, 1, 0, 0, 1); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/hestia/behavioral/BehavioralTestCaseServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/hestia/behavioral/BehavioralTestCaseServiceTest.java index 802284ffd99f..f646dd652ded 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/hestia/behavioral/BehavioralTestCaseServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/hestia/behavioral/BehavioralTestCaseServiceTest.java @@ -78,6 +78,11 @@ class BehavioralTestCaseServiceTest extends AbstractLocalCILocalVCIntegrationTes private ProgrammingExercise exercise; + @Override + protected String getTestPrefix() { + return TEST_PREFIX; + } + @BeforeEach void initTestCase() { userUtilService.addUsers(TEST_PREFIX, 0, 0, 0, 1); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/icl/AbstractLocalCILocalVCIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/icl/AbstractLocalCILocalVCIntegrationTest.java index 08fe3a292db2..0f559f0c3a50 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/icl/AbstractLocalCILocalVCIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/icl/AbstractLocalCILocalVCIntegrationTest.java @@ -30,9 +30,7 @@ import de.tum.cit.aet.artemis.programming.util.ProgrammingExerciseUtilService; import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationLocalCILocalVCTest; -public class AbstractLocalCILocalVCIntegrationTest extends AbstractSpringIntegrationLocalCILocalVCTest { - - protected static final String TEST_PREFIX = "localvclocalciintegration"; +public abstract class AbstractLocalCILocalVCIntegrationTest extends AbstractSpringIntegrationLocalCILocalVCTest { @Autowired protected TeamRepository teamRepository; @@ -112,20 +110,23 @@ public class AbstractLocalCILocalVCIntegrationTest extends AbstractSpringIntegra protected String auxiliaryRepositorySlug; + protected abstract String getTestPrefix(); + @BeforeEach void initUsersAndExercise() throws JsonProcessingException { // The port cannot be injected into the LocalVCLocalCITestService because {local.server.port} is not available when the class is instantiated. // Thus, "inject" the port from here. localVCLocalCITestService.setPort(port); - List users = userUtilService.addUsers(TEST_PREFIX, 2, 1, 0, 2); - student1Login = TEST_PREFIX + "student1"; + String testPrefix = getTestPrefix(); + List users = userUtilService.addUsers(testPrefix, 2, 1, 0, 2); + student1Login = testPrefix + "student1"; student1 = users.stream().filter(user -> student1Login.equals(user.getLogin())).findFirst().orElseThrow(); - student2Login = TEST_PREFIX + "student2"; - tutor1Login = TEST_PREFIX + "tutor1"; - instructor1Login = TEST_PREFIX + "instructor1"; + student2Login = testPrefix + "student2"; + tutor1Login = testPrefix + "tutor1"; + instructor1Login = testPrefix + "instructor1"; instructor1 = users.stream().filter(user -> instructor1Login.equals(user.getLogin())).findFirst().orElseThrow(); - instructor2Login = TEST_PREFIX + "instructor2"; + instructor2Login = testPrefix + "instructor2"; instructor2 = users.stream().filter(user -> instructor2Login.equals(user.getLogin())).findFirst().orElseThrow(); // Remove instructor2 from the instructor group of the course. instructor2.setGroups(Set.of()); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalCIIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalCIIntegrationTest.java index 871bfbb73616..7d17a712b4ec 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalCIIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalCIIntegrationTest.java @@ -81,6 +81,8 @@ @Execution(ExecutionMode.SAME_THREAD) class LocalCIIntegrationTest extends AbstractLocalCILocalVCIntegrationTest { + private static final String TEST_PREFIX = "localciint"; + @Autowired private LocalVCServletService localVCServletService; @@ -99,6 +101,11 @@ class LocalCIIntegrationTest extends AbstractLocalCILocalVCIntegrationTest { @Value("${artemis.user-management.internal-admin.password}") private String localVCPassword; + @Override + protected String getTestPrefix() { + return TEST_PREFIX; + } + private LocalRepository studentAssignmentRepository; private LocalRepository testsRepository; @@ -262,7 +269,8 @@ void testCommitHashNull() { // Should still work because in that case the latest commit should be retrieved from the repository. localVCServletService.processNewPush(null, studentAssignmentRepository.originGit.getRepository()); - localVCLocalCITestService.testLatestSubmission(studentParticipation.getId(), commitHash, 1, false); + // ToDo: Investigate why specifically this test requires so much time (all other << 5s) + localVCLocalCITestService.testLatestSubmission(studentParticipation.getId(), commitHash, 1, false, 120); } @Test @@ -291,7 +299,7 @@ void testProjectTypeIsNull() { @Test @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") - void testCannotFindResults() { + void testResultsNotFound() { ProgrammingExerciseStudentParticipation studentParticipation = localVCLocalCITestService.createParticipation(programmingExercise, student1Login); // Should return a build result that indicates that the build failed. diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalCIResourceIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalCIResourceIntegrationTest.java index 21aa7037c377..3b7ced720320 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalCIResourceIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalCIResourceIntegrationTest.java @@ -42,6 +42,8 @@ class LocalCIResourceIntegrationTest extends AbstractLocalCILocalVCIntegrationTest { + private static final String TEST_PREFIX = "localciresourceint"; + @Autowired @Qualifier("hazelcastInstance") private HazelcastInstance hazelcastInstance; @@ -75,6 +77,11 @@ class LocalCIResourceIntegrationTest extends AbstractLocalCILocalVCIntegrationTe protected IMap buildAgentInformation; + @Override + protected String getTestPrefix() { + return TEST_PREFIX; + } + @BeforeEach void createJobs() { // temporarily remove listener to avoid triggering build job processing @@ -136,11 +143,10 @@ void clearDataStructures() { @WithMockUser(username = TEST_PREFIX + "admin", roles = "ADMIN") void testGetQueuedBuildJobs_returnsJobs() throws Exception { var retrievedJobs = request.get("/api/admin/queued-jobs", HttpStatus.OK, List.class); - assertThat(retrievedJobs).isEmpty(); // Adding a lot of jobs as they get processed very quickly due to mocking queuedJobs.addAll(List.of(job1, job2)); var retrievedJobs1 = request.get("/api/admin/queued-jobs", HttpStatus.OK, List.class); - assertThat(retrievedJobs1).hasSize(2); + assertThat(retrievedJobs1).hasSize(retrievedJobs.size() + 2); } @Test diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalCIResultServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalCIResultServiceTest.java index e0947713cf57..a951d065e0cc 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalCIResultServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalCIResultServiceTest.java @@ -14,9 +14,16 @@ class LocalCIResultServiceTest extends AbstractLocalCILocalVCIntegrationTest { + private static final String TEST_PREFIX = "localciresultservice"; + @Autowired private LocalCIResultService localCIResultService; + @Override + protected String getTestPrefix() { + return TEST_PREFIX; + } + @Test void testThrowsExceptionWhenResultIsNotLocalCIBuildResult() { var wrongBuildResult = ProgrammingExerciseFactory.generateTestResultDTO("some-name", "some-repository", ZonedDateTime.now().minusSeconds(10), diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalVCIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalVCIntegrationTest.java index e57b850240a2..1531a4e2649a 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalVCIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalVCIntegrationTest.java @@ -37,6 +37,8 @@ */ class LocalVCIntegrationTest extends AbstractLocalCILocalVCIntegrationTest { + private static final String TEST_PREFIX = "localvcint"; + private LocalRepository assignmentRepository; private LocalRepository templateRepository; @@ -60,6 +62,11 @@ void initRepositories() throws GitAPIException, IOException, URISyntaxException testsRepository = localVCLocalCITestService.createAndConfigureLocalRepository(projectKey1, projectKey1.toLowerCase() + "-tests"); } + @Override + protected String getTestPrefix() { + return TEST_PREFIX; + } + @AfterEach void removeRepositories() throws IOException { assignmentRepository.resetLocalRepo(); @@ -77,7 +84,16 @@ void testFetchPush_repositoryDoesNotExist() throws IOException, GitAPIException, // Delete the remote repository. someRepository.originGit.close(); - FileUtils.deleteDirectory(someRepository.originRepoFile); + try { + FileUtils.deleteDirectory(someRepository.originRepoFile); + } + catch (IOException exception) { + // JGit creates a lock file in each repository that could cause deletion problems. + if (exception.getMessage().contains("gc.log.lock")) { + return; + } + throw exception; + } // Try to fetch from the remote repository. localVCLocalCITestService.testFetchThrowsException(someRepository.localGit, student1Login, USER_PASSWORD, projectKey, repositorySlug, InvalidRemoteException.class, ""); @@ -122,7 +138,7 @@ void testFetchPush_usingVcsAccessToken() { @Test void testFetchPush_wrongCredentials() throws InvalidNameException { - var student1 = new LdapUserDto().login(TEST_PREFIX + "student1"); + var student1 = new LdapUserDto().login(getTestPrefix() + "student1"); student1.setUid(new LdapName("cn=student1,ou=test,o=lab")); var fakeUser = new LdapUserDto().login(localVCBaseUsername); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalVCLocalCIIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalVCLocalCIIntegrationTest.java index 526d5b8a522f..877ccd6493e0 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalVCLocalCIIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalVCLocalCIIntegrationTest.java @@ -87,6 +87,8 @@ class LocalVCLocalCIIntegrationTest extends AbstractLocalCILocalVCIntegrationTes private static final Logger log = LoggerFactory.getLogger(LocalVCLocalCIIntegrationTest.class); + private static final String TEST_PREFIX = "localvcciint"; + @Autowired private ExamUtilService examUtilService; @@ -124,6 +126,11 @@ class LocalVCLocalCIIntegrationTest extends AbstractLocalCILocalVCIntegrationTes protected IQueue queuedJobs; + @Override + protected String getTestPrefix() { + return TEST_PREFIX; + } + @BeforeAll void setupAll() { CredentialsProvider.setDefault(new UsernamePasswordCredentialsProvider(localVCUsername, localVCPassword)); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalVCLocalCITestService.java b/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalVCLocalCITestService.java index 08c02eedceed..c6d7556e3766 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalVCLocalCITestService.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalVCLocalCITestService.java @@ -54,6 +54,7 @@ import de.tum.cit.aet.artemis.assessment.domain.Result; import de.tum.cit.aet.artemis.assessment.domain.Visibility; +import de.tum.cit.aet.artemis.assessment.service.ParticipantScoreScheduleService; import de.tum.cit.aet.artemis.assessment.test_repository.ResultTestRepository; import de.tum.cit.aet.artemis.core.domain.User; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; @@ -91,6 +92,9 @@ public class LocalVCLocalCITestService { @Autowired private ParticipationVcsAccessTokenService participationVcsAccessTokenService; + @Autowired + private ParticipantScoreScheduleService participantScoreScheduleService; + @Autowired private ResultTestRepository resultRepository; @@ -573,7 +577,11 @@ public void testLatestSubmission(Long participationId, String expectedCommitHash int expectedCodeIssueCount, Integer timeoutInSeconds) { // wait for result to be persisted Duration timeoutDuration = timeoutInSeconds != null ? Duration.ofSeconds(timeoutInSeconds) : Duration.ofSeconds(DEFAULT_AWAITILITY_TIMEOUT_IN_SECONDS); - await().atMost(timeoutDuration).until(() -> resultRepository.findFirstWithSubmissionsByParticipationIdOrderByCompletionDateDesc(participationId).isPresent()); + await().atMost(timeoutDuration).until(() -> { + participantScoreScheduleService.executeScheduledTasks(); + await().until(participantScoreScheduleService::isIdle); + return resultRepository.findFirstWithSubmissionsByParticipationIdOrderByCompletionDateDesc(participationId).isPresent(); + }); Authentication auth = SecurityContextHolder.getContext().getAuthentication(); List submissions = programmingSubmissionRepository.findAllByParticipationIdWithResults(participationId); @@ -609,6 +617,10 @@ public void testLatestSubmission(Long participationId, String expectedCommitHash testLatestSubmission(participationId, expectedCommitHash, expectedSuccessfulTestCaseCount, buildFailed, false, 0, null); } + public void testLatestSubmission(Long participationId, String expectedCommitHash, int expectedSuccessfulTestCaseCount, boolean buildFailed, int timeoutInSeconds) { + testLatestSubmission(participationId, expectedCommitHash, expectedSuccessfulTestCaseCount, buildFailed, false, 0, timeoutInSeconds); + } + /** * Perform a push operation and fail if there was no exception. * diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalVCSshIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalVCSshIntegrationTest.java index ac20db1c209c..bca0ed60beb9 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalVCSshIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/icl/LocalVCSshIntegrationTest.java @@ -12,6 +12,7 @@ import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; +import java.util.Objects; import java.util.concurrent.TimeUnit; import org.apache.sshd.client.SshClient; @@ -20,6 +21,7 @@ import org.apache.sshd.common.SshException; import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; import org.apache.sshd.common.config.keys.writer.openssh.OpenSSHKeyPairResourceWriter; +import org.apache.sshd.common.session.helpers.AbstractSession; import org.apache.sshd.server.SshServer; import org.apache.sshd.server.session.ServerSession; import org.junit.jupiter.api.Test; @@ -35,9 +37,16 @@ @Profile(PROFILE_LOCALVC) class LocalVCSshIntegrationTest extends LocalVCIntegrationTest { + private static final String TEST_PREFIX = "localvcsshint"; + @Autowired private SshServer sshServer; + @Override + protected String getTestPrefix() { + return TEST_PREFIX; + } + private final String hostname = "localhost"; private final int port = 7921; @@ -111,8 +120,8 @@ void testAuthenticationFailure() { void testConnectOverSshAndReceivePack() throws IOException, GeneralSecurityException { try (var client = clientConnectToArtemisSshServer()) { assertThat(client).isNotNull(); - var serverSessions = sshServer.getActiveSessions(); - var serverSession = serverSessions.getFirst(); + var user = userTestRepository.getUser(); + var serverSession = getCurrentServerSession(user); final var uploadCommandString = "git-upload-pack '/git/" + projectKey1 + "/" + templateRepositorySlug + "'"; @@ -144,9 +153,13 @@ private SshGitCommand setupCommand(String commandString, ServerSession serverSes return command; } + /** + * Note: Don't count unattached sessions as a potential result from previous tests. + * See {@link org.apache.sshd.server.SshServer#getActiveSessions} + * and {@link org.apache.sshd.common.session.helpers.AbstractSession#getSession}. + */ private SshClient clientConnectToArtemisSshServer() throws GeneralSecurityException, IOException { var serverSessions = sshServer.getActiveSessions(); - var numberOfSessions = serverSessions.size(); localVCLocalCITestService.createParticipation(programmingExercise, student1Login); KeyPair keyPair = setupKeyPairAndAddToUser(); User user = userTestRepository.getUser(); @@ -155,6 +168,7 @@ private SshClient clientConnectToArtemisSshServer() throws GeneralSecurityExcept client.start(); ClientSession clientSession; + int numberOfSessions = serverSessions.size(); try { ConnectFuture connectFuture = client.connect(user.getName(), hostname, port); connectFuture.await(10, TimeUnit.SECONDS); @@ -169,11 +183,20 @@ private SshClient clientConnectToArtemisSshServer() throws GeneralSecurityExcept } serverSessions = sshServer.getActiveSessions(); + var attachedServerSessions = serverSessions.stream().filter(Objects::nonNull).count(); assertThat(clientSession.isAuthenticated()).isTrue(); - assertThat(serverSessions.size()).isEqualTo(numberOfSessions + 1); + assertThat(attachedServerSessions).as("There are more server sessions activated than expected.").isEqualTo(numberOfSessions + 1); return client; } + private AbstractSession getCurrentServerSession(User user) { + var serverSessions = sshServer.getActiveSessions(); + // parallel tests might create additional sessions, we need to be specific + var serverSession = serverSessions.stream().filter(session -> user.getName().equals(session.getUsername())).findFirst(); + + return serverSession.orElseThrow(() -> new IllegalStateException("No server session found for user " + user.getName())); + } + private KeyPair setupKeyPairAndAddToUser() throws GeneralSecurityException, IOException { User user = userTestRepository.getUser(); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/icl/MultipleHostKeyProviderTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/icl/MultipleHostKeyProviderTest.java index bf9c4cde79fc..a386ae6ccc2d 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/icl/MultipleHostKeyProviderTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/icl/MultipleHostKeyProviderTest.java @@ -13,6 +13,13 @@ @Profile(PROFILE_LOCALVC) class MultipleHostKeyProviderTest extends AbstractLocalCILocalVCIntegrationTest { + private static final String TEST_PREFIX = "multiplehostkeyprovider"; + + @Override + protected String getTestPrefix() { + return TEST_PREFIX; + } + @Test void testMultipleHostKeyProvider() { MultipleHostKeyProvider multipleHostKeyProvider = new MultipleHostKeyProvider(Path.of("./")); diff --git a/src/test/java/de/tum/cit/aet/artemis/quiz/QuizSubmissionIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/quiz/QuizSubmissionIntegrationTest.java index 5a56c62d5273..41736fa7c6a9 100644 --- a/src/test/java/de/tum/cit/aet/artemis/quiz/QuizSubmissionIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/quiz/QuizSubmissionIntegrationTest.java @@ -1,12 +1,12 @@ package de.tum.cit.aet.artemis.quiz; +import static de.tum.cit.aet.artemis.core.config.Constants.EXERCISE_TOPIC_ROOT; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; import java.io.IOException; import java.time.Duration; @@ -414,7 +414,7 @@ void testQuizSubmitPractice_badRequest() throws Exception { Result result = request.postWithResponseBody("/api/exercises/" + quizExerciseServer.getId() + "/submissions/practice", quizSubmission, Result.class, HttpStatus.BAD_REQUEST); assertThat(result).isNull(); - verifyNoInteractions(websocketMessagingService); + verifyNoWebsocketMessageForExercise(quizExerciseServer); } @Test @@ -431,7 +431,7 @@ void testQuizSubmitPractice_badRequest_exam() throws Exception { Result result = request.postWithResponseBody("/api/exercises/" + quizExerciseServer.getId() + "/submissions/practice", quizSubmission, Result.class, HttpStatus.BAD_REQUEST); assertThat(result).isNull(); - verifyNoInteractions(websocketMessagingService); + verifyNoWebsocketMessageForExercise(quizExerciseServer); } @Test @@ -451,7 +451,7 @@ void testQuizSubmitPractice_forbidden() throws Exception { QuizExercise quizExercise = QuizExerciseFactory.createQuiz(course, ZonedDateTime.now().minusSeconds(4), null, QuizMode.SYNCHRONIZED); quizExerciseService.save(quizExercise); request.postWithResponseBody("/api/exercises/" + quizExercise.getId() + "/submissions/practice", new QuizSubmission(), Result.class, HttpStatus.FORBIDDEN); - verifyNoInteractions(websocketMessagingService); + verifyNoWebsocketMessageForExercise(quizExercise); } @Test @@ -757,6 +757,12 @@ private QuizExercise setupQuizExerciseParameters() { return quizExercise; } + private void verifyNoWebsocketMessageForExercise(QuizExercise exercise) { + String topic = EXERCISE_TOPIC_ROOT + exercise.getId() + "/newResults"; + verify(websocketMessagingService, never()).sendMessage(eq(topic), any()); + verify(websocketMessagingService, never()).sendMessageToUser(any(), eq(topic), any()); + } + @Nested @Isolated class QuizSubmitLiveModeIsolatedTest { diff --git a/src/test/java/de/tum/cit/aet/artemis/quiz/util/QuizExerciseUtilService.java b/src/test/java/de/tum/cit/aet/artemis/quiz/util/QuizExerciseUtilService.java index 261ac6f912b0..2c378bb69fe9 100644 --- a/src/test/java/de/tum/cit/aet/artemis/quiz/util/QuizExerciseUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/quiz/util/QuizExerciseUtilService.java @@ -31,7 +31,7 @@ import de.tum.cit.aet.artemis.exercise.domain.TeamAssignmentConfig; import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; +import de.tum.cit.aet.artemis.exercise.repository.ExerciseTestRepository; import de.tum.cit.aet.artemis.exercise.repository.TeamRepository; import de.tum.cit.aet.artemis.exercise.test_repository.StudentParticipationTestRepository; import de.tum.cit.aet.artemis.quiz.domain.DragAndDropMapping; @@ -70,7 +70,7 @@ public class QuizExerciseUtilService { private CourseTestRepository courseRepo; @Autowired - private ExerciseRepository exerciseRepo; + private ExerciseTestRepository exerciseRepository; @Autowired private QuizSubmissionTestRepository quizSubmissionRepository; @@ -137,7 +137,7 @@ public Course addCourseWithOneQuizExercise(String title) { assertThat(quizExercise.isValid()).isTrue(); course.addExercises(quizExercise); course = courseRepo.save(course); - quizExercise = exerciseRepo.save(quizExercise); + quizExercise = exerciseRepository.save(quizExercise); assertThat(courseRepo.findWithEagerExercisesById(course.getId()).getExercises()).as("course contains the exercise").contains(quizExercise); return course; } diff --git a/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractArtemisIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractArtemisIntegrationTest.java index 57706d00e482..469e4f117837 100644 --- a/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractArtemisIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractArtemisIntegrationTest.java @@ -4,7 +4,9 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import java.time.Instant; import java.util.List; +import java.util.Optional; import jakarta.mail.internet.MimeMessage; @@ -23,6 +25,7 @@ import org.springframework.context.annotation.Import; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.util.ReflectionTestUtils; import de.tum.cit.aet.artemis.assessment.service.ParticipantScoreScheduleService; import de.tum.cit.aet.artemis.assessment.test_repository.ResultTestRepository; @@ -51,7 +54,7 @@ import de.tum.cit.aet.artemis.core.util.RequestUtilService; import de.tum.cit.aet.artemis.core.util.ThrowingProducer; import de.tum.cit.aet.artemis.exam.service.ExamAccessService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; +import de.tum.cit.aet.artemis.exercise.repository.ExerciseTestRepository; import de.tum.cit.aet.artemis.exercise.util.ExerciseUtilService; import de.tum.cit.aet.artemis.lti.service.Lti13Service; import de.tum.cit.aet.artemis.modeling.service.ModelingSubmissionService; @@ -186,7 +189,7 @@ public abstract class AbstractArtemisIntegrationTest implements MockDelegate { protected UserTestRepository userTestRepository; @Autowired - protected ExerciseRepository exerciseRepository; + protected ExerciseTestRepository exerciseRepository; @Autowired protected ResultTestRepository resultRepository; @@ -199,6 +202,14 @@ void mockMailService() { doNothing().when(javaMailSender).send(any(MimeMessage.class)); } + @BeforeEach + void resetParticipantScoreScheduler() { + // Prevents the ParticipantScoreScheduleService from scheduling tasks related to prior results + ReflectionTestUtils.setField(participantScoreScheduleService, "lastScheduledRun", Optional.of(Instant.now())); + ParticipantScoreScheduleService.DEFAULT_WAITING_TIME_FOR_SCHEDULED_TASKS = 100; + participantScoreScheduleService.activate(); + } + @AfterEach void stopQuizScheduler() { scheduleService.clearAllTasks(); diff --git a/src/test/java/de/tum/cit/aet/artemis/text/util/TextExerciseUtilService.java b/src/test/java/de/tum/cit/aet/artemis/text/util/TextExerciseUtilService.java index fb1effac7c64..f52f9e7de82d 100644 --- a/src/test/java/de/tum/cit/aet/artemis/text/util/TextExerciseUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/text/util/TextExerciseUtilService.java @@ -34,7 +34,7 @@ import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationFactory; import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; +import de.tum.cit.aet.artemis.exercise.repository.ExerciseTestRepository; import de.tum.cit.aet.artemis.exercise.test_repository.ParticipationTestRepository; import de.tum.cit.aet.artemis.exercise.test_repository.StudentParticipationTestRepository; import de.tum.cit.aet.artemis.exercise.test_repository.SubmissionTestRepository; @@ -62,7 +62,7 @@ public class TextExerciseUtilService { private static final ZonedDateTime futureFutureTimestamp = ZonedDateTime.now().plusDays(2); @Autowired - private ExerciseRepository exerciseRepo; + private ExerciseTestRepository exerciseRepository; @Autowired private CourseTestRepository courseRepo; @@ -194,7 +194,7 @@ public TextExercise createIndividualTextExercise(Course course, ZonedDateTime re TextExercise textExercise = TextExerciseFactory.generateTextExercise(releaseDate, dueDate, assessmentDueDate, course); textExercise.setMaxPoints(10.0); textExercise.setBonusPoints(0.0); - return exerciseRepo.save(textExercise); + return exerciseRepository.save(textExercise); } /** @@ -211,7 +211,7 @@ public TextExercise createTeamTextExercise(Course course, ZonedDateTime releaseD teamTextExercise.setMaxPoints(10.0); teamTextExercise.setBonusPoints(0.0); teamTextExercise.setMode(ExerciseMode.TEAM); - return exerciseRepo.save(teamTextExercise); + return exerciseRepository.save(teamTextExercise); } /** @@ -226,7 +226,7 @@ public Course addCourseWithOneReleasedTextExercise(String title) { textExercise.setTitle(title); course.addExercises(textExercise); course = courseRepo.save(course); - textExercise = exerciseRepo.save(textExercise); + textExercise = exerciseRepository.save(textExercise); assertThat(courseRepo.findWithEagerExercisesById(course.getId()).getExercises()).as("course contains the exercise").contains(textExercise); assertThat(textExercise.getPresentationScoreEnabled()).as("presentation score is enabled").isTrue(); @@ -268,7 +268,7 @@ public TextExercise addCourseExamExerciseGroupWithOneTextExercise(String title) if (title != null) { textExercise.setTitle(title); } - return exerciseRepo.save(textExercise); + return exerciseRepository.save(textExercise); } /** @@ -288,7 +288,7 @@ public TextExercise addCourseExamExerciseGroupWithOneTextExercise() { public TextExercise addCourseExamWithReviewDatesExerciseGroupWithOneTextExercise() { ExerciseGroup exerciseGroup = examUtilService.addExerciseGroupWithExamWithReviewDatesAndCourse(true); TextExercise textExercise = TextExerciseFactory.generateTextExerciseForExam(exerciseGroup); - return exerciseRepo.save(textExercise); + return exerciseRepository.save(textExercise); } /** @@ -493,7 +493,7 @@ public Course addCourseWithOneFinishedTextExercise() { finishedTextExercise.setTitle("Finished"); course.addExercises(finishedTextExercise); course = courseRepo.save(course); - exerciseRepo.save(finishedTextExercise); + exerciseRepository.save(finishedTextExercise); return course; } diff --git a/src/test/javascript/spec/component/assessment-dashboard/exercise-assessment-dashboard.component.spec.ts b/src/test/javascript/spec/component/assessment-dashboard/exercise-assessment-dashboard.component.spec.ts index 92a1d051230e..b122fb9fd8b9 100644 --- a/src/test/javascript/spec/component/assessment-dashboard/exercise-assessment-dashboard.component.spec.ts +++ b/src/test/javascript/spec/component/assessment-dashboard/exercise-assessment-dashboard.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { ArtemisTestModule } from '../../test.module'; import { MockComponent, MockDirective, MockModule, MockPipe, MockProvider } from 'ng-mocks'; -import { ActivatedRoute, Router, convertToParamMap } from '@angular/router'; +import { ActivatedRoute, Router, RouterModule, convertToParamMap } from '@angular/router'; import { of, throwError } from 'rxjs'; import { HttpErrorResponse, HttpHeaders, HttpResponse } from '@angular/common/http'; import { SidePanelComponent } from 'app/shared/side-panel/side-panel.component'; @@ -57,7 +57,6 @@ import { MockTranslateValuesDirective } from '../../helpers/mocks/directive/mock import { PieChartModule } from '@swimlane/ngx-charts'; import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service'; -import { RouterTestingModule } from '@angular/router/testing'; import { SortService } from 'app/shared/service/sort.service'; import { ArtemisMarkdownService } from 'app/shared/markdown.service'; import { AccountService } from 'app/core/auth/account.service'; @@ -211,7 +210,7 @@ describe('ExerciseAssessmentDashboardComponent', () => { }), }, } as any as ActivatedRoute; - const imports = [ArtemisTestModule, RouterTestingModule.withRoutes([]), MockModule(PieChartModule)]; + const imports = [ArtemisTestModule, MockModule(PieChartModule), RouterModule.forRoot([])]; const declarations = [ ExerciseAssessmentDashboardComponent, diff --git a/src/test/javascript/spec/component/assessment-shared/assessment-header.component.spec.ts b/src/test/javascript/spec/component/assessment-shared/assessment-header.component.spec.ts index 6c151bfbce91..a85e5e47bc16 100644 --- a/src/test/javascript/spec/component/assessment-shared/assessment-header.component.spec.ts +++ b/src/test/javascript/spec/component/assessment-shared/assessment-header.component.spec.ts @@ -6,7 +6,6 @@ import { AssessmentHeaderComponent } from 'app/assessment/assessment-header/asse import { ArtemisTestModule } from '../../test.module'; import { Result } from 'app/entities/result.model'; import { AlertOverlayComponent } from 'app/shared/alert/alert-overlay.component'; -import { RouterTestingModule } from '@angular/router/testing'; import { AssessmentWarningComponent } from 'app/assessment/assessment-warning/assessment-warning.component'; import { MockProvider } from 'ng-mocks'; import { Exercise, ExerciseType } from 'app/entities/exercise.model'; @@ -25,6 +24,7 @@ import { MockTranslateValuesDirective } from '../../helpers/mocks/directive/mock import { NgbTooltipMocksModule } from '../../helpers/mocks/directive/ngbTooltipMocks.module'; import { NgbAlertsMocksModule } from '../../helpers/mocks/directive/ngbAlertsMocks.module'; import { AssessmentNote } from 'app/entities/assessment-note.model'; +import { RouterModule } from '@angular/router'; describe('AssessmentHeaderComponent', () => { let component: AssessmentHeaderComponent; @@ -49,7 +49,7 @@ describe('AssessmentHeaderComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule, NgbTooltipMocksModule, NgbAlertsMocksModule], + imports: [ArtemisTestModule, NgbTooltipMocksModule, NgbAlertsMocksModule, RouterModule.forRoot([])], declarations: [AssessmentHeaderComponent, AssessmentWarningComponent, AlertOverlayComponent, TranslateDirective, ArtemisTranslatePipe, MockTranslateValuesDirective], providers: [ { diff --git a/src/test/javascript/spec/component/competencies/competency-management/competency-management.component.spec.ts b/src/test/javascript/spec/component/competencies/competency-management/competency-management.component.spec.ts index ff928301bb37..b0305c56b76d 100644 --- a/src/test/javascript/spec/component/competencies/competency-management/competency-management.component.spec.ts +++ b/src/test/javascript/spec/component/competencies/competency-management/competency-management.component.spec.ts @@ -4,9 +4,8 @@ import { MockComponent, MockDirective, MockPipe, MockProvider } from 'ng-mocks'; import { of } from 'rxjs'; import { Competency, CompetencyRelation, CompetencyWithTailRelationDTO, CourseCompetencyProgress, CourseCompetencyType } from 'app/entities/competency.model'; import { CompetencyManagementComponent } from 'app/course/competencies/competency-management/competency-management.component'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, provideRouter } from '@angular/router'; import { DeleteButtonDirective } from 'app/shared/delete-dialog/delete-button.directive'; -import { RouterTestingModule } from '@angular/router/testing'; import { TextUnit } from 'app/entities/lecture-unit/textUnit.model'; import { AccountService } from 'app/core/auth/account.service'; import { ArtemisTestModule } from '../../../test.module'; @@ -47,7 +46,7 @@ describe('CompetencyManagementComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule.withRoutes([]), NgbProgressbar], + imports: [ArtemisTestModule, NgbProgressbar], declarations: [ CompetencyManagementComponent, MockHasAnyAuthorityDirective, @@ -61,6 +60,7 @@ describe('CompetencyManagementComponent', () => { MockDirective(DeleteButtonDirective), ], providers: [ + provideRouter([]), MockProvider(AccountService), MockProvider(AlertService), { provide: NgbModal, useClass: MockNgbModalService }, diff --git a/src/test/javascript/spec/component/competencies/competency-popover.component.spec.ts b/src/test/javascript/spec/component/competencies/competency-popover.component.spec.ts index 95d919c34ec0..551d03446489 100644 --- a/src/test/javascript/spec/component/competencies/competency-popover.component.spec.ts +++ b/src/test/javascript/spec/component/competencies/competency-popover.component.spec.ts @@ -3,11 +3,11 @@ import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testin import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { MockComponent, MockPipe } from 'ng-mocks'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; -import { RouterTestingModule } from '@angular/router/testing'; import { CompetenciesPopoverComponent } from 'app/course/competencies/competencies-popover/competencies-popover.component'; import { By } from '@angular/platform-browser'; import { Component } from '@angular/core'; import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'; +import { RouterModule } from '@angular/router'; @Component({ selector: 'jhi-statistics', @@ -29,7 +29,7 @@ describe('CompetencyPopoverComponent', () => { TestBed.configureTestingModule({ imports: [ NgbPopoverModule, - RouterTestingModule.withRoutes([ + RouterModule.forRoot([ { path: 'courses/:courseId/competencies', component: DummyStatisticsComponent }, { path: 'course-management/:courseId/competency-management', component: DummyManagementComponent }, ]), diff --git a/src/test/javascript/spec/component/complaints/complaints-for-tutor.component.spec.ts b/src/test/javascript/spec/component/complaints/complaints-for-tutor.component.spec.ts index c8fdeedcc452..867a683b12d1 100644 --- a/src/test/javascript/spec/component/complaints/complaints-for-tutor.component.spec.ts +++ b/src/test/javascript/spec/component/complaints/complaints-for-tutor.component.spec.ts @@ -5,7 +5,6 @@ import { ComplaintsForTutorComponent } from 'app/complaints/complaints-for-tutor import { ExerciseGroup } from 'app/entities/exercise-group.model'; import { TextareaCounterComponent } from 'app/shared/textarea/textarea-counter.component'; import { MockComponent, MockDirective, MockPipe, MockProvider } from 'ng-mocks'; -import { RouterTestingModule } from '@angular/router/testing'; import { AlertService } from 'app/core/util/alert.service'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; @@ -18,6 +17,7 @@ import { of } from 'rxjs'; import { Course } from 'app/entities/course.model'; import { Exercise } from 'app/entities/exercise.model'; import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { provideRouter } from '@angular/router'; describe('ComplaintsForTutorComponent', () => { let complaintsForTutorComponent: ComplaintsForTutorComponent; @@ -29,7 +29,7 @@ describe('ComplaintsForTutorComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [RouterTestingModule.withRoutes([]), FormsModule], + imports: [FormsModule], declarations: [ ComplaintsForTutorComponent, MockPipe(ArtemisTranslatePipe), @@ -37,7 +37,7 @@ describe('ComplaintsForTutorComponent', () => { MockComponent(TextareaCounterComponent), MockDirective(TranslateDirective), ], - providers: [MockProvider(ComplaintResponseService), MockProvider(ComplaintService), MockProvider(AlertService)], + providers: [provideRouter([]), MockProvider(ComplaintResponseService), MockProvider(ComplaintService), MockProvider(AlertService)], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/course/course-exercises.component.spec.ts b/src/test/javascript/spec/component/course/course-exercises.component.spec.ts index 3a2e57aa1588..0fe1c7509d2d 100644 --- a/src/test/javascript/spec/component/course/course-exercises.component.spec.ts +++ b/src/test/javascript/spec/component/course/course-exercises.component.spec.ts @@ -6,7 +6,6 @@ import { of } from 'rxjs'; import { Course } from 'app/entities/course.model'; import { MockComponent, MockDirective, MockModule, MockPipe } from 'ng-mocks'; import { OrionFilterDirective } from 'app/shared/orion/orion-filter.directive'; -import { RouterTestingModule } from '@angular/router/testing'; import { MockHasAnyAuthorityDirective } from '../../helpers/mocks/directive/mock-has-any-authority.directive'; import { TranslateService } from '@ngx-translate/core'; import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; @@ -15,7 +14,7 @@ import { CourseExercisesComponent } from 'app/overview/course-exercises/course-e import { CourseExerciseRowComponent } from 'app/overview/course-exercises/course-exercise-row.component'; import { SidePanelComponent } from 'app/shared/side-panel/side-panel.component'; import { MockTranslateService, TranslatePipeMock } from '../../helpers/mocks/service/mock-translate.service'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, RouterModule } from '@angular/router'; import { ModelingExercise } from 'app/entities/modeling-exercise.model'; import { Exercise } from 'app/entities/exercise.model'; import dayjs from 'dayjs/esm'; @@ -45,7 +44,7 @@ describe('CourseExercisesComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, FormsModule, RouterTestingModule.withRoutes([]), MockModule(ReactiveFormsModule), MockDirective(TranslateDirective)], + imports: [ArtemisTestModule, FormsModule, RouterModule.forRoot([]), MockModule(ReactiveFormsModule), MockDirective(TranslateDirective)], declarations: [ CourseExercisesComponent, SidebarComponent, diff --git a/src/test/javascript/spec/component/course/course-management.component.spec.ts b/src/test/javascript/spec/component/course/course-management.component.spec.ts index b1b33b125911..947e6a5a3477 100644 --- a/src/test/javascript/spec/component/course/course-management.component.spec.ts +++ b/src/test/javascript/spec/component/course/course-management.component.spec.ts @@ -11,7 +11,6 @@ import { Course } from 'app/entities/course.model'; import { GuidedTourService } from 'app/guided-tour/guided-tour.service'; import { MockComponent, MockDirective, MockPipe, MockProvider } from 'ng-mocks'; import { OrionFilterDirective } from 'app/shared/orion/orion-filter.directive'; -import { RouterTestingModule } from '@angular/router/testing'; import { MockHasAnyAuthorityDirective } from '../../helpers/mocks/directive/mock-has-any-authority.directive'; import { TranslateService } from '@ngx-translate/core'; import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; @@ -25,6 +24,7 @@ import { SortByDirective } from 'app/shared/sort/sort-by.directive'; import { SortDirective } from 'app/shared/sort/sort.directive'; import { DocumentationButtonComponent } from 'app/shared/components/documentation-button/documentation-button.component'; import { CourseAccessStorageService } from 'app/course/course-access-storage.service'; +import { provideRouter } from '@angular/router'; describe('CourseManagementComponent', () => { let fixture: ComponentFixture; @@ -90,7 +90,7 @@ describe('CourseManagementComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule.withRoutes([])], + imports: [ArtemisTestModule], declarations: [ CourseManagementComponent, MockDirective(OrionFilterDirective), @@ -104,6 +104,7 @@ describe('CourseManagementComponent', () => { MockComponent(DocumentationButtonComponent), ], providers: [ + provideRouter([]), { provide: LocalStorageService, useClass: MockSyncStorage }, { provide: SessionStorageService, useClass: MockSyncStorage }, MockProvider(TranslateService), diff --git a/src/test/javascript/spec/component/course/course-overview.component.spec.ts b/src/test/javascript/spec/component/course/course-overview.component.spec.ts index b411f3760596..89d980f29817 100644 --- a/src/test/javascript/spec/component/course/course-overview.component.spec.ts +++ b/src/test/javascript/spec/component/course/course-overview.component.spec.ts @@ -5,10 +5,9 @@ import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testin import { CourseManagementService } from 'app/course/manage/course-management.service'; import { ArtemisTestModule } from '../../test.module'; import { HttpHeaders, HttpResponse } from '@angular/common/http'; -import { ActivatedRoute, Params, Router } from '@angular/router'; +import { ActivatedRoute, Params, Router, RouterModule } from '@angular/router'; import { Course, CourseInformationSharingConfiguration } from 'app/entities/course.model'; import { MockComponent, MockDirective, MockModule, MockPipe, MockProvider } from 'ng-mocks'; -import { RouterTestingModule } from '@angular/router/testing'; import { MockHasAnyAuthorityDirective } from '../../helpers/mocks/directive/mock-has-any-authority.directive'; import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; import { CourseExerciseRowComponent } from 'app/overview/course-exercises/course-exercise-row.component'; @@ -165,8 +164,8 @@ describe('CourseOverviewComponent', () => { TestBed.configureTestingModule({ imports: [ + RouterModule.forRoot([]), ArtemisTestModule, - RouterTestingModule.withRoutes([]), MockModule(MatSidenavModule), MockModule(NgbTooltipModule), MockModule(BrowserAnimationsModule), diff --git a/src/test/javascript/spec/component/course/course-statistics/course-management-statistics.component.spec.ts b/src/test/javascript/spec/component/course/course-statistics/course-management-statistics.component.spec.ts index dde47fbdeadd..5f96004ea5ac 100644 --- a/src/test/javascript/spec/component/course/course-statistics/course-management-statistics.component.spec.ts +++ b/src/test/javascript/spec/component/course/course-statistics/course-management-statistics.component.spec.ts @@ -3,7 +3,6 @@ import { ArtemisTestModule } from '../../../test.module'; import { MockSyncStorage } from '../../../helpers/mocks/service/mock-sync-storage.service'; import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; import { MockComponent, MockDirective, MockPipe } from 'ng-mocks'; -import { RouterTestingModule } from '@angular/router/testing'; import { MockHasAnyAuthorityDirective } from '../../../helpers/mocks/directive/mock-has-any-authority.directive'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; @@ -15,6 +14,7 @@ import { of } from 'rxjs'; import { StatisticsAverageScoreGraphComponent } from 'app/shared/statistics-graph/statistics-average-score-graph.component'; import { ExerciseType } from 'app/entities/exercise.model'; import { DocumentationButtonComponent } from 'app/shared/components/documentation-button/documentation-button.component'; +import { RouterModule } from '@angular/router'; describe('CourseManagementStatisticsComponent', () => { let fixture: ComponentFixture; @@ -31,7 +31,7 @@ describe('CourseManagementStatisticsComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule.withRoutes([])], + imports: [ArtemisTestModule, RouterModule.forRoot([])], declarations: [ CourseManagementStatisticsComponent, MockComponent(StatisticsGraphComponent), diff --git a/src/test/javascript/spec/component/course/course-unenrollment/course-unenrollment-modal.component.spec.ts b/src/test/javascript/spec/component/course/course-unenrollment/course-unenrollment-modal.component.spec.ts index f95c539004ac..3c939b4ecf7a 100644 --- a/src/test/javascript/spec/component/course/course-unenrollment/course-unenrollment-modal.component.spec.ts +++ b/src/test/javascript/spec/component/course/course-unenrollment/course-unenrollment-modal.component.spec.ts @@ -12,9 +12,8 @@ import { By } from '@angular/platform-browser'; import { TranslateService } from '@ngx-translate/core'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { ReactiveFormsModule } from '@angular/forms'; -import { RouterTestingModule } from '@angular/router/testing'; import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; -import { Router } from '@angular/router'; +import { Router, provideRouter } from '@angular/router'; describe('CourseRegistrationButtonComponent', () => { let fixture: ComponentFixture; @@ -29,9 +28,9 @@ describe('CourseRegistrationButtonComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, ReactiveFormsModule, RouterTestingModule.withRoutes([])], + imports: [ArtemisTestModule, ReactiveFormsModule], declarations: [CourseUnenrollmentModalComponent, MockPipe(ArtemisTranslatePipe), MockPipe(ArtemisDatePipe)], - providers: [MockProvider(CourseManagementService), MockProvider(AlertService), MockProvider(TranslateService)], + providers: [provideRouter([]), MockProvider(CourseManagementService), MockProvider(AlertService), MockProvider(TranslateService)], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/course/course.component.spec.ts b/src/test/javascript/spec/component/course/course.component.spec.ts index 0922550c27e3..925a54864297 100644 --- a/src/test/javascript/spec/component/course/course.component.spec.ts +++ b/src/test/javascript/spec/component/course/course.component.spec.ts @@ -1,9 +1,8 @@ import { HttpHeaders, HttpResponse } from '@angular/common/http'; import { HttpTestingController } from '@angular/common/http/testing'; import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { Location } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; import { TranslateService } from '@ngx-translate/core'; import { DueDateStat } from 'app/course/dashboards/due-date-stat.model'; import { CourseForDashboardDTO } from 'app/course/manage/course-for-dashboard-dto'; @@ -34,6 +33,8 @@ import { of } from 'rxjs'; import dayjs from 'dayjs/esm'; import { Exam } from 'app/entities/exam/exam.model'; import { CourseAccessStorageService } from 'app/course/course-access-storage.service'; +import { SearchFilterPipe } from 'app/shared/pipes/search-filter.pipe'; +import { SearchFilterComponent } from 'app/shared/search-filter/search-filter.component'; const endDate1 = dayjs().add(1, 'days'); const visibleDate1 = dayjs().subtract(1, 'days'); @@ -93,7 +94,7 @@ describe('CoursesComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule.withRoutes([{ path: 'courses/:courseId/exams/:examId', component: DummyComponent }])], + imports: [ArtemisTestModule, RouterModule.forRoot([{ path: 'courses/:courseId/exams/:examId', component: DummyComponent }])], declarations: [ CoursesComponent, MockDirective(MockHasAnyAuthorityDirective), @@ -101,10 +102,12 @@ describe('CoursesComponent', () => { MockDirective(SortDirective), MockDirective(SortByDirective), MockPipe(ArtemisDatePipe), + MockPipe(SearchFilterPipe), MockComponent(CourseExerciseRowComponent), MockComponent(CourseExercisesComponent), MockComponent(CourseRegistrationComponent), MockComponent(CourseCardComponent), + MockComponent(SearchFilterComponent), ], providers: [ { provide: LocalStorageService, useClass: MockSyncStorage }, diff --git a/src/test/javascript/spec/component/course/detail/course-detail-doughnut-chart.component.spec.ts b/src/test/javascript/spec/component/course/detail/course-detail-doughnut-chart.component.spec.ts index ad3246741090..d73a030743dd 100644 --- a/src/test/javascript/spec/component/course/detail/course-detail-doughnut-chart.component.spec.ts +++ b/src/test/javascript/spec/component/course/detail/course-detail-doughnut-chart.component.spec.ts @@ -1,4 +1,3 @@ -import { RouterTestingModule } from '@angular/router/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ArtemisTestModule } from '../../../test.module'; import { CourseDetailDoughnutChartComponent } from 'app/course/manage/detail/course-detail-doughnut-chart.component'; @@ -7,6 +6,7 @@ import { MockModule, MockPipe } from 'ng-mocks'; import { DoughnutChartType } from 'app/course/manage/detail/course-detail.component'; import { Course } from 'app/entities/course.model'; import { PieChartModule } from '@swimlane/ngx-charts'; +import { provideRouter } from '@angular/router'; describe('CourseDetailDoughnutChartComponent', () => { let fixture: ComponentFixture; @@ -19,8 +19,9 @@ describe('CourseDetailDoughnutChartComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule.withRoutes([]), MockModule(PieChartModule)], + imports: [ArtemisTestModule, MockModule(PieChartModule)], declarations: [CourseDetailDoughnutChartComponent, MockPipe(ArtemisTranslatePipe)], + providers: [provideRouter([])], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/exam/exam-scores/exam-scores-average-scores-graph.component.spec.ts b/src/test/javascript/spec/component/exam/exam-scores/exam-scores-average-scores-graph.component.spec.ts index 5bf796d51879..f962626e8e37 100644 --- a/src/test/javascript/spec/component/exam/exam-scores/exam-scores-average-scores-graph.component.spec.ts +++ b/src/test/javascript/spec/component/exam/exam-scores/exam-scores-average-scores-graph.component.spec.ts @@ -1,5 +1,4 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; import { TranslateService } from '@ngx-translate/core'; import { MockDirective, MockModule, MockPipe, MockProvider } from 'ng-mocks'; import { of } from 'rxjs'; @@ -16,6 +15,7 @@ import { NgxChartsSingleSeriesDataEntry } from 'app/shared/chart/ngx-charts-data import { ExerciseType } from 'app/entities/exercise.model'; import { LocaleConversionService } from 'app/shared/service/locale-conversion.service'; import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { RouterModule } from '@angular/router'; describe('ExamScoresAverageScoresGraphComponent', () => { let fixture: ComponentFixture; @@ -55,7 +55,7 @@ describe('ExamScoresAverageScoresGraphComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule.withRoutes([]), MockModule(BarChartModule)], + imports: [ArtemisTestModule, MockModule(BarChartModule), RouterModule.forRoot([])], declarations: [ExamScoresAverageScoresGraphComponent, MockPipe(ArtemisTranslatePipe), MockDirective(TranslateDirective)], providers: [ MockProvider(CourseManagementService, { diff --git a/src/test/javascript/spec/component/exam/exam-update.component.spec.ts b/src/test/javascript/spec/component/exam/exam-update.component.spec.ts index 7dd87ad9bef8..88fad0011056 100644 --- a/src/test/javascript/spec/component/exam/exam-update.component.spec.ts +++ b/src/test/javascript/spec/component/exam/exam-update.component.spec.ts @@ -4,10 +4,9 @@ import { Component } from '@angular/core'; import cloneDeep from 'lodash-es/cloneDeep'; import { FormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { RouterTestingModule } from '@angular/router/testing'; -import { ActivatedRoute, Router, UrlSegment } from '@angular/router'; +import { ActivatedRoute, Router, UrlSegment, provideRouter } from '@angular/router'; import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { HttpClientModule, HttpErrorResponse, HttpResponse } from '@angular/common/http'; +import { HttpErrorResponse, HttpResponse, provideHttpClient } from '@angular/common/http'; import { MockComponent, MockDirective, MockModule, MockPipe, MockProvider } from 'ng-mocks'; import { ExamUpdateComponent, prepareExamForImport } from 'app/exam/manage/exams/exam-update.component'; @@ -43,6 +42,7 @@ import { TitleChannelNameComponent } from 'app/shared/form/title-channel-name/ti import { UMLDiagramType } from '@ls1intum/apollon'; import { TextExercise } from 'app/entities/text/text-exercise.model'; import { MarkdownEditorMonacoComponent } from 'app/shared/markdown-editor/monaco/markdown-editor-monaco.component'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; @Component({ template: '', @@ -73,7 +73,7 @@ describe('ExamUpdateComponent', () => { describe('create and edit exams', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [RouterTestingModule.withRoutes(routes), MockModule(NgbModule), TranslateModule.forRoot(), FormsModule, HttpClientModule, ArtemisExamModePickerModule], + imports: [MockModule(NgbModule), TranslateModule.forRoot(), FormsModule, ArtemisExamModePickerModule], declarations: [ ExamUpdateComponent, MockComponent(FormDateTimePickerComponent), @@ -89,6 +89,9 @@ describe('ExamUpdateComponent', () => { MockComponent(TitleChannelNameComponent), ], providers: [ + provideHttpClient(), + provideHttpClientTesting(), + provideRouter(routes), { provide: LocalStorageService, useClass: MockSyncStorage }, { provide: SessionStorageService, useClass: MockSyncStorage }, MockDirective(TranslateDirective), @@ -601,7 +604,7 @@ describe('ExamUpdateComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [RouterTestingModule.withRoutes(routes), MockModule(NgbModule), TranslateModule.forRoot(), FormsModule, HttpClientModule, ArtemisExamModePickerModule], + imports: [MockModule(NgbModule), TranslateModule.forRoot(), FormsModule, ArtemisExamModePickerModule], declarations: [ ExamUpdateComponent, ExamExerciseImportComponent, @@ -621,6 +624,9 @@ describe('ExamUpdateComponent', () => { MockComponent(TitleChannelNameComponent), ], providers: [ + provideHttpClient(), + provideHttpClientTesting(), + provideRouter(routes), { provide: LocalStorageService, useClass: MockSyncStorage }, { provide: SessionStorageService, useClass: MockSyncStorage }, MockDirective(TranslateDirective), diff --git a/src/test/javascript/spec/component/exam/manage/exam-students-attendance-check.component.spec.ts b/src/test/javascript/spec/component/exam/manage/exam-students-attendance-check.component.spec.ts index dfe43434b530..4418d84f29ac 100644 --- a/src/test/javascript/spec/component/exam/manage/exam-students-attendance-check.component.spec.ts +++ b/src/test/javascript/spec/component/exam/manage/exam-students-attendance-check.component.spec.ts @@ -1,6 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ActivatedRoute, Router, UrlSegment, convertToParamMap } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; +import { ActivatedRoute, Router, UrlSegment, convertToParamMap, provideRouter } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { User } from 'app/core/user/user.model'; import { Course } from 'app/entities/course.model'; @@ -12,9 +11,8 @@ import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { SortService } from 'app/shared/service/sort.service'; import { SortDirective } from 'app/shared/sort/sort.directive'; import { HttpResponse } from '@angular/common/http'; -import { of } from 'rxjs'; +import { Observable, of } from 'rxjs'; import { MockDirective, MockPipe } from 'ng-mocks'; -import { Observable } from 'rxjs'; import { MockRouter } from '../../../helpers/mocks/mock-router'; import { MockNgbModalService } from '../../../helpers/mocks/service/mock-ngb-modal.service'; import { MockTranslateService } from '../../../helpers/mocks/service/mock-translate.service'; @@ -43,9 +41,10 @@ describe('ExamStudentsAttendanceCheckComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule], + imports: [ArtemisTestModule], declarations: [ExamStudentsAttendanceCheckComponent, MockDirective(TranslateDirective), MockDirective(SortDirective), MockPipe(ArtemisTranslatePipe)], providers: [ + provideRouter([]), { provide: TranslateService, useClass: MockTranslateService }, { provide: NgbModal, useClass: MockNgbModalService }, { provide: Router, useClass: MockRouter }, diff --git a/src/test/javascript/spec/component/exam/manage/exam-students.component.spec.ts b/src/test/javascript/spec/component/exam/manage/exam-students.component.spec.ts index 068acb5558ea..cdcea3157be7 100644 --- a/src/test/javascript/spec/component/exam/manage/exam-students.component.spec.ts +++ b/src/test/javascript/spec/component/exam/manage/exam-students.component.spec.ts @@ -1,7 +1,6 @@ import { HttpResponse } from '@angular/common/http'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ActivatedRoute, UrlSegment, convertToParamMap } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; +import { ActivatedRoute, UrlSegment, convertToParamMap, provideRouter } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { NgxDatatableModule } from '@siemens/ngx-datatable'; import { User } from 'app/core/user/user.model'; @@ -61,7 +60,7 @@ describe('ExamStudentsComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, NgxDatatableModule, RouterTestingModule], + imports: [ArtemisTestModule, NgxDatatableModule], declarations: [ ExamStudentsComponent, MockComponent(UsersImportButtonComponent), @@ -71,10 +70,7 @@ describe('ExamStudentsComponent', () => { MockDirective(DeleteButtonDirective), MockPipe(ArtemisTranslatePipe), ], - providers: [ - { provide: TranslateService, useClass: MockTranslateService }, - { provide: ActivatedRoute, useValue: route }, - ], + providers: [provideRouter([]), { provide: TranslateService, useClass: MockTranslateService }, { provide: ActivatedRoute, useValue: route }], }).compileComponents(); fixture = TestBed.createComponent(ExamStudentsComponent); diff --git a/src/test/javascript/spec/component/exam/manage/exams/exam-checklist-exercisegroup-table.component.spec.ts b/src/test/javascript/spec/component/exam/manage/exams/exam-checklist-exercisegroup-table.component.spec.ts index 99af4dd418ad..60b382f6d9cb 100644 --- a/src/test/javascript/spec/component/exam/manage/exams/exam-checklist-exercisegroup-table.component.spec.ts +++ b/src/test/javascript/spec/component/exam/manage/exams/exam-checklist-exercisegroup-table.component.spec.ts @@ -1,6 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MockComponent, MockDirective, MockPipe } from 'ng-mocks'; -import { RouterTestingModule } from '@angular/router/testing'; import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; import { Component } from '@angular/core'; import { HasAnyAuthorityDirective } from 'app/shared/auth/has-any-authority.directive'; @@ -13,6 +12,7 @@ import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { TranslateDirective } from 'app/shared/language/translate.directive'; import { ExerciseGroupVariantColumn } from 'app/entities/exercise-group-variant-column.model'; import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; @Component({ template: '', @@ -58,17 +58,7 @@ describe('ExamChecklistExerciseGroupTableComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - RouterTestingModule.withRoutes([ - { path: 'course-management/:courseId/exams/:examId/edit', component: DummyComponent }, - { path: 'course-management/:courseId/exams/:examId/exercise-groups', component: DummyComponent }, - { path: 'course-management/:courseId/exams/:examId/assessment-dashboard', component: DummyComponent }, - { path: 'course-management/:courseId/exams/:examId/scores', component: DummyComponent }, - { path: 'course-management/:courseId/exams/:examId/student-exams', component: DummyComponent }, - { path: 'course-management/:courseId/exams/:examId/test-runs', component: DummyComponent }, - { path: 'course-management/:courseId/exams/:examId/students', component: DummyComponent }, - ]), - ], + imports: [], declarations: [ DummyComponent, MockPipe(ArtemisTranslatePipe), @@ -80,7 +70,19 @@ describe('ExamChecklistExerciseGroupTableComponent', () => { ProgressBarComponent, MockComponent(FaIconComponent), ], - providers: [provideHttpClient(), provideHttpClientTesting()], + providers: [ + provideRouter([ + { path: 'course-management/:courseId/exams/:examId/edit', component: DummyComponent }, + { path: 'course-management/:courseId/exams/:examId/exercise-groups', component: DummyComponent }, + { path: 'course-management/:courseId/exams/:examId/assessment-dashboard', component: DummyComponent }, + { path: 'course-management/:courseId/exams/:examId/scores', component: DummyComponent }, + { path: 'course-management/:courseId/exams/:examId/student-exams', component: DummyComponent }, + { path: 'course-management/:courseId/exams/:examId/test-runs', component: DummyComponent }, + { path: 'course-management/:courseId/exams/:examId/students', component: DummyComponent }, + ]), + provideHttpClient(), + provideHttpClientTesting(), + ], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/exam/manage/exams/exam-detail.component.spec.ts b/src/test/javascript/spec/component/exam/manage/exams/exam-detail.component.spec.ts index 9cfa217a8c51..811b2574c994 100644 --- a/src/test/javascript/spec/component/exam/manage/exams/exam-detail.component.spec.ts +++ b/src/test/javascript/spec/component/exam/manage/exams/exam-detail.component.spec.ts @@ -3,8 +3,7 @@ import { provideHttpClientTesting } from '@angular/common/http/testing'; import { Component } from '@angular/core'; import { ComponentFixture, TestBed, discardPeriodicTasks, fakeAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { ActivatedRoute, Data, Router } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; +import { ActivatedRoute, Data, Router, RouterModule } from '@angular/router'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { AccountService } from 'app/core/auth/account.service'; import { Course } from 'app/entities/course.model'; @@ -62,7 +61,7 @@ describe('ExamDetailComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ - RouterTestingModule.withRoutes([ + RouterModule.forRoot([ { path: 'course-management/:courseId/exams/:examId/edit', component: DummyComponent }, { path: 'course-management/:courseId/exams/:examId/exercise-groups', component: DummyComponent }, { diff --git a/src/test/javascript/spec/component/exam/manage/exercise-groups/exercise-groups.component.spec.ts b/src/test/javascript/spec/component/exam/manage/exercise-groups/exercise-groups.component.spec.ts index 040752cb4ffb..d46517c338eb 100644 --- a/src/test/javascript/spec/component/exam/manage/exercise-groups/exercise-groups.component.spec.ts +++ b/src/test/javascript/spec/component/exam/manage/exercise-groups/exercise-groups.component.spec.ts @@ -1,7 +1,6 @@ import { HttpResponse } from '@angular/common/http'; import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { ActivatedRoute, Router, convertToParamMap } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; +import { ActivatedRoute, Router, convertToParamMap, provideRouter } from '@angular/router'; import { faCheckDouble, faFileUpload, faFont, faKeyboard, faProjectDiagram } from '@fortawesome/free-solid-svg-icons'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { AlertService } from 'app/core/util/alert.service'; @@ -59,7 +58,7 @@ describe('Exercise Groups Component', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule], + imports: [ArtemisTestModule], declarations: [ ExerciseGroupsComponent, MockComponent(ExamExerciseRowButtonsComponent), @@ -74,6 +73,7 @@ describe('Exercise Groups Component', () => { MockDirective(TranslateDirective), ], providers: [ + provideRouter([]), MockProvider(AlertService), { provide: ActivatedRoute, useValue: route }, { provide: Router, useClass: MockRouter }, diff --git a/src/test/javascript/spec/component/exam/manage/student-exams/student-exam-detail-table-row.component.spec.ts b/src/test/javascript/spec/component/exam/manage/student-exams/student-exam-detail-table-row.component.spec.ts index 64ad9c2198c0..94355a8b80fd 100644 --- a/src/test/javascript/spec/component/exam/manage/student-exams/student-exam-detail-table-row.component.spec.ts +++ b/src/test/javascript/spec/component/exam/manage/student-exams/student-exam-detail-table-row.component.spec.ts @@ -4,7 +4,6 @@ import { MockComponent, MockDirective, MockPipe, MockProvider } from 'ng-mocks'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgxDatatableModule } from '@siemens/ngx-datatable'; import { TranslateModule } from '@ngx-translate/core'; -import { RouterTestingModule } from '@angular/router/testing'; import { ReactiveFormsModule } from '@angular/forms'; import { Exercise, ExerciseType } from 'app/entities/exercise.model'; import { ModelingExercise } from 'app/entities/modeling-exercise.model'; @@ -24,6 +23,7 @@ import { AlertService } from 'app/core/util/alert.service'; import { TranslateDirective } from 'app/shared/language/translate.directive'; import { faCheckDouble, faFileUpload, faKeyboard, faProjectDiagram } from '@fortawesome/free-solid-svg-icons'; import { UMLDiagramType } from '@ls1intum/apollon'; +import { provideRouter } from '@angular/router'; describe('StudentExamDetailTableRowComponent', () => { let studentExamDetailTableRowComponentFixture: ComponentFixture; @@ -45,9 +45,9 @@ describe('StudentExamDetailTableRowComponent', () => { exercise.studentParticipations = [studentParticipation]; return TestBed.configureTestingModule({ - imports: [RouterTestingModule.withRoutes([]), NgbModule, NgxDatatableModule, ReactiveFormsModule, TranslateModule.forRoot()], + imports: [NgbModule, NgxDatatableModule, ReactiveFormsModule, TranslateModule.forRoot()], declarations: [StudentExamDetailTableRowComponent, MockComponent(DataTableComponent), MockTranslateValuesDirective, MockPipe(ArtemisTranslatePipe)], - providers: [MockProvider(AlertService), MockDirective(TranslateDirective)], + providers: [provideRouter([]), MockProvider(AlertService), MockDirective(TranslateDirective)], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/exam/manage/student-exams/student-exam-detail.component.spec.ts b/src/test/javascript/spec/component/exam/manage/student-exams/student-exam-detail.component.spec.ts index 4ec566ca9d4f..73e1d256c63c 100644 --- a/src/test/javascript/spec/component/exam/manage/student-exams/student-exam-detail.component.spec.ts +++ b/src/test/javascript/spec/component/exam/manage/student-exams/student-exam-detail.component.spec.ts @@ -8,11 +8,10 @@ import { MockComponent, MockDirective, MockPipe, MockProvider } from 'ng-mocks'; import { StudentExamService } from 'app/exam/manage/student-exams/student-exam.service'; import { HttpResponse, provideHttpClient } from '@angular/common/http'; import { of } from 'rxjs'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, RouterModule } from '@angular/router'; import { NgbModal, NgbModalRef, NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgxDatatableModule } from '@siemens/ngx-datatable'; import { TranslateModule } from '@ngx-translate/core'; -import { RouterTestingModule } from '@angular/router/testing'; import { NgForm, NgModel, ReactiveFormsModule } from '@angular/forms'; import { Exercise } from 'app/entities/exercise.model'; import { ModelingExercise } from 'app/entities/modeling-exercise.model'; @@ -128,7 +127,7 @@ describe('StudentExamDetailComponent', () => { } as StudentExamWithGradeDTO; await TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule.withRoutes([]), NgbModule, NgxDatatableModule, ReactiveFormsModule, TranslateModule.forRoot()], + imports: [ArtemisTestModule, NgbModule, NgxDatatableModule, ReactiveFormsModule, TranslateModule.forRoot(), RouterModule.forRoot([])], declarations: [ StudentExamDetailComponent, MockComponent(DataTableComponent), diff --git a/src/test/javascript/spec/component/exam/manage/student-exams/student-exams.component.spec.ts b/src/test/javascript/spec/component/exam/manage/student-exams/student-exams.component.spec.ts index 224db870bc21..17e860891743 100644 --- a/src/test/javascript/spec/component/exam/manage/student-exams/student-exams.component.spec.ts +++ b/src/test/javascript/spec/component/exam/manage/student-exams/student-exams.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ActivatedRoute, Params, convertToParamMap } from '@angular/router'; +import { ActivatedRoute, Params, convertToParamMap, provideRouter } from '@angular/router'; import { StudentExamsComponent } from 'app/exam/manage/student-exams/student-exams.component'; import { ExamManagementService } from 'app/exam/manage/exam-management.service'; import { MockComponent, MockDirective, MockModule, MockPipe, MockProvider } from 'ng-mocks'; @@ -7,7 +7,6 @@ import { StudentExamService } from 'app/exam/manage/student-exams/student-exam.s import { CourseManagementService } from 'app/course/manage/course-management.service'; import { TranslateService } from '@ngx-translate/core'; import { NgxDatatableModule } from '@siemens/ngx-datatable'; -import { RouterTestingModule } from '@angular/router/testing'; import { ArtemisDurationFromSecondsPipe } from 'app/shared/pipes/artemis-duration-from-seconds.pipe'; import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; import { MockLocalStorageService } from '../../../../helpers/mocks/service/mock-local-storage.service'; @@ -49,6 +48,7 @@ describe('StudentExamsComponent', () => { const referenceDateNow = dayjs(); const providers = [ + provideRouter([]), MockProvider(ExamManagementService, { find: () => { return of( @@ -208,7 +208,7 @@ describe('StudentExamsComponent', () => { studentExams = [studentExamOne, studentExamTwo]; return TestBed.configureTestingModule({ - imports: [RouterTestingModule.withRoutes([]), MockModule(NgxDatatableModule)], + imports: [MockModule(NgxDatatableModule)], declarations: [ StudentExamsComponent, MockComponent(StudentExamStatusComponent), diff --git a/src/test/javascript/spec/component/exam/participate/exam-navigation-sidebar.component.spec.ts b/src/test/javascript/spec/component/exam/participate/exam-navigation-sidebar.component.spec.ts index 45ce49fc2b4d..3a9102c72e8e 100644 --- a/src/test/javascript/spec/component/exam/participate/exam-navigation-sidebar.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/exam-navigation-sidebar.component.spec.ts @@ -20,6 +20,7 @@ import { TranslateService } from '@ngx-translate/core'; import { facSaveSuccess, facSaveWarning } from 'src/main/webapp/content/icons/icons'; import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; import { ExamLiveEventsButtonComponent } from 'app/exam/participate/events/exam-live-events-button.component'; +import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; describe('ExamNavigationSidebarComponent', () => { let fixture: ComponentFixture; @@ -33,7 +34,7 @@ describe('ExamNavigationSidebarComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, TranslateTestingModule, MockModule(NgbTooltipModule)], + imports: [ArtemisTestModule, TranslateTestingModule, MockModule(NgbTooltipModule), MockModule(ArtemisSharedCommonModule)], declarations: [ExamNavigationSidebarComponent, MockComponent(ExamTimerComponent), MockComponent(ExamLiveEventsButtonComponent)], providers: [ ExamParticipationService, diff --git a/src/test/javascript/spec/component/exam/participate/exam-start-information/exam-start-information.component.spec.ts b/src/test/javascript/spec/component/exam/participate/exam-start-information/exam-start-information.component.spec.ts index 6332ee962a6c..c675ea69d8c7 100644 --- a/src/test/javascript/spec/component/exam/participate/exam-start-information/exam-start-information.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/exam-start-information/exam-start-information.component.spec.ts @@ -1,5 +1,4 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; import { User } from 'app/core/user/user.model'; import { Exam } from 'app/entities/exam/exam.model'; import { StudentExam } from 'app/entities/student-exam.model'; @@ -15,6 +14,7 @@ import { ArtemisSharedModule } from 'app/shared/shared.module'; import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; import { ArtemisExamSharedModule } from 'app/exam/shared/exam-shared.module'; import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { provideRouter } from '@angular/router'; let fixture: ComponentFixture; let component: ExamStartInformationComponent; @@ -40,7 +40,7 @@ describe('ExamStartInformationComponent', () => { studentExam = { id: 1, exam, user, workingTime: 60, submitted: true } as StudentExam; return TestBed.configureTestingModule({ - imports: [RouterTestingModule.withRoutes([]), ArtemisSharedModule, ArtemisSharedComponentModule, ArtemisExamSharedModule], + imports: [ArtemisSharedModule, ArtemisSharedComponentModule, ArtemisExamSharedModule], declarations: [ ExamStartInformationComponent, MockComponent(StudentExamWorkingTimeComponent), @@ -50,6 +50,7 @@ describe('ExamStartInformationComponent', () => { MockPipe(ArtemisDatePipe), MockPipe(ArtemisDurationFromSecondsPipe), ], + providers: [provideRouter([])], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/exam/participate/exercises/file-upload-exam-submission.component.spec.ts b/src/test/javascript/spec/component/exam/participate/exercises/file-upload-exam-submission.component.spec.ts index 83319f461faf..9c10ec5ca996 100644 --- a/src/test/javascript/spec/component/exam/participate/exercises/file-upload-exam-submission.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/exercises/file-upload-exam-submission.component.spec.ts @@ -77,10 +77,9 @@ describe('FileUploadExamSubmissionComponent', () => { expect(FileUploadExamSubmissionComponent).not.toBeNull(); }); - it('should show exercise group title', () => { - comp.exercise.exerciseGroup = { title: 'Test Group' } as ExerciseGroup; + it('should show static text in header', () => { fixture.detectChanges(); - const el = fixture.debugElement.query((de) => de.nativeElement.textContent === comp.exercise.exerciseGroup?.title); + const el = fixture.debugElement.query((de) => de.nativeElement.textContent === 'artemisApp.exam.yourSolution'); expect(el).not.toBeNull(); }); diff --git a/src/test/javascript/spec/component/exam/participate/exercises/modeling-exam-submission.component.spec.ts b/src/test/javascript/spec/component/exam/participate/exercises/modeling-exam-submission.component.spec.ts index 1277bc869032..60731362866e 100644 --- a/src/test/javascript/spec/component/exam/participate/exercises/modeling-exam-submission.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/exercises/modeling-exam-submission.component.spec.ts @@ -16,7 +16,6 @@ import { ArtemisTestModule } from '../../../../test.module'; import { IncludedInScoreBadgeComponent } from 'app/exercises/shared/exercise-headers/included-in-score-badge.component'; import { ExamExerciseUpdateHighlighterComponent } from 'app/exam/participate/exercises/exam-exercise-update-highlighter/exam-exercise-update-highlighter.component'; import { NgbTooltipMocksModule } from '../../../../helpers/mocks/directive/ngbTooltipMocks.module'; -import { ExerciseGroup } from 'app/entities/exercise-group.model'; import { SubmissionVersion } from 'app/entities/submission-version.model'; describe('ModelingExamSubmissionComponent', () => { @@ -69,10 +68,9 @@ describe('ModelingExamSubmissionComponent', () => { expect(ModelingExamSubmissionComponent).not.toBeNull(); }); - it('should show exercise group title', () => { - comp.exercise.exerciseGroup = { title: 'Test Group' } as ExerciseGroup; + it('should show static text in header', () => { fixture.detectChanges(); - const el = fixture.debugElement.query((de) => de.nativeElement.textContent === comp.exercise.exerciseGroup?.title); + const el = fixture.debugElement.query((de) => de.nativeElement.textContent === 'artemisApp.exam.yourSolution'); expect(el).not.toBeNull(); }); diff --git a/src/test/javascript/spec/component/exam/participate/exercises/quiz-exam-submission.component.spec.ts b/src/test/javascript/spec/component/exam/participate/exercises/quiz-exam-submission.component.spec.ts index 1ec885212f1e..c83015c99c75 100644 --- a/src/test/javascript/spec/component/exam/participate/exercises/quiz-exam-submission.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/exercises/quiz-exam-submission.component.spec.ts @@ -1,5 +1,4 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; import { AnswerOption } from 'app/entities/quiz/answer-option.model'; import { DragAndDropMapping } from 'app/entities/quiz/drag-and-drop-mapping.model'; import { DragAndDropQuestion } from 'app/entities/quiz/drag-and-drop-question.model'; @@ -25,6 +24,7 @@ import { SubmissionVersion } from 'app/entities/submission-version.model'; import { ModelingSubmission } from 'app/entities/modeling-submission.model'; import { QuizExercise } from 'app/entities/quiz/quiz-exercise.model'; import { Course } from 'app/entities/course.model'; +import { provideRouter } from '@angular/router'; describe('QuizExamSubmissionComponent', () => { let fixture: ComponentFixture; @@ -47,7 +47,7 @@ describe('QuizExamSubmissionComponent', () => { shortAnswerQuestion.id = 3; return TestBed.configureTestingModule({ - imports: [RouterTestingModule.withRoutes([]), NgbTooltipMocksModule], + imports: [NgbTooltipMocksModule], declarations: [ QuizExamSubmissionComponent, MockPipe(ArtemisTranslatePipe), @@ -56,7 +56,7 @@ describe('QuizExamSubmissionComponent', () => { MockComponent(DragAndDropQuestionComponent), MockComponent(ShortAnswerQuestionComponent), ], - providers: [MockProvider(ArtemisQuizService)], + providers: [provideRouter([]), MockProvider(ArtemisQuizService)], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/exam/participate/general-information/exam-general-information.component.spec.ts b/src/test/javascript/spec/component/exam/participate/general-information/exam-general-information.component.spec.ts index 9ead47365e2e..ce662f79bdf6 100644 --- a/src/test/javascript/spec/component/exam/participate/general-information/exam-general-information.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/general-information/exam-general-information.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; +import { provideRouter } from '@angular/router'; import { User } from 'app/core/user/user.model'; import { Exam } from 'app/entities/exam/exam.model'; import { StudentExam } from 'app/entities/student-exam.model'; @@ -35,7 +35,6 @@ describe('ExamGeneralInformationComponent', () => { studentExam = { id: 1, exam, user, workingTime: 60, submitted: true } as StudentExam; return TestBed.configureTestingModule({ - imports: [RouterTestingModule.withRoutes([])], declarations: [ ExamGeneralInformationComponent, MockComponent(StudentExamWorkingTimeComponent), @@ -43,6 +42,7 @@ describe('ExamGeneralInformationComponent', () => { MockPipe(ArtemisDatePipe), MockPipe(ArtemisDurationFromSecondsPipe), ], + providers: [provideRouter([])], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/exam/participate/summary/exam-result-summary.component.spec.ts b/src/test/javascript/spec/component/exam/participate/summary/exam-result-summary.component.spec.ts index a36b5b62eb0e..f144828d2e74 100644 --- a/src/test/javascript/spec/component/exam/participate/summary/exam-result-summary.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/summary/exam-result-summary.component.spec.ts @@ -1,8 +1,7 @@ -import { HttpClientModule, HttpResponse } from '@angular/common/http'; +import { HttpResponse, provideHttpClient } from '@angular/common/http'; import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { ActivatedRoute, convertToParamMap } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; +import { ActivatedRoute, convertToParamMap, provideRouter } from '@angular/router'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { ComplaintsStudentViewComponent } from 'app/complaints/complaints-for-students/complaints-student-view.component'; import { ThemeService } from 'app/core/theme/theme.service'; @@ -56,6 +55,7 @@ import { AlertService } from 'app/core/util/alert.service'; import { ProgrammingExerciseExampleSolutionRepoDownloadComponent } from 'app/exercises/programming/shared/actions/programming-exercise-example-solution-repo-download.component'; import * as ExamUtils from 'app/exam/participate/exam.utils'; import { CollapsibleCardComponent } from 'app/exam/participate/summary/collapsible-card.component'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; let fixture: ComponentFixture; let component: ExamResultSummaryComponent; @@ -172,7 +172,7 @@ const gradeInfo: StudentExamWithGradeDTO = { function sharedSetup(url: string[]) { beforeEach(() => { return TestBed.configureTestingModule({ - imports: [RouterTestingModule.withRoutes([]), HttpClientModule, NgbCollapseMocksModule], + imports: [NgbCollapseMocksModule], declarations: [ ExamResultSummaryComponent, MockComponent(TestRunRibbonComponent), @@ -197,6 +197,9 @@ function sharedSetup(url: string[]) { MockComponent(CollapsibleCardComponent), ], providers: [ + provideRouter([]), + provideHttpClient(), + provideHttpClientTesting(), { provide: ActivatedRoute, useValue: { diff --git a/src/test/javascript/spec/component/exam/participate/summary/result-overview/exam-result-overview.component.spec.ts b/src/test/javascript/spec/component/exam/participate/summary/result-overview/exam-result-overview.component.spec.ts index f6e001ff7ff1..a486b4350061 100644 --- a/src/test/javascript/spec/component/exam/participate/summary/result-overview/exam-result-overview.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/summary/result-overview/exam-result-overview.component.spec.ts @@ -1,6 +1,5 @@ import dayjs from 'dayjs/esm'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; import { MockComponent, MockModule, MockPipe, MockProvider } from 'ng-mocks'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { User } from 'app/core/user/user.model'; @@ -23,6 +22,7 @@ import { ExerciseResult, StudentExamWithGradeDTO } from 'app/exam/exam-scores/ex import { GradingKeyTableComponent } from 'app/grading-system/grading-key-overview/grading-key/grading-key-table.component'; import { CollapsibleCardComponent } from 'app/exam/participate/summary/collapsible-card.component'; import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; let fixture: ComponentFixture; let component: ExamResultOverviewComponent; @@ -129,7 +129,7 @@ const textExerciseResult = { describe('ExamResultOverviewComponent', () => { beforeEach(() => { return TestBed.configureTestingModule({ - imports: [RouterTestingModule.withRoutes([]), MockModule(NgbModule)], + imports: [MockModule(NgbModule)], declarations: [ ExamResultOverviewComponent, MockComponent(FaIconComponent), @@ -137,7 +137,7 @@ describe('ExamResultOverviewComponent', () => { MockComponent(GradingKeyTableComponent), MockComponent(CollapsibleCardComponent), ], - providers: [provideHttpClient(), provideHttpClientTesting(), MockProvider(ExerciseService)], + providers: [provideRouter([]), provideHttpClient(), provideHttpClientTesting(), MockProvider(ExerciseService)], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/exam/test-run/test-run-management.component.spec.ts b/src/test/javascript/spec/component/exam/test-run/test-run-management.component.spec.ts index 40d92651dcfa..16c0eb4b276d 100644 --- a/src/test/javascript/spec/component/exam/test-run/test-run-management.component.spec.ts +++ b/src/test/javascript/spec/component/exam/test-run/test-run-management.component.spec.ts @@ -1,8 +1,7 @@ -import { HttpClientModule, HttpErrorResponse, HttpResponse } from '@angular/common/http'; +import { HttpErrorResponse, HttpResponse, provideHttpClient } from '@angular/common/http'; import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { ActivatedRoute, convertToParamMap } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; +import { ActivatedRoute, RouterModule, convertToParamMap } from '@angular/router'; import { FontAwesomeTestingModule } from '@fortawesome/angular-fontawesome/testing'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; @@ -29,6 +28,7 @@ import { MockTranslateService } from '../../../helpers/mocks/service/mock-transl import { SortDirective } from 'app/shared/sort/sort.directive'; import { AlertService } from 'app/core/util/alert.service'; import { NgbTooltipMocksModule } from '../../../helpers/mocks/directive/ngbTooltipMocks.module'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; describe('Test Run Management Component', () => { let component: TestRunManagementComponent; @@ -49,7 +49,7 @@ describe('Test Run Management Component', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [RouterTestingModule.withRoutes([]), FontAwesomeTestingModule, TranslateModule.forRoot(), HttpClientModule, NgbTooltipMocksModule], + imports: [RouterModule.forRoot([]), FontAwesomeTestingModule, TranslateModule.forRoot(), NgbTooltipMocksModule], declarations: [ TestRunManagementComponent, @@ -61,6 +61,8 @@ describe('Test Run Management Component', () => { MockDirective(TranslateDirective), ], providers: [ + provideHttpClient(), + provideHttpClientTesting(), { provide: LocalStorageService, useClass: MockSyncStorage }, { provide: SessionStorageService, useClass: MockSyncStorage }, { provide: TranslateService, useClass: MockTranslateService }, diff --git a/src/test/javascript/spec/component/exercises/shared/exercise-scores/exercise-scores.component.spec.ts b/src/test/javascript/spec/component/exercises/shared/exercise-scores/exercise-scores.component.spec.ts index e04d0625a7e2..78386edfe0c9 100644 --- a/src/test/javascript/spec/component/exercises/shared/exercise-scores/exercise-scores.component.spec.ts +++ b/src/test/javascript/spec/component/exercises/shared/exercise-scores/exercise-scores.component.spec.ts @@ -1,8 +1,7 @@ import { HttpResponse } from '@angular/common/http'; import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { NgModel } from '@angular/forms'; -import { ActivatedRoute } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; +import { ActivatedRoute, provideRouter } from '@angular/router'; import { NgxDatatableModule } from '@siemens/ngx-datatable'; import { CourseManagementService } from 'app/course/manage/course-management.service'; import { AssessmentType } from 'app/entities/assessment-type.model'; @@ -121,7 +120,7 @@ describe('Exercise Scores Component', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule.withRoutes([]), MockModule(NgxDatatableModule)], + imports: [ArtemisTestModule, MockModule(NgxDatatableModule)], declarations: [ ExerciseScoresComponent, MockComponent(ExerciseScoresExportButtonComponent), @@ -137,6 +136,7 @@ describe('Exercise Scores Component', () => { MockDirective(NgModel), ], providers: [ + provideRouter([]), { provide: ExerciseService, useClass: MockExerciseService }, { provide: ActivatedRoute, useValue: route }, { provide: ResultService, useClass: MockResultService }, diff --git a/src/test/javascript/spec/component/exercises/shared/exercise-statistics.component.spec.ts b/src/test/javascript/spec/component/exercises/shared/exercise-statistics.component.spec.ts index 7d813e9bd19a..525e5a2482e2 100644 --- a/src/test/javascript/spec/component/exercises/shared/exercise-statistics.component.spec.ts +++ b/src/test/javascript/spec/component/exercises/shared/exercise-statistics.component.spec.ts @@ -3,7 +3,6 @@ import { ArtemisTestModule } from '../../../test.module'; import { MockSyncStorage } from '../../../helpers/mocks/service/mock-sync-storage.service'; import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; import { MockComponent, MockDirective, MockPipe, MockProvider } from 'ng-mocks'; -import { RouterTestingModule } from '@angular/router/testing'; import { MockHasAnyAuthorityDirective } from '../../../helpers/mocks/directive/mock-has-any-authority.directive'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; @@ -20,6 +19,7 @@ import { HttpResponse } from '@angular/common/http'; import { Exercise } from 'app/entities/exercise.model'; import { ExerciseDetailStatisticsComponent } from 'app/exercises/shared/statistics/exercise-detail-statistics.component'; import { TranslateService } from '@ngx-translate/core'; +import { RouterModule } from '@angular/router'; describe('ExerciseStatisticsComponent', () => { let fixture: ComponentFixture; @@ -53,7 +53,7 @@ describe('ExerciseStatisticsComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule.withRoutes([])], + imports: [ArtemisTestModule, RouterModule.forRoot([])], declarations: [ ExerciseStatisticsComponent, MockComponent(StatisticsGraphComponent), diff --git a/src/test/javascript/spec/component/exercises/shared/result.spec.ts b/src/test/javascript/spec/component/exercises/shared/result.spec.ts index 72c2cd1aafca..ff076e80d8c8 100644 --- a/src/test/javascript/spec/component/exercises/shared/result.spec.ts +++ b/src/test/javascript/spec/component/exercises/shared/result.spec.ts @@ -12,7 +12,7 @@ import { TranslateService } from '@ngx-translate/core'; import { cloneDeep } from 'lodash-es'; import { Submission } from 'app/entities/submission.model'; import { ExerciseType } from 'app/entities/exercise.model'; -import { faQuestionCircle, faTimesCircle } from '@fortawesome/free-regular-svg-icons'; +import { faCheckCircle, faQuestionCircle, faTimesCircle } from '@fortawesome/free-regular-svg-icons'; import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model'; import { ModelingExercise } from 'app/entities/modeling-exercise.model'; import { ProgrammingExerciseStudentParticipation } from 'app/entities/participation/programming-exercise-student-participation.model'; @@ -141,11 +141,31 @@ describe('ResultComponent', () => { expect(component.result!.participation).toEqual(participation1); expect(component.submission).toEqual(submission1); expect(component.textColorClass).toBe('text-secondary'); - expect(component.resultIconClass).toEqual(faQuestionCircle); + expect(component.resultIconClass).toEqual(faCheckCircle); expect(component.resultString).toBe('artemisApp.result.resultString.short (artemisApp.result.preliminary)'); expect(component.templateStatus).toBe(ResultTemplateStatus.HAS_RESULT); }); + it('should set (automatic athena) results for programming exercise', () => { + const submission1: Submission = { id: 1 }; + const result1: Result = { id: 1, submission: submission1, score: 0.8, assessmentType: AssessmentType.AUTOMATIC_ATHENA, successful: true }; + const result2: Result = { id: 2 }; + const participation1 = cloneDeep(programmingParticipation); + participation1.results = [result1, result2]; + component.participation = participation1; + component.showUngradedResults = true; + + fixture.detectChanges(); + + expect(component.result).toEqual(result1); + expect(component.result!.participation).toEqual(participation1); + expect(component.submission).toEqual(submission1); + expect(component.textColorClass).toBe('text-secondary'); + expect(component.resultIconClass).toEqual(faCheckCircle); + expect(component.resultString).toBe('artemisApp.result.resultString.automaticAIFeedbackSuccessful (artemisApp.result.preliminary)'); + expect(component.templateStatus).toBe(ResultTemplateStatus.HAS_RESULT); + }); + it('should set (automatic athena) results for text exercise', () => { const submission1: Submission = { id: 1 }; const result1: Result = { id: 1, submission: submission1, score: 1, assessmentType: AssessmentType.AUTOMATIC_ATHENA, successful: true }; @@ -161,7 +181,7 @@ describe('ResultComponent', () => { expect(component.result!.participation).toEqual(participation1); expect(component.submission).toEqual(submission1); expect(component.textColorClass).toBe('text-secondary'); - expect(component.resultIconClass).toEqual(faQuestionCircle); + expect(component.resultIconClass).toEqual(faCheckCircle); expect(component.resultString).toBe('artemisApp.result.resultString.short (artemisApp.result.preliminary)'); }); diff --git a/src/test/javascript/spec/component/file-upload-assessment/file-upload-assessment.component.spec.ts b/src/test/javascript/spec/component/file-upload-assessment/file-upload-assessment.component.spec.ts index 2f3af3647b19..14485bc32e2e 100644 --- a/src/test/javascript/spec/component/file-upload-assessment/file-upload-assessment.component.spec.ts +++ b/src/test/javascript/spec/component/file-upload-assessment/file-upload-assessment.component.spec.ts @@ -1,5 +1,4 @@ import { ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; import { JhiLanguageHelper } from 'app/core/language/language.helper'; import { AccountService } from 'app/core/auth/account.service'; import { of, throwError } from 'rxjs'; @@ -8,7 +7,7 @@ import dayjs from 'dayjs/esm'; import { ArtemisTestModule } from '../../test.module'; import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service'; import { MockComponent, MockPipe } from 'ng-mocks'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { FileUploadAssessmentComponent } from 'app/exercises/file-upload/assess/file-upload-assessment.component'; import { DebugElement } from '@angular/core'; import { MockAccountService } from '../../helpers/mocks/service/mock-account.service'; @@ -72,7 +71,7 @@ describe('FileUploadAssessmentComponent', () => { beforeEach(() => { return TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule.withRoutes([routes[0]])], + imports: [ArtemisTestModule, RouterModule.forRoot([routes[0]])], declarations: [ FileUploadAssessmentComponent, MockComponent(UpdatingResultComponent), diff --git a/src/test/javascript/spec/component/file-upload-exercise/file-upload-exercise-detail.component.spec.ts b/src/test/javascript/spec/component/file-upload-exercise/file-upload-exercise-detail.component.spec.ts index 33728abe32c1..50a4daac6d0b 100644 --- a/src/test/javascript/spec/component/file-upload-exercise/file-upload-exercise-detail.component.spec.ts +++ b/src/test/javascript/spec/component/file-upload-exercise/file-upload-exercise-detail.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, provideRouter } from '@angular/router'; import { HttpHeaders, HttpResponse } from '@angular/common/http'; import { of } from 'rxjs'; import { ArtemisTestModule } from '../../test.module'; @@ -7,7 +7,6 @@ import { FileUploadExerciseDetailComponent } from 'app/exercises/file-upload/man import { MockFileUploadExerciseService, fileUploadExercise } from '../../helpers/mocks/service/mock-file-upload-exercise.service'; import { JhiLanguageHelper } from 'app/core/language/language.helper'; import { AlertService } from 'app/core/util/alert.service'; -import { RouterTestingModule } from '@angular/router/testing'; import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service'; import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; import { FileUploadExerciseService } from 'app/exercises/file-upload/manage/file-upload-exercise.service'; @@ -58,7 +57,7 @@ describe('FileUploadExercise Management Detail Component', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule], + imports: [ArtemisTestModule], declarations: [ FileUploadExerciseDetailComponent, MockPipe(HtmlForMarkdownPipe), @@ -68,6 +67,7 @@ describe('FileUploadExercise Management Detail Component', () => { MockComponent(DocumentationButtonComponent), ], providers: [ + provideRouter([]), JhiLanguageHelper, AlertService, { provide: ActivatedRoute, useValue: route }, diff --git a/src/test/javascript/spec/component/file-upload-submission/file-upload-submission.component.spec.ts b/src/test/javascript/spec/component/file-upload-submission/file-upload-submission.component.spec.ts index fdd4524e6dbe..7373ed41e712 100644 --- a/src/test/javascript/spec/component/file-upload-submission/file-upload-submission.component.spec.ts +++ b/src/test/javascript/spec/component/file-upload-submission/file-upload-submission.component.spec.ts @@ -1,12 +1,11 @@ import { ComponentFixture, TestBed, fakeAsync, flush, tick } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; import { AccountService } from 'app/core/auth/account.service'; import { ArtemisTestModule } from '../../test.module'; import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service'; import { MockParticipationWebsocketService } from '../../helpers/mocks/service/mock-participation-websocket.service'; import { MockComponent, MockPipe } from 'ng-mocks'; import { AlertService } from 'app/core/util/alert.service'; -import { Router } from '@angular/router'; +import { Router, RouterModule } from '@angular/router'; import { DebugElement } from '@angular/core'; import { By } from '@angular/platform-browser'; import { MockAccountService } from '../../helpers/mocks/service/mock-account.service'; @@ -54,7 +53,7 @@ describe('FileUploadSubmissionComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, NgxDatatableModule, TranslateModule.forRoot(), RouterTestingModule.withRoutes([routes[0]])], + imports: [ArtemisTestModule, NgxDatatableModule, TranslateModule.forRoot(), RouterModule.forRoot([routes[0]])], declarations: [ FileUploadSubmissionComponent, MockComponent(ComplaintsForTutorComponent), diff --git a/src/test/javascript/spec/component/footer/footer.component.spec.ts b/src/test/javascript/spec/component/footer/footer.component.spec.ts index 276359d939ee..074119bd8e9c 100644 --- a/src/test/javascript/spec/component/footer/footer.component.spec.ts +++ b/src/test/javascript/spec/component/footer/footer.component.spec.ts @@ -1,10 +1,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; import { FooterComponent } from 'app/shared/layouts/footer/footer.component'; import { TranslateModule } from '@ngx-translate/core'; import { ArtemisTestModule } from '../../test.module'; import { MockPipe } from 'ng-mocks'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; +import { RouterModule } from '@angular/router'; describe('FooterComponent', () => { let component: FooterComponent; @@ -13,7 +13,8 @@ describe('FooterComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [FooterComponent, MockPipe(ArtemisTranslatePipe)], - imports: [ArtemisTestModule, RouterTestingModule, TranslateModule.forRoot()], + imports: [ArtemisTestModule, TranslateModule.forRoot(), RouterModule.forRoot([])], + providers: [], }).compileComponents(); }); diff --git a/src/test/javascript/spec/component/grading-system/grading-system.component.spec.ts b/src/test/javascript/spec/component/grading-system/grading-system.component.spec.ts index 4f30fbc65658..31fad3a99ef5 100644 --- a/src/test/javascript/spec/component/grading-system/grading-system.component.spec.ts +++ b/src/test/javascript/spec/component/grading-system/grading-system.component.spec.ts @@ -6,10 +6,9 @@ import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { GradingSystemInfoModalComponent } from 'app/grading-system/grading-system-info-modal/grading-system-info-modal.component'; import { NgModel } from '@angular/forms'; import { BehaviorSubject } from 'rxjs'; -import { ActivatedRoute, Params } from '@angular/router'; +import { ActivatedRoute, Params, RouterModule } from '@angular/router'; import { GradingSystemComponent } from 'app/grading-system/grading-system.component'; import { BaseGradingSystemComponent } from 'app/grading-system/base-grading-system/base-grading-system.component'; -import { RouterTestingModule } from '@angular/router/testing'; import { DocumentationButtonComponent } from 'app/shared/components/documentation-button/documentation-button.component'; describe('Grading System Component', () => { @@ -21,7 +20,7 @@ describe('Grading System Component', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule], + imports: [ArtemisTestModule, RouterModule.forRoot([])], declarations: [ GradingSystemComponent, MockDirective(NgModel), diff --git a/src/test/javascript/spec/component/iris/settings/iris-course-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-course-settings-update.component.spec.ts index c5f5a5af8fa8..87c4fcb5989d 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-course-settings-update.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-course-settings-update.component.spec.ts @@ -8,8 +8,7 @@ import { ButtonComponent } from 'app/shared/components/button.component'; import { IrisCommonSubSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component'; import { IrisGlobalAutoupdateSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component'; import { mockEmptySettings, mockSettings } from './mock-settings'; -import { ActivatedRoute, Params } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; +import { ActivatedRoute, Params, provideRouter } from '@angular/router'; import { NgModel } from '@angular/forms'; import { IrisCourseSettingsUpdateComponent } from 'app/iris/settings/iris-course-settings-update/iris-course-settings-update.component'; import { By } from '@angular/platform-browser'; @@ -29,7 +28,7 @@ describe('IrisCourseSettingsUpdateComponent Component', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule], + imports: [ArtemisTestModule], declarations: [ IrisCourseSettingsUpdateComponent, IrisSettingsUpdateComponent, @@ -38,7 +37,7 @@ describe('IrisCourseSettingsUpdateComponent Component', () => { MockComponent(ButtonComponent), MockDirective(NgModel), ], - providers: [MockProvider(IrisSettingsService), { provide: ActivatedRoute, useValue: route }], + providers: [provideRouter([]), MockProvider(IrisSettingsService), { provide: ActivatedRoute, useValue: route }], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/iris/settings/iris-enabled.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-enabled.component.spec.ts index ba02154c4bc5..f4aba8617eee 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-enabled.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-enabled.component.spec.ts @@ -4,7 +4,6 @@ import { IrisSettingsService } from 'app/iris/settings/shared/iris-settings.serv import { MockProvider } from 'ng-mocks'; import { of } from 'rxjs'; import { mockSettings } from './mock-settings'; -import { RouterTestingModule } from '@angular/router/testing'; import { IrisSettings } from 'app/entities/iris/settings/iris-settings.model'; import { HttpResponse } from '@angular/common/http'; import { IrisEnabledComponent } from 'app/iris/settings/shared/iris-enabled.component'; @@ -12,6 +11,7 @@ import { TranslatePipeMock } from '../../../helpers/mocks/service/mock-translate import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model'; import { Course } from 'app/entities/course.model'; import { IrisSubSettingsType } from 'app/entities/iris/settings/iris-sub-settings.model'; +import { provideRouter } from '@angular/router'; describe('IrisEnabledComponent', () => { let comp: IrisEnabledComponent; @@ -26,9 +26,9 @@ describe('IrisEnabledComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule], + imports: [ArtemisTestModule], declarations: [IrisEnabledComponent, TranslatePipeMock], - providers: [MockProvider(IrisSettingsService)], + providers: [provideRouter([]), MockProvider(IrisSettingsService)], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/iris/settings/iris-exercise-settings-update.component.spec.ts b/src/test/javascript/spec/component/iris/settings/iris-exercise-settings-update.component.spec.ts index d99ce839e8a5..98d0ff3f8770 100644 --- a/src/test/javascript/spec/component/iris/settings/iris-exercise-settings-update.component.spec.ts +++ b/src/test/javascript/spec/component/iris/settings/iris-exercise-settings-update.component.spec.ts @@ -9,8 +9,7 @@ import { IrisCommonSubSettingsUpdateComponent } from 'app/iris/settings/iris-set import { IrisGlobalAutoupdateSettingsUpdateComponent } from 'app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component'; import { mockSettings } from './mock-settings'; import { IrisExerciseSettingsUpdateComponent } from 'app/iris/settings/iris-exercise-settings-update/iris-exercise-settings-update.component'; -import { ActivatedRoute, Params } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; +import { ActivatedRoute, Params, provideRouter } from '@angular/router'; import { NgModel } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { IrisSettings } from 'app/entities/iris/settings/iris-settings.model'; @@ -29,7 +28,7 @@ describe('IrisExerciseSettingsUpdateComponent Component', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule], + imports: [ArtemisTestModule], declarations: [ IrisExerciseSettingsUpdateComponent, IrisSettingsUpdateComponent, @@ -38,7 +37,7 @@ describe('IrisExerciseSettingsUpdateComponent Component', () => { MockComponent(ButtonComponent), MockDirective(NgModel), ], - providers: [MockProvider(IrisSettingsService), { provide: ActivatedRoute, useValue: route }], + providers: [provideRouter([]), MockProvider(IrisSettingsService), { provide: ActivatedRoute, useValue: route }], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/lecture-unit/unit-creation-card.component.spec.ts b/src/test/javascript/spec/component/lecture-unit/unit-creation-card.component.spec.ts index 7fa293c29ec7..7be1e3df1990 100644 --- a/src/test/javascript/spec/component/lecture-unit/unit-creation-card.component.spec.ts +++ b/src/test/javascript/spec/component/lecture-unit/unit-creation-card.component.spec.ts @@ -3,18 +3,18 @@ import { MockComponent, MockDirective, MockPipe, MockProvider } from 'ng-mocks'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { UnitCreationCardComponent } from 'app/lecture/lecture-unit/lecture-unit-management/unit-creation-card/unit-creation-card.component'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; -import { RouterTestingModule } from '@angular/router/testing'; import { DocumentationButtonComponent } from 'app/shared/components/documentation-button/documentation-button.component'; import { TranslateService } from '@ngx-translate/core'; import { TranslateDirective } from 'app/shared/language/translate.directive'; import { LectureUnitType } from 'app/entities/lecture-unit/lectureUnit.model'; +import { RouterModule } from '@angular/router'; describe('UnitCreationCardComponent', () => { let unitCreationCardComponentFixture: ComponentFixture; let unitCreationCardComponent: UnitCreationCardComponent; beforeEach(() => { TestBed.configureTestingModule({ - imports: [RouterTestingModule], + imports: [RouterModule.forRoot([])], declarations: [ UnitCreationCardComponent, MockPipe(ArtemisTranslatePipe), diff --git a/src/test/javascript/spec/component/modeling-assessment-editor/modeling-assessment-editor.component.spec.ts b/src/test/javascript/spec/component/modeling-assessment-editor/modeling-assessment-editor.component.spec.ts index 65e4e0edb5b5..14cdcc02d9d0 100644 --- a/src/test/javascript/spec/component/modeling-assessment-editor/modeling-assessment-editor.component.spec.ts +++ b/src/test/javascript/spec/component/modeling-assessment-editor/modeling-assessment-editor.component.spec.ts @@ -1,7 +1,6 @@ import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { ActivatedRoute, ParamMap, Router, convertToParamMap } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; +import { ActivatedRoute, ParamMap, Router, RouterModule, convertToParamMap } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { AssessmentLayoutComponent } from 'app/assessment/assessment-layout/assessment-layout.component'; import { ComplaintService, EntityResponseType } from 'app/complaints/complaint.service'; @@ -63,7 +62,7 @@ describe('ModelingAssessmentEditorComponent', () => { beforeEach(() => { paramMapSubject = new BehaviorSubject(convertToParamMap({})); TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule], + imports: [ArtemisTestModule, RouterModule.forRoot([])], declarations: [ ModelingAssessmentEditorComponent, MockComponent(AssessmentLayoutComponent), diff --git a/src/test/javascript/spec/component/modeling-submission/modeling-submission-team.component.spec.ts b/src/test/javascript/spec/component/modeling-submission/modeling-submission-team.component.spec.ts index 0f44f1b0e6fd..1ebae9d40337 100644 --- a/src/test/javascript/spec/component/modeling-submission/modeling-submission-team.component.spec.ts +++ b/src/test/javascript/spec/component/modeling-submission/modeling-submission-team.component.spec.ts @@ -8,8 +8,7 @@ import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.s import { MockParticipationWebsocketService } from '../../helpers/mocks/service/mock-participation-websocket.service'; import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; import { TranslateService } from '@ngx-translate/core'; -import { RouterTestingModule } from '@angular/router/testing'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, RouterModule } from '@angular/router'; import { ParticipationWebsocketService } from 'app/overview/participation-websocket.service'; import { ChangeDetectorRef, DebugElement } from '@angular/core'; import { By } from '@angular/platform-browser'; @@ -65,7 +64,7 @@ describe('ModelingSubmissionComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule.withRoutes([routes[0]])], + imports: [ArtemisTestModule, RouterModule.forRoot([routes[0]])], declarations: [ ModelingSubmissionComponent, MockComponent(ModelingEditorComponent), diff --git a/src/test/javascript/spec/component/modeling-submission/modeling-submission.component.spec.ts b/src/test/javascript/spec/component/modeling-submission/modeling-submission.component.spec.ts index 439db7c5dcb9..4f5ecdb8616a 100644 --- a/src/test/javascript/spec/component/modeling-submission/modeling-submission.component.spec.ts +++ b/src/test/javascript/spec/component/modeling-submission/modeling-submission.component.spec.ts @@ -8,8 +8,7 @@ import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.s import { MockParticipationWebsocketService } from '../../helpers/mocks/service/mock-participation-websocket.service'; import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; import { TranslateService } from '@ngx-translate/core'; -import { RouterTestingModule } from '@angular/router/testing'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, RouterModule } from '@angular/router'; import { ParticipationWebsocketService } from 'app/overview/participation-websocket.service'; import { ChangeDetectorRef, DebugElement } from '@angular/core'; import { By } from '@angular/platform-browser'; @@ -61,7 +60,7 @@ describe('ModelingSubmissionComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule.withRoutes([routes[0]])], + imports: [ArtemisTestModule, RouterModule.forRoot([routes[0]])], declarations: [ ModelingSubmissionComponent, MockComponent(ModelingEditorComponent), diff --git a/src/test/javascript/spec/component/overview/course-conversations/course-wide-search.component.spec.ts b/src/test/javascript/spec/component/overview/course-conversations/course-wide-search.component.spec.ts index 969c54184708..288ed74a8c86 100644 --- a/src/test/javascript/spec/component/overview/course-conversations/course-wide-search.component.spec.ts +++ b/src/test/javascript/spec/component/overview/course-conversations/course-wide-search.component.spec.ts @@ -14,11 +14,12 @@ import { BehaviorSubject } from 'rxjs'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { MessageInlineInputComponent } from 'app/shared/metis/message/message-inline-input/message-inline-input.component'; import { PostCreateEditModalComponent } from 'app/shared/metis/posting-create-edit-modal/post-create-edit-modal/post-create-edit-modal.component'; -import { MockComponent, MockPipe, MockProvider } from 'ng-mocks'; +import { MockComponent, MockDirective, MockPipe, MockProvider } from 'ng-mocks'; import { PostSortCriterion, SortDirection } from 'app/shared/metis/metis.util'; import { metisExamChannelDTO, metisExerciseChannelDTO, metisGeneralChannelDTO, metisLectureChannelDTO } from '../../../helpers/sample/metis-sample-data'; import { getElement } from '../../../helpers/utils/general.utils'; import { NgbTooltipMocksModule } from '../../../helpers/mocks/directive/ngbTooltipMocks.module'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; @Directive({ // eslint-disable-next-line @angular-eslint/directive-selector @@ -68,6 +69,7 @@ describe('CourseWideSearchComponent', () => { MockComponent(PostingThreadComponent), MockComponent(MessageInlineInputComponent), MockComponent(PostCreateEditModalComponent), + MockDirective(TranslateDirective), ], providers: [MockProvider(MetisConversationService), MockProvider(MetisService), MockProvider(NgbModal)], }).compileComponents(); diff --git a/src/test/javascript/spec/component/overview/course-conversations/dialogs/conversation-detail-dialog/tabs/conversation-members/conversation-member-row/conversation-member-row.component.spec.ts b/src/test/javascript/spec/component/overview/course-conversations/dialogs/conversation-detail-dialog/tabs/conversation-members/conversation-member-row/conversation-member-row.component.spec.ts index e58af8239cc1..133fc64978b9 100644 --- a/src/test/javascript/spec/component/overview/course-conversations/dialogs/conversation-detail-dialog/tabs/conversation-members/conversation-member-row/conversation-member-row.component.spec.ts +++ b/src/test/javascript/spec/component/overview/course-conversations/dialogs/conversation-detail-dialog/tabs/conversation-members/conversation-member-row/conversation-member-row.component.spec.ts @@ -22,6 +22,7 @@ import { of } from 'rxjs'; import { isGroupChatDTO } from 'app/entities/metis/conversation/group-chat.model'; import { By } from '@angular/platform-browser'; import { NgbDropdownMocksModule } from '../../../../../../../../helpers/mocks/directive/ngbDropdownMocks.module'; +import { getElement } from '../../../../../../../../helpers/utils/general.utils'; const memberTemplate = { id: 1, @@ -167,6 +168,11 @@ examples.forEach((activeConversation) => { } })); + it('should display default profile picture', () => { + fixture.detectChanges(); + expect(getElement(fixture.debugElement, '.conversation-member-row-default-profile-picture')).not.toBeNull(); + }); + function checkGrantModeratorButton(shouldExist: boolean) { const grantModeratorRoleButton = fixture.debugElement.query(By.css('.grant-moderator')); if (shouldExist) { diff --git a/src/test/javascript/spec/component/overview/course-conversations/layout/conversation-header/conversation-header.component.spec.ts b/src/test/javascript/spec/component/overview/course-conversations/layout/conversation-header/conversation-header.component.spec.ts index 5af6ce680ba9..1bec632a62d4 100644 --- a/src/test/javascript/spec/component/overview/course-conversations/layout/conversation-header/conversation-header.component.spec.ts +++ b/src/test/javascript/spec/component/overview/course-conversations/layout/conversation-header/conversation-header.component.spec.ts @@ -1,7 +1,6 @@ import { ComponentFixture, TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing'; import { ConversationHeaderComponent } from 'app/overview/course-conversations/layout/conversation-header/conversation-header.component'; import { Location } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; import { MockComponent, MockPipe, MockProvider } from 'ng-mocks'; import { ChannelIconComponent } from 'app/overview/course-conversations/other/channel-icon/channel-icon.component'; import { GroupChatIconComponent } from 'app/overview/course-conversations/other/group-chat-icon/group-chat-icon.component'; @@ -28,6 +27,7 @@ import { MockMetisService } from '../../../../../helpers/mocks/service/mock-meti import { MetisModule } from 'app/shared/metis/metis.module'; import { MockTranslateService } from '../../../../../helpers/mocks/service/mock-translate.service'; import { TranslateService } from '@ngx-translate/core'; +import { provideRouter } from '@angular/router'; const examples: ConversationDTO[] = [ generateOneToOneChatDTO({}), @@ -54,14 +54,14 @@ examples.forEach((activeConversation) => { MockComponent(GroupChatIconComponent), MockComponent(FaIconComponent), MockPipe(ArtemisTranslatePipe), - RouterTestingModule.withRoutes([ + ], + imports: [MetisModule], + providers: [ + provideRouter([ { path: 'courses/:courseId/lectures/:lectureId', component: CourseLectureDetailsComponent }, { path: 'courses/:courseId/exercises/:exerciseId', component: CourseExerciseDetailsComponent }, { path: 'courses/:courseId/exams/:examId', component: ExamDetailComponent }, ]), - ], - imports: [MetisModule], - providers: [ MockProvider(NgbModal), MockProvider(MetisConversationService), MockProvider(ConversationService), diff --git a/src/test/javascript/spec/component/overview/course-exams/course-exams.component.spec.ts b/src/test/javascript/spec/component/overview/course-exams/course-exams.component.spec.ts index 60590bae4bde..e49832ad6990 100644 --- a/src/test/javascript/spec/component/overview/course-exams/course-exams.component.spec.ts +++ b/src/test/javascript/spec/component/overview/course-exams/course-exams.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { Course } from 'app/entities/course.model'; import { CourseExamsComponent } from 'app/overview/course-exams/course-exams.component'; import { Exam } from 'app/entities/exam/exam.model'; @@ -15,7 +15,6 @@ import { CourseStorageService } from 'app/course/manage/course-storage.service'; import { SidebarComponent } from 'app/shared/sidebar/sidebar.component'; import { SearchFilterComponent } from 'app/shared/search-filter/search-filter.component'; import { SearchFilterPipe } from 'app/shared/pipes/search-filter.pipe'; -import { RouterTestingModule } from '@angular/router/testing'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MockRouter } from '../../../helpers/mocks/mock-router'; import { CourseOverviewService } from 'app/overview/course-overview.service'; @@ -101,7 +100,7 @@ describe('CourseExamsComponent', () => { router.navigate.mockImplementation(() => Promise.resolve(true)); TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule, MockModule(FormsModule), MockModule(ReactiveFormsModule), MockDirective(TranslateDirective)], + imports: [ArtemisTestModule, RouterModule.forRoot([]), MockModule(FormsModule), MockModule(ReactiveFormsModule), MockDirective(TranslateDirective)], declarations: [CourseExamsComponent, SidebarComponent, SearchFilterComponent, MockPipe(ArtemisTranslatePipe), MockPipe(SearchFilterPipe)], providers: [ { provide: Router, useValue: router }, diff --git a/src/test/javascript/spec/component/overview/course-exercises/course-exercise-row.component.spec.ts b/src/test/javascript/spec/component/overview/course-exercises/course-exercise-row.component.spec.ts index 6966044517da..1c4ed04de6f7 100644 --- a/src/test/javascript/spec/component/overview/course-exercises/course-exercise-row.component.spec.ts +++ b/src/test/javascript/spec/component/overview/course-exercises/course-exercise-row.component.spec.ts @@ -30,9 +30,9 @@ import { IncludedInScoreBadgeComponent } from 'app/exercises/shared/exercise-hea import { ArtemisTimeAgoPipe } from 'app/shared/pipes/artemis-time-ago.pipe'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { OrionFilterDirective } from 'app/shared/orion/orion-filter.directive'; -import { RouterTestingModule } from '@angular/router/testing'; import { CourseExerciseService } from 'app/exercises/shared/course-exercises/course-exercise.service'; import { ExerciseCategoriesComponent } from 'app/shared/exercise-categories/exercise-categories.component'; +import { RouterModule } from '@angular/router'; @Component({ template: '', @@ -51,11 +51,11 @@ describe('CourseExerciseRowComponent', () => { imports: [ ArtemisTestModule, TranslateModule.forRoot(), - NgbModule, - RouterTestingModule.withRoutes([ + RouterModule.forRoot([ { path: 'courses/:courseId/exercises', component: DummyComponent }, { path: 'courses/:courseId/exercises/:exerciseId', component: DummyComponent }, ]), + NgbModule, ], declarations: [ MockComponent(SubmissionResultStatusComponent), diff --git a/src/test/javascript/spec/component/overview/course-lectures/course-lecture-row.component.spec.ts b/src/test/javascript/spec/component/overview/course-lectures/course-lecture-row.component.spec.ts index 2cf30064e71f..31dc9dba62d7 100644 --- a/src/test/javascript/spec/component/overview/course-lectures/course-lecture-row.component.spec.ts +++ b/src/test/javascript/spec/component/overview/course-lectures/course-lecture-row.component.spec.ts @@ -1,6 +1,5 @@ import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { RouterTestingModule } from '@angular/router/testing'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { Course } from 'app/entities/course.model'; import { Lecture } from 'app/entities/lecture.model'; @@ -12,7 +11,7 @@ import dayjs from 'dayjs/esm'; import { MockComponent, MockDirective, MockPipe } from 'ng-mocks'; import { Location } from '@angular/common'; import { Component } from '@angular/core'; -import { Router } from '@angular/router'; +import { Router, RouterModule } from '@angular/router'; import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; @Component({ @@ -29,11 +28,11 @@ describe('CourseLectureRow', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ - RouterTestingModule.withRoutes([ + MockDirective(NgbTooltip), + RouterModule.forRoot([ { path: 'courses/:courseId/lectures', component: DummyComponent }, { path: 'courses/:courseId/lectures/:lectureId', component: DummyComponent }, ]), - MockDirective(NgbTooltip), ], declarations: [ DummyComponent, diff --git a/src/test/javascript/spec/component/overview/course-lectures/course-lectures.component.spec.ts b/src/test/javascript/spec/component/overview/course-lectures/course-lectures.component.spec.ts index 55048ea53f59..0a2732347f41 100644 --- a/src/test/javascript/spec/component/overview/course-lectures/course-lectures.component.spec.ts +++ b/src/test/javascript/spec/component/overview/course-lectures/course-lectures.component.spec.ts @@ -1,6 +1,6 @@ import { Component, Input } from '@angular/core'; import { ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, RouterModule } from '@angular/router'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { NgbDropdownModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateService } from '@ngx-translate/core'; @@ -8,8 +8,8 @@ import { LocalStorageService } from 'ngx-webstorage'; import { Course } from 'app/entities/course.model'; import { Lecture } from 'app/entities/lecture.model'; import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service'; -import { CourseLecturesComponent } from '../../../../../../main/webapp/app/overview/course-lectures/course-lectures.component'; -import { SidebarComponent } from '../../../../../../main/webapp/app/shared/sidebar/sidebar.component'; +import { CourseLecturesComponent } from 'app/overview/course-lectures/course-lectures.component'; +import { SidebarComponent } from 'app/shared/sidebar/sidebar.component'; import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { SearchFilterPipe } from 'app/shared/pipes/search-filter.pipe'; @@ -21,14 +21,14 @@ import { MockTranslateService } from '../../../helpers/mocks/service/mock-transl import { TranslateDirective } from 'app/shared/language/translate.directive'; import { CourseStorageService } from 'app/course/manage/course-storage.service'; import { MockSyncStorage } from '../../../helpers/mocks/service/mock-sync-storage.service'; -import { CourseOverviewService } from '../../../../../../main/webapp/app/overview/course-overview.service'; -import { HttpClientModule } from '@angular/common/http'; -import { ProfileService } from '../../../../../../main/webapp/app/shared/layouts/profiles/profile.service'; +import { CourseOverviewService } from 'app/overview/course-overview.service'; +import { provideHttpClient } from '@angular/common/http'; +import { ProfileService } from 'app/shared/layouts/profiles/profile.service'; import { MockProfileService } from '../../../helpers/mocks/service/mock-profile.service'; -import { ProfileInfo } from '../../../../../../main/webapp/app/shared/layouts/profiles/profile-info.model'; -import { RouterTestingModule } from '@angular/router/testing'; +import { ProfileInfo } from 'app/shared/layouts/profiles/profile-info.model'; import { SearchFilterComponent } from 'app/shared/search-filter/search-filter.component'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; @Component({ selector: 'jhi-course-lecture-row', template: '' }) class CourseLectureRowStubComponent { @@ -67,7 +67,7 @@ describe('CourseLectures', () => { course.lectures = [lecture1, lecture2, lecture3]; TestBed.configureTestingModule({ - imports: [NgbDropdownModule, HttpClientModule, RouterTestingModule, MockModule(FormsModule), MockModule(ReactiveFormsModule), MockModule(NgbTooltipModule)], + imports: [NgbDropdownModule, RouterModule.forRoot([]), MockModule(FormsModule), MockModule(ReactiveFormsModule), MockModule(NgbTooltipModule)], declarations: [ CourseLecturesComponent, CourseLectureRowStubComponent, @@ -81,6 +81,8 @@ describe('CourseLectures', () => { MockDirective(TranslateDirective), ], providers: [ + provideHttpClient(), + provideHttpClientTesting(), MockProvider(CourseStorageService, { getCourse: () => { return course; diff --git a/src/test/javascript/spec/component/overview/course-statistics/course-statistics.component.spec.ts b/src/test/javascript/spec/component/overview/course-statistics/course-statistics.component.spec.ts index b38eefa60f4e..29e6c4bf98f7 100644 --- a/src/test/javascript/spec/component/overview/course-statistics/course-statistics.component.spec.ts +++ b/src/test/javascript/spec/component/overview/course-statistics/course-statistics.component.spec.ts @@ -1,7 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { ActivatedRoute } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; +import { ActivatedRoute, provideRouter } from '@angular/router'; import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; import { BarChartModule, PieChartModule } from '@swimlane/ngx-charts'; import { CourseScores } from 'app/course/course-scores/course-scores'; @@ -329,7 +328,7 @@ describe('CourseStatisticsComponent', () => { beforeEach(() => { return TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule, TreeviewModule.forRoot(), MockModule(PieChartModule), MockModule(BarChartModule), MockModule(NgbTooltipModule)], + imports: [ArtemisTestModule, TreeviewModule.forRoot(), MockModule(PieChartModule), MockModule(BarChartModule), MockModule(NgbTooltipModule)], declarations: [ CourseStatisticsComponent, MockComponent(CourseCompetenciesComponent), @@ -340,6 +339,7 @@ describe('CourseStatisticsComponent', () => { TranslateDirective, ], providers: [ + provideRouter([]), MockProvider(ArtemisNavigationUtilService), MockProvider(ChartCategoryFilter), { diff --git a/src/test/javascript/spec/component/overview/course-statistics/visualizations/exercise-scores-chart.component.spec.ts b/src/test/javascript/spec/component/overview/course-statistics/visualizations/exercise-scores-chart.component.spec.ts index 76416a42e4f9..f791c8b813c8 100644 --- a/src/test/javascript/spec/component/overview/course-statistics/visualizations/exercise-scores-chart.component.spec.ts +++ b/src/test/javascript/spec/component/overview/course-statistics/visualizations/exercise-scores-chart.component.spec.ts @@ -5,9 +5,8 @@ import { MockDirective, MockModule, MockPipe, MockProvider } from 'ng-mocks'; import { ChartNode, ExerciseScoresChartComponent } from 'app/overview/visualizations/exercise-scores-chart/exercise-scores-chart.component'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { of } from 'rxjs'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, provideRouter } from '@angular/router'; import { ExerciseScoresChartService, ExerciseScoresDTO } from 'app/overview/visualizations/exercise-scores-chart.service'; -import { RouterTestingModule } from '@angular/router/testing'; import { ExerciseType } from 'app/entities/exercise.model'; import dayjs from 'dayjs/esm'; import { HttpResponse } from '@angular/common/http'; @@ -43,9 +42,10 @@ describe('ExerciseScoresChartComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, MockModule(LineChartModule), RouterTestingModule.withRoutes([]), MockModule(BrowserAnimationsModule)], + imports: [ArtemisTestModule, MockModule(LineChartModule), MockModule(BrowserAnimationsModule)], declarations: [ExerciseScoresChartComponent, MockPipe(ArtemisTranslatePipe), MockDirective(TranslateDirective)], providers: [ + provideRouter([]), MockProvider(AlertService), MockProvider(ArtemisNavigationUtilService), { provide: TranslateService, useClass: MockTranslateService }, diff --git a/src/test/javascript/spec/component/overview/exercise-details/request-feedback-button/request-feedback-button.component.spec.ts b/src/test/javascript/spec/component/overview/exercise-details/request-feedback-button/request-feedback-button.component.spec.ts new file mode 100644 index 000000000000..94219fdd2420 --- /dev/null +++ b/src/test/javascript/spec/component/overview/exercise-details/request-feedback-button/request-feedback-button.component.spec.ts @@ -0,0 +1,228 @@ +import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { MockProvider } from 'ng-mocks'; +import { ProfileService } from 'app/shared/layouts/profiles/profile.service'; +import { Observable, of } from 'rxjs'; +import { Exercise, ExerciseType } from 'app/entities/exercise.model'; +import { StudentParticipation } from 'app/entities/participation/student-participation.model'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; +import { AlertService } from 'app/core/util/alert.service'; +import { CourseExerciseService } from 'app/exercises/shared/course-exercises/course-exercise.service'; +import { HttpClient, HttpResponse } from '@angular/common/http'; +import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service'; +import { RequestFeedbackButtonComponent } from 'app/overview/exercise-details/request-feedback-button/request-feedback-button.component'; +import { ArtemisTestModule } from '../../../../test.module'; +import { MockProfileService } from '../../../../helpers/mocks/service/mock-profile.service'; +import { ProfileInfo } from 'app/shared/layouts/profiles/profile-info.model'; + +describe('RequestFeedbackButtonComponent', () => { + let component: RequestFeedbackButtonComponent; + let fixture: ComponentFixture; + let debugElement: DebugElement; + let profileService: ProfileService; + let alertService: AlertService; + let courseExerciseService: CourseExerciseService; + let exerciseService: ExerciseService; + + beforeEach(() => { + return TestBed.configureTestingModule({ + imports: [ArtemisTestModule, RequestFeedbackButtonComponent], + providers: [{ provide: ProfileService, useClass: MockProfileService }, MockProvider(HttpClient)], + }) + .compileComponents() + .then(() => { + fixture = TestBed.createComponent(RequestFeedbackButtonComponent); + component = fixture.componentInstance; + debugElement = fixture.debugElement; + courseExerciseService = debugElement.injector.get(CourseExerciseService); + exerciseService = debugElement.injector.get(ExerciseService); + profileService = debugElement.injector.get(ProfileService); + alertService = debugElement.injector.get(AlertService); + }); + }); + + function setAthenaEnabled(enabled: boolean) { + jest.spyOn(profileService, 'getProfileInfo').mockReturnValue(of({ activeProfiles: enabled ? ['athena'] : [] } as ProfileInfo)); + } + + function mockExerciseDetails(exercise: Exercise) { + jest.spyOn(exerciseService, 'getExerciseDetails').mockReturnValue(of(new HttpResponse({ body: { exercise: exercise } }))); + } + + it('should handle errors when requestFeedback fails', fakeAsync(() => { + setAthenaEnabled(true); + const participation = { + id: 1, + submissions: [{ id: 1, submitted: true }], + testRun: false, + } as StudentParticipation; + const exercise = { id: 1, type: ExerciseType.TEXT, course: undefined, studentParticipations: [participation] } as Exercise; + fixture.componentRef.setInput('exercise', exercise); + mockExerciseDetails(exercise); + + jest.spyOn(courseExerciseService, 'requestFeedback').mockReturnValue( + new Observable((subscriber) => { + subscriber.error({ error: { errorKey: 'someError' } }); + }), + ); + jest.spyOn(alertService, 'error'); + + component.requestFeedback(); + tick(); + + expect(alertService.error).toHaveBeenCalledWith('artemisApp.exercise.someError'); + })); + + it('should display the button when Athena is enabled and it is not an exam exercise', fakeAsync(() => { + setAthenaEnabled(true); + const exercise = { id: 1, type: ExerciseType.TEXT, course: {} } as Exercise; // course undefined means exam exercise + fixture.componentRef.setInput('exercise', exercise); + mockExerciseDetails(exercise); + + component.ngOnInit(); + tick(); + fixture.detectChanges(); + + const button = debugElement.query(By.css('button')); + expect(button).not.toBeNull(); + expect(button.nativeElement.disabled).toBeTrue(); + })); + + it('should not display the button when it is an exam exercise', fakeAsync(() => { + setAthenaEnabled(true); + fixture.componentRef.setInput('exercise', { id: 1, type: ExerciseType.TEXT, course: undefined } as Exercise); + + component.ngOnInit(); + tick(); + fixture.detectChanges(); + + const button = debugElement.query(By.css('button')); + const link = debugElement.query(By.css('a')); + expect(button).toBeNull(); + expect(link).toBeNull(); + })); + + it('should disable the button when participation is missing', fakeAsync(() => { + setAthenaEnabled(true); + const exercise = { id: 1, type: ExerciseType.TEXT, course: {}, studentParticipations: undefined } as Exercise; + fixture.componentRef.setInput('exercise', exercise); + mockExerciseDetails(exercise); + + component.ngOnInit(); + tick(); + fixture.detectChanges(); + + const button = debugElement.query(By.css('button')); + expect(button).not.toBeNull(); + expect(button.nativeElement.disabled).toBeTrue(); + })); + + it('should display the correct button label and style when Athena is enabled', fakeAsync(() => { + setAthenaEnabled(true); + const participation = { + id: 1, + submissions: [{ id: 1, submitted: true }], + } as StudentParticipation; + const exercise = { id: 1, type: ExerciseType.TEXT, course: {}, studentParticipations: [participation] } as Exercise; + fixture.componentRef.setInput('exercise', exercise); + component.isExamExercise = false; + mockExerciseDetails(exercise); + + component.ngOnInit(); + tick(); + fixture.detectChanges(); + + const button = debugElement.query(By.css('button')); + expect(button).not.toBeNull(); + + const span = button.query(By.css('span')); + expect(span.nativeElement.textContent).toContain('artemisApp.exerciseActions.requestAutomaticFeedback'); + })); + + it('should call requestFeedback() when button is clicked', fakeAsync(() => { + setAthenaEnabled(true); + const participation = { + id: 1, + submissions: [{ id: 1, submitted: false }], + testRun: false, + } as StudentParticipation; + const exercise = { id: 1, type: ExerciseType.PROGRAMMING, studentParticipations: [participation], course: {} } as Exercise; + fixture.componentRef.setInput('exercise', exercise); + + mockExerciseDetails(exercise); + + component.ngOnInit(); + tick(); + fixture.detectChanges(); + + jest.spyOn(component, 'requestFeedback'); + jest.spyOn(courseExerciseService, 'requestFeedback').mockReturnValue( + new Observable((subscriber) => { + subscriber.next(); + subscriber.complete(); + }), + ); + + const button = debugElement.query(By.css('a')); + button.nativeElement.click(); + tick(); + + expect(component.requestFeedback).toHaveBeenCalled(); + })); + + it('should show an alert when requestFeedback() is called and conditions are not satisfied', fakeAsync(() => { + setAthenaEnabled(true); + + const exercise = { id: 1, type: ExerciseType.TEXT, course: {} } as Exercise; + fixture.componentRef.setInput('exercise', exercise); + + jest.spyOn(component, 'hasAthenaResultForLatestSubmission').mockReturnValue(true); + jest.spyOn(alertService, 'warning'); + + component.requestFeedback(); + + expect(alertService.warning).toHaveBeenCalled(); + })); + + it('should disable the button if latest submission is not submitted or feedback is generating', fakeAsync(() => { + setAthenaEnabled(true); + const participation = { + id: 1, + submissions: [{ id: 1, submitted: false }], + testRun: false, + } as StudentParticipation; + const exercise = { id: 1, type: ExerciseType.TEXT, studentParticipations: [participation], course: {} } as Exercise; + fixture.componentRef.setInput('exercise', exercise); + fixture.componentRef.setInput('isGeneratingFeedback', false); + mockExerciseDetails(exercise); + + component.ngOnInit(); + tick(); + fixture.detectChanges(); + + const button = debugElement.query(By.css('button')); + expect(button).not.toBeNull(); + expect(button.nativeElement.disabled).toBeTrue(); + })); + + it('should enable the button if latest submission is submitted and feedback is not generating', fakeAsync(() => { + setAthenaEnabled(true); + const participation = { + id: 1, + submissions: [{ id: 1, submitted: true }], + testRun: false, + } as StudentParticipation; + const exercise = { id: 1, type: ExerciseType.TEXT, course: {}, studentParticipations: [participation] } as Exercise; + fixture.componentRef.setInput('exercise', exercise); + fixture.componentRef.setInput('isGeneratingFeedback', false); + mockExerciseDetails(exercise); + + component.ngOnInit(); + tick(); + fixture.detectChanges(); + + const button = debugElement.query(By.css('button')); + expect(button).not.toBeNull(); + expect(button.nativeElement.disabled).toBeFalse(); + })); +}); diff --git a/src/test/javascript/spec/component/participation-submission/participation-submission.component.spec.ts b/src/test/javascript/spec/component/participation-submission/participation-submission.component.spec.ts index e7a7f5ca10e2..a00664f4e5a5 100644 --- a/src/test/javascript/spec/component/participation-submission/participation-submission.component.spec.ts +++ b/src/test/javascript/spec/component/participation-submission/participation-submission.component.spec.ts @@ -5,7 +5,7 @@ import dayjs from 'dayjs/esm'; import { ArtemisTestModule } from '../../test.module'; import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service'; import { MockComponent, MockDirective, MockPipe } from 'ng-mocks'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { BehaviorSubject, of, throwError } from 'rxjs'; import { UnreferencedFeedbackDetailComponent } from 'app/assessment/unreferenced-feedback-detail/unreferenced-feedback-detail.component'; import { DebugElement } from '@angular/core'; @@ -21,7 +21,6 @@ import { UpdatingResultComponent } from 'app/exercises/shared/result/updating-re import { Submission, SubmissionExerciseType, SubmissionType } from 'app/entities/submission.model'; import { StudentParticipation } from 'app/entities/participation/student-participation.model'; import { TextSubmission } from 'app/entities/text/text-submission.model'; -import { RouterTestingModule } from '@angular/router/testing'; import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service'; import { Exercise, ExerciseType } from 'app/entities/exercise.model'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; @@ -89,7 +88,7 @@ describe('ParticipationSubmissionComponent', () => { beforeEach(() => { return TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule, NgxDatatableModule], + imports: [ArtemisTestModule, NgxDatatableModule, RouterModule.forRoot([])], declarations: [ ParticipationSubmissionComponent, MockComponent(UpdatingResultComponent), diff --git a/src/test/javascript/spec/component/programming-assessment/code-editor-tutor-assessment-container.component.spec.ts b/src/test/javascript/spec/component/programming-assessment/code-editor-tutor-assessment-container.component.spec.ts index 92350e8377d9..4177be93e1d9 100644 --- a/src/test/javascript/spec/component/programming-assessment/code-editor-tutor-assessment-container.component.spec.ts +++ b/src/test/javascript/spec/component/programming-assessment/code-editor-tutor-assessment-container.component.spec.ts @@ -32,12 +32,11 @@ import { Course } from 'app/entities/course.model'; import { delay } from 'rxjs/operators'; import { ProgrammingSubmissionService } from 'app/exercises/programming/participate/programming-submission.service'; import { ComplaintResponse } from 'app/entities/complaint-response.model'; -import { ActivatedRoute, Router, convertToParamMap } from '@angular/router'; +import { ActivatedRoute, Router, convertToParamMap, provideRouter } from '@angular/router'; import { ProgrammingExerciseService } from 'app/exercises/programming/manage/services/programming-exercise.service'; import { CodeEditorRepositoryFileService } from 'app/exercises/programming/shared/code-editor/service/code-editor-repository.service'; import { CodeEditorFileBrowserComponent } from 'app/exercises/programming/shared/code-editor/file-browser/code-editor-file-browser.component'; import { FileType } from 'app/exercises/programming/shared/code-editor/model/code-editor.model'; -import { RouterTestingModule } from '@angular/router/testing'; import { CodeEditorContainerComponent } from 'app/exercises/programming/shared/code-editor/container/code-editor-container.component'; import { ResultComponent } from 'app/exercises/shared/result/result.component'; import { IncludedInScoreBadgeComponent } from 'app/exercises/shared/exercise-headers/included-in-score-badge.component'; @@ -173,7 +172,7 @@ describe('CodeEditorTutorAssessmentContainerComponent', () => { beforeEach(() => { return TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule], + imports: [ArtemisTestModule], declarations: [ CodeEditorTutorAssessmentContainerComponent, MockComponent(ProgrammingAssessmentRepoExportButtonComponent), @@ -204,7 +203,7 @@ describe('CodeEditorTutorAssessmentContainerComponent', () => { ExtensionPointDirective, ], providers: [ - MockProvider(Router), + provideRouter([]), { provide: TranslateService, useClass: MockTranslateService }, { provide: ParticipationWebsocketService, useClass: MockParticipationWebsocketService }, { provide: RepositoryFileService, useClass: MockRepositoryFileService }, diff --git a/src/test/javascript/spec/component/shared/code-button.component.spec.ts b/src/test/javascript/spec/component/shared/code-button.component.spec.ts index 72d26a2d07b6..5665d6d4a1ef 100644 --- a/src/test/javascript/spec/component/shared/code-button.component.spec.ts +++ b/src/test/javascript/spec/component/shared/code-button.component.spec.ts @@ -26,9 +26,9 @@ import { MockProfileService } from '../../helpers/mocks/service/mock-profile.ser import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service'; import { MockTranslateService } from '../../helpers/mocks/service/mock-translate.service'; import { ArtemisTestModule } from '../../test.module'; -import { RouterTestingModule } from '@angular/router/testing'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { provideRouter } from '@angular/router'; describe('CodeButtonComponent', () => { let component: CodeButtonComponent; @@ -91,7 +91,7 @@ describe('CodeButtonComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, ClipboardModule, NgbPopoverModule, RouterTestingModule.withRoutes([])], + imports: [ArtemisTestModule, ClipboardModule, NgbPopoverModule], declarations: [ CodeButtonComponent, MockComponent(ExerciseActionButtonComponent), @@ -102,6 +102,7 @@ describe('CodeButtonComponent', () => { MockDirective(TranslateDirective), ], providers: [ + provideRouter([]), MockProvider(AlertService), { provide: FeatureToggleService, useClass: MockFeatureToggleService }, { provide: AccountService, useClass: MockAccountService }, diff --git a/src/test/javascript/spec/component/shared/main.component.spec.ts b/src/test/javascript/spec/component/shared/main.component.spec.ts index 9fa80d3863d1..2f639ad2429f 100644 --- a/src/test/javascript/spec/component/shared/main.component.spec.ts +++ b/src/test/javascript/spec/component/shared/main.component.spec.ts @@ -1,10 +1,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { TranslateTestingModule } from '../../helpers/mocks/service/mock-translate.service'; +import { MockTranslateService, TranslateTestingModule } from '../../helpers/mocks/service/mock-translate.service'; import { ArtemisTestModule } from '../../test.module'; import { By } from '@angular/platform-browser'; import { JhiMainComponent } from 'app/shared/layouts/main/main.component'; import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service'; -import { MockTranslateService } from '../../helpers/mocks/service/mock-translate.service'; import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; import { TranslateService } from '@ngx-translate/core'; import { ThemeService } from 'app/core/theme/theme.service'; @@ -13,7 +12,7 @@ import { MockComponent } from 'ng-mocks'; import { AlertOverlayComponent } from 'app/shared/alert/alert-overlay.component'; import { PageRibbonComponent } from 'app/shared/layouts/profiles/page-ribbon.component'; import { NotificationPopupComponent } from 'app/shared/notification/notification-popup/notification-popup.component'; -import { RouterTestingModule } from '@angular/router/testing'; +import { RouterModule } from '@angular/router'; // Mock the initialize method class MockThemeService { @@ -28,7 +27,7 @@ describe('JhiMainComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, TranslateTestingModule, RouterTestingModule], + imports: [ArtemisTestModule, TranslateTestingModule, RouterModule.forRoot([])], declarations: [JhiMainComponent, MockComponent(AlertOverlayComponent), MockComponent(PageRibbonComponent), MockComponent(NotificationPopupComponent)], providers: [ { provide: LocalStorageService, useClass: MockSyncStorage }, diff --git a/src/test/javascript/spec/component/shared/metis/post/post.component.spec.ts b/src/test/javascript/spec/component/shared/metis/post/post.component.spec.ts index a5618407cc41..dac357f94bec 100644 --- a/src/test/javascript/spec/component/shared/metis/post/post.component.spec.ts +++ b/src/test/javascript/spec/component/shared/metis/post/post.component.spec.ts @@ -23,11 +23,10 @@ import { unsortedAnswerArray, } from '../../../../helpers/sample/metis-sample-data'; import { MockQueryParamsDirective, MockRouterLinkDirective } from '../../../../helpers/mocks/directive/mock-router-link.directive'; -import { RouterTestingModule } from '@angular/router/testing'; import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; import { MetisConversationService } from 'app/shared/metis/metis-conversation.service'; import { OneToOneChatService } from 'app/shared/metis/conversations/one-to-one-chat.service'; -import { Router, RouterState } from '@angular/router'; +import { Router, RouterState, provideRouter } from '@angular/router'; import { of } from 'rxjs'; import { OneToOneChatDTO } from 'app/entities/metis/conversation/one-to-one-chat.model'; import { HttpResponse } from '@angular/common/http'; @@ -47,8 +46,9 @@ describe('PostComponent', () => { beforeEach(() => { return TestBed.configureTestingModule({ - imports: [RouterTestingModule, MockDirective(NgbTooltip)], + imports: [MockDirective(NgbTooltip)], providers: [ + provideRouter([]), { provide: MetisService, useClass: MockMetisService }, { provide: Router, useClass: MockRouter }, MockProvider(MetisConversationService), diff --git a/src/test/javascript/spec/component/shared/notification/notification-popup.component.spec.ts b/src/test/javascript/spec/component/shared/notification/notification-popup.component.spec.ts index b7a85f93e7dd..a628c21d022e 100644 --- a/src/test/javascript/spec/component/shared/notification/notification-popup.component.spec.ts +++ b/src/test/javascript/spec/component/shared/notification/notification-popup.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { Router } from '@angular/router'; +import { Router, RouterModule } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; import { ReplaySubject } from 'rxjs'; @@ -11,7 +11,6 @@ import { MockSyncStorage } from '../../../helpers/mocks/service/mock-sync-storag import { MockNotificationService } from '../../../helpers/mocks/service/mock-notification.service'; import { MockTranslateService } from '../../../helpers/mocks/service/mock-translate.service'; import { NEW_MESSAGE_TITLE, Notification, QUIZ_EXERCISE_STARTED_TEXT, QUIZ_EXERCISE_STARTED_TITLE } from 'app/entities/notification.model'; -import { RouterTestingModule } from '@angular/router/testing'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { MockPipe } from 'ng-mocks'; @@ -53,7 +52,7 @@ describe('Notification Popup Component', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule.withRoutes([])], + imports: [ArtemisTestModule, RouterModule.forRoot([])], declarations: [NotificationPopupComponent, MockPipe(ArtemisTranslatePipe)], providers: [ { provide: LocalStorageService, useClass: MockSyncStorage }, diff --git a/src/test/javascript/spec/component/shared/result.component.spec.ts b/src/test/javascript/spec/component/shared/result.component.spec.ts index d72fdc5f3bd2..e013c1e03e58 100644 --- a/src/test/javascript/spec/component/shared/result.component.spec.ts +++ b/src/test/javascript/spec/component/shared/result.component.spec.ts @@ -370,7 +370,6 @@ describe('ResultComponent', () => { it('should use special handling if result is an automatic AI result', () => { comp.result = { ...mockResult, score: 90, assessmentType: AssessmentType.AUTOMATIC_ATHENA }; - jest.spyOn(Result, 'isAthenaAIResult').mockReturnValue(true); comp.evaluate(); diff --git a/src/test/javascript/spec/component/shared/sidebar/conversation-options.component.spec.ts b/src/test/javascript/spec/component/shared/sidebar/conversation-options.component.spec.ts index a89b2264612a..0fc2fad98e4c 100644 --- a/src/test/javascript/spec/component/shared/sidebar/conversation-options.component.spec.ts +++ b/src/test/javascript/spec/component/shared/sidebar/conversation-options.component.spec.ts @@ -14,7 +14,6 @@ import { CourseExerciseDetailsComponent } from 'app/overview/exercise-details/co import { ExamDetailComponent } from 'app/exam/manage/exams/exam-detail.component'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { ConversationService } from 'app/shared/metis/conversations/conversation.service'; -import { RouterTestingModule } from '@angular/router/testing'; import { AlertService } from 'app/core/util/alert.service'; import { MetisService } from 'app/shared/metis/metis.service'; import { MockMetisService } from '../../../helpers/mocks/service/mock-metis-service.service'; @@ -27,6 +26,7 @@ import { ConversationDetailDialogComponent } from 'app/overview/course-conversat import { MetisConversationService } from 'app/shared/metis/metis-conversation.service'; import { isOneToOneChatDTO } from 'app/entities/metis/conversation/one-to-one-chat.model'; import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { provideRouter } from '@angular/router'; const examples: (() => ConversationDTO)[] = [ () => generateOneToOneChatDTO({}), @@ -51,17 +51,14 @@ examples.forEach((conversation) => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - ArtemisSharedModule, - NgbDropdownMocksModule, - RouterTestingModule.withRoutes([ + imports: [ArtemisSharedModule, NgbDropdownMocksModule], + declarations: [ConversationOptionsComponent, MockComponent(FaIconComponent), MockPipe(ArtemisTranslatePipe), MockDirective(TranslateDirective)], + providers: [ + provideRouter([ { path: 'courses/:courseId/lectures/:lectureId', component: CourseLectureDetailsComponent }, { path: 'courses/:courseId/exercises/:exerciseId', component: CourseExerciseDetailsComponent }, { path: 'courses/:courseId/exams/:examId', component: ExamDetailComponent }, ]), - ], - declarations: [ConversationOptionsComponent, MockComponent(FaIconComponent), MockPipe(ArtemisTranslatePipe), MockDirective(TranslateDirective)], - providers: [ MockProvider(ConversationService), MockProvider(MetisConversationService), MockProvider(AlertService), diff --git a/src/test/javascript/spec/component/statistics/statistics-average-score-graph.component.spec.ts b/src/test/javascript/spec/component/statistics/statistics-average-score-graph.component.spec.ts index 2626dffc45e1..d77c1251079e 100644 --- a/src/test/javascript/spec/component/statistics/statistics-average-score-graph.component.spec.ts +++ b/src/test/javascript/spec/component/statistics/statistics-average-score-graph.component.spec.ts @@ -1,6 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ArtemisTestModule } from '../../test.module'; -import { RouterTestingModule } from '@angular/router/testing'; import { MockDirective, MockModule, MockPipe, MockProvider } from 'ng-mocks'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { PerformanceInterval, StatisticsAverageScoreGraphComponent } from 'app/shared/statistics-graph/statistics-average-score-graph.component'; @@ -13,6 +12,7 @@ import { ChartExerciseTypeFilter } from 'app/shared/chart/chart-exercise-type-fi import { ChartCategoryFilter } from 'app/shared/chart/chart-category-filter'; import { TranslateDirective } from 'app/shared/language/translate.directive'; import { ExerciseCategory } from 'app/entities/exercise-category.model'; +import { provideRouter } from '@angular/router'; describe('StatisticsAverageScoreGraphComponent', () => { let fixture: ComponentFixture; @@ -78,9 +78,9 @@ describe('StatisticsAverageScoreGraphComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule.withRoutes([]), MockModule(BarChartModule)], + imports: [ArtemisTestModule, MockModule(BarChartModule)], declarations: [StatisticsAverageScoreGraphComponent, MockPipe(ArtemisTranslatePipe), MockDirective(TranslateDirective)], - providers: [MockProvider(ArtemisNavigationUtilService), MockProvider(ChartExerciseTypeFilter), MockProvider(ChartCategoryFilter)], + providers: [provideRouter([]), MockProvider(ArtemisNavigationUtilService), MockProvider(ChartExerciseTypeFilter), MockProvider(ChartCategoryFilter)], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/statistics/statistics-graph.component.spec.ts b/src/test/javascript/spec/component/statistics/statistics-graph.component.spec.ts index 7675dfe64dbf..6fce3eef9959 100644 --- a/src/test/javascript/spec/component/statistics/statistics-graph.component.spec.ts +++ b/src/test/javascript/spec/component/statistics/statistics-graph.component.spec.ts @@ -1,7 +1,6 @@ import { HttpTestingController } from '@angular/common/http/testing'; import { SimpleChange } from '@angular/core'; import { ArtemisTestModule } from '../../test.module'; -import { RouterTestingModule } from '@angular/router/testing'; import { TranslateService } from '@ngx-translate/core'; import { StatisticsGraphComponent } from 'app/shared/statistics-graph/statistics-graph.component'; import { StatisticsService } from 'app/shared/statistics-graph/statistics.service'; @@ -13,6 +12,7 @@ import { of } from 'rxjs'; import { MockTranslateService } from '../../helpers/mocks/service/mock-translate.service'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { BarChartModule } from '@swimlane/ngx-charts'; +import { provideRouter } from '@angular/router'; describe('StatisticsGraphComponent', () => { let fixture: ComponentFixture; @@ -22,9 +22,9 @@ describe('StatisticsGraphComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule.withRoutes([]), MockModule(BarChartModule)], + imports: [ArtemisTestModule, MockModule(BarChartModule)], declarations: [StatisticsGraphComponent, MockPipe(ArtemisTranslatePipe)], - providers: [{ provide: TranslateService, useClass: MockTranslateService }], + providers: [provideRouter([]), { provide: TranslateService, useClass: MockTranslateService }], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/statistics/statistics.component.spec.ts b/src/test/javascript/spec/component/statistics/statistics.component.spec.ts index 84b45d345bb3..060d6f991100 100644 --- a/src/test/javascript/spec/component/statistics/statistics.component.spec.ts +++ b/src/test/javascript/spec/component/statistics/statistics.component.spec.ts @@ -3,13 +3,13 @@ import { ArtemisTestModule } from '../../test.module'; import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service'; import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; import { MockComponent, MockDirective, MockPipe } from 'ng-mocks'; -import { RouterTestingModule } from '@angular/router/testing'; import { MockHasAnyAuthorityDirective } from '../../helpers/mocks/directive/mock-has-any-authority.directive'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; import { StatisticsComponent } from 'app/admin/statistics/statistics.component'; import { StatisticsGraphComponent } from 'app/shared/statistics-graph/statistics-graph.component'; import { SpanType } from 'app/entities/statistics.model'; +import { provideRouter } from '@angular/router'; describe('StatisticsComponent', () => { let fixture: ComponentFixture; @@ -17,7 +17,7 @@ describe('StatisticsComponent', () => { beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule.withRoutes([])], + imports: [ArtemisTestModule], declarations: [ StatisticsComponent, MockComponent(StatisticsGraphComponent), @@ -25,10 +25,7 @@ describe('StatisticsComponent', () => { MockPipe(ArtemisTranslatePipe), MockPipe(ArtemisDatePipe), ], - providers: [ - { provide: LocalStorageService, useClass: MockSyncStorage }, - { provide: SessionStorageService, useClass: MockSyncStorage }, - ], + providers: [provideRouter([]), { provide: LocalStorageService, useClass: MockSyncStorage }, { provide: SessionStorageService, useClass: MockSyncStorage }], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/team/teams.component.spec.ts b/src/test/javascript/spec/component/team/teams.component.spec.ts index f4b3cf2b454a..9ace2666920a 100644 --- a/src/test/javascript/spec/component/team/teams.component.spec.ts +++ b/src/test/javascript/spec/component/team/teams.component.spec.ts @@ -6,7 +6,7 @@ import { NgModel } from '@angular/forms'; import { TeamService } from 'app/exercises/shared/team/team.service'; import { TeamsComponent } from 'app/exercises/shared/team/teams.component'; import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service'; -import { ActivatedRoute, Router, convertToParamMap } from '@angular/router'; +import { ActivatedRoute, Router, convertToParamMap, provideRouter } from '@angular/router'; import { of } from 'rxjs'; import { MockTeamService, mockTeams } from '../../helpers/mocks/service/mock-team.service'; import { MockExerciseService } from '../../helpers/mocks/service/mock-exercise.service'; @@ -22,8 +22,6 @@ import { NgxDatatableModule } from '@siemens/ngx-datatable'; import { TeamStudentsListComponent } from 'app/exercises/shared/team/team-participate/team-students-list.component'; import { MockRouterLinkDirective } from '../../helpers/mocks/directive/mock-router-link.directive'; import { TeamDeleteButtonComponent } from 'app/exercises/shared/team/team-update-dialog/team-delete-button.component'; -import { RouterTestingModule } from '@angular/router/testing'; -import { teamRoute } from 'app/exercises/shared/team/team.route'; describe('TeamsComponent', () => { let comp: TeamsComponent; @@ -38,7 +36,7 @@ describe('TeamsComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, MockModule(NgxDatatableModule), RouterTestingModule.withRoutes([teamRoute[0]])], + imports: [ArtemisTestModule, MockModule(NgxDatatableModule)], declarations: [ TeamsComponent, MockDirective(NgModel), @@ -52,6 +50,7 @@ describe('TeamsComponent', () => { MockComponent(TeamDeleteButtonComponent), ], providers: [ + provideRouter([]), { provide: ActivatedRoute, useValue: route }, { provide: ParticipationService, useClass: MockParticipationService }, { provide: ExerciseService, useClass: MockExerciseService }, diff --git a/src/test/javascript/spec/component/text-editor/text-editor.component.spec.ts b/src/test/javascript/spec/component/text-editor/text-editor.component.spec.ts index 6bfb8cf13d4f..79fbe6ae6f3e 100644 --- a/src/test/javascript/spec/component/text-editor/text-editor.component.spec.ts +++ b/src/test/javascript/spec/component/text-editor/text-editor.component.spec.ts @@ -1,6 +1,6 @@ import { DebugElement } from '@angular/core'; import dayjs from 'dayjs/esm'; -import { ActivatedRoute, convertToParamMap } from '@angular/router'; +import { ActivatedRoute, RouterModule, convertToParamMap } from '@angular/router'; import { ComponentFixture, TestBed, fakeAsync, flush, tick } from '@angular/core/testing'; import { AlertService } from 'app/core/util/alert.service'; import { ArtemisTestModule } from '../../test.module'; @@ -8,7 +8,6 @@ import { TranslateService } from '@ngx-translate/core'; import { MockTextEditorService } from '../../helpers/mocks/service/mock-text-editor.service'; import { TextEditorService } from 'app/exercises/text/participate/text-editor.service'; import { BehaviorSubject } from 'rxjs'; -import { RouterTestingModule } from '@angular/router/testing'; import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service'; import { MockComponent, MockDirective, MockPipe } from 'ng-mocks'; @@ -65,7 +64,7 @@ describe('TextEditorComponent', () => { beforeEach(() => { return TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule.withRoutes([textEditorRoute[0]])], + imports: [ArtemisTestModule, RouterModule.forRoot([textEditorRoute[0]])], declarations: [ TextEditorComponent, MockComponent(SubmissionResultStatusComponent), diff --git a/src/test/javascript/spec/component/text-submission-assessment/text-submission-assessment.component.spec.ts b/src/test/javascript/spec/component/text-submission-assessment/text-submission-assessment.component.spec.ts index 1a550e93428c..c17c369af9ce 100644 --- a/src/test/javascript/spec/component/text-submission-assessment/text-submission-assessment.component.spec.ts +++ b/src/test/javascript/spec/component/text-submission-assessment/text-submission-assessment.component.spec.ts @@ -17,7 +17,7 @@ import { TextSubmission } from 'app/entities/text/text-submission.model'; import { Result } from 'app/entities/result.model'; import dayjs from 'dayjs/esm'; import { StudentParticipation } from 'app/entities/participation/student-participation.model'; -import { ActivatedRoute, Router, convertToParamMap } from '@angular/router'; +import { ActivatedRoute, Router, convertToParamMap, provideRouter } from '@angular/router'; import { ConfirmIconComponent } from 'app/shared/confirm-icon/confirm-icon.component'; import { Course } from 'app/entities/course.model'; import { ManualTextblockSelectionComponent } from 'app/exercises/text/assess/manual-textblock-selection/manual-textblock-selection.component'; @@ -26,7 +26,6 @@ import { TextBlock, TextBlockType } from 'app/entities/text/text-block.model'; import { Feedback, FeedbackType } from 'app/entities/feedback.model'; import { ComplaintResponse } from 'app/entities/complaint-response.model'; import { AlertService } from 'app/core/util/alert.service'; -import { RouterTestingModule } from '@angular/router/testing'; import { SubmissionService } from 'app/exercises/shared/submission/submission.service'; import { GradingInstructionLinkIconComponent } from 'app/shared/grading-instruction-link-icon/grading-instruction-link-icon.component'; import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service'; @@ -146,7 +145,7 @@ describe('TextSubmissionAssessmentComponent', () => { } as unknown as ActivatedRoute; TestBed.configureTestingModule({ - imports: [ArtemisTestModule, RouterTestingModule], + imports: [ArtemisTestModule], declarations: [ TextSubmissionAssessmentComponent, TextAssessmentAreaComponent, @@ -164,6 +163,7 @@ describe('TextSubmissionAssessmentComponent', () => { MockDirective(TranslateDirective), ], providers: [ + provideRouter([]), { provide: ActivatedRoute, useValue: mockActivatedRoute }, { provide: LocalStorageService, useClass: MockSyncStorage }, { provide: SessionStorageService, useClass: MockSyncStorage }, diff --git a/src/test/javascript/spec/component/tutorial-groups/course-tutorial-groups/course-tutorial-group-card.component.spec.ts b/src/test/javascript/spec/component/tutorial-groups/course-tutorial-groups/course-tutorial-group-card.component.spec.ts index de181aa7add3..ccc0b8a126b7 100644 --- a/src/test/javascript/spec/component/tutorial-groups/course-tutorial-groups/course-tutorial-group-card.component.spec.ts +++ b/src/test/javascript/spec/component/tutorial-groups/course-tutorial-groups/course-tutorial-group-card.component.spec.ts @@ -1,5 +1,4 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; import { CourseTutorialGroupCardComponent } from 'app/overview/course-tutorial-groups/course-tutorial-group-card/course-tutorial-group-card.component'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { TranslateService } from '@ngx-translate/core'; @@ -10,6 +9,7 @@ import { Course } from 'app/entities/course.model'; import { TranslateDirective } from 'app/shared/language/translate.directive'; import { MockComponent, MockDirective } from 'ng-mocks'; import { TranslatePipeMock } from '../../../helpers/mocks/service/mock-translate.service'; +import { RouterModule } from '@angular/router'; describe('CourseTutorialGroupCardComponent', () => { let component: CourseTutorialGroupCardComponent; @@ -19,7 +19,7 @@ describe('CourseTutorialGroupCardComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [RouterTestingModule.withRoutes([])], + imports: [RouterModule.forRoot([])], declarations: [CourseTutorialGroupCardComponent, MockComponent(FaIconComponent), TranslatePipeMock, MockDirective(TranslateDirective)], providers: [ { diff --git a/src/test/javascript/spec/component/tutorial-groups/shared/tutorial-group-detail.component.spec.ts b/src/test/javascript/spec/component/tutorial-groups/shared/tutorial-group-detail.component.spec.ts index 1981fa850091..02d89da561e4 100644 --- a/src/test/javascript/spec/component/tutorial-groups/shared/tutorial-group-detail.component.spec.ts +++ b/src/test/javascript/spec/component/tutorial-groups/shared/tutorial-group-detail.component.spec.ts @@ -1,5 +1,4 @@ import { TutorialGroupDetailComponent } from 'app/course/tutorial-groups/shared/tutorial-group-detail/tutorial-group-detail.component'; -import { RouterTestingModule } from '@angular/router/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { MockComponent, MockPipe, MockProvider } from 'ng-mocks'; @@ -21,6 +20,7 @@ import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; import dayjs from 'dayjs/esm'; import { TutorialGroupSessionStatus } from 'app/entities/tutorial-group/tutorial-group-session.model'; import { provideHttpClient } from '@angular/common/http'; +import { RouterModule } from '@angular/router'; @Component({ selector: 'jhi-mock-header', template: '
' }) class MockHeaderComponent { @@ -57,7 +57,7 @@ describe('TutorialGroupDetailWrapperTest', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [NgbTooltipMocksModule, RouterTestingModule.withRoutes([])], + imports: [NgbTooltipMocksModule, RouterModule.forRoot([])], declarations: [ TutorialGroupDetailComponent, DetailOverviewListComponent, @@ -109,7 +109,7 @@ describe('TutorialGroupDetailComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [NgbTooltipMocksModule, RouterTestingModule.withRoutes([])], + imports: [NgbTooltipMocksModule, RouterModule.forRoot([])], declarations: [ TutorialGroupDetailComponent, MockPipe(ArtemisTranslatePipe), diff --git a/src/test/javascript/spec/component/tutorial-groups/shared/tutorial-group-row.component.spec.ts b/src/test/javascript/spec/component/tutorial-groups/shared/tutorial-group-row.component.spec.ts index dda5cb2ef561..8395b83f29fd 100644 --- a/src/test/javascript/spec/component/tutorial-groups/shared/tutorial-group-row.component.spec.ts +++ b/src/test/javascript/spec/component/tutorial-groups/shared/tutorial-group-row.component.spec.ts @@ -1,5 +1,4 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; import { TutorialGroupRowComponent } from 'app/course/tutorial-groups/shared/tutorial-groups-table/tutorial-group-row/tutorial-group-row.component'; import { TutorialGroup } from 'app/entities/tutorial-group/tutorial-group.model'; import { MockComponent, MockPipe } from 'ng-mocks'; @@ -8,6 +7,7 @@ import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; import { generateExampleTutorialGroup } from '../helpers/tutorialGroupExampleModels'; import { TutorialGroupUtilizationIndicatorComponent } from 'app/course/tutorial-groups/shared/tutorial-group-utilization-indicator/tutorial-group-utilization-indicator.component'; import { MeetingPatternPipe } from 'app/course/tutorial-groups/shared/meeting-pattern.pipe'; +import { RouterModule } from '@angular/router'; describe('TutorialGroupRowComponent', () => { let component: TutorialGroupRowComponent; @@ -16,7 +16,7 @@ describe('TutorialGroupRowComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [RouterTestingModule.withRoutes([])], + imports: [RouterModule.forRoot([])], declarations: [ TutorialGroupRowComponent, MockComponent(TutorialGroupUtilizationIndicatorComponent), @@ -24,6 +24,7 @@ describe('TutorialGroupRowComponent', () => { MockPipe(ArtemisTranslatePipe), MockPipe(MeetingPatternPipe), ], + providers: [], }).compileComponents(); fixture = TestBed.createComponent(TutorialGroupRowComponent); diff --git a/src/test/javascript/spec/component/tutorial-groups/tutorial-groups-management/tutorial-group-management-resolve.service.spec.ts b/src/test/javascript/spec/component/tutorial-groups/tutorial-groups-management/tutorial-group-management-resolve.service.spec.ts index ebdcbe69938e..0105de66beb2 100644 --- a/src/test/javascript/spec/component/tutorial-groups/tutorial-groups-management/tutorial-group-management-resolve.service.spec.ts +++ b/src/test/javascript/spec/component/tutorial-groups/tutorial-groups-management/tutorial-group-management-resolve.service.spec.ts @@ -1,12 +1,11 @@ import { TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; import { provideHttpClientTesting } from '@angular/common/http/testing'; import { of } from 'rxjs'; import { HttpResponse, provideHttpClient } from '@angular/common/http'; import { CourseManagementService } from 'app/course/manage/course-management.service'; import { Course } from 'app/entities/course.model'; -import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; +import { ActivatedRouteSnapshot, Router, RouterStateSnapshot, provideRouter } from '@angular/router'; import { TutorialGroupManagementResolve } from 'app/course/tutorial-groups/tutorial-groups-management/tutorial-group-management-resolve.service'; import { MockProvider } from 'ng-mocks'; import { MockTranslateService } from '../../../helpers/mocks/service/mock-translate.service'; @@ -20,8 +19,9 @@ describe('TutorialGroupManagementResolve', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [RouterTestingModule], + imports: [], providers: [ + provideRouter([]), provideHttpClient(), provideHttpClientTesting(), TutorialGroupManagementResolve, diff --git a/src/test/javascript/spec/component/utils/result.utils.spec.ts b/src/test/javascript/spec/component/utils/result.utils.spec.ts index 4796766de234..24303c3e59af 100644 --- a/src/test/javascript/spec/component/utils/result.utils.spec.ts +++ b/src/test/javascript/spec/component/utils/result.utils.spec.ts @@ -15,6 +15,7 @@ import { faCheckCircle, faQuestionCircle, faTimesCircle } from '@fortawesome/fre import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'; import { ExerciseType } from 'app/entities/exercise.model'; import { Result } from 'app/entities/result.model'; +import dayjs from 'dayjs/esm'; describe('ResultUtils', () => { it('should filter out all non unreferenced feedbacks', () => { @@ -69,7 +70,7 @@ describe('ResultUtils', () => { { result: { score: 0, successful: undefined, assessmentType: AssessmentType.AUTOMATIC_ATHENA }, templateStatus: ResultTemplateStatus.IS_GENERATING_FEEDBACK, - expected: 'text-primary', + expected: 'text-secondary', }, { result: { score: 0, successful: true, assessmentType: AssessmentType.AUTOMATIC_ATHENA }, @@ -128,7 +129,12 @@ describe('ResultUtils', () => { expected: faTimesCircle, }, { - result: { feedbacks: [{ type: FeedbackType.AUTOMATIC, text: 'AI result being generated test case' }], assessmentType: AssessmentType.AUTOMATIC_ATHENA }, + result: { + feedbacks: [{ type: FeedbackType.AUTOMATIC, text: 'AI result being generated test case' }], + assessmentType: AssessmentType.AUTOMATIC_ATHENA, + successful: undefined, + completionDate: dayjs().add(5, 'minutes'), + }, templateStatus: ResultTemplateStatus.IS_GENERATING_FEEDBACK, expected: faCircleNotch, }, @@ -138,9 +144,10 @@ describe('ResultUtils', () => { participation: { type: ParticipationType.STUDENT, exercise: { type: ExerciseType.TEXT } }, successful: true, assessmentType: AssessmentType.AUTOMATIC_ATHENA, + completionDate: dayjs().subtract(5, 'minutes'), } as Result, templateStatus: ResultTemplateStatus.HAS_RESULT, - expected: faQuestionCircle, + expected: faCheckCircle, }, { result: { @@ -148,9 +155,10 @@ describe('ResultUtils', () => { participation: { type: ParticipationType.STUDENT, exercise: { type: ExerciseType.TEXT } }, successful: false, assessmentType: AssessmentType.AUTOMATIC_ATHENA, + completionDate: dayjs().subtract(5, 'minutes'), } as Result, templateStatus: ResultTemplateStatus.HAS_RESULT, - expected: faQuestionCircle, + expected: faTimesCircle, }, ])('should correctly determine result icon', ({ result, templateStatus, expected }) => { expect(getResultIconClass(result, templateStatus!)).toBe(expected); diff --git a/src/test/javascript/spec/integration/code-editor/code-editor-container.integration.spec.ts b/src/test/javascript/spec/integration/code-editor/code-editor-container.integration.spec.ts index 406dce4f6a5c..d55cc49a6983 100644 --- a/src/test/javascript/spec/integration/code-editor/code-editor-container.integration.spec.ts +++ b/src/test/javascript/spec/integration/code-editor/code-editor-container.integration.spec.ts @@ -74,6 +74,7 @@ import { CodeEditorHeaderComponent } from 'app/exercises/programming/shared/code import { AlertService } from 'app/core/util/alert.service'; import { MockResizeObserver } from '../../helpers/mocks/service/mock-resize-observer'; import { CodeEditorMonacoComponent } from 'app/exercises/programming/shared/code-editor/monaco/code-editor-monaco.component'; +import { RequestFeedbackButtonComponent } from 'app/overview/exercise-details/request-feedback-button/request-feedback-button.component'; import { MonacoEditorComponent } from '../../../../../main/webapp/app/shared/monaco-editor/monaco-editor.component'; describe('CodeEditorContainerIntegration', () => { @@ -123,6 +124,7 @@ describe('CodeEditorContainerIntegration', () => { TreeviewItemComponent, MockPipe(ArtemisDatePipe), MockComponent(CodeEditorTutorAssessmentInlineFeedbackComponent), + MockComponent(RequestFeedbackButtonComponent), ], providers: [ CodeEditorConflictStateService, diff --git a/src/test/javascript/spec/integration/guided-tour/guided-tour.integration.spec.ts b/src/test/javascript/spec/integration/guided-tour/guided-tour.integration.spec.ts index 57b934b9497c..3ebe67f4a26e 100644 --- a/src/test/javascript/spec/integration/guided-tour/guided-tour.integration.spec.ts +++ b/src/test/javascript/spec/integration/guided-tour/guided-tour.integration.spec.ts @@ -9,7 +9,6 @@ import { GuidedTourComponent } from 'app/guided-tour/guided-tour.component'; import { MockAccountService } from '../../helpers/mocks/service/mock-account.service'; import { AccountService } from 'app/core/auth/account.service'; import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service'; -import { RouterTestingModule } from '@angular/router/testing'; import { courseOverviewTour } from 'app/guided-tour/tours/course-overview-tour'; import { CoursesComponent } from 'app/overview/courses.component'; import { MockTranslateService } from '../../helpers/mocks/service/mock-translate.service'; @@ -43,6 +42,7 @@ import { JhiConnectionWarningComponent } from 'app/shared/connection-warning/con import { NgbCollapse, NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; import { NgbDropdownMocksModule } from '../../helpers/mocks/directive/ngbDropdownMocks.module'; import { DocumentationButtonComponent } from 'app/shared/components/documentation-button/documentation-button.component'; +import { RouterModule } from '@angular/router'; describe('Guided tour integration', () => { const user = { id: 1 } as User; @@ -61,7 +61,7 @@ describe('Guided tour integration', () => { TestBed.configureTestingModule({ imports: [ ArtemisTestModule, - RouterTestingModule.withRoutes([ + RouterModule.forRoot([ { path: 'courses', component: MockComponent(CoursesComponent), diff --git a/src/test/javascript/spec/service/bonus.service.spec.ts b/src/test/javascript/spec/service/bonus.service.spec.ts index f3886fe5f197..7c76635a53be 100644 --- a/src/test/javascript/spec/service/bonus.service.spec.ts +++ b/src/test/javascript/spec/service/bonus.service.spec.ts @@ -2,13 +2,13 @@ import { TestBed, fakeAsync, tick } from '@angular/core/testing'; import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { GradeType, GradingScale } from 'app/entities/grading-scale.model'; import { take } from 'rxjs/operators'; -import { RouterTestingModule } from '@angular/router/testing'; import { GradeStep, GradeStepsDTO } from 'app/entities/grade-step.model'; import { BonusService } from 'app/grading-system/bonus/bonus.service'; import { Bonus, BonusExample, BonusStrategy } from 'app/entities/bonus.model'; import { GradingSystemService } from 'app/grading-system/grading-system.service'; import { cloneDeep } from 'lodash-es'; import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; describe('Bonus Service', () => { type GradeStepBuilder = { @@ -91,8 +91,8 @@ describe('Bonus Service', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [RouterTestingModule], - providers: [provideHttpClient(), provideHttpClientTesting()], + imports: [], + providers: [provideRouter([]), provideHttpClient(), provideHttpClientTesting()], }); service = TestBed.inject(BonusService); httpMock = TestBed.inject(HttpTestingController); diff --git a/src/test/javascript/spec/service/grading-system.service.spec.ts b/src/test/javascript/spec/service/grading-system.service.spec.ts index e35b36c63437..4ab33d2cff99 100644 --- a/src/test/javascript/spec/service/grading-system.service.spec.ts +++ b/src/test/javascript/spec/service/grading-system.service.spec.ts @@ -3,11 +3,11 @@ import { HttpTestingController, provideHttpClientTesting } from '@angular/common import { GradingSystemService } from 'app/grading-system/grading-system.service'; import { GradeType, GradingScale } from 'app/entities/grading-scale.model'; import { take } from 'rxjs/operators'; -import { RouterTestingModule } from '@angular/router/testing'; import { GradeDTO, GradeStep, GradeStepsDTO } from 'app/entities/grade-step.model'; import { of } from 'rxjs'; import { HttpResponse, provideHttpClient } from '@angular/common/http'; import { cloneDeep } from 'lodash-es'; +import { provideRouter } from '@angular/router'; describe('Grading System Service', () => { let service: GradingSystemService; @@ -42,8 +42,8 @@ describe('Grading System Service', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [RouterTestingModule], - providers: [provideHttpClient(), provideHttpClientTesting()], + imports: [], + providers: [provideRouter([]), provideHttpClient(), provideHttpClientTesting()], }); service = TestBed.inject(GradingSystemService); httpMock = TestBed.inject(HttpTestingController); diff --git a/src/test/javascript/spec/service/notification.service.spec.ts b/src/test/javascript/spec/service/notification.service.spec.ts index 6c0beb2c4a0c..19c5d0600c33 100644 --- a/src/test/javascript/spec/service/notification.service.spec.ts +++ b/src/test/javascript/spec/service/notification.service.spec.ts @@ -2,7 +2,7 @@ import { HttpTestingController, TestRequest, provideHttpClientTesting } from '@a import { NotificationService } from 'app/shared/notification/notification.service'; import { MockSyncStorage } from '../helpers/mocks/service/mock-sync-storage.service'; import { TestBed } from '@angular/core/testing'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, Router, provideRouter } from '@angular/router'; import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; import { TranslateTestingModule } from '../helpers/mocks/service/mock-translate.service'; import { @@ -16,7 +16,6 @@ import { Notification, } from 'app/entities/notification.model'; import { MockRouter } from '../helpers/mocks/mock-router'; -import { RouterTestingModule } from '@angular/router/testing'; import { CourseManagementService } from 'app/course/manage/course-management.service'; import { BehaviorSubject, Subject } from 'rxjs'; import { AccountService } from 'app/core/auth/account.service'; @@ -156,9 +155,10 @@ describe('Notification Service', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [TranslateTestingModule, RouterTestingModule.withRoutes([])], + imports: [TranslateTestingModule], declarations: [MockPipe(ArtemisTranslatePipe)], providers: [ + provideRouter([]), provideHttpClient(), provideHttpClientTesting(), { provide: LocalStorageService, useClass: MockSyncStorage }, diff --git a/src/test/javascript/spec/service/result.service.spec.ts b/src/test/javascript/spec/service/result.service.spec.ts index 7bd887f9cb9e..e6c24cf75850 100644 --- a/src/test/javascript/spec/service/result.service.spec.ts +++ b/src/test/javascript/spec/service/result.service.spec.ts @@ -307,8 +307,10 @@ describe('ResultService', () => { it('should return correct string for Athena non graded successful feedback', () => { programmingExercise.assessmentDueDate = dayjs().subtract(5, 'minutes'); - expect(resultService.getResultString(result6, programmingExercise)).toBe('artemisApp.result.resultString.automaticAIFeedbackSuccessful'); - expect(translateServiceSpy).toHaveBeenCalledOnce(); + expect(resultService.getResultString(result6, programmingExercise)).toBe( + 'artemisApp.result.resultString.automaticAIFeedbackSuccessful (artemisApp.result.preliminary)', + ); + expect(translateServiceSpy).toHaveBeenCalledTimes(2); }); it('should return correct string for Athena non graded unsuccessful feedback', () => { diff --git a/src/test/javascript/spec/service/user-route-access.service.spec.ts b/src/test/javascript/spec/service/user-route-access.service.spec.ts index d835a689234b..5c8f583c7075 100644 --- a/src/test/javascript/spec/service/user-route-access.service.spec.ts +++ b/src/test/javascript/spec/service/user-route-access.service.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { UserRouteAccessService } from 'app/core/auth/user-route-access-service'; -import { ActivatedRouteSnapshot, Route, Router } from '@angular/router'; +import { ActivatedRouteSnapshot, Route, Router, RouterModule } from '@angular/router'; import { ArtemisTestModule } from '../test.module'; import { TranslateService } from '@ngx-translate/core'; import { MockTranslateService } from '../helpers/mocks/service/mock-translate.service'; @@ -8,7 +8,6 @@ import { MockSyncStorage } from '../helpers/mocks/service/mock-sync-storage.serv import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; import { AccountService } from 'app/core/auth/account.service'; import { MockAccountService } from '../helpers/mocks/service/mock-account.service'; -import { RouterTestingModule } from '@angular/router/testing'; import { provideHttpClientTesting } from '@angular/common/http/testing'; import { Mutable } from '../helpers/mutable'; import { mockedActivatedRouteSnapshot } from '../helpers/mocks/activated-route/mock-activated-route-snapshot'; @@ -40,7 +39,7 @@ describe('UserRouteAccessService', () => { TestBed.configureTestingModule({ imports: [ ArtemisTestModule, - RouterTestingModule.withRoutes([ + RouterModule.forRoot([ { path: route, component: CourseExerciseDetailsComponent, diff --git a/src/test/resources/config/application.yml b/src/test/resources/config/application.yml index f48155253d32..ee94c5be7573 100644 --- a/src/test/resources/config/application.yml +++ b/src/test/resources/config/application.yml @@ -70,6 +70,8 @@ artemis: default: "~~invalid~~" javascript: default: "~~invalid~~" + r: + default: "~~invalid~~" spring: application: