diff --git a/README.md b/README.md index fe75623b422f..9b6118e0fe28 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ Refer to [Using JHipster in production](http://www.jhipster.tech/production) for The following command can automate the deployment to a server. The example shows the deployment to the main Artemis test server (which runs a virtual machine): ```shell -./artemis-server-cli deploy username@artemistest.ase.in.tum.de -w build/libs/Artemis-7.5.5.war +./artemis-server-cli deploy username@artemistest.ase.in.tum.de -w build/libs/Artemis-7.5.6.war ``` ## Architecture diff --git a/build.gradle b/build.gradle index f242e5341d52..1375d3298583 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,7 @@ plugins { } group = "de.tum.cit.aet.artemis" -version = "7.5.5" +version = "7.5.6" description = "Interactive Learning with Individual Feedback" java { diff --git a/docs/admin/telemetry.rst b/docs/admin/telemetry.rst index 95c3b8168f3a..b2f8634253ab 100644 --- a/docs/admin/telemetry.rst +++ b/docs/admin/telemetry.rst @@ -26,7 +26,7 @@ Example configuration in `application-prod.yml`: telemetry: enabled: true sendAdminDetails: false - destination: telemetry.artemis.cit.tum.de + destination: https://telemetry.artemis.cit.tum.de info: contact: contactMailAddress@cit.tum.de diff --git a/package-lock.json b/package-lock.json index 3cc579e1ab93..bc1d3b7dff03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "artemis", - "version": "7.5.5", + "version": "7.5.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "artemis", - "version": "7.5.5", + "version": "7.5.6", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index c7308492a9e4..03ab87fb7b80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "artemis", - "version": "7.5.5", + "version": "7.5.6", "description": "Interactive Learning with Individual Feedback", "private": true, "license": "MIT", diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/LocalVCServletService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/LocalVCServletService.java index 90186a8c2c31..470b7f815322 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/LocalVCServletService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/LocalVCServletService.java @@ -129,6 +129,7 @@ public void setLocalVCBaseUrl(URL localVCBaseUrl) { // Cache the retrieved repositories for quicker access. // The resolveRepository method is called multiple times per request. + // Key: repositoryPath --> Value: Repository private final Map repositories = new HashMap<>(); public LocalVCServletService(AuthenticationManager authenticationManager, UserRepository userRepository, ProgrammingExerciseRepository programmingExerciseRepository, @@ -441,18 +442,23 @@ public void authorizeUser(String repositoryTypeOrUserName, User user, Programmin catch (AccessForbiddenException e) { throw new LocalVCForbiddenException(e); } + // TODO: retrieving the git commit hash should be done ASYNC together with storing the log in the database to avoid long waiting times during permission check String commitHash = null; try { if (repositoryActionType == RepositoryActionType.READ) { - commitHash = getLatestCommitHash(repositories.get(localVCRepositoryUri.getRelativeRepositoryPath().toString())); + String relativeRepositoryPath = localVCRepositoryUri.getRelativeRepositoryPath().toString(); + try (Repository repository = resolveRepository(relativeRepositoryPath)) { + commitHash = getLatestCommitHash(repository); + } } + // Write a access log entry to the database + String finalCommitHash = commitHash; + vcsAccessLogService.ifPresent(service -> service.storeAccessLog(user, participation, repositoryActionType, authenticationMechanism, finalCommitHash, ipAddress)); } - catch (GitAPIException e) { - log.warn("Failed to obtain commit hash for repository {}. Error: {}", localVCRepositoryUri.getRelativeRepositoryPath().toString(), e.getMessage()); + // NOTE: we intentionally catch all issues here to avoid that the user is blocked from accessing the repository + catch (Exception e) { + log.warn("Failed to obtain commit hash or store access log for repository {}. Error: {}", localVCRepositoryUri.getRelativeRepositoryPath().toString(), e.getMessage()); } - // Write a access log entry to the database - String finalCommitHash = commitHash; - vcsAccessLogService.ifPresent(service -> service.storeAccessLog(user, participation, repositoryActionType, authenticationMechanism, finalCommitHash, ipAddress)); } /** @@ -520,9 +526,16 @@ public void processNewPush(String commitHash, Repository repository) { // Process push to any repository other than the test repository. processNewPushToRepository(participation, commit); - // For push the correct commitHash is only available here, therefore the preliminary null value is overwritten - String finalCommitHash = commitHash; - vcsAccessLogService.ifPresent(service -> service.updateCommitHash(participation, finalCommitHash)); + try { + // For push the correct commitHash is only available here, therefore the preliminary null value is overwritten + String finalCommitHash = commitHash; + vcsAccessLogService.ifPresent(service -> service.updateCommitHash(participation, finalCommitHash)); + } + // NOTE: we intentionally catch all issues here to avoid that the user is blocked from accessing the repository + catch (Exception e) { + log.warn("Failed to obtain commit hash or store access log for repository {}. Error: {}", localVCRepositoryUri.getRelativeRepositoryPath().toString(), + e.getMessage()); + } } catch (GitAPIException | IOException e) { // This catch clause does not catch exceptions that happen during runBuildJob() as that method is called asynchronously. diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/VcsAccessLogService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/VcsAccessLogService.java index d77b37c02a7f..57330b4d51e0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/VcsAccessLogService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/VcsAccessLogService.java @@ -44,6 +44,7 @@ public class VcsAccessLogService { * @param commitHash The latest commit hash * @param ipAddress The ip address of the user accessing the repository */ + // TODO: this should be ASYNC to avoid long waiting times during permission check public void storeAccessLog(User user, ProgrammingExerciseParticipation participation, RepositoryActionType actionType, AuthenticationMechanism authenticationMechanism, String commitHash, String ipAddress) { log.debug("Storing access operation for user {}", user); @@ -59,6 +60,7 @@ public void storeAccessLog(User user, ProgrammingExerciseParticipation participa * @param participation The participation to which the repository belongs to * @param commitHash The newest commit hash which should get set for the access log entry */ + // TODO: this should be ASYNC to avoid long waiting times during permission check public void updateCommitHash(ProgrammingExerciseParticipation participation, String commitHash) { vcsAccessLogRepository.findNewestByParticipationIdWhereCommitHashIsNull(participation.getId()).ifPresent(entry -> { entry.setCommitHash(commitHash); diff --git a/src/main/java/de/tum/cit/aet/artemis/quiz/web/QuizParticipationResource.java b/src/main/java/de/tum/cit/aet/artemis/quiz/web/QuizParticipationResource.java index ce0f750ba6af..41c6fc8173c9 100644 --- a/src/main/java/de/tum/cit/aet/artemis/quiz/web/QuizParticipationResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/quiz/web/QuizParticipationResource.java @@ -3,7 +3,6 @@ import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; import java.time.ZonedDateTime; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -27,7 +26,6 @@ import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; import de.tum.cit.aet.artemis.exercise.service.ParticipationService; import de.tum.cit.aet.artemis.quiz.domain.QuizExercise; -import de.tum.cit.aet.artemis.quiz.domain.QuizSubmission; import de.tum.cit.aet.artemis.quiz.repository.QuizExerciseRepository; import de.tum.cit.aet.artemis.quiz.repository.QuizSubmissionRepository; import de.tum.cit.aet.artemis.quiz.repository.SubmittedAnswerRepository; @@ -92,18 +90,9 @@ public ResponseEntity startParticipation(@PathVariable Long StudentParticipation participation = participationService.startExercise(exercise, user, true); - Optional optionalResult = resultRepository.findFirstByParticipationIdAndRatedOrderByCompletionDateDesc(participation.getId(), true); - Result result; - if (optionalResult.isPresent()) { - var quizSubmission = (QuizSubmission) optionalResult.get().getSubmission(); - var submittedAnswers = submittedAnswerRepository.findBySubmission(quizSubmission); - quizSubmission.setSubmittedAnswers(submittedAnswers); - result = optionalResult.get(); - } - else { - result = new Result(); - result.setSubmission(quizSubmissionRepository.findWithEagerSubmittedAnswersByParticipationId(participation.getId()).orElseThrow()); - } + // NOTE: starting exercise prevents that two participation will exist, but ensures that a submission is created + var result = resultRepository.findFirstByParticipationIdAndRatedOrderByCompletionDateDesc(participation.getId(), true).orElse(new Result()); + result.setSubmission(quizSubmissionRepository.findWithEagerSubmittedAnswersByParticipationId(participation.getId()).orElseThrow()); participation.setResults(Set.of(result)); participation.setExercise(exercise); diff --git a/src/main/resources/config/application-dev.yml b/src/main/resources/config/application-dev.yml index 23abff9c330b..51563c3c3898 100644 --- a/src/main/resources/config/application-dev.yml +++ b/src/main/resources/config/application-dev.yml @@ -146,4 +146,4 @@ artemis: telemetry: enabled: false # Disable sending any telemetry information to the telemetry service by setting this to false sendAdminDetails: false # Include the admins email and name in the telemetry data. Set to false to disable - destination: telemetry.artemis.cit.tum.de + destination: https://telemetry.artemis.cit.tum.de diff --git a/src/main/resources/config/application-prod.yml b/src/main/resources/config/application-prod.yml index 576e5ca01cde..9a9830d29e58 100644 --- a/src/main/resources/config/application-prod.yml +++ b/src/main/resources/config/application-prod.yml @@ -20,7 +20,7 @@ artemis: telemetry: enabled: true # Disable sending any telemetry information to the telemetry service by setting this to false sendAdminDetails: true # Include personal identifiable information of the admin, including email and name in the telemetry data - destination: telemetry.artemis.cit.tum.de + destination: https://telemetry.artemis.cit.tum.de spring: devtools: diff --git a/src/main/webapp/app/account/account.module.ts b/src/main/webapp/app/account/account.module.ts deleted file mode 100644 index 198ab10e3ea2..000000000000 --- a/src/main/webapp/app/account/account.module.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; - -import { ArtemisSharedModule } from 'app/shared/shared.module'; - -import { PasswordStrengthBarComponent } from './password/password-strength-bar.component'; -import { RegisterComponent } from './register/register.component'; -import { ActivateComponent } from './activate/activate.component'; -import { PasswordComponent } from './password/password.component'; -import { PasswordResetInitComponent } from './password-reset/init/password-reset-init.component'; -import { PasswordResetFinishComponent } from './password-reset/finish/password-reset-finish.component'; -import { SettingsComponent } from './settings/settings.component'; -import { accountState } from './account.route'; -import { ExternalUserPasswordResetModalComponent } from 'app/account/password-reset/external/external-user-password-reset-modal.component'; - -@NgModule({ - imports: [ArtemisSharedModule, RouterModule.forChild(accountState)], - declarations: [ - ActivateComponent, - RegisterComponent, - PasswordComponent, - PasswordStrengthBarComponent, - PasswordResetInitComponent, - PasswordResetFinishComponent, - ExternalUserPasswordResetModalComponent, - SettingsComponent, - ], -}) -export class ArtemisAccountModule {} diff --git a/src/main/webapp/app/account/account.route.ts b/src/main/webapp/app/account/account.route.ts deleted file mode 100644 index beaf183b7b85..000000000000 --- a/src/main/webapp/app/account/account.route.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Routes } from '@angular/router'; -import { ActivateComponent } from 'app/account/activate/activate.component'; -import { PasswordComponent } from 'app/account/password/password.component'; -import { Authority } from 'app/shared/constants/authority.constants'; -import { UserRouteAccessService } from 'app/core/auth/user-route-access-service'; -import { PasswordResetFinishComponent } from 'app/account/password-reset/finish/password-reset-finish.component'; -import { PasswordResetInitComponent } from 'app/account/password-reset/init/password-reset-init.component'; -import { RegisterComponent } from 'app/account/register/register.component'; -import { SettingsComponent } from 'app/account/settings/settings.component'; - -export const accountState: Routes = [ - { - path: '', - children: [ - { - path: 'activate', - component: ActivateComponent, - data: { - authorities: [], - pageTitle: 'activate.title', - }, - }, - { - path: 'password', - component: PasswordComponent, - data: { - authorities: [Authority.USER], - pageTitle: 'global.menu.account.password', - }, - canActivate: [UserRouteAccessService], - }, - { - path: 'reset/finish', - component: PasswordResetFinishComponent, - data: { - authorities: [], - pageTitle: 'global.menu.account.password', - }, - }, - { - path: 'reset/request', - component: PasswordResetInitComponent, - data: { - authorities: [], - pageTitle: 'global.menu.account.password', - }, - }, - { - path: 'register', - component: RegisterComponent, - data: { - authorities: [], - pageTitle: 'register.title', - }, - }, - { - path: 'settings', - component: SettingsComponent, - data: { - authorities: [Authority.USER], - pageTitle: 'global.menu.account.settings', - }, - canActivate: [UserRouteAccessService], - }, - ], - }, -]; diff --git a/src/main/webapp/app/account/activate/activate.component.ts b/src/main/webapp/app/account/activate/activate.component.ts index 4bb2af36f1c3..a05a908b695d 100644 --- a/src/main/webapp/app/account/activate/activate.component.ts +++ b/src/main/webapp/app/account/activate/activate.component.ts @@ -1,25 +1,26 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; - +import { Component, OnInit, inject } from '@angular/core'; +import { ActivatedRoute, RouterLink } from '@angular/router'; +import { ArtemisSharedModule } from 'app/shared/shared.module'; import { ActivateService } from './activate.service'; import { ProfileService } from 'app/shared/layouts/profiles/profile.service'; import { mergeMap } from 'rxjs/operators'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; @Component({ selector: 'jhi-activate', templateUrl: './activate.component.html', + standalone: true, + imports: [TranslateDirective, RouterLink, ArtemisSharedModule], }) export class ActivateComponent implements OnInit { + private activateService = inject(ActivateService); + private route = inject(ActivatedRoute); + private profileService = inject(ProfileService); + error = false; success = false; isRegistrationEnabled = false; - constructor( - private activateService: ActivateService, - private route: ActivatedRoute, - private profileService: ProfileService, - ) {} - /** * Checks if the user can be activated with ActivateService */ diff --git a/src/main/webapp/app/account/activate/activate.service.ts b/src/main/webapp/app/account/activate/activate.service.ts index 5a69a4bec97d..c66f9b04b736 100644 --- a/src/main/webapp/app/account/activate/activate.service.ts +++ b/src/main/webapp/app/account/activate/activate.service.ts @@ -1,10 +1,10 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class ActivateService { - constructor(private http: HttpClient) {} + private http = inject(HttpClient); /** * Sends request to the server to activate the user diff --git a/src/main/webapp/app/account/password-reset/external/external-user-password-reset-modal.component.ts b/src/main/webapp/app/account/password-reset/external/external-user-password-reset-modal.component.ts index 36821418bd62..d122291f564d 100644 --- a/src/main/webapp/app/account/password-reset/external/external-user-password-reset-modal.component.ts +++ b/src/main/webapp/app/account/password-reset/external/external-user-password-reset-modal.component.ts @@ -1,16 +1,20 @@ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { ArtemisSharedModule } from 'app/shared/shared.module'; @Component({ selector: 'jhi-external-user-password-reset-modal', templateUrl: './external-user-password-reset-modal.component.html', + standalone: true, + imports: [TranslateDirective, ArtemisSharedModule], }) export class ExternalUserPasswordResetModalComponent { + private activeModal = inject(NgbActiveModal); + externalCredentialProvider: string; externalPasswordResetLink: string; - constructor(private activeModal: NgbActiveModal) {} - /** * Closes the dialog, removes the query parameter and shows a helper message */ diff --git a/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.ts b/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.ts index 2ff807085f83..35cbb5f0483c 100644 --- a/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.ts +++ b/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.ts @@ -1,15 +1,24 @@ -import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; - +import { AfterViewInit, Component, ElementRef, OnInit, ViewChild, inject } from '@angular/core'; +import { ActivatedRoute, RouterLink } from '@angular/router'; +import { PasswordStrengthBarComponent } from 'app/account/password/password-strength-bar.component'; +import { ArtemisSharedModule } from 'app/shared/shared.module'; import { PasswordResetFinishService } from './password-reset-finish.service'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH } from 'app/app.constants'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; @Component({ selector: 'jhi-password-reset-finish', templateUrl: './password-reset-finish.component.html', + standalone: true, + imports: [TranslateDirective, RouterLink, FormsModule, ReactiveFormsModule, PasswordStrengthBarComponent, ArtemisSharedCommonModule, ArtemisSharedModule], }) export class PasswordResetFinishComponent implements OnInit, AfterViewInit { + private passwordResetFinishService = inject(PasswordResetFinishService); + private route = inject(ActivatedRoute); + private fb = inject(FormBuilder); + @ViewChild('newPassword', { static: false }) newPassword?: ElementRef; @@ -24,12 +33,6 @@ export class PasswordResetFinishComponent implements OnInit, AfterViewInit { passwordForm: FormGroup; - constructor( - private passwordResetFinishService: PasswordResetFinishService, - private route: ActivatedRoute, - private fb: FormBuilder, - ) {} - ngOnInit() { this.route.queryParams.subscribe((params) => { if (params['key']) { diff --git a/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts b/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts index 30f9fab8c89c..a2cc00dbb74d 100644 --- a/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts +++ b/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts @@ -1,10 +1,10 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class PasswordResetFinishService { - constructor(private http: HttpClient) {} + private http = inject(HttpClient); save(key: string, newPassword: string): Observable { return this.http.post('api/public/account/reset-password/finish', { key, newPassword }); diff --git a/src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts b/src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts index 07e63dbea65d..3e245c20e2b6 100644 --- a/src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts +++ b/src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts @@ -1,19 +1,30 @@ -import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, OnInit, ViewChild, inject } from '@angular/core'; +import { ArtemisSharedModule } from 'app/shared/shared.module'; import { PasswordResetInitService } from './password-reset-init.service'; import { ProfileService } from 'app/shared/layouts/profiles/profile.service'; -import { FormBuilder } from '@angular/forms'; +import { FormsModule } from '@angular/forms'; import { AlertService } from 'app/core/util/alert.service'; import { HttpErrorResponse } from '@angular/common/http'; import { onError } from 'app/shared/util/global.utils'; import { TranslateService } from '@ngx-translate/core'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { ExternalUserPasswordResetModalComponent } from 'app/account/password-reset/external/external-user-password-reset-modal.component'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; @Component({ selector: 'jhi-password-reset-init', templateUrl: './password-reset-init.component.html', + standalone: true, + imports: [TranslateDirective, FormsModule, ArtemisSharedCommonModule, ArtemisSharedModule], }) export class PasswordResetInitComponent implements OnInit, AfterViewInit { + private passwordResetInitService = inject(PasswordResetInitService); + private profileService = inject(ProfileService); + private alertService = inject(AlertService); + private translateService = inject(TranslateService); + private modalService = inject(NgbModal); + @ViewChild('emailUsername', { static: false }) emailUsernameElement?: ElementRef; emailUsernameValue = ''; @@ -22,15 +33,6 @@ export class PasswordResetInitComponent implements OnInit, AfterViewInit { externalPasswordResetLink?: string; externalResetModalRef: NgbModalRef | undefined; - constructor( - private passwordResetInitService: PasswordResetInitService, - private fb: FormBuilder, - private profileService: ProfileService, - private alertService: AlertService, - private translateService: TranslateService, - private modalService: NgbModal, - ) {} - ngOnInit() { this.profileService.getProfileInfo().subscribe((profileInfo) => { if (profileInfo) { diff --git a/src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts b/src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts index a4b0c62ac684..f8b221d3c29d 100644 --- a/src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts +++ b/src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts @@ -1,10 +1,10 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class PasswordResetInitService { - constructor(private http: HttpClient) {} + private http = inject(HttpClient); save(mail: string): Observable { return this.http.post('api/public/account/reset-password/init', mail); diff --git a/src/main/webapp/app/account/password/password-strength-bar.component.ts b/src/main/webapp/app/account/password/password-strength-bar.component.ts index f550256d2edf..d09537aedab9 100644 --- a/src/main/webapp/app/account/password/password-strength-bar.component.ts +++ b/src/main/webapp/app/account/password/password-strength-bar.component.ts @@ -1,4 +1,6 @@ -import { Component, ElementRef, Input, Renderer2 } from '@angular/core'; +import { Component, ElementRef, Input, Renderer2, inject } from '@angular/core'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { ArtemisSharedModule } from 'app/shared/shared.module'; @Component({ selector: 'jhi-password-strength-bar', @@ -13,14 +15,14 @@ import { Component, ElementRef, Input, Renderer2 } from '@angular/core'; `, styleUrls: ['password-strength-bar.scss'], + standalone: true, + imports: [TranslateDirective, ArtemisSharedModule], }) export class PasswordStrengthBarComponent { - colors = ['#F00', '#F90', '#FF0', '#9F0', '#0F0']; + private renderer = inject(Renderer2); + private elementRef = inject(ElementRef); - constructor( - private renderer: Renderer2, - private elementRef: ElementRef, - ) {} + colors = ['#F00', '#F90', '#FF0', '#9F0', '#0F0']; measureStrength(p: string): number { let force = 0; diff --git a/src/main/webapp/app/account/password/password.component.ts b/src/main/webapp/app/account/password/password.component.ts index 5bec957b70b9..b2a600dd97bb 100644 --- a/src/main/webapp/app/account/password/password.component.ts +++ b/src/main/webapp/app/account/password/password.component.ts @@ -1,17 +1,25 @@ -import { Component, OnInit } from '@angular/core'; - +import { Component, OnInit, inject } from '@angular/core'; import { User } from 'app/core/user/user.model'; import { AccountService } from 'app/core/auth/account.service'; +import { ArtemisSharedModule } from 'app/shared/shared.module'; import { PasswordService } from './password.service'; -import { ProfileService } from 'app/shared/layouts/profiles/profile.service'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH } from 'app/app.constants'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { PasswordStrengthBarComponent } from './password-strength-bar.component'; +import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; @Component({ selector: 'jhi-password', templateUrl: './password.component.html', + standalone: true, + imports: [TranslateDirective, FormsModule, ReactiveFormsModule, PasswordStrengthBarComponent, ArtemisSharedCommonModule, ArtemisSharedModule], }) export class PasswordComponent implements OnInit { + private passwordService = inject(PasswordService); + private accountService = inject(AccountService); + private fb = inject(FormBuilder); + readonly PASSWORD_MIN_LENGTH = PASSWORD_MIN_LENGTH; readonly PASSWORD_MAX_LENGTH = PASSWORD_MAX_LENGTH; @@ -22,13 +30,6 @@ export class PasswordComponent implements OnInit { passwordForm: FormGroup; passwordResetEnabled = false; - constructor( - private passwordService: PasswordService, - private accountService: AccountService, - private profileService: ProfileService, - private fb: FormBuilder, - ) {} - ngOnInit() { this.accountService.identity().then((user) => { this.user = user; diff --git a/src/main/webapp/app/account/password/password.service.ts b/src/main/webapp/app/account/password/password.service.ts index e09e5ef58b93..5001a2fa0644 100644 --- a/src/main/webapp/app/account/password/password.service.ts +++ b/src/main/webapp/app/account/password/password.service.ts @@ -1,10 +1,10 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class PasswordService { - constructor(private http: HttpClient) {} + private http = inject(HttpClient); /** * Sets a new password for the current user. Receives an HTTP 400 if the old password is incorrect. diff --git a/src/main/webapp/app/account/register/register.component.ts b/src/main/webapp/app/account/register/register.component.ts index aa784f8f3f67..15ea460bfb73 100644 --- a/src/main/webapp/app/account/register/register.component.ts +++ b/src/main/webapp/app/account/register/register.component.ts @@ -1,19 +1,29 @@ -import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, OnInit, ViewChild, inject } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; - import { RegisterService } from 'app/account/register/register.service'; import { User } from 'app/core/user/user.model'; import { ACCOUNT_REGISTRATION_BLOCKED, EMAIL_ALREADY_USED_TYPE, LOGIN_ALREADY_USED_TYPE } from 'app/shared/constants/error.constants'; import { ProfileService } from 'app/shared/layouts/profiles/profile.service'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { TranslateService } from '@ngx-translate/core'; import { PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, USERNAME_MAX_LENGTH, USERNAME_MIN_LENGTH } from 'app/app.constants'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { ArtemisSharedModule } from 'app/shared/shared.module'; +import { PasswordStrengthBarComponent } from '../password/password-strength-bar.component'; +import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; @Component({ selector: 'jhi-register', templateUrl: './register.component.html', + standalone: true, + imports: [TranslateDirective, FormsModule, ReactiveFormsModule, PasswordStrengthBarComponent, ArtemisSharedCommonModule, ArtemisSharedModule], }) export class RegisterComponent implements OnInit, AfterViewInit { + private translateService = inject(TranslateService); + private registerService = inject(RegisterService); + private fb = inject(FormBuilder); + private profileService = inject(ProfileService); + @ViewChild('login', { static: false }) login?: ElementRef; @@ -36,13 +46,6 @@ export class RegisterComponent implements OnInit, AfterViewInit { allowedEmailPattern?: string; allowedEmailPatternReadable?: string; - constructor( - private translateService: TranslateService, - private registerService: RegisterService, - private fb: FormBuilder, - private profileService: ProfileService, - ) {} - ngAfterViewInit(): void { if (this.login) { this.login.nativeElement.focus(); diff --git a/src/main/webapp/app/account/register/register.service.ts b/src/main/webapp/app/account/register/register.service.ts index 3c2a0d47bb0f..66ec64db0042 100644 --- a/src/main/webapp/app/account/register/register.service.ts +++ b/src/main/webapp/app/account/register/register.service.ts @@ -1,11 +1,11 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { User } from 'app/core/user/user.model'; @Injectable({ providedIn: 'root' }) export class RegisterService { - constructor(private http: HttpClient) {} + private http = inject(HttpClient); /** * Registers a new user. This is only possible if the password is long enough and there is no other user with the diff --git a/src/main/webapp/app/account/settings/settings.component.ts b/src/main/webapp/app/account/settings/settings.component.ts index b29f833a5144..75e9187aa1bf 100644 --- a/src/main/webapp/app/account/settings/settings.component.ts +++ b/src/main/webapp/app/account/settings/settings.component.ts @@ -1,30 +1,32 @@ -import { Component, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; - +import { Component, OnInit, inject } from '@angular/core'; +import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { AccountService } from 'app/core/auth/account.service'; import { ProfileService } from 'app/shared/layouts/profiles/profile.service'; import { LANGUAGES } from 'app/core/language/language.constants'; import { User } from 'app/core/user/user.model'; import { TranslateService } from '@ngx-translate/core'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; +import { ArtemisSharedModule } from 'app/shared/shared.module'; @Component({ selector: 'jhi-settings', templateUrl: './settings.component.html', + standalone: true, + imports: [TranslateDirective, FormsModule, ReactiveFormsModule, ArtemisSharedCommonModule, ArtemisSharedModule], }) export class SettingsComponent implements OnInit { + private accountService = inject(AccountService); + private fb = inject(FormBuilder); + private translateService = inject(TranslateService); + private profileService = inject(ProfileService); + success = false; account: User; languages = LANGUAGES; settingsForm: FormGroup; isRegistrationEnabled = false; - constructor( - private accountService: AccountService, - private fb: FormBuilder, - private translateService: TranslateService, - private profileService: ProfileService, - ) {} - ngOnInit() { this.profileService.getProfileInfo().subscribe((profileInfo) => { if (profileInfo) { diff --git a/src/main/webapp/app/app-routing.module.ts b/src/main/webapp/app/app-routing.module.ts index b95970e4be38..ee02e755f3a2 100644 --- a/src/main/webapp/app/app-routing.module.ts +++ b/src/main/webapp/app/app-routing.module.ts @@ -1,11 +1,10 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { UserRouteAccessService } from 'app/core/auth/user-route-access-service'; +import { Authority } from 'app/shared/constants/authority.constants'; import { navbarRoute } from 'app/shared/layouts/navbar/navbar.route'; import { errorRoute } from 'app/shared/layouts/error/error.route'; import { ArtemisNavigationUtilService } from 'app/utils/navigation.utils'; -import { AboutIrisComponent } from 'app/iris/about-iris/about-iris.component'; -import { ProblemStatementComponent } from './overview/exercise-details/problem-statement/problem-statement.component'; -import { StandaloneFeedbackComponent } from './exercises/shared/feedback/standalone-feedback/standalone-feedback.component'; const LAYOUT_ROUTES: Routes = [navbarRoute, ...errorRoute]; @@ -17,15 +16,14 @@ const LAYOUT_ROUTES: Routes = [navbarRoute, ...errorRoute]; { path: '', loadComponent: () => import('./home/home.component').then((m) => m.HomeComponent), + data: { + pageTitle: 'home.title', + }, }, { path: 'admin', loadChildren: () => import('./admin/admin.module').then((m) => m.ArtemisAdminModule), }, - { - path: 'account', - loadChildren: () => import('./account/account.module').then((m) => m.ArtemisAccountModule), - }, { path: 'privacy', loadChildren: () => import('./core/legal/privacy.module').then((m) => m.ArtemisPrivacyModule), @@ -47,6 +45,64 @@ const LAYOUT_ROUTES: Routes = [navbarRoute, ...errorRoute]; path: 'courses/:courseId/exercises/:exerciseId/teams', loadChildren: () => import('./exercises/shared/team/team.module').then((m) => m.ArtemisTeamModule), }, + // ===== ACCOUNT ==== + { + path: 'account', + children: [ + { + path: 'activate', + pathMatch: 'full', + loadComponent: () => import('./account/activate/activate.component').then((m) => m.ActivateComponent), + data: { + pageTitle: 'activate.title', + }, + }, + { + path: 'password', + pathMatch: 'full', + loadComponent: () => import('./account/password/password.component').then((m) => m.PasswordComponent), + data: { + authorities: [Authority.USER], + pageTitle: 'global.menu.account.password', + }, + canActivate: [UserRouteAccessService], + }, + { + path: 'reset/finish', + pathMatch: 'full', + loadComponent: () => import('./account/password-reset/finish/password-reset-finish.component').then((m) => m.PasswordResetFinishComponent), + data: { + pageTitle: 'global.menu.account.password', + }, + }, + { + path: 'reset/request', + pathMatch: 'full', + loadComponent: () => import('./account/password-reset/init/password-reset-init.component').then((m) => m.PasswordResetInitComponent), + data: { + pageTitle: 'global.menu.account.password', + }, + }, + { + path: 'register', + pathMatch: 'full', + loadComponent: () => import('./account/register/register.component').then((m) => m.RegisterComponent), + data: { + pageTitle: 'register.title', + }, + }, + { + path: 'settings', + pathMatch: 'full', + loadComponent: () => import('./account/settings/settings.component').then((m) => m.SettingsComponent), + data: { + authorities: [Authority.USER], + pageTitle: 'global.menu.account.settings', + }, + canActivate: [UserRouteAccessService], + }, + ], + }, // ===== COURSE MANAGEMENT ===== { path: 'course-management', @@ -82,17 +138,17 @@ const LAYOUT_ROUTES: Routes = [navbarRoute, ...errorRoute]; { path: 'courses/:courseId/exercises/:exerciseId/problem-statement', pathMatch: 'full', - component: ProblemStatementComponent, + loadComponent: () => import('./overview/exercise-details/problem-statement/problem-statement.component').then((m) => m.ProblemStatementComponent), }, { pathMatch: 'full', path: 'courses/:courseId/exercises/:exerciseId/problem-statement/:participationId', - component: ProblemStatementComponent, + loadComponent: () => import('./overview/exercise-details/problem-statement/problem-statement.component').then((m) => m.ProblemStatementComponent), }, { path: 'courses/:courseId/exercises/:exerciseId/participations/:participationId/results/:resultId/feedback', pathMatch: 'full', - component: StandaloneFeedbackComponent, + loadComponent: () => import('./exercises/shared/feedback/standalone-feedback/standalone-feedback.component').then((m) => m.StandaloneFeedbackComponent), }, // ===== EXAM ===== @@ -119,8 +175,8 @@ const LAYOUT_ROUTES: Routes = [navbarRoute, ...errorRoute]; }, { path: 'about-iris', - component: AboutIrisComponent, pathMatch: 'full', + loadComponent: () => import('./iris/about-iris/about-iris.component').then((m) => m.AboutIrisComponent), }, ], { enableTracing: false, onSameUrlNavigation: 'reload' }, diff --git a/src/main/webapp/app/shared/layouts/profiles/page-ribbon.component.ts b/src/main/webapp/app/shared/layouts/profiles/page-ribbon.component.ts index 19e3bfe31634..ac757f87fb4b 100644 --- a/src/main/webapp/app/shared/layouts/profiles/page-ribbon.component.ts +++ b/src/main/webapp/app/shared/layouts/profiles/page-ribbon.component.ts @@ -22,7 +22,9 @@ export class PageRibbonComponent implements OnInit { ngOnInit() { this.profileService.getProfileInfo().subscribe((profileInfo) => { if (profileInfo) { - this.ribbonEnv = profileInfo.ribbonEnv; + if (profileInfo.inDevelopment) { + this.ribbonEnv = 'dev'; + } if (profileInfo.inProduction && profileInfo.testServer) { this.ribbonEnv = 'test'; } diff --git a/src/main/webapp/app/shared/layouts/profiles/profile-info.model.ts b/src/main/webapp/app/shared/layouts/profiles/profile-info.model.ts index 9d7bafb69cde..293383532080 100644 --- a/src/main/webapp/app/shared/layouts/profiles/profile-info.model.ts +++ b/src/main/webapp/app/shared/layouts/profiles/profile-info.model.ts @@ -7,6 +7,7 @@ export class ProfileInfo { public activeProfiles: string[]; public ribbonEnv: string; public inProduction: boolean; + public inDevelopment: boolean; public openApiEnabled?: boolean; public sentry?: { dsn: string }; public postHog?: { diff --git a/src/main/webapp/app/shared/layouts/profiles/profile.service.ts b/src/main/webapp/app/shared/layouts/profiles/profile.service.ts index ac828dc310ea..f3a21ec5fbd6 100644 --- a/src/main/webapp/app/shared/layouts/profiles/profile.service.ts +++ b/src/main/webapp/app/shared/layouts/profiles/profile.service.ts @@ -44,6 +44,7 @@ export class ProfileService { profileInfo.ribbonEnv = ribbonProfiles[0]; } profileInfo.inProduction = profileInfo.activeProfiles.includes('prod'); + profileInfo.inDevelopment = profileInfo.activeProfiles.includes('dev'); profileInfo.openApiEnabled = profileInfo.activeProfiles.includes('openapi'); } profileInfo.ribbonEnv = profileInfo.ribbonEnv ?? ''; diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/localvcci/BuildAgentConfigurationTest.java b/src/test/java/de/tum/cit/aet/artemis/buildagent/service/BuildAgentConfigurationTest.java similarity index 95% rename from src/test/java/de/tum/cit/aet/artemis/programming/localvcci/BuildAgentConfigurationTest.java rename to src/test/java/de/tum/cit/aet/artemis/buildagent/service/BuildAgentConfigurationTest.java index 4c1ac9044ae9..69a23bfc177d 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/localvcci/BuildAgentConfigurationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/buildagent/service/BuildAgentConfigurationTest.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.localvcci; +package de.tum.cit.aet.artemis.buildagent.service; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/localvcci/BuildAgentDockerServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/buildagent/service/BuildAgentDockerServiceTest.java similarity index 97% rename from src/test/java/de/tum/cit/aet/artemis/programming/localvcci/BuildAgentDockerServiceTest.java rename to src/test/java/de/tum/cit/aet/artemis/buildagent/service/BuildAgentDockerServiceTest.java index 3b514091deae..1da83f9c87c3 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/localvcci/BuildAgentDockerServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/buildagent/service/BuildAgentDockerServiceTest.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.localvcci; +package de.tum.cit.aet.artemis.buildagent.service; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; @@ -30,8 +30,6 @@ import de.tum.cit.aet.artemis.buildagent.dto.BuildConfig; import de.tum.cit.aet.artemis.buildagent.dto.BuildJobQueueItem; -import de.tum.cit.aet.artemis.buildagent.service.BuildAgentDockerService; -import de.tum.cit.aet.artemis.buildagent.service.BuildLogsMap; import de.tum.cit.aet.artemis.core.exception.LocalCIException; import de.tum.cit.aet.artemis.programming.domain.build.BuildJob; import de.tum.cit.aet.artemis.programming.domain.build.BuildStatus; diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/localvcci/BuildAgentSshAuthenticationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/buildagent/service/BuildAgentSshAuthenticationIntegrationTest.java similarity index 80% rename from src/test/java/de/tum/cit/aet/artemis/programming/localvcci/BuildAgentSshAuthenticationIntegrationTest.java rename to src/test/java/de/tum/cit/aet/artemis/buildagent/service/BuildAgentSshAuthenticationIntegrationTest.java index 9caa0bdc3ef9..d964d68558db 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/localvcci/BuildAgentSshAuthenticationIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/buildagent/service/BuildAgentSshAuthenticationIntegrationTest.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.localvcci; +package de.tum.cit.aet.artemis.buildagent.service; import static org.assertj.core.api.Assertions.assertThat; @@ -8,14 +8,11 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.map.IMap; import de.tum.cit.aet.artemis.buildagent.dto.BuildAgentInformation; -import de.tum.cit.aet.artemis.buildagent.service.BuildAgentSshKeyService; -import de.tum.cit.aet.artemis.buildagent.service.SharedQueueProcessingService; import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationLocalCILocalVCTest; class BuildAgentSshAuthenticationIntegrationTest extends AbstractSpringIntegrationLocalCILocalVCTest { @@ -30,9 +27,6 @@ class BuildAgentSshAuthenticationIntegrationTest extends AbstractSpringIntegrati @Autowired private SharedQueueProcessingService sharedQueueProcessingService; - @Value("${artemis.version-control.ssh-private-key-folder-path}") - protected String gitSshPrivateKeyPath; - @Test void testWriteSSHKey() { boolean sshPrivateKeyExists = Files.exists(Path.of(System.getProperty("java.io.tmpdir"), "id_rsa")); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/localvcci/BuildResultTest.java b/src/test/java/de/tum/cit/aet/artemis/buildagent/service/BuildResultTest.java similarity index 91% rename from src/test/java/de/tum/cit/aet/artemis/programming/localvcci/BuildResultTest.java rename to src/test/java/de/tum/cit/aet/artemis/buildagent/service/BuildResultTest.java index 104d6cda1207..b7ad99348575 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/localvcci/BuildResultTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/buildagent/service/BuildResultTest.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.localvcci; +package de.tum.cit.aet.artemis.buildagent.service; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/de/tum/cit/aet/artemis/buildagent/service/connectors/localci/TestResultXmlParserTest.java b/src/test/java/de/tum/cit/aet/artemis/buildagent/service/TestResultXmlParserTest.java similarity index 97% rename from src/test/java/de/tum/cit/aet/artemis/buildagent/service/connectors/localci/TestResultXmlParserTest.java rename to src/test/java/de/tum/cit/aet/artemis/buildagent/service/TestResultXmlParserTest.java index 083e6ac0a047..3af5e6b64a69 100644 --- a/src/test/java/de/tum/cit/aet/artemis/buildagent/service/connectors/localci/TestResultXmlParserTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/buildagent/service/TestResultXmlParserTest.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.buildagent.service.connectors.localci; +package de.tum.cit.aet.artemis.buildagent.service; import static org.assertj.core.api.Assertions.assertThat; @@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test; import de.tum.cit.aet.artemis.buildagent.dto.BuildResult; -import de.tum.cit.aet.artemis.buildagent.service.TestResultXmlParser; class TestResultXmlParserTest { @@ -89,7 +88,7 @@ void testSuccessfulTests() throws IOException { TestResultXmlParser.processTestResultFile(exampleXml, failedTests, successfulTests); assertThat(failedTests).isEmpty(); assertThat(successfulTests).hasSize(4); - assertThat(successfulTests).map(test -> test.getName()).containsExactlyInAnyOrder("testMergeSort()", "testUseBubbleSortForSmallList()", "testBubbleSort()", + assertThat(successfulTests).map(BuildResult.LocalCITestJobDTO::getName).containsExactlyInAnyOrder("testMergeSort()", "testUseBubbleSortForSmallList()", "testBubbleSort()", "testUseMergeSortForBigList()"); } diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/ContinuousIntegrationTestService.java b/src/test/java/de/tum/cit/aet/artemis/programming/ContinuousIntegrationTestService.java index 7268656989bf..0567c82c6a80 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/ContinuousIntegrationTestService.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/ContinuousIntegrationTestService.java @@ -180,10 +180,4 @@ public void testHealthException() throws Exception { assertThat(health.isUp()).isFalse(); assertThat(health.exception()).isNotNull(); } - - public void testConfigureBuildPlan() throws Exception { - mockDelegate.mockConfigureBuildPlan(participation, defaultBranch); - continuousIntegrationService.configureBuildPlan(participation, defaultBranch); - mockDelegate.verifyMocks(); - } } diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseBuildPlanTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseBuildPlanTest.java index e91fb769e4fa..074fe70e54b9 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseBuildPlanTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseBuildPlanTest.java @@ -30,7 +30,7 @@ class ProgrammingExerciseBuildPlanTest extends AbstractSpringIntegrationGitlabCI stage: test script: - echo "Test" - """; + """; @Autowired private ProgrammingExerciseTestRepository programmingExerciseRepository; diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseServiceIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseServiceIntegrationTest.java index a415b8303670..4ab18f1ce22a 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseServiceIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseServiceIntegrationTest.java @@ -28,7 +28,6 @@ import de.tum.cit.aet.artemis.programming.domain.submissionpolicy.SubmissionPenaltyPolicy; import de.tum.cit.aet.artemis.programming.domain.submissionpolicy.SubmissionPolicy; import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseImportBasicService; -import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseService; import de.tum.cit.aet.artemis.programming.test_repository.ProgrammingExerciseTestRepository; import de.tum.cit.aet.artemis.programming.util.ProgrammingExerciseFactory; import de.tum.cit.aet.artemis.programming.util.ProgrammingExerciseUtilService; @@ -40,9 +39,6 @@ class ProgrammingExerciseServiceIntegrationTest extends AbstractSpringIntegratio private static final String BASE_RESOURCE = "/api/programming-exercises"; - @Autowired - ProgrammingExerciseService programmingExerciseService; - @Autowired ProgrammingExerciseImportBasicService programmingExerciseImportBasicService; 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 6295e3ea8944..2d985741a637 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 @@ -82,18 +82,18 @@ void setUp() { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void shouldResetCourseExerciseTestCases() throws Exception { + void shouldResetCourseExerciseTestCases() { testResetTestCases(programmingExercise, Visibility.ALWAYS); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void shouldResetExamExerciseTestCases() throws Exception { + void shouldResetExamExerciseTestCases() { programmingExercise.setExerciseGroup(new ExerciseGroup()); testResetTestCases(programmingExercise, Visibility.AFTER_DUE_DATE); } - private void testResetTestCases(ProgrammingExercise programmingExercise, Visibility expectedVisibility) throws Exception { + private void testResetTestCases(ProgrammingExercise programmingExercise, Visibility expectedVisibility) { String dummyHash = "9b3a9bd71a0d80e5bbc42204c319ed3d1d4f0d6d"; when(gitService.getLastCommitHash(any())).thenReturn(ObjectId.fromString(dummyHash)); participationUtilService.addProgrammingParticipationWithResultForExercise(programmingExercise, TEST_PREFIX + "student1"); @@ -121,8 +121,7 @@ private void testResetTestCases(ProgrammingExercise programmingExercise, Visibil @Test @WithMockUser(username = TEST_PREFIX + "tutor1", roles = "TA") - void shouldUpdateTestWeight() throws Exception { - ; + void shouldUpdateTestWeight() { String dummyHash = "9b3a9bd71a0d80e5bbc42204c319ed3d1d4f0d6d"; doReturn(ObjectId.fromString(dummyHash)).when(gitService).getLastCommitHash(any()); @@ -155,7 +154,7 @@ void shouldUpdateTestWeight() throws Exception { @ParameterizedTest(name = "{displayName} [{index}] {argumentsWithNames}") @EnumSource(AssessmentType.class) @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void shouldAllowTestCaseWeightSumZero(AssessmentType assessmentType) throws Exception { + void shouldAllowTestCaseWeightSumZero(AssessmentType assessmentType) { programmingExercise.setAssessmentType(assessmentType); programmingExerciseRepository.save(programmingExercise); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingSubmissionIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingSubmissionIntegrationTest.java index 3584538982e9..4d53882cd461 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingSubmissionIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingSubmissionIntegrationTest.java @@ -254,8 +254,8 @@ void triggerBuildForExerciseAsInstructor() throws Exception { String login2 = TEST_PREFIX + "student2"; String login3 = TEST_PREFIX + "student3"; final var firstParticipation = participationUtilService.addStudentParticipationForProgrammingExercise(exercise, login1); - final var secondParticipation = participationUtilService.addStudentParticipationForProgrammingExercise(exercise, login2); - final var thirdParticipation = participationUtilService.addStudentParticipationForProgrammingExercise(exercise, login3); + participationUtilService.addStudentParticipationForProgrammingExercise(exercise, login2); + participationUtilService.addStudentParticipationForProgrammingExercise(exercise, login3); // Set test cases changed to true; after the build run it should be false; exercise.setTestCasesChanged(true); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/StaticCodeAnalysisIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/StaticCodeAnalysisIntegrationTest.java index 02b0f6afc7d5..06f673f808ee 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/StaticCodeAnalysisIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/StaticCodeAnalysisIntegrationTest.java @@ -156,8 +156,7 @@ void testUpdateStaticCodeAnalysisCategories(ProgrammingLanguage programmingLangu doReturn(ObjectId.fromString(dummyHash)).when(gitService).getLastCommitHash(any()); var programmingExSCAEnabled = programmingExerciseUtilService.addCourseWithOneProgrammingExerciseAndStaticCodeAnalysisCategories(programmingLanguage); - ProgrammingExercise exerciseWithSolutionParticipation = programmingExerciseRepository - .findWithTemplateAndSolutionParticipationTeamAssignmentConfigCategoriesById(programmingExSCAEnabled.getId()).orElseThrow(); + programmingExerciseRepository.findWithTemplateAndSolutionParticipationTeamAssignmentConfigCategoriesById(programmingExSCAEnabled.getId()).orElseThrow(); var endpoint = parameterizeEndpoint("/api/programming-exercises/{exerciseId}/static-code-analysis-categories", programmingExSCAEnabled); // Change the first category var categoryIterator = programmingExSCAEnabled.getStaticCodeAnalysisCategories().iterator(); @@ -349,8 +348,7 @@ void testImportCategories() throws Exception { staticCodeAnalysisCategoryRepository.saveAll(categories); - ProgrammingExercise exerciseWithSolutionParticipation = programmingExerciseRepository - .findWithTemplateAndSolutionParticipationTeamAssignmentConfigCategoriesById(programmingExerciseSCAEnabled.getId()).orElseThrow(); + programmingExerciseRepository.findWithTemplateAndSolutionParticipationTeamAssignmentConfigCategoriesById(programmingExerciseSCAEnabled.getId()).orElseThrow(); var endpoint = parameterizeEndpoint("/api/programming-exercises/{exerciseId}/static-code-analysis-categories/import", programmingExerciseSCAEnabled); var newCategories = request.patchWithResponseBodyList(endpoint + "?sourceExerciseId=" + sourceExercise.getId(), null, StaticCodeAnalysisCategory.class, HttpStatus.OK); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/StaticCodeAnalysisParserUnitTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/StaticCodeAnalysisParserUnitTest.java index f2519d178e72..2e8886ec0fbc 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/StaticCodeAnalysisParserUnitTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/StaticCodeAnalysisParserUnitTest.java @@ -51,22 +51,6 @@ private void testParserWithNullValue() throws ParserException { parser.transformToJSONReport(null); } - /** - * Compares the parsed JSON report with the expected JSON report - * - * @param fileName The name of the file contains the report as generated by the different tools - * @param expected The expected output - * @throws ParserException If an exception occurs that is not already handled by the parser itself, e.g. caused by the json-parsing - */ - private void testParserWithString(String fileName, String expected) throws ParserException { - File toolReport = REPORTS_FOLDER_PATH.resolve(fileName).toFile(); - - ReportParser parser = new ReportParser(); - String actual = parser.transformToJSONReport(toolReport); - - assertThat(actual).isEqualTo(expected); - } - @Test void testCheckstyleParser() throws IOException { try { diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/SubmissionPolicyIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/SubmissionPolicyIntegrationTest.java index f7da35caab33..660eec38c4f6 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/SubmissionPolicyIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/SubmissionPolicyIntegrationTest.java @@ -260,7 +260,6 @@ void test_updateSubmissionPolicy_ok_lockRepositoryPolicy_newLimitSmaller() throw programmingExerciseUtilService.addProgrammingSubmissionToResultAndParticipation(new Result().score(20.0), participation1, TEST_PREFIX + "commit1"); programmingExerciseUtilService.addProgrammingSubmissionToResultAndParticipation(new Result().score(25.0), participation2, TEST_PREFIX + "commit2"); programmingExerciseUtilService.addProgrammingSubmissionToResultAndParticipation(new Result().score(30.0), participation2, TEST_PREFIX + "commit3"); - String repositoryName = programmingExercise.getProjectKey().toLowerCase() + "-" + TEST_PREFIX + "student2"; User student2 = userTestRepository.getUserByLoginElseThrow(TEST_PREFIX + "student2"); gitlabRequestMockProvider.enableMockingOfRequests(); mockSetRepositoryPermissionsToReadOnly(participation2.getVcsRepositoryUri(), programmingExercise.getProjectKey(), Set.of(student2)); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/TestRepositoryResourceIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/TestRepositoryResourceIntegrationTest.java index 32b3decaa0cb..dfaf90733fac 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/TestRepositoryResourceIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/TestRepositoryResourceIntegrationTest.java @@ -15,7 +15,6 @@ import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Optional; import org.apache.commons.io.FileUtils; @@ -38,6 +37,7 @@ import de.tum.cit.aet.artemis.programming.domain.Repository; import de.tum.cit.aet.artemis.programming.dto.FileMove; import de.tum.cit.aet.artemis.programming.dto.RepositoryStatusDTO; +import de.tum.cit.aet.artemis.programming.dto.RepositoryStatusDTOType; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseBuildConfigRepository; import de.tum.cit.aet.artemis.programming.service.GitService; import de.tum.cit.aet.artemis.programming.test_repository.ProgrammingExerciseTestRepository; @@ -492,10 +492,11 @@ void testGetStatus_cannotAccessRepository() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void testIsClean() throws Exception { + void testRepositoryState() throws Exception { programmingExerciseRepository.save(programmingExercise); doReturn(true).when(gitService).isRepositoryCached(any()); - var status = request.get(testRepoBaseUrl + programmingExercise.getId(), HttpStatus.OK, Map.class); - assertThat(status).isNotEmpty(); + var status = request.get(testRepoBaseUrl + programmingExercise.getId(), HttpStatus.OK, RepositoryStatusDTO.class); + assertThat(status.repositoryStatus()).isEqualTo(RepositoryStatusDTOType.UNCOMMITTED_CHANGES); + // TODO: also test other states } } diff --git a/src/test/javascript/spec/component/account/activate.component.spec.ts b/src/test/javascript/spec/component/account/activate.component.spec.ts index 7a169a261259..edc821e56057 100644 --- a/src/test/javascript/spec/component/account/activate.component.spec.ts +++ b/src/test/javascript/spec/component/account/activate.component.spec.ts @@ -16,8 +16,7 @@ describe('ActivateComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule], - declarations: [ActivateComponent], + imports: [ArtemisTestModule, ActivateComponent], providers: [ { provide: ActivatedRoute, useValue: new MockActivatedRoute({ key: 'ABC123' }) }, { provide: LocalStorageService, useClass: MockSyncStorage }, diff --git a/src/test/javascript/spec/component/account/password-reset-finish.component.spec.ts b/src/test/javascript/spec/component/account/password-reset-finish.component.spec.ts index 666674f1bd4e..589e72207e91 100644 --- a/src/test/javascript/spec/component/account/password-reset-finish.component.spec.ts +++ b/src/test/javascript/spec/component/account/password-reset-finish.component.spec.ts @@ -20,8 +20,7 @@ describe('Component Tests', () => { beforeEach(() => { fixture = TestBed.configureTestingModule({ - imports: [ArtemisTestModule], - declarations: [PasswordResetFinishComponent], + imports: [ArtemisTestModule, PasswordResetFinishComponent], providers: [ FormBuilder, { diff --git a/src/test/javascript/spec/component/account/password-reset-init.component.spec.ts b/src/test/javascript/spec/component/account/password-reset-init.component.spec.ts index 5370cc47e9d6..48be5495b08d 100644 --- a/src/test/javascript/spec/component/account/password-reset-init.component.spec.ts +++ b/src/test/javascript/spec/component/account/password-reset-init.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, TestBed, inject } from '@angular/core/testing'; -import { FormBuilder, NgModel } from '@angular/forms'; +import { FormBuilder } from '@angular/forms'; import { of, throwError } from 'rxjs'; import { ArtemisTestModule } from '../../test.module'; import { PasswordResetInitComponent } from 'app/account/password-reset/init/password-reset-init.component'; @@ -7,12 +7,13 @@ import { PasswordResetInitService } from 'app/account/password-reset/init/passwo import { ProfileService } from 'app/shared/layouts/profiles/profile.service'; import { MockProfileService } from '../../helpers/mocks/service/mock-profile.service'; import { AlertService } from 'app/core/util/alert.service'; -import { MockDirective, MockProvider } from 'ng-mocks'; +import { MockProvider } from 'ng-mocks'; import { MockTranslateService, TranslatePipeMock } from '../../helpers/mocks/service/mock-translate.service'; import { TranslateService } from '@ngx-translate/core'; import { MockNgbModalService } from '../../helpers/mocks/service/mock-ngb-modal.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { By } from '@angular/platform-browser'; +import { FormsModule } from 'app/forms/forms.module'; describe('PasswordResetInitComponent', () => { let fixture: ComponentFixture; @@ -20,8 +21,8 @@ describe('PasswordResetInitComponent', () => { beforeEach(() => { return TestBed.configureTestingModule({ - imports: [ArtemisTestModule], - declarations: [PasswordResetInitComponent, TranslatePipeMock, MockDirective(NgModel)], + imports: [ArtemisTestModule, FormsModule, PasswordResetInitComponent], + declarations: [TranslatePipeMock], providers: [ FormBuilder, { provide: ProfileService, useClass: MockProfileService }, diff --git a/src/test/javascript/spec/component/account/password-strength-bar.component.spec.ts b/src/test/javascript/spec/component/account/password-strength-bar.component.spec.ts index 5d4b3812f961..b8bee1a3e552 100644 --- a/src/test/javascript/spec/component/account/password-strength-bar.component.spec.ts +++ b/src/test/javascript/spec/component/account/password-strength-bar.component.spec.ts @@ -1,6 +1,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { PasswordStrengthBarComponent } from 'app/account/password/password-strength-bar.component'; +import { MockTranslateService } from '../../helpers/mocks/service/mock-translate.service'; +import { TranslateService } from '@ngx-translate/core'; describe('Component Tests', () => { describe('PasswordStrengthBarComponent', () => { @@ -9,7 +11,8 @@ describe('Component Tests', () => { beforeEach(() => { TestBed.configureTestingModule({ - declarations: [PasswordStrengthBarComponent], + imports: [PasswordStrengthBarComponent], + providers: [{ provide: TranslateService, useClass: MockTranslateService }], }).compileComponents(); }); diff --git a/src/test/javascript/spec/component/account/password.component.spec.ts b/src/test/javascript/spec/component/account/password.component.spec.ts index 2b3d0d038aa8..1b3c7b7db8b7 100644 --- a/src/test/javascript/spec/component/account/password.component.spec.ts +++ b/src/test/javascript/spec/component/account/password.component.spec.ts @@ -22,8 +22,7 @@ describe('Password Component Tests', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule], - declarations: [PasswordComponent], + imports: [ArtemisTestModule, PasswordComponent], providers: [ FormBuilder, { provide: LocalStorageService, useClass: MockSyncStorage }, diff --git a/src/test/javascript/spec/component/account/register.component.spec.ts b/src/test/javascript/spec/component/account/register.component.spec.ts index 483139f4dcbd..4a1687f87812 100644 --- a/src/test/javascript/spec/component/account/register.component.spec.ts +++ b/src/test/javascript/spec/component/account/register.component.spec.ts @@ -7,6 +7,7 @@ import { EMAIL_ALREADY_USED_TYPE, LOGIN_ALREADY_USED_TYPE } from 'app/shared/con import { RegisterService } from 'app/account/register/register.service'; import { RegisterComponent } from 'app/account/register/register.component'; import { User } from 'app/core/user/user.model'; +import { ElementRef } from '@angular/core'; import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service'; import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; import { ProfileService } from 'app/shared/layouts/profiles/profile.service'; @@ -22,8 +23,7 @@ describe('Register Component Tests', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule], - declarations: [RegisterComponent], + imports: [ArtemisTestModule, RegisterComponent], providers: [ FormBuilder, { provide: LocalStorageService, useClass: MockSyncStorage }, @@ -153,5 +153,18 @@ describe('Register Component Tests', () => { expect(comp.error).toBeTrue(); }), )); + + it('should focus login input if login is defined', () => { + const focusSpy = jest.fn(); + comp.login = { + nativeElement: { + focus: focusSpy, + }, + } as ElementRef; + + comp.ngAfterViewInit(); + + expect(focusSpy).toHaveBeenCalled(); + }); }); }); diff --git a/src/test/javascript/spec/component/account/settings.component.spec.ts b/src/test/javascript/spec/component/account/settings.component.spec.ts index cafa1ffd6062..2ef45049bbbe 100644 --- a/src/test/javascript/spec/component/account/settings.component.spec.ts +++ b/src/test/javascript/spec/component/account/settings.component.spec.ts @@ -32,8 +32,7 @@ describe('SettingsComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule], - declarations: [SettingsComponent], + imports: [ArtemisTestModule, SettingsComponent], providers: [ FormBuilder, { provide: LocalStorageService, useClass: MockSyncStorage }, 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 6423338cddbf..72d26a2d07b6 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 @@ -63,6 +63,7 @@ describe('CodeButtonComponent', () => { externalUserManagementURL: '', features: [], inProduction: false, + inDevelopment: true, programmingLanguageFeatures: [], ribbonEnv: '', sshCloneURLTemplate: 'ssh://git@gitlab.ase.in.tum.de:7999/', diff --git a/src/test/javascript/spec/service/activate.service.spec.ts b/src/test/javascript/spec/service/activate.service.spec.ts index 837055a9ba39..98c5e9e4cb38 100644 --- a/src/test/javascript/spec/service/activate.service.spec.ts +++ b/src/test/javascript/spec/service/activate.service.spec.ts @@ -1,19 +1,25 @@ import { ActivateService } from 'app/account/activate/activate.service'; import { MockHttpService } from '../helpers/mocks/service/mock-http.service'; -import { HttpParams } from '@angular/common/http'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { TestBed } from '@angular/core/testing'; describe('ActivateService', () => { let activateService: ActivateService; - let httpService: MockHttpService; + let httpService: HttpClient; let getStub: jest.SpyInstance; const getURL = 'api/public/activate'; beforeEach(() => { - httpService = new MockHttpService(); - // @ts-ignore - activateService = new ActivateService(httpService); - getStub = jest.spyOn(httpService, 'get'); + TestBed.configureTestingModule({ + providers: [{ provide: HttpClient, useClass: MockHttpService }], + }) + .compileComponents() + .then(() => { + httpService = TestBed.inject(HttpClient); + activateService = TestBed.inject(ActivateService); + getStub = jest.spyOn(httpService, 'get'); + }); }); afterEach(() => { diff --git a/src/test/javascript/spec/service/profile.service.spec.ts b/src/test/javascript/spec/service/profile.service.spec.ts index cdca0124e906..2d3c984d5345 100644 --- a/src/test/javascript/spec/service/profile.service.spec.ts +++ b/src/test/javascript/spec/service/profile.service.spec.ts @@ -174,6 +174,7 @@ describe('ProfileService', () => { }, }, inProduction: true, + inDevelopment: false, openApiEnabled: true, sentry: { dsn: 'https://ceeb3e72ec094684aefbb132f87231f2@sentry.ase.in.tum.de/2' }, features: [FeatureToggle.ProgrammingExercises, FeatureToggle.PlagiarismChecks],