From 953e19ac3eff657c0f846c4829aa1d6766127953 Mon Sep 17 00:00:00 2001 From: denniskp <122602355+denniskp@users.noreply.github.com> Date: Tue, 8 Jul 2025 12:49:27 +0200 Subject: [PATCH] feat: Refactor role system (#75) --- .../codespark/api/common/UserRoleType.java | 6 -- .../tinf22b6/codespark/api/model/Account.java | 32 +++--- .../tinf22b6/codespark/api/model/Role.java | 28 +++++ .../api/repository/RoleRepository.java | 11 ++ .../codespark/api/security/JwtFilter.java | 9 +- .../security/WebSocketAuthInterceptor.java | 8 +- .../api/service/AccountServiceImpl.java | 23 ++-- .../service/common/PredefinedUserRole.java | 13 +++ src/main/resources/application-prod.yml | 5 - src/main/resources/application-test.yml | 3 + src/main/resources/application.yml | 2 +- .../changelog/changes/002_roles-refactor.yaml | 100 ++++++++++++++++++ .../db/changelog/db.changelog-master.yaml | 2 + .../repository/AccountRepositoryTests.java | 6 +- .../ConversationRepositoryTests.java | 6 +- .../repository/ExamDateRepositoryTests.java | 6 +- .../repository/UserBadgeRepositoryTests.java | 14 +-- .../UserLessonProgressRepositoryTests.java | 14 +-- .../VerificationTokenRepositoryTests.java | 5 +- .../api/service/AccountServiceImplTests.java | 16 ++- .../api/service/AuthServiceImplTests.java | 4 +- 21 files changed, 240 insertions(+), 73 deletions(-) delete mode 100644 src/main/java/de/dhbw/tinf22b6/codespark/api/common/UserRoleType.java create mode 100644 src/main/java/de/dhbw/tinf22b6/codespark/api/model/Role.java create mode 100644 src/main/java/de/dhbw/tinf22b6/codespark/api/repository/RoleRepository.java create mode 100644 src/main/java/de/dhbw/tinf22b6/codespark/api/service/common/PredefinedUserRole.java create mode 100644 src/main/resources/db/changelog/changes/002_roles-refactor.yaml diff --git a/src/main/java/de/dhbw/tinf22b6/codespark/api/common/UserRoleType.java b/src/main/java/de/dhbw/tinf22b6/codespark/api/common/UserRoleType.java deleted file mode 100644 index 1fa48c2..0000000 --- a/src/main/java/de/dhbw/tinf22b6/codespark/api/common/UserRoleType.java +++ /dev/null @@ -1,6 +0,0 @@ -package de.dhbw.tinf22b6.codespark.api.common; - -public enum UserRoleType { - USER, - ADMIN -} diff --git a/src/main/java/de/dhbw/tinf22b6/codespark/api/model/Account.java b/src/main/java/de/dhbw/tinf22b6/codespark/api/model/Account.java index 824fd6d..b90fbe8 100644 --- a/src/main/java/de/dhbw/tinf22b6/codespark/api/model/Account.java +++ b/src/main/java/de/dhbw/tinf22b6/codespark/api/model/Account.java @@ -1,15 +1,12 @@ package de.dhbw.tinf22b6.codespark.api.model; -import de.dhbw.tinf22b6.codespark.api.common.UserRoleType; import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; +import java.util.*; @Getter @Setter @@ -29,15 +26,26 @@ public class Account { @Column(nullable = false) private String password; - @Enumerated(EnumType.STRING) - private UserRoleType role; - @Column(length = 512) private String profileImageUrl; @Column(nullable = false) private boolean verified; + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable( + name = "account_role", + joinColumns = @JoinColumn(name = "account_id"), + inverseJoinColumns = @JoinColumn(name = "role_id") + ) + private Set roles = new HashSet<>(); + + @Column(nullable = false, updatable = false) + private LocalDateTime creationDate; + + @Column(nullable = false) + private LocalDateTime lastLogin; + @OneToOne(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true) private Conversation conversation; @@ -47,19 +55,13 @@ public class Account { @OneToOne(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true) private ExamDate examDate; - @Column(nullable = false, updatable = false) - private LocalDateTime creationDate; - - @Column(nullable = false) - private LocalDateTime lastLogin; - - public Account(String username, String email, String password, UserRoleType role, boolean verified, + public Account(String username, String email, String password, boolean verified, Set roles, LocalDateTime creationDate, LocalDateTime lastLogin) { this.username = username; this.email = email; this.password = password; - this.role = role; this.verified = verified; + this.roles = roles; this.creationDate = creationDate; this.lastLogin = lastLogin; } diff --git a/src/main/java/de/dhbw/tinf22b6/codespark/api/model/Role.java b/src/main/java/de/dhbw/tinf22b6/codespark/api/model/Role.java new file mode 100644 index 0000000..42231b3 --- /dev/null +++ b/src/main/java/de/dhbw/tinf22b6/codespark/api/model/Role.java @@ -0,0 +1,28 @@ +package de.dhbw.tinf22b6.codespark.api.model; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.UUID; + +@Getter +@Setter +@NoArgsConstructor +@Entity +public class Role { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @Column(unique = true, nullable = false) + private String name; + + private String description; + + public Role(String name, String description) { + this.name = name; + this.description = description; + } +} diff --git a/src/main/java/de/dhbw/tinf22b6/codespark/api/repository/RoleRepository.java b/src/main/java/de/dhbw/tinf22b6/codespark/api/repository/RoleRepository.java new file mode 100644 index 0000000..a473b24 --- /dev/null +++ b/src/main/java/de/dhbw/tinf22b6/codespark/api/repository/RoleRepository.java @@ -0,0 +1,11 @@ +package de.dhbw.tinf22b6.codespark.api.repository; + +import de.dhbw.tinf22b6.codespark.api.model.Role; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; +import java.util.UUID; + +public interface RoleRepository extends JpaRepository { + Optional findByName(String name); +} diff --git a/src/main/java/de/dhbw/tinf22b6/codespark/api/security/JwtFilter.java b/src/main/java/de/dhbw/tinf22b6/codespark/api/security/JwtFilter.java index 4c136e1..acb17ec 100644 --- a/src/main/java/de/dhbw/tinf22b6/codespark/api/security/JwtFilter.java +++ b/src/main/java/de/dhbw/tinf22b6/codespark/api/security/JwtFilter.java @@ -20,7 +20,7 @@ import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; -import java.util.Collections; +import java.util.List; @Component public class JwtFilter extends OncePerRequestFilter { @@ -50,9 +50,12 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, Account account = accountRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("Username not found")); + List authorities = account.getRoles().stream() + .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName())) + .toList(); + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( - // Prefix with 'ROLE_' so 'hasRole()' can be used instead of 'hasAuthority()' - account, null, Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + account.getRole().name())) + account, null, authorities ); SecurityContext context = SecurityContextHolder.createEmptyContext(); diff --git a/src/main/java/de/dhbw/tinf22b6/codespark/api/security/WebSocketAuthInterceptor.java b/src/main/java/de/dhbw/tinf22b6/codespark/api/security/WebSocketAuthInterceptor.java index bcd927a..d71132a 100644 --- a/src/main/java/de/dhbw/tinf22b6/codespark/api/security/WebSocketAuthInterceptor.java +++ b/src/main/java/de/dhbw/tinf22b6/codespark/api/security/WebSocketAuthInterceptor.java @@ -16,7 +16,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; -import java.util.Collections; +import java.util.List; @Component public class WebSocketAuthInterceptor implements ChannelInterceptor { @@ -48,8 +48,12 @@ public Message preSend(@NonNull Message message, @NonNull MessageChannel c Account account = accountRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("Username not found")); + List authorities = account.getRoles().stream() + .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName())) + .toList(); + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( - account.getUsername(), null, Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + account.getRole().name())) + account.getUsername(), null, authorities ); SecurityContextHolder.getContext().setAuthentication(authToken); diff --git a/src/main/java/de/dhbw/tinf22b6/codespark/api/service/AccountServiceImpl.java b/src/main/java/de/dhbw/tinf22b6/codespark/api/service/AccountServiceImpl.java index 397a4f5..cf64988 100644 --- a/src/main/java/de/dhbw/tinf22b6/codespark/api/service/AccountServiceImpl.java +++ b/src/main/java/de/dhbw/tinf22b6/codespark/api/service/AccountServiceImpl.java @@ -2,13 +2,10 @@ import com.cloudinary.Cloudinary; import com.cloudinary.utils.ObjectUtils; -import de.dhbw.tinf22b6.codespark.api.common.UserRoleType; import de.dhbw.tinf22b6.codespark.api.common.VerificationTokenType; -import de.dhbw.tinf22b6.codespark.api.exception.AccountAlreadyExistsException; -import de.dhbw.tinf22b6.codespark.api.exception.ExpiredVerificationTokenException; -import de.dhbw.tinf22b6.codespark.api.exception.ImageUploadException; -import de.dhbw.tinf22b6.codespark.api.exception.InvalidVerificationTokenException; +import de.dhbw.tinf22b6.codespark.api.exception.*; import de.dhbw.tinf22b6.codespark.api.model.Account; +import de.dhbw.tinf22b6.codespark.api.model.Role; import de.dhbw.tinf22b6.codespark.api.model.VerificationToken; import de.dhbw.tinf22b6.codespark.api.payload.request.AccountCreateRequest; import de.dhbw.tinf22b6.codespark.api.payload.request.PasswordResetRequest; @@ -16,7 +13,9 @@ import de.dhbw.tinf22b6.codespark.api.payload.response.AccountDetailsResponse; import de.dhbw.tinf22b6.codespark.api.payload.response.UploadImageResponse; import de.dhbw.tinf22b6.codespark.api.repository.AccountRepository; +import de.dhbw.tinf22b6.codespark.api.repository.RoleRepository; import de.dhbw.tinf22b6.codespark.api.repository.VerificationTokenRepository; +import de.dhbw.tinf22b6.codespark.api.service.common.PredefinedUserRole; import de.dhbw.tinf22b6.codespark.api.service.interfaces.AccountService; import de.dhbw.tinf22b6.codespark.api.service.interfaces.EmailService; import org.springframework.beans.factory.annotation.Autowired; @@ -29,14 +28,13 @@ import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; +import java.util.*; @Service public class AccountServiceImpl implements AccountService { private final AccountRepository accountRepository; private final VerificationTokenRepository verificationTokenRepository; + private final RoleRepository roleRepository; private final EmailService emailService; private final PasswordEncoder passwordEncoder; private final Cloudinary cloudinary; @@ -44,12 +42,14 @@ public class AccountServiceImpl implements AccountService { public AccountServiceImpl(@Autowired AccountRepository accountRepository, @Autowired VerificationTokenRepository verificationTokenRepository, + @Autowired RoleRepository roleRepository, @Autowired EmailService emailService, @Autowired PasswordEncoder passwordEncoder, @Autowired Cloudinary cloudinary, @Autowired Environment env) { this.accountRepository = accountRepository; this.verificationTokenRepository = verificationTokenRepository; + this.roleRepository = roleRepository; this.emailService = emailService; this.passwordEncoder = passwordEncoder; this.cloudinary = cloudinary; @@ -77,10 +77,13 @@ public void createAccount(AccountCreateRequest request) { throw new AccountAlreadyExistsException("This username is already taken."); } + Role userRole = roleRepository.findByName(PredefinedUserRole.USER.getName()) + .orElseThrow(() -> new EntryNotFoundException("The requested role does not exists.")); + String encodedPassword = passwordEncoder.encode(request.getPassword()); LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC); - Account account = new Account(request.getUsername(), request.getEmail(), encodedPassword, - UserRoleType.USER, false, now, now); + Account account = new Account(request.getUsername(), request.getEmail(), encodedPassword, false, + Set.of(userRole), now, now); accountRepository.save(account); String token = UUID.randomUUID().toString(); diff --git a/src/main/java/de/dhbw/tinf22b6/codespark/api/service/common/PredefinedUserRole.java b/src/main/java/de/dhbw/tinf22b6/codespark/api/service/common/PredefinedUserRole.java new file mode 100644 index 0000000..a9b3757 --- /dev/null +++ b/src/main/java/de/dhbw/tinf22b6/codespark/api/service/common/PredefinedUserRole.java @@ -0,0 +1,13 @@ +package de.dhbw.tinf22b6.codespark.api.service.common; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum PredefinedUserRole { + USER("USER"), + ADMIN("ADMIN"); + + private final String name; +} diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 8a4675c..0a9eb8d 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -1,7 +1,2 @@ -spring: - jpa: - hibernate: - ddl-auto: none - app: base-url: "https://codespark-api.up.railway.app" diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 933e3b3..74ddc1d 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -9,6 +9,9 @@ spring: path: /h2-console liquibase: enabled: false + jpa: + hibernate: + ddl-auto: create-drop auth: jwt: diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e284909..0e3c687 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,7 +5,7 @@ spring: password: ${DATABASE_PASSWORD} jpa: hibernate: - ddl-auto: update + ddl-auto: none show-sql: false open-in-view: false liquibase: diff --git a/src/main/resources/db/changelog/changes/002_roles-refactor.yaml b/src/main/resources/db/changelog/changes/002_roles-refactor.yaml new file mode 100644 index 0000000..18eb83e --- /dev/null +++ b/src/main/resources/db/changelog/changes/002_roles-refactor.yaml @@ -0,0 +1,100 @@ +databaseChangeLog: + - changeSet: + id: 2 + author: denniskp + comment: "Remove old enum-based role column from account table" + changes: + - dropColumn: + tableName: account + columnName: role + + - changeSet: + id: 2.1 + author: denniskp + comment: "Create role table" + changes: + - createTable: + tableName: role + columns: + - column: + name: id + type: UUID + constraints: + primaryKey: true + - column: + name: name + type: VARCHAR(50) + constraints: + nullable: false + unique: true + - column: + name: description + type: VARCHAR(255) + constraints: + nullable: true + + - changeSet: + id: 2.2 + author: denniskp + comment: "Insert default USER and ADMIN roles" + changes: + - insert: + tableName: role + columns: + - column: { name: id, valueComputed: random_uuid() } + - column: { name: name, value: USER } + - column: { name: description, value: Default user role } + - insert: + tableName: role + columns: + - column: { name: id, valueComputed: random_uuid() } + - column: { name: name, value: ADMIN } + - column: { name: description, value: Administrator role } + + - changeSet: + id: 2.3 + author: denniskp + comment: "Create account_role join table" + changes: + - createTable: + tableName: account_role + columns: + - column: + name: account_id + type: UUID + constraints: + nullable: false + - column: + name: role_id + type: UUID + constraints: + nullable: false + - addForeignKeyConstraint: + baseTableName: account_role + baseColumnNames: account_id + referencedTableName: account + referencedColumnNames: id + onDelete: CASCADE + constraintName: fk_account_role_account + - addForeignKeyConstraint: + baseTableName: account_role + baseColumnNames: role_id + referencedTableName: role + referencedColumnNames: id + onDelete: CASCADE + constraintName: fk_account_role_role + + - changeSet: + id: 2.4 + author: denniskp + comment: "Assign USER role to all existing accounts" + changes: + - sql: + comment: "Insert USER role assignments into account_role" + splitStatements: false + sql: | + INSERT INTO account_role (account_id, role_id) + SELECT a.id, r.id + FROM account a + CROSS JOIN role r + WHERE r.name = 'USER'; diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 2f11b67..861a3eb 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -1,3 +1,5 @@ databaseChangeLog: - include: file: db/changelog/changes/001_add-account-timestamps.yaml + - include: + file: db/changelog/changes/002_roles-refactor.yaml diff --git a/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/AccountRepositoryTests.java b/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/AccountRepositoryTests.java index 5f2c0fd..4bbfea4 100644 --- a/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/AccountRepositoryTests.java +++ b/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/AccountRepositoryTests.java @@ -1,6 +1,5 @@ package de.dhbw.tinf22b6.codespark.api.repository; -import de.dhbw.tinf22b6.codespark.api.common.UserRoleType; import de.dhbw.tinf22b6.codespark.api.model.Account; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -8,6 +7,7 @@ import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; +import java.util.Collections; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -21,7 +21,7 @@ class AccountRepositoryTests { @Test void testFindByUsername_shouldReturnAccount() { Account account = new Account("john_doe", "john@example.com", "hashed_pwd", - UserRoleType.USER, true, LocalDateTime.now(), LocalDateTime.now()); + true, Collections.emptySet(), LocalDateTime.now(), LocalDateTime.now()); accountRepository.save(account); Optional result = accountRepository.findByUsername("john_doe"); @@ -33,7 +33,7 @@ void testFindByUsername_shouldReturnAccount() { @Test void findByEmail_shouldReturnAccount() { Account account = new Account("jane_doe", "jane@example.com", "hashed_pwd", - UserRoleType.USER, false, LocalDateTime.now(), LocalDateTime.now()); + false, Collections.emptySet(), LocalDateTime.now(), LocalDateTime.now()); accountRepository.save(account); Optional result = accountRepository.findByEmail("jane@example.com"); diff --git a/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/ConversationRepositoryTests.java b/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/ConversationRepositoryTests.java index 884672c..4d4be94 100644 --- a/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/ConversationRepositoryTests.java +++ b/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/ConversationRepositoryTests.java @@ -1,6 +1,5 @@ package de.dhbw.tinf22b6.codespark.api.repository; -import de.dhbw.tinf22b6.codespark.api.common.UserRoleType; import de.dhbw.tinf22b6.codespark.api.model.Account; import de.dhbw.tinf22b6.codespark.api.model.Conversation; import org.junit.jupiter.api.Test; @@ -9,6 +8,7 @@ import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; +import java.util.Collections; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -24,8 +24,8 @@ class ConversationRepositoryTests { @Test void testFindByAccount_shouldReturnConversation() { - Account account = new Account("user1", "user1@example.com", "password", UserRoleType.USER, - true, LocalDateTime.now(), LocalDateTime.now()); + Account account = new Account("user1", "user1@example.com", "password", + true, Collections.emptySet(), LocalDateTime.now(), LocalDateTime.now()); account = accountRepository.save(account); Conversation conversation = new Conversation(account); diff --git a/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/ExamDateRepositoryTests.java b/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/ExamDateRepositoryTests.java index a0a2a1f..5c3a13e 100644 --- a/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/ExamDateRepositoryTests.java +++ b/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/ExamDateRepositoryTests.java @@ -1,6 +1,5 @@ package de.dhbw.tinf22b6.codespark.api.repository; -import de.dhbw.tinf22b6.codespark.api.common.UserRoleType; import de.dhbw.tinf22b6.codespark.api.model.Account; import de.dhbw.tinf22b6.codespark.api.model.ExamDate; import org.junit.jupiter.api.Test; @@ -9,6 +8,7 @@ import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; +import java.util.Collections; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -24,8 +24,8 @@ class ExamDateRepositoryTests { @Test void testFindByAccount_shouldReturnExamDate() { - Account account = new Account("student1", "student1@example.com", "password", UserRoleType.USER, - true, LocalDateTime.now(), LocalDateTime.now()); + Account account = new Account("student1", "student1@example.com", "password", + true, Collections.emptySet(), LocalDateTime.now(), LocalDateTime.now()); account = accountRepository.save(account); ExamDate examDate = new ExamDate(LocalDateTime.of(2025, 7, 15, 10, 0)); diff --git a/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/UserBadgeRepositoryTests.java b/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/UserBadgeRepositoryTests.java index 6319a4e..9dc14d4 100644 --- a/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/UserBadgeRepositoryTests.java +++ b/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/UserBadgeRepositoryTests.java @@ -1,7 +1,6 @@ package de.dhbw.tinf22b6.codespark.api.repository; import de.dhbw.tinf22b6.codespark.api.common.BadgeType; -import de.dhbw.tinf22b6.codespark.api.common.UserRoleType; import de.dhbw.tinf22b6.codespark.api.model.Account; import de.dhbw.tinf22b6.codespark.api.model.Badge; import de.dhbw.tinf22b6.codespark.api.model.UserBadge; @@ -11,6 +10,7 @@ import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; +import java.util.Collections; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -29,8 +29,8 @@ class UserBadgeRepositoryTests { @Test void testFindByAccountAndBadge_shouldReturnUserBadge() { - Account account = new Account("user1", "user1@example.com", "secret", UserRoleType.USER, - true, LocalDateTime.now(), LocalDateTime.now()); + Account account = new Account("user1", "user1@example.com", "secret", + true, Collections.emptySet(), LocalDateTime.now(), LocalDateTime.now()); account = accountRepository.save(account); Badge badge = new Badge("First Win", "Earned after completing a lesson", BadgeType.FIRST_LESSON_COMPLETED, "icon.png"); @@ -47,8 +47,8 @@ void testFindByAccountAndBadge_shouldReturnUserBadge() { @Test void testExistsByAccountAndBadge_shouldReturnTrue() { - Account account = new Account("user2", "user2@example.com", "pass", UserRoleType.USER, - true, LocalDateTime.now(), LocalDateTime.now()); + Account account = new Account("user2", "user2@example.com", "pass", + true, Collections.emptySet(), LocalDateTime.now(), LocalDateTime.now()); account = accountRepository.save(account); Badge badge = new Badge("Achiever", "Earned after completing a chapter", BadgeType.FIRST_CHAPTER_COMPLETED, "icon2.png"); @@ -64,8 +64,8 @@ void testExistsByAccountAndBadge_shouldReturnTrue() { @Test void testExistsByAccountAndBadge_shouldReturnFalseWhenNotExists() { - Account account = accountRepository.save(new Account("user3", "user3@example.com", "pw", UserRoleType.USER, - true, LocalDateTime.now(), LocalDateTime.now())); + Account account = accountRepository.save(new Account("user3", "user3@example.com", "pw", + true, Collections.emptySet(), LocalDateTime.now(), LocalDateTime.now())); Badge badge = badgeRepository.save(new Badge("Unobtainable", "Never awarded", BadgeType.ALL_CHAPTERS_COMPLETED, "icon3.png")); boolean exists = userBadgeRepository.existsByAccountAndBadge(account, badge); diff --git a/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/UserLessonProgressRepositoryTests.java b/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/UserLessonProgressRepositoryTests.java index e099fc4..16e7238 100644 --- a/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/UserLessonProgressRepositoryTests.java +++ b/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/UserLessonProgressRepositoryTests.java @@ -2,7 +2,6 @@ import de.dhbw.tinf22b6.codespark.api.common.LessonProgressState; import de.dhbw.tinf22b6.codespark.api.common.LessonType; -import de.dhbw.tinf22b6.codespark.api.common.UserRoleType; import de.dhbw.tinf22b6.codespark.api.model.Account; import de.dhbw.tinf22b6.codespark.api.model.Chapter; import de.dhbw.tinf22b6.codespark.api.model.TheoryLesson; @@ -13,6 +12,7 @@ import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -35,8 +35,8 @@ class UserLessonProgressRepositoryTests { @Test void testFindByAccountAndLesson_shouldReturnProgress() { - Account account = accountRepository.save(new Account("testUser", "user@example.com", "pw", UserRoleType.USER, - true, LocalDateTime.now(), LocalDateTime.now())); + Account account = accountRepository.save(new Account("testUser", "user@example.com", "pw", + true, Collections.emptySet(), LocalDateTime.now(), LocalDateTime.now())); Chapter chapter = chapterRepository.save(new Chapter("Chapter 1", "Intro", null, null)); TheoryLesson lesson = lessonRepository.save(new TheoryLesson("L1", "Theory", LessonType.THEORY,chapter, null, null, "Sample text")); @@ -50,8 +50,8 @@ void testFindByAccountAndLesson_shouldReturnProgress() { @Test void testFindByAccountAndState_shouldReturnCorrectList() { - Account account = accountRepository.save(new Account("testUser2", "user2@example.com", "pw", UserRoleType.USER, - true, LocalDateTime.now(), LocalDateTime.now())); + Account account = accountRepository.save(new Account("testUser2", "user2@example.com", "pw", + true, Collections.emptySet(), LocalDateTime.now(), LocalDateTime.now())); Chapter chapter = chapterRepository.save(new Chapter("Chapter 2", "Intro 2", null, null)); TheoryLesson lesson1 = lessonRepository.save(new TheoryLesson("L2", "Theory 2", LessonType.THEORY, chapter, null, null, "Text 1")); @@ -68,8 +68,8 @@ void testFindByAccountAndState_shouldReturnCorrectList() { @Test void testFindByAccountAndLesson_shouldReturnEmptyWhenNone() { - Account account = accountRepository.save(new Account("user3", "user3@example.com", "pw", UserRoleType.USER, - true, LocalDateTime.now(), LocalDateTime.now())); + Account account = accountRepository.save(new Account("user3", "user3@example.com", "pw", + true, Collections.emptySet(), LocalDateTime.now(), LocalDateTime.now())); Chapter chapter = chapterRepository.save(new Chapter("Chapter X", "X", null, null)); TheoryLesson lesson = lessonRepository.save(new TheoryLesson("L4", "No progress", LessonType.THEORY, chapter, null, null, "Empty")); diff --git a/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/VerificationTokenRepositoryTests.java b/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/VerificationTokenRepositoryTests.java index 2d6ab5e..f0524cc 100644 --- a/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/VerificationTokenRepositoryTests.java +++ b/src/test/java/de/dhbw/tinf22b6/codespark/api/repository/VerificationTokenRepositoryTests.java @@ -10,6 +10,7 @@ import java.time.Instant; import java.time.LocalDateTime; +import java.util.Collections; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -25,8 +26,8 @@ class VerificationTokenRepositoryTests { @Test void testFindByTokenAndType_shouldReturnToken() { - Account account = new Account("user1", "test@example.com", "pass", null, - true, LocalDateTime.now(), LocalDateTime.now()); + Account account = new Account("user1", "test@example.com", "pass", + true, Collections.emptySet(), LocalDateTime.now(), LocalDateTime.now()); accountRepository.save(account); VerificationToken token = new VerificationToken( diff --git a/src/test/java/de/dhbw/tinf22b6/codespark/api/service/AccountServiceImplTests.java b/src/test/java/de/dhbw/tinf22b6/codespark/api/service/AccountServiceImplTests.java index e4ec400..d21f5a7 100644 --- a/src/test/java/de/dhbw/tinf22b6/codespark/api/service/AccountServiceImplTests.java +++ b/src/test/java/de/dhbw/tinf22b6/codespark/api/service/AccountServiceImplTests.java @@ -2,13 +2,13 @@ import com.cloudinary.Cloudinary; import com.cloudinary.Uploader; -import de.dhbw.tinf22b6.codespark.api.common.UserRoleType; import de.dhbw.tinf22b6.codespark.api.common.VerificationTokenType; import de.dhbw.tinf22b6.codespark.api.exception.AccountAlreadyExistsException; import de.dhbw.tinf22b6.codespark.api.exception.ExpiredVerificationTokenException; import de.dhbw.tinf22b6.codespark.api.exception.ImageUploadException; import de.dhbw.tinf22b6.codespark.api.exception.InvalidVerificationTokenException; import de.dhbw.tinf22b6.codespark.api.model.Account; +import de.dhbw.tinf22b6.codespark.api.model.Role; import de.dhbw.tinf22b6.codespark.api.model.VerificationToken; import de.dhbw.tinf22b6.codespark.api.payload.request.AccountCreateRequest; import de.dhbw.tinf22b6.codespark.api.payload.request.PasswordResetRequest; @@ -16,7 +16,9 @@ import de.dhbw.tinf22b6.codespark.api.payload.response.AccountDetailsResponse; import de.dhbw.tinf22b6.codespark.api.payload.response.UploadImageResponse; import de.dhbw.tinf22b6.codespark.api.repository.AccountRepository; +import de.dhbw.tinf22b6.codespark.api.repository.RoleRepository; import de.dhbw.tinf22b6.codespark.api.repository.VerificationTokenRepository; +import de.dhbw.tinf22b6.codespark.api.service.common.PredefinedUserRole; import de.dhbw.tinf22b6.codespark.api.service.interfaces.AccountService; import de.dhbw.tinf22b6.codespark.api.service.interfaces.EmailService; import org.junit.jupiter.api.BeforeEach; @@ -28,6 +30,7 @@ import java.io.IOException; import java.time.Instant; import java.time.LocalDateTime; +import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -39,6 +42,7 @@ class AccountServiceImplTests { private AccountRepository accountRepository; private VerificationTokenRepository verificationTokenRepository; + private RoleRepository roleRepository; private EmailService emailService; private PasswordEncoder passwordEncoder; private Cloudinary cloudinary; @@ -50,6 +54,7 @@ class AccountServiceImplTests { void setUp() { accountRepository = mock(AccountRepository.class); verificationTokenRepository = mock(VerificationTokenRepository.class); + roleRepository = mock(RoleRepository.class); emailService = mock(EmailService.class); passwordEncoder = mock(PasswordEncoder.class); cloudinary = mock(Cloudinary.class); @@ -57,15 +62,15 @@ void setUp() { file = mock(MultipartFile.class); accountService = new AccountServiceImpl( - accountRepository, verificationTokenRepository, emailService, - passwordEncoder, cloudinary, env + accountRepository, verificationTokenRepository, roleRepository, + emailService, passwordEncoder, cloudinary, env ); } @Test void getAccountDetails_shouldReturnCorrectResponse() { Account account = new Account("user", "user@example.com", "pass", - UserRoleType.USER, true, LocalDateTime.now(), LocalDateTime.now()); + true, Collections.emptySet(), LocalDateTime.now(), LocalDateTime.now()); account.setId(UUID.randomUUID()); account.setProfileImageUrl("http://image.url"); @@ -78,9 +83,12 @@ void getAccountDetails_shouldReturnCorrectResponse() { @Test void createAccount_shouldCreateNewAccount() { + Role userRole = new Role(PredefinedUserRole.USER.getName(), "User"); + AccountCreateRequest request = new AccountCreateRequest("user", "user@example.com", "password"); when(accountRepository.findByEmail("user@example.com")).thenReturn(Optional.empty()); when(accountRepository.findByUsername("user")).thenReturn(Optional.empty()); + when(roleRepository.findByName(any(String.class))).thenReturn(Optional.of(userRole)); when(passwordEncoder.encode("password")).thenReturn("hashed"); when(env.getRequiredProperty(any(), eq(Long.class))).thenReturn(1000L); diff --git a/src/test/java/de/dhbw/tinf22b6/codespark/api/service/AuthServiceImplTests.java b/src/test/java/de/dhbw/tinf22b6/codespark/api/service/AuthServiceImplTests.java index 2c04ad3..3039cd1 100644 --- a/src/test/java/de/dhbw/tinf22b6/codespark/api/service/AuthServiceImplTests.java +++ b/src/test/java/de/dhbw/tinf22b6/codespark/api/service/AuthServiceImplTests.java @@ -1,6 +1,5 @@ package de.dhbw.tinf22b6.codespark.api.service; -import de.dhbw.tinf22b6.codespark.api.common.UserRoleType; import de.dhbw.tinf22b6.codespark.api.exception.InvalidAccountCredentialsException; import de.dhbw.tinf22b6.codespark.api.exception.InvalidRefreshTokenException; import de.dhbw.tinf22b6.codespark.api.exception.UnverifiedAccountException; @@ -15,6 +14,7 @@ import org.junit.jupiter.api.Test; import org.springframework.security.crypto.password.PasswordEncoder; +import java.util.Collections; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -44,7 +44,7 @@ void loginAccount_shouldReturnTokenResponse_whenCredentialsAreValid() { account.setUsername("testuser"); account.setPassword("encoded"); account.setVerified(true); - account.setRole(UserRoleType.USER); + account.setRoles(Collections.emptySet()); when(accountRepository.findByUsername("testuser")).thenReturn(Optional.of(account)); when(passwordEncoder.matches("password", "encoded")).thenReturn(true);