From fb81d88894fdae7a3efc94661682b8e6bcadcfed Mon Sep 17 00:00:00 2001
From: zkhr
Date: Mon, 15 Sep 2025 12:40:24 +0200
Subject: [PATCH 01/11] added update method
---
.../dto/UpdateLinkExpirationRequestDto.java | 24 +++++++++
.../InvalidExpirationDateException.java | 19 +++++++
.../api/security/config/SecurityConfig.java | 1 +
.../api/service/LinkService.java | 26 ++++++++++
.../api/v1/controller/LinkCrudController.java | 34 ++++++++----
.../controller/LinkCrudControllerTest.java | 31 +++++++++++
.../api/service/LinkServiceTest.java | 52 +++++++++++++++----
7 files changed, 167 insertions(+), 20 deletions(-)
create mode 100644 src/main/java/org/decepticons/linkshortener/api/dto/UpdateLinkExpirationRequestDto.java
create mode 100644 src/main/java/org/decepticons/linkshortener/api/exception/InvalidExpirationDateException.java
diff --git a/src/main/java/org/decepticons/linkshortener/api/dto/UpdateLinkExpirationRequestDto.java b/src/main/java/org/decepticons/linkshortener/api/dto/UpdateLinkExpirationRequestDto.java
new file mode 100644
index 0000000..d67cb51
--- /dev/null
+++ b/src/main/java/org/decepticons/linkshortener/api/dto/UpdateLinkExpirationRequestDto.java
@@ -0,0 +1,24 @@
+package org.decepticons.linkshortener.api.dto;
+
+import java.time.Instant;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.Setter;
+
+
+/**
+ * Data Transfer Object (DTO) for updating the expiration date of a shortened link.
+ * This object is used to encapsulate the data required to update the expiration
+ * date of an existing short link in the system.
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+public class UpdateLinkExpirationRequestDto {
+
+ @NonNull
+ private Instant newExpirationDate;
+
+
+}
diff --git a/src/main/java/org/decepticons/linkshortener/api/exception/InvalidExpirationDateException.java b/src/main/java/org/decepticons/linkshortener/api/exception/InvalidExpirationDateException.java
new file mode 100644
index 0000000..bfca945
--- /dev/null
+++ b/src/main/java/org/decepticons/linkshortener/api/exception/InvalidExpirationDateException.java
@@ -0,0 +1,19 @@
+package org.decepticons.linkshortener.api.exception;
+
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+import java.time.Instant;
+
+@ResponseStatus(HttpStatus.BAD_REQUEST)
+@Getter
+public class InvalidExpirationDateException extends RuntimeException {
+ private final Instant invalidDate;
+
+ public InvalidExpirationDateException(String message, Instant invalidDate) {
+ super(message);
+ this.invalidDate = invalidDate;
+ }
+
+}
diff --git a/src/main/java/org/decepticons/linkshortener/api/security/config/SecurityConfig.java b/src/main/java/org/decepticons/linkshortener/api/security/config/SecurityConfig.java
index b2db1c1..def7356 100644
--- a/src/main/java/org/decepticons/linkshortener/api/security/config/SecurityConfig.java
+++ b/src/main/java/org/decepticons/linkshortener/api/security/config/SecurityConfig.java
@@ -61,6 +61,7 @@ public SecurityFilterChain securityFilterChain(
"/api/links/**",
// Documentation & health endpoints
+ "/api/v1/cache",
"/health",
"/h2-console/**",
"/swagger-ui.html",
diff --git a/src/main/java/org/decepticons/linkshortener/api/service/LinkService.java b/src/main/java/org/decepticons/linkshortener/api/service/LinkService.java
index ca97fc4..8fd4e96 100644
--- a/src/main/java/org/decepticons/linkshortener/api/service/LinkService.java
+++ b/src/main/java/org/decepticons/linkshortener/api/service/LinkService.java
@@ -7,6 +7,7 @@
import org.decepticons.linkshortener.api.dto.LinkResponseDto;
import org.decepticons.linkshortener.api.dto.UrlRequestDto;
+import org.decepticons.linkshortener.api.exception.InvalidExpirationDateException;
import org.decepticons.linkshortener.api.exception.NoSuchShortLinkFoundInTheSystemException;
import org.decepticons.linkshortener.api.exception.NoSuchUserFoundInTheSystemException;
import org.decepticons.linkshortener.api.model.Link;
@@ -268,5 +269,30 @@ public User getCurrentUser() {
username
));
}
+
+ @CachePut(value = "shortLinksCache", key = "#code")
+ public LinkResponseDto updateLinkExpiration(String code, Instant newExpirationDate) {
+ Link link = linkRepository.findByCode(code)
+ .orElseThrow(() -> new NoSuchShortLinkFoundInTheSystemException(
+ "No such short link found in the system: " + code,
+ code
+ ));
+
+ if (!link.getOwner().getId().equals(getCurrentUserId())) {
+ throw new AccessDeniedException("You are not allowed to update this link");
+ }
+
+ if (newExpirationDate.isBefore(Instant.now())) {
+ throw new InvalidExpirationDateException(
+ "Expiration date must be in the future",
+ newExpirationDate
+ );
+ }
+
+ link.setExpiresAt(newExpirationDate);
+ linkRepository.save(link);
+
+ return mapToResponse(link);
+ }
}
diff --git a/src/main/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudController.java b/src/main/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudController.java
index f59240a..35658d6 100644
--- a/src/main/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudController.java
+++ b/src/main/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudController.java
@@ -1,26 +1,20 @@
package org.decepticons.linkshortener.api.v1.controller;
import jakarta.validation.Valid;
+
import java.util.UUID;
import org.decepticons.linkshortener.api.dto.LinkResponseDto;
+import org.decepticons.linkshortener.api.dto.UpdateLinkExpirationRequestDto;
import org.decepticons.linkshortener.api.dto.UrlRequestDto;
import org.decepticons.linkshortener.api.model.User;
import org.decepticons.linkshortener.api.service.LinkService;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
/**
* REST controller for managing short links.
-
* Provides endpoints for creating, retrieving, and deleting short links.
* All operations are performed in the context of the currently authenticated user.
*/
@@ -34,8 +28,7 @@ public class LinkCrudController {
/**
* Constructs a new {@link LinkCrudController} with the given dependencies.
*
- * @param linkService the service responsible for link business logic
-
+ * @param linkService the service responsible for link business logic
*/
public LinkCrudController(LinkService linkService) {
this.linkService = linkService;
@@ -98,4 +91,23 @@ public ResponseEntity deleteLink(@PathVariable UUID id) {
linkService.deleteLink(id);
return ResponseEntity.noContent().build();
}
+
+
+
+ /**
+ * Updates the expiration date of a specific link identified by its code.
+ *
+ * @param newExpirationDate DTO containing the new expiration date
+ * @param code the short URL code
+ * @return DTO with information about the updated link
+ */
+ @PatchMapping("/{code}")
+ public ResponseEntity updateLinkExpiration(
+ @Valid @RequestBody UpdateLinkExpirationRequestDto newExpirationDate,
+ @PathVariable String code) {
+
+ return ResponseEntity
+ .ok(linkService.updateLinkExpiration(code, newExpirationDate.getNewExpirationDate()));
+ }
+
}
diff --git a/src/test/java/org/decepticons/linkshortener/api/controller/LinkCrudControllerTest.java b/src/test/java/org/decepticons/linkshortener/api/controller/LinkCrudControllerTest.java
index 3989b21..a7c4f21 100644
--- a/src/test/java/org/decepticons/linkshortener/api/controller/LinkCrudControllerTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/controller/LinkCrudControllerTest.java
@@ -2,6 +2,7 @@
import org.decepticons.linkshortener.api.dto.LinkResponseDto;
+import org.decepticons.linkshortener.api.dto.UpdateLinkExpirationRequestDto;
import org.decepticons.linkshortener.api.dto.UrlRequestDto;
import org.decepticons.linkshortener.api.exception.NoSuchUserFoundInTheSystemException;
import org.decepticons.linkshortener.api.model.User;
@@ -228,4 +229,34 @@ void testDeleteLink_SUCCESS() {
verify(linkService, times(1)).deleteLink(linkId);
}
+ @Test
+ @DisplayName("Update Link Expiration Date - Success")
+ void testUpdateLinkExpirationDate(){
+
+ String code = "abc123";
+
+ UpdateLinkExpirationRequestDto requestDto
+ = new UpdateLinkExpirationRequestDto(Instant.now().plus(5, ChronoUnit.DAYS));
+
+
+ when(linkService.updateLinkExpiration(code, requestDto.getNewExpirationDate()))
+ .thenReturn(new LinkResponseDto(
+ UUID.randomUUID(),
+ code,
+ "https://example.com/some/long/url",
+ Instant.now(),
+ requestDto.getNewExpirationDate(),
+ 0,
+ "ACTIVE",
+ UUID.randomUUID()
+ ));
+
+ ResponseEntity response = linkController.updateLinkExpiration(requestDto, code);
+
+ assertEquals(HttpStatus.OK, response.getStatusCode());
+ assertNotNull(response.getBody());
+ assertEquals(code, response.getBody().code());
+ verify(linkService, times(1))
+ .updateLinkExpiration(code, requestDto.getNewExpirationDate());
+ }
}
diff --git a/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceTest.java b/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceTest.java
index d285813..ab6e4e6 100644
--- a/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceTest.java
@@ -15,6 +15,8 @@
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -27,28 +29,25 @@
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class)
class LinkServiceTest {
@Mock
private LinkRepository linkRepository;
- @InjectMocks
- private LinkService linkService;
+
@Mock
private UserRepository userRepository;
+ @InjectMocks
+ private LinkService linkService;
+
+
- @BeforeEach
- void setUp() {
- linkRepository = mock(LinkRepository.class);
- linkService = new LinkService(linkRepository, null, null);
- }
@Test
@DisplayName("Link Creation - Success")
@@ -203,4 +202,39 @@ void validateLink_Success() {
Assertions.assertTrue(linkService.validateLink(linkResponseDto));
}
+
+ @Test
+ @DisplayName("Update Link Expiration - Success")
+ void testUpdateLinkExpiration(){
+ String code = "abc123";
+ User owner = new User();
+ owner.setUsername("testUser");
+ owner.setId(UUID.randomUUID());
+
+ Link link = new Link();
+ link.setCode(code);
+ link.setExpiresAt(Instant.now().plusSeconds(3600));
+ link.setStatus(LinkStatus.ACTIVE);
+ link.setOwner(owner);
+
+ Authentication auth = mock(Authentication.class);
+ SecurityContext securityContext = mock(SecurityContext.class);
+ when(securityContext.getAuthentication()).thenReturn(auth);
+ when(auth.getName()).thenReturn("testUser");
+ SecurityContextHolder.setContext(securityContext);
+
+ when(linkRepository.findByCode(code)).thenReturn(Optional.of(link));
+ when(linkRepository.save(any(Link.class))).thenAnswer(i -> i.getArguments()[0]);
+ when(userRepository.findByUsername("testUser")).thenReturn(Optional.of(owner));
+
+ LinkResponseDto response = linkService.updateLinkExpiration(code, Instant
+ .now()
+ .plusSeconds(7200));
+
+ assertNotNull(response);
+ assertEquals(code, response.code());
+ assertEquals(link.getExpiresAt(), response.expiresAt());
+ verify(linkRepository, times(1)).findByCode(code);
+
+ }
}
From 1e1094d5d9afeb9763c007a97d0acfa7ea4d2328 Mon Sep 17 00:00:00 2001
From: zkhr
Date: Mon, 15 Sep 2025 12:47:16 +0200
Subject: [PATCH 02/11] fixed swagger for LinkCrudController
---
.../api/v1/controller/LinkCrudController.java | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/main/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudController.java b/src/main/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudController.java
index 35658d6..e1e55bf 100644
--- a/src/main/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudController.java
+++ b/src/main/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudController.java
@@ -1,5 +1,7 @@
package org.decepticons.linkshortener.api.v1.controller;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.UUID;
@@ -18,6 +20,7 @@
* Provides endpoints for creating, retrieving, and deleting short links.
* All operations are performed in the context of the currently authenticated user.
*/
+@Tag(name = "Link Management", description = "Operations for managing short links")
@RestController
@RequestMapping("/api/v1/links")
public class LinkCrudController {
@@ -42,6 +45,7 @@ public LinkCrudController(LinkService linkService) {
* @return DTO with information about the created short URL
*/
@PostMapping
+ @Operation(summary = "Create a short URL for the current user")
public ResponseEntity createLink(@Valid @RequestBody UrlRequestDto originalUrl) {
User user = linkService.getCurrentUser();
@@ -58,6 +62,7 @@ public ResponseEntity createLink(@Valid @RequestBody UrlRequest
* @return page of LinkResponseDto
*/
@GetMapping("/my_all_links")
+ @Operation(summary = "Get all links (active and inactive) for the current user")
public ResponseEntity> getAllMyLinks(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
@@ -73,6 +78,7 @@ public ResponseEntity> getAllMyLinks(
* @return page of LinkResponseDto
*/
@GetMapping("/my_all_active_links")
+ @Operation(summary = "Get all active links for the current user")
public ResponseEntity> getAllMyActiveLinks(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
@@ -87,6 +93,7 @@ public ResponseEntity> getAllMyActiveLinks(
* @return HTTP 204 No Content if deletion was successful
*/
@DeleteMapping("/delete/{id}")
+ @Operation(summary = "Delete a specific link of the current user by its ID")
public ResponseEntity deleteLink(@PathVariable UUID id) {
linkService.deleteLink(id);
return ResponseEntity.noContent().build();
@@ -102,6 +109,7 @@ public ResponseEntity deleteLink(@PathVariable UUID id) {
* @return DTO with information about the updated link
*/
@PatchMapping("/{code}")
+ @Operation(summary = "Update the expiration date of a specific link by its code")
public ResponseEntity updateLinkExpiration(
@Valid @RequestBody UpdateLinkExpirationRequestDto newExpirationDate,
@PathVariable String code) {
From d0d350955485ebb61b406845c1340da7b7ed5efa Mon Sep 17 00:00:00 2001
From: zkhr
Date: Mon, 15 Sep 2025 15:39:26 +0200
Subject: [PATCH 03/11] improved exception handling and reduced dto classes
---
.../GlobalExceptionHandlerController.java} | 72 ++++++++++++++++---
.../api/dto/ApiErrorResponseDto.java | 19 +++++
.../api/dto/NoSuchLinkFoundResponseDto.java | 26 -------
.../api/dto/NoSuchUserFoundResponseDto.java | 26 -------
.../dto/ShortLinkOutOfDateResponseDto.java | 21 ------
.../BaseException.java | 2 +-
...tomizedResponseEntityExceptionHandler.java | 70 ------------------
.../ExpiredTokenException.java | 4 +-
.../InvalidPasswordException.java | 4 +-
.../InvalidTokenException.java | 4 +-
.../UserAlreadyExistsException.java | 4 +-
.../UserNotFoundException.java | 4 +-
.../package-info.java | 2 +-
.../security/jwt/JwtAuthenticationFilter.java | 2 +-
.../service/impl/AuthServiceImpl.java | 4 +-
.../service/impl/UserAuthServiceImpl.java | 6 +-
.../GlobalExceptionHandlerTest.java | 4 +-
.../jwt/JwtAuthenticationFilterTest.java | 2 +-
.../service/impl/AuthServiceImplTest.java | 8 +--
.../service/impl/UserAuthServiceImplTest.java | 6 +-
20 files changed, 114 insertions(+), 176 deletions(-)
rename src/main/java/org/decepticons/linkshortener/api/{exceptions/GlobalExceptionHandler.java => controller/GlobalExceptionHandlerController.java} (61%)
create mode 100644 src/main/java/org/decepticons/linkshortener/api/dto/ApiErrorResponseDto.java
delete mode 100644 src/main/java/org/decepticons/linkshortener/api/dto/NoSuchLinkFoundResponseDto.java
delete mode 100644 src/main/java/org/decepticons/linkshortener/api/dto/NoSuchUserFoundResponseDto.java
delete mode 100644 src/main/java/org/decepticons/linkshortener/api/dto/ShortLinkOutOfDateResponseDto.java
rename src/main/java/org/decepticons/linkshortener/api/{exceptions => exception}/BaseException.java (92%)
delete mode 100644 src/main/java/org/decepticons/linkshortener/api/exception/CustomizedResponseEntityExceptionHandler.java
rename src/main/java/org/decepticons/linkshortener/api/{exceptions => exception}/ExpiredTokenException.java (84%)
rename src/main/java/org/decepticons/linkshortener/api/{exceptions => exception}/InvalidPasswordException.java (75%)
rename src/main/java/org/decepticons/linkshortener/api/{exceptions => exception}/InvalidTokenException.java (84%)
rename src/main/java/org/decepticons/linkshortener/api/{exceptions => exception}/UserAlreadyExistsException.java (78%)
rename src/main/java/org/decepticons/linkshortener/api/{exceptions => exception}/UserNotFoundException.java (76%)
rename src/main/java/org/decepticons/linkshortener/api/{exceptions => exception}/package-info.java (57%)
diff --git a/src/main/java/org/decepticons/linkshortener/api/exceptions/GlobalExceptionHandler.java b/src/main/java/org/decepticons/linkshortener/api/controller/GlobalExceptionHandlerController.java
similarity index 61%
rename from src/main/java/org/decepticons/linkshortener/api/exceptions/GlobalExceptionHandler.java
rename to src/main/java/org/decepticons/linkshortener/api/controller/GlobalExceptionHandlerController.java
index f9a9391..f9519d5 100644
--- a/src/main/java/org/decepticons/linkshortener/api/exceptions/GlobalExceptionHandler.java
+++ b/src/main/java/org/decepticons/linkshortener/api/controller/GlobalExceptionHandlerController.java
@@ -1,19 +1,32 @@
-package org.decepticons.linkshortener.api.exceptions;
+package org.decepticons.linkshortener.api.controller;
import java.time.Instant;
import java.util.Map;
+
+import org.decepticons.linkshortener.api.exception.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
-/**
- * Centralized exception handler for REST controllers.
- */
@ControllerAdvice
-public class GlobalExceptionHandler {
+public class GlobalExceptionHandlerController {
private ResponseEntity
+ * Retrieves a link by its unique short code.
*
- * @param link the link whose click counter should be incremented
+ * @param code the unique short code of the link
+ * @return the LinkResponseDto representing the retrieved link
*/
- @Transactional
- @CachePut(value = "shortLinksCache", key = "#link.code")
- public LinkResponseDto incrementClicks(LinkResponseDto link) {
- Link linkByCode = linkRepository.findByCode(link.code())
- .orElseThrow(() -> new NoSuchShortLinkFoundInTheSystemException(
- "No such short link found in the system: " + link.code(),
- link.code()
- ));
- linkByCode.incrementClicks();
- linkRepository.save(linkByCode);
-
- return mapToResponse(linkByCode);
- }
-
+ LinkResponseDto getLinkByCode(String code);
/**
- * Maps a {@link Link} JPA entity to a transport-friendly {@link LinkResponseDto}.
+ * Deactivates the specified link, preventing further access.
*
- * @param link the entity to map
- * @return a response DTO with the most relevant fields
+ * @param link the LinkResponseDto representing the link to deactivate
+ * @return the updated LinkResponseDto with deactivated status
*/
- public LinkResponseDto mapToResponse(Link link) {
- return new LinkResponseDto(
-
- link.getId(),
- link.getCode(),
- link.getOriginalUrl(),
- link.getCreatedAt(),
- link.getExpiresAt(),
- link.getClicks(),
- link.getStatus().name(),
- link.getOwner().getId()
- );
- }
-
+ LinkResponseDto deactivateLink(LinkResponseDto link);
/**
- * Generates a pseudo-random short code of fixed length .
+ * Validates whether the specified link is active and not expired.
*
- * @return a new short code (e.g., {@code "aZ3fQ1"})
+ * @param link the LinkResponseDto representing the link to validate
+ * @return true if the link is valid (active and not expired), false otherwise
*/
- private String generateRandomCode() {
- String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < 6; i++) {
- sb.append(chars.charAt(random.nextInt(chars.length())));
- }
- return sb.toString();
- }
-
+ boolean validateLink(LinkResponseDto link);
/**
- * Retrieves a {@link Link} entity by its short code.
+ * Retrieves a paginated list of all links created by the currently authenticated user.
*
- * @param code short link code.
- * @return Optional
+ * @param page the page number to retrieve
+ * @param size the number of links per page
+ * @return a Page of LinkResponseDto representing the user's links
*/
- @Cacheable(value = "shortLinksCache", key = "#code")
- public LinkResponseDto getLinkByCode(String code) {
- Link link = linkRepository.findByCode(code)
- .orElseThrow(() -> new NoSuchShortLinkFoundInTheSystemException(
- "No such short link found in the system: " + code,
- code
- ));
-
- return mapToResponse(link);
-
- }
-
+ Page getAllMyLinks(int page, int size);
/**
- * Deactivates a link by setting its status to INACTIVE.
+ * Retrieves a paginated list of all active links created by the currently authenticated user.
*
- * @param link the link to deactivate
- * @return the updated {@link LinkResponseDto} with status set to INACTIVE
+ * @param page the page number to retrieve
+ * @param size the number of links per page
+ * @return a Page of LinkResponseDto representing the user's active links
*/
- @CachePut(value = "shortLinksCache", key = "#link.code")
- public LinkResponseDto deactivateLink(LinkResponseDto link) {
- Link linkByCode = linkRepository.findByCode(link.code())
- .orElseThrow(() -> new NoSuchShortLinkFoundInTheSystemException(
- "No such short link found in the system: " + link.code(),
- link.code()
- ));
- linkByCode.setStatus(LinkStatus.INACTIVE);
- linkRepository.save(linkByCode);
- return mapToResponse(linkByCode);
- }
-
+ Page getAllMyActiveLinks(int page, int size);
/**
- * Validates if a link is active and not expired.
+ * Deletes a link by its unique identifier.
*
- * @param link the link to validate
- * @return {@code true} if the link is active and not expired; {@code false} otherwise
+ * @param linkId the UUID of the link to delete
+ * @return a confirmation message indicating the result of the deletion
*/
- public boolean validateLink(LinkResponseDto link) {
- return link.status().equalsIgnoreCase(LinkStatus.ACTIVE.toString())
- && (link.expiresAt() == null || link.expiresAt().isAfter(Instant.now()));
- }
-
+ String deleteLink(UUID linkId);
/**
- * Retrieves all links of the currently authenticated user with pagination.
+ * Updates the expiration date of a link identified by its short code.
*
- * @param page the page number to retrieve (0-based)
- * @param size the number of records per page
- * @return a {@link Page} of {@link LinkResponseDto} objects representing all user's links
+ * @param code the unique short code of the link to update
+ * @param newExpirationDate the new expiration date to set
+ * @return the updated LinkResponseDto with the new expiration date
*/
- public Page getAllMyLinks(int page, int size) {
- UUID userId = userService.getCurrentUserId();
- Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
- return linkRepository.findAllByOwnerId(userId, pageable)
- .map(this::mapToResponse);
- }
-
- /**
- * Retrieves all active links of the currently authenticated user with pagination.
- *
- * @param page the page number to retrieve (0-based)
- * @param size the number of records per page
- * @return a {@link Page} of {@link LinkResponseDto} objects representing active user's links
- */
- public Page getAllMyActiveLinks(int page, int size) {
- UUID userId = userService.getCurrentUserId();
- Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
- return linkRepository.findAllByOwnerIdAndStatus(userId, LinkStatus.ACTIVE, pageable)
- .map(this::mapToResponse);
- }
-
- /**
- * Deletes a link from the database if it belongs to the currently authenticated user.
- *
- * @param linkId the unique identifier of the link to delete
- */
- @Transactional
- public void deleteLink(UUID linkId) {
- UUID currentUserId = userService.getCurrentUserId();
-
- Link link = linkRepository.findById(linkId)
- .orElseThrow(() -> new NoSuchShortLinkFoundInTheSystemException(
- "No such short link found in the system", linkId.toString()
- ));
-
-
- if (!link.getOwner().getId().equals(currentUserId)) {
- throw new AccessDeniedException("You are not allowed to delete this link");
- }
-
- linkRepository.delete(link);
- cacheEvictService.evictLink(link.getCode());
- }
-
- /**
- * Retrieves the UUID of the currently authenticated user from the security context.
- *
- * @return the UUID of the authenticated user
- */
-
-
-
-
- @CachePut(value = "shortLinksCache", key = "#code")
- public LinkResponseDto updateLinkExpiration(String code, Instant newExpirationDate) {
- Link link = linkRepository.findByCode(code)
- .orElseThrow(() -> new NoSuchShortLinkFoundInTheSystemException(
- "No such short link found in the system: " + code,
- code
- ));
-
- if (!link.getOwner().getId().equals(userService.getCurrentUserId())) {
- throw new AccessDeniedException("You are not allowed to update this link");
- }
-
- if (newExpirationDate.isBefore(Instant.now())) {
- throw new InvalidExpirationDateException(
- "Expiration date must be in the future",
- newExpirationDate
- );
- }
-
- link.setExpiresAt(newExpirationDate);
- linkRepository.save(link);
-
- return mapToResponse(link);
- }
+ LinkResponseDto updateLinkExpiration(String code, Instant newExpirationDate);
}
-
diff --git a/src/main/java/org/decepticons/linkshortener/api/service/UserService.java b/src/main/java/org/decepticons/linkshortener/api/service/UserService.java
index 6880ac1..af05c4c 100644
--- a/src/main/java/org/decepticons/linkshortener/api/service/UserService.java
+++ b/src/main/java/org/decepticons/linkshortener/api/service/UserService.java
@@ -1,42 +1,25 @@
package org.decepticons.linkshortener.api.service;
-import org.decepticons.linkshortener.api.exception.NoSuchUserFoundInTheSystemException;
-import org.decepticons.linkshortener.api.model.User;
-import org.decepticons.linkshortener.api.repository.UserRepository;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.stereotype.Service;
-
import java.util.UUID;
-
-@Service
-public class UserService {
-
- public UserRepository userRepository;
-
- public UserService(UserRepository userRepository) {
- this.userRepository = userRepository;
- }
-
- public UUID getCurrentUserId() {
- String username = SecurityContextHolder.getContext().getAuthentication().getName();
- User user = userRepository.findByUsername(username)
- .orElseThrow(() -> new NoSuchUserFoundInTheSystemException(
- "No such user found in the system: " + username,
- username
- ));
- return user.getId();
-
- }
-
- public User getCurrentUser() {
- String username = SecurityContextHolder.getContext().getAuthentication().getName();
- return userRepository.findByUsername(username)
- .orElseThrow(() -> new NoSuchUserFoundInTheSystemException(
- "No such user found in the system: " + username,
- username
- ));
- }
-
+import org.decepticons.linkshortener.api.model.User;
+/**
+ * Service interface for retrieving information about the currently authenticated user.
+ */
+public interface UserService {
+
+ /**
+ * Retrieves the currently authenticated user.
+ *
+ * @return the User object representing the current user
+ */
+ User getCurrentUser();
+
+ /**
+ * Retrieves the unique identifier (UUID) of the currently authenticated user.
+ *
+ * @return the UUID of the current user
+ */
+ UUID getCurrentUserId();
}
diff --git a/src/main/java/org/decepticons/linkshortener/api/v1/controller/CacheController.java b/src/main/java/org/decepticons/linkshortener/api/v1/controller/CacheController.java
deleted file mode 100644
index cf54335..0000000
--- a/src/main/java/org/decepticons/linkshortener/api/v1/controller/CacheController.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package org.decepticons.linkshortener.api.v1.controller;
-
-import org.decepticons.linkshortener.api.service.CacheInspectionService;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-/**
- * REST controller for cache inspection.
- * Provides an endpoint to inspect the contents of the short links cache.
- * This is primarily for debugging and monitoring purposes.
- * Note: In a production environment, access to this endpoint should be
- * restricted to authorized personnel only, as it may expose sensitive data.
- */
-@RestController
-@RequestMapping("/api/v1/cache")
-public class CacheController {
-
- private final CacheInspectionService cacheInspectionService;
-
- /**
- * Constructs a new {@link CacheController} with the given dependencies.
- *
- * @param cacheInspectionService the service responsible for cache inspection
- */
- public CacheController(CacheInspectionService cacheInspectionService) {
- this.cacheInspectionService = cacheInspectionService;
- }
-
- /**
- * Endpoint to inspect the contents of the short links cache.
- * This is primarily for debugging and monitoring purposes.
- * Note: In a production environment, access to this endpoint should be
- * restricted to authorized personnel only, as it may expose sensitive data.
- */
- @GetMapping
- public void inspectCache() {
- cacheInspectionService.printCache("shortLinksCache");
- }
-}
diff --git a/src/main/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudController.java b/src/main/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudController.java
index cec2d5e..b040eda 100644
--- a/src/main/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudController.java
+++ b/src/main/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudController.java
@@ -3,17 +3,24 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
-
import java.util.UUID;
import org.decepticons.linkshortener.api.dto.LinkResponseDto;
import org.decepticons.linkshortener.api.dto.UpdateLinkExpirationRequestDto;
import org.decepticons.linkshortener.api.dto.UrlRequestDto;
-import org.decepticons.linkshortener.api.model.User;
import org.decepticons.linkshortener.api.service.LinkService;
-import org.decepticons.linkshortener.api.service.UserService;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+
/**
@@ -27,16 +34,16 @@
public class LinkCrudController {
private final LinkService linkService;
- private final UserService userService;
+
/**
* Constructs a new {@link LinkCrudController} with the given dependencies.
*
* @param linkService the service responsible for link business logic
*/
- public LinkCrudController(LinkService linkService, UserService userService) {
+ public LinkCrudController(LinkService linkService) {
this.linkService = linkService;
- this.userService = userService;
+
}
@@ -49,9 +56,9 @@ public LinkCrudController(LinkService linkService, UserService userService) {
@PostMapping
@Operation(summary = "Create a short URL for the current user")
public ResponseEntity createLink(@Valid @RequestBody UrlRequestDto originalUrl) {
- User user = userService.getCurrentUser();
- LinkResponseDto link = linkService.createLink(originalUrl, user);
+
+ LinkResponseDto link = linkService.createLink(originalUrl);
return ResponseEntity.status(201).body(link);
}
@@ -116,8 +123,9 @@ public ResponseEntity updateLinkExpiration(
@Valid @RequestBody UpdateLinkExpirationRequestDto newExpirationDate,
@PathVariable String code) {
- return ResponseEntity
- .ok(linkService.updateLinkExpiration(code, newExpirationDate.getNewExpirationDate()));
+ return ResponseEntity
+ .ok(linkService.updateLinkExpiration(code, newExpirationDate.getNewExpirationDate()));
}
+
}
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index 8fa7c4b..0593788 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -10,6 +10,9 @@
logging:
level:
org.springdoc: DEBUG
+ root: DEBUG # set global logging to DEBUG
+ org.springframework: INFO # optional: keep Spring framework logs at INFO to reduce noise
+ com.yourpackage: DEBUG # set your own package to DEBUG
springdoc:
api-docs:
@@ -21,7 +24,7 @@ springdoc:
spring:
profiles:
- default: dev
+ default: dev # default profile if none specified
server:
port: ${SERVER_PORT:8080}
@@ -30,8 +33,11 @@ link:
expiration-days: ${LINK_EXPIRATION_DAYS:7}
jwt:
+ secret: ${JWT_SECRET:dev-secret-change-me}
ttl-seconds: ${JWT_TTL_SECONDS:3600}
+
+
---
# ===================== DEV (H2 in-memory DB) =====================
spring:
diff --git a/src/test/java/org/decepticons/linkshortener/api/controller/LinkCrudControllerTest.java b/src/test/java/org/decepticons/linkshortener/api/controller/LinkCrudControllerTest.java
index b7cc9a7..5cf5f5f 100644
--- a/src/test/java/org/decepticons/linkshortener/api/controller/LinkCrudControllerTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/controller/LinkCrudControllerTest.java
@@ -7,7 +7,7 @@
import org.decepticons.linkshortener.api.exception.NoSuchUserFoundInTheSystemException;
import org.decepticons.linkshortener.api.model.User;
import org.decepticons.linkshortener.api.service.LinkService;
-import org.decepticons.linkshortener.api.service.UserService;
+import org.decepticons.linkshortener.api.service.impl.UserServiceImpl;
import org.decepticons.linkshortener.api.v1.controller.LinkCrudController;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@@ -41,7 +41,7 @@ class LinkCrudControllerTest {
private LinkService linkService;
@Mock
- UserService userService;
+ UserServiceImpl userServiceImpl;
@InjectMocks
@@ -79,10 +79,8 @@ void testCreateShortLink_Success() {
fakeUser.getId()
);
- when(userService.getCurrentUser()).thenReturn(fakeUser);
-
- when(linkService.createLink(urlRequestDto, fakeUser)).thenReturn(fakeResponse);
+ when(linkService.createLink(urlRequestDto)).thenReturn(fakeResponse);
ResponseEntity response = linkController.createLink(urlRequestDto);
@@ -93,37 +91,34 @@ void testCreateShortLink_Success() {
assertEquals("https://example.com/some/long/url", response.getBody().originalUrl());
- verify(userService).getCurrentUser();
- verify(linkService).createLink(urlRequestDto, fakeUser);
+
+ verify(linkService).createLink(urlRequestDto);
}
@Test
@DisplayName("Create Short Link - User Not Found")
- void testCreateShortLink_UserNotFound() {
-
+ void testCreateShortLink_UserNotFound_Controller() {
UrlRequestDto urlRequestDto = new UrlRequestDto();
urlRequestDto.setUrl("https://example.com/some/long/url");
- when(userService.getCurrentUser())
- .thenThrow(new NoSuchUserFoundInTheSystemException("No such user", "someName"));
-
+ when(linkService.createLink(Mockito.any(UrlRequestDto.class)))
+ .thenThrow(new NoSuchUserFoundInTheSystemException("User not found", "some-user-id"));
- Exception ex = null;
- try {
- linkController.createLink(urlRequestDto);
- }catch (Exception exception){
- ex = exception;
- }
+ NoSuchUserFoundInTheSystemException ex = assertThrows(
+ NoSuchUserFoundInTheSystemException.class,
+ () -> linkController.createLink(urlRequestDto)
+ );
- assertNotNull(ex);
- assertInstanceOf(NoSuchUserFoundInTheSystemException.class, ex);
+ assertEquals("User not found", ex.getMessage());
- verify(linkService, Mockito.never()).createLink(Mockito.any(), Mockito.any());
+ verify(linkService).createLink(urlRequestDto);
}
+
+
@Test
@DisplayName("Get All My Links - Success")
void getAllMyLinks_SUCCESS() {
@@ -222,8 +217,11 @@ void getAllMyActiveLinks_SUCCESS() {
void testDeleteLink_SUCCESS() {
UUID linkId = UUID.randomUUID();
+ String mockCode = "abc123";
+
+ ResponseEntity mockResponse = ResponseEntity.noContent().build();
- doNothing().when(linkService).deleteLink(linkId);
+ when(linkService.deleteLink(linkId)).thenReturn(mockCode);
ResponseEntity response = linkController.deleteLink(linkId);
diff --git a/src/test/java/org/decepticons/linkshortener/api/controller/RedirectControllerTest.java b/src/test/java/org/decepticons/linkshortener/api/controller/RedirectControllerTest.java
index 0bf9642..122da62 100644
--- a/src/test/java/org/decepticons/linkshortener/api/controller/RedirectControllerTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/controller/RedirectControllerTest.java
@@ -4,12 +4,11 @@
import jakarta.servlet.http.HttpServletResponse;
import org.decepticons.linkshortener.api.dto.LinkResponseDto;
import org.decepticons.linkshortener.api.model.User;
-import org.decepticons.linkshortener.api.service.LinkService;
+import org.decepticons.linkshortener.api.service.impl.LinkServiceImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
-import org.mockito.Mockito;
import java.io.IOException;
@@ -24,7 +23,7 @@ class RedirectControllerTest {
@InjectMocks
private RedirectController redirectController;
@Mock
- private LinkService linkService;
+ private LinkServiceImpl linkServiceImpl;
@Mock
private HttpServletResponse httpServletResponse;
@@ -46,16 +45,16 @@ void verifyRedirectToOriginalUrl_SUCCESS() throws IOException {
);
- when(linkService.getLinkByCode(code)).thenReturn(responseDto);
- when(linkService.incrementClicks(responseDto)).thenReturn(responseDto);
- when(linkService.validateLink(responseDto)).thenReturn(true);
+ when(linkServiceImpl.getLinkByCode(code)).thenReturn(responseDto);
+ when(linkServiceImpl.incrementClicks(responseDto)).thenReturn(responseDto);
+ when(linkServiceImpl.validateLink(responseDto)).thenReturn(true);
doNothing().when(httpServletResponse).sendRedirect(responseDto.originalUrl());
redirectController.redirect(code, httpServletResponse);
verify(httpServletResponse, times(1)).sendRedirect("https://www.example.com");
- verify(linkService, times(1)).incrementClicks(responseDto);
+ verify(linkServiceImpl, times(1)).incrementClicks(responseDto);
}
@Test
@@ -75,8 +74,8 @@ void verifyExceptionThrownWhenLinkNotValid_SUCCESS() throws IOException {
owner.getId()
);
- when(linkService.getLinkByCode(code)).thenReturn(responseDto);
- when(linkService.validateLink(responseDto)).thenReturn(false );
+ when(linkServiceImpl.getLinkByCode(code)).thenReturn(responseDto);
+ when(linkServiceImpl.validateLink(responseDto)).thenReturn(false );
Exception ex = null;
diff --git a/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceMethodsTest.java b/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplMethodsTest.java
similarity index 87%
rename from src/test/java/org/decepticons/linkshortener/api/service/LinkServiceMethodsTest.java
rename to src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplMethodsTest.java
index 6425963..86e0e1f 100644
--- a/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceMethodsTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplMethodsTest.java
@@ -5,7 +5,8 @@
import org.decepticons.linkshortener.api.model.LinkStatus;
import org.decepticons.linkshortener.api.model.User;
import org.decepticons.linkshortener.api.repository.LinkRepository;
-import org.decepticons.linkshortener.api.repository.UserRepository;
+import org.decepticons.linkshortener.api.service.impl.LinkServiceImpl;
+import org.decepticons.linkshortener.api.service.impl.UserServiceImpl;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@@ -30,18 +31,16 @@
@ExtendWith(MockitoExtension.class)
-public class LinkServiceMethodsTest {
+public class LinkServiceImplMethodsTest {
@Mock
private LinkRepository linkRepository;
@Mock
- private UserService userService;
+ private UserServiceImpl userServiceImpl;
- @Mock
- private CacheEvictService cacheEvictService;
@InjectMocks
- private LinkService linkService;
+ private LinkServiceImpl linkService;
private final UUID testUserId = UUID.randomUUID();
private final String testUsername = "testuser";
@@ -73,7 +72,7 @@ void getAllMyLinks_SUCCESS() {
Page mockPage = new PageImpl<>(List.of(link1));
- when(userService.getCurrentUserId()).thenReturn(testUserId);
+ when(userServiceImpl.getCurrentUserId()).thenReturn(testUserId);
when(linkRepository.findAllByOwnerId(eq(testUserId), any(Pageable.class)))
.thenReturn(mockPage);
@@ -93,7 +92,7 @@ void getAllMyActiveLinks_SUCCESS() {
Page mockPage = new PageImpl<>(List.of(link1));
- when(userService.getCurrentUserId()).thenReturn(testUserId);
+ when(userServiceImpl.getCurrentUserId()).thenReturn(testUserId);
when(linkRepository.findAllByOwnerIdAndStatus(eq(testUserId), eq(LinkStatus.ACTIVE), any(Pageable.class)))
.thenReturn(mockPage);
@@ -115,13 +114,12 @@ void deleteLink_SUCCESS() {
link.setCode("abc123");
link.setOwner(owner);
- when(userService.getCurrentUserId()).thenReturn(testUserId);
+ when(userServiceImpl.getCurrentUserId()).thenReturn(testUserId);
when(linkRepository.findById(linkId)).thenReturn(Optional.of(link));
linkService.deleteLink(linkId);
verify(linkRepository, times(1)).delete(link);
- verify(cacheEvictService, times(1)).evictLink("abc123");
}
diff --git a/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceTest.java b/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplTest.java
similarity index 84%
rename from src/test/java/org/decepticons/linkshortener/api/service/LinkServiceTest.java
rename to src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplTest.java
index 41a6f61..7c54024 100644
--- a/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplTest.java
@@ -6,15 +6,14 @@
import org.decepticons.linkshortener.api.model.LinkStatus;
import org.decepticons.linkshortener.api.model.User;
import org.decepticons.linkshortener.api.repository.LinkRepository;
-import org.decepticons.linkshortener.api.repository.UserRepository;
+import org.decepticons.linkshortener.api.service.impl.LinkServiceImpl;
+import org.decepticons.linkshortener.api.service.impl.UserServiceImpl;
import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -22,7 +21,6 @@
import java.time.Instant;
import java.time.temporal.ChronoUnit;
-import java.util.List;
import java.util.Optional;
import java.util.UUID;
@@ -32,16 +30,16 @@
import static org.mockito.Mockito.*;
@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class)
-class LinkServiceTest {
+class LinkServiceImplTest {
@Mock
private LinkRepository linkRepository;
@Mock
- private UserService userService;
+ private UserServiceImpl userServiceImpl;
@InjectMocks
- private LinkService linkService;
+ private LinkServiceImpl linkServiceImpl;
@@ -59,10 +57,10 @@ void testLinkCreation_Success() {
urlRequestDto.setUrl("https://www.example.com");
-
+ when(userServiceImpl.getCurrentUser()).thenReturn(user);
when(linkRepository.save(any(Link.class))).thenAnswer(i -> i.getArguments()[0]);
- LinkResponseDto result = linkService.createLink(urlRequestDto, user);
+ LinkResponseDto result = linkServiceImpl.createLink(urlRequestDto);
assertEquals(urlRequestDto.getUrl(), result.originalUrl());
assertEquals(user.getId(), result.ownerId());
@@ -88,13 +86,11 @@ void incrementOfClicks_Success() {
link.setCode("abc123");
link.setClicks(0);
+ when(linkRepository.incrementClicksByCodeNative("abc123")).thenReturn(1);
when(linkRepository.findByCode("abc123")).thenReturn(Optional.of(link));
- when(linkRepository.save(any(Link.class))).thenAnswer(i -> i.getArguments()[0]);
-
-
- linkService.incrementClicks(linkResponseDto);
+ LinkResponseDto result = linkServiceImpl.incrementClicks(linkResponseDto);
+ assertNotEquals(1, result.clicks());
- assertNotEquals(0, link.getClicks());
}
@Test
@@ -112,7 +108,7 @@ void testMapToResponse_Success() {
link.setExpiresAt(Instant.now().plus(2, ChronoUnit.DAYS));
link.setOwner(owner);
- LinkResponseDto dto = linkService.mapToResponse(link);
+ LinkResponseDto dto = linkServiceImpl.mapToResponse(link);
assertEquals(link.getCode(), dto.code());
assertEquals(link.getOriginalUrl(), dto.originalUrl());
@@ -142,7 +138,7 @@ void getLinkByCode_Success() {
when(linkRepository.findByCode("abc123")).thenReturn(Optional.of(link));
- LinkResponseDto result = linkService.getLinkByCode("abc123");
+ LinkResponseDto result = linkServiceImpl.getLinkByCode("abc123");
assertEquals("abc123", result.code());
assertEquals("https://example.com", result.originalUrl());
@@ -178,7 +174,7 @@ void deactivateLink_Success() {
when(linkRepository.findByCode("abc123")).thenReturn(Optional.of(link));
when(linkRepository.save(any(Link.class))).thenAnswer(i -> i.getArguments()[0]);
- LinkResponseDto result = linkService.deactivateLink(linkResponseDto);
+ LinkResponseDto result = linkServiceImpl.deactivateLink(linkResponseDto);
assertEquals("INACTIVE", result.status());
}
@@ -198,8 +194,8 @@ void validateLink_Success() {
UUID.randomUUID()
);
- linkService.validateLink(linkResponseDto);
- Assertions.assertTrue(linkService.validateLink(linkResponseDto));
+ linkServiceImpl.validateLink(linkResponseDto);
+ Assertions.assertTrue(linkServiceImpl.validateLink(linkResponseDto));
}
@@ -223,9 +219,9 @@ void testUpdateLinkExpiration(){
when(linkRepository.findByCode(code)).thenReturn(Optional.of(link));
when(linkRepository.save(any(Link.class))).thenAnswer(i -> i.getArguments()[0]);
- when(userService.getCurrentUserId()).thenReturn(owner.getId());
+ when(userServiceImpl.getCurrentUserId()).thenReturn(owner.getId());
- LinkResponseDto response = linkService.updateLinkExpiration(code, Instant
+ LinkResponseDto response = linkServiceImpl.updateLinkExpiration(code, Instant
.now()
.plusSeconds(7200));
diff --git a/src/test/java/org/decepticons/linkshortener/api/service/UserServiceImplTest.java b/src/test/java/org/decepticons/linkshortener/api/service/UserServiceImplTest.java
new file mode 100644
index 0000000..74bbba0
--- /dev/null
+++ b/src/test/java/org/decepticons/linkshortener/api/service/UserServiceImplTest.java
@@ -0,0 +1,98 @@
+package org.decepticons.linkshortener.api.service;
+
+import org.decepticons.linkshortener.api.exception.NoSuchUserFoundInTheSystemException;
+import org.decepticons.linkshortener.api.model.User;
+import org.decepticons.linkshortener.api.repository.UserRepository;
+import org.decepticons.linkshortener.api.service.impl.UserServiceImpl;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class UserServiceImplTest {
+ @Mock
+ private UserRepository userRepository;
+
+ @InjectMocks
+ private UserServiceImpl userService;
+
+ @Mock
+ private SecurityContext securityContext;
+
+ @Mock
+ private Authentication authentication;
+
+ @BeforeEach
+ void setUp() {
+ when(authentication.getName()).thenReturn("testuser");
+ when(securityContext.getAuthentication()).thenReturn(authentication);
+
+
+ org.springframework.security.core.context.SecurityContextHolder.setContext(securityContext);
+ }
+
+ @AfterEach
+ void tearDown() {
+ org.springframework.security.core.context.SecurityContextHolder.clearContext();
+ }
+
+ @Test
+ @DisplayName("Test getCurrentUser returns correct User")
+ void testGetCurrentUser_Success() {
+ User fakeUser = new User();
+ fakeUser.setId(UUID.randomUUID());
+ fakeUser.setUsername("testuser");
+
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(fakeUser));
+
+ User result = userService.getCurrentUser();
+
+ assertNotNull(result);
+ assertEquals("testuser", result.getUsername());
+ }
+
+ @Test
+ @DisplayName("Test getCurrentUserId returns correct UUID")
+ void testGetCurrentUserId_Success() {
+ UUID fakeId = UUID.randomUUID();
+ User fakeUser = new User();
+ fakeUser.setId(fakeId);
+ fakeUser.setUsername("testuser");
+
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(fakeUser));
+
+ UUID result = userService.getCurrentUserId();
+
+ assertEquals(fakeId, result);
+ }
+
+ @Test
+ @DisplayName("Test getCurrentUser throws exception when user not found")
+ void testGetCurrentUser_UserNotFound() {
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.empty());
+
+ NoSuchUserFoundInTheSystemException ex = assertThrows(
+ NoSuchUserFoundInTheSystemException.class,
+ () -> userService.getCurrentUser()
+ );
+
+ assertTrue(ex.getMessage().contains("No such user found in the system"));
+ }
+
+}
diff --git a/src/test/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudControllerTest.java b/src/test/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudControllerTest.java
index 45e2747..957385f 100644
--- a/src/test/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudControllerTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudControllerTest.java
@@ -1,9 +1,8 @@
package org.decepticons.linkshortener.api.v1.controller;
import org.decepticons.linkshortener.api.dto.LinkResponseDto;
-import org.decepticons.linkshortener.api.service.LinkService;
+import org.decepticons.linkshortener.api.service.impl.LinkServiceImpl;
import org.junit.jupiter.api.Test;
-import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
@@ -11,6 +10,7 @@
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
@@ -20,6 +20,7 @@
import java.util.UUID;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@@ -31,7 +32,7 @@ class LinkCrudControllerTest {
private MockMvc mockMvc;
@MockitoBean
- private LinkService linkService;
+ private LinkServiceImpl linkServiceImpl;
@Test
@WithMockUser(username = "testuser", roles = {"USER"})
@@ -53,7 +54,7 @@ void testGetAllMyLinks() throws Exception {
1
);
- Mockito.when(linkService.getAllMyLinks(anyInt(), anyInt())).thenReturn(page);
+ when(linkServiceImpl.getAllMyLinks(anyInt(), anyInt())).thenReturn(page);
mockMvc.perform(get("/api/v1/links/my_all_links")
.param("page", "0")
@@ -85,7 +86,7 @@ void testGetAllMyActiveLinks() throws Exception {
1
);
- Mockito.when(linkService.getAllMyActiveLinks(anyInt(), anyInt())).thenReturn(page);
+ when(linkServiceImpl.getAllMyActiveLinks(anyInt(), anyInt())).thenReturn(page);
mockMvc.perform(get("/api/v1/links/my_all_active_links")
.param("page", "0")
@@ -100,12 +101,17 @@ void testGetAllMyActiveLinks() throws Exception {
@Test
@WithMockUser(username = "testuser", roles = {"USER"})
void testDeleteLinkSuccess() throws Exception {
+ ResponseEntity responseEntity = ResponseEntity.noContent().build();
UUID existingId = UUID.randomUUID();
- Mockito.doNothing().when(linkService).deleteLink(existingId);
+
+ when(linkServiceImpl.deleteLink(existingId))
+ .thenReturn(existingId.toString());
mockMvc.perform(delete("/api/v1/links/delete/{id}", existingId.toString()))
.andExpect(status().isNoContent());
+
+ verify(linkServiceImpl, times(1)).deleteLink(existingId);
}
}
From a22ee4b544a94084cdc03a58a60cb0bd5e79a2d3 Mon Sep 17 00:00:00 2001
From: zkhr
Date: Mon, 15 Sep 2025 20:30:47 +0200
Subject: [PATCH 07/11] fixed logical problems
---
.../api/service/impl/LinkServiceImpl.java | 295 ++++++++++++++++++
.../api/service/impl/UserServiceImpl.java | 53 ++++
.../api/controller/CacheControllerTest.java | 30 --
3 files changed, 348 insertions(+), 30 deletions(-)
create mode 100644 src/main/java/org/decepticons/linkshortener/api/service/impl/LinkServiceImpl.java
create mode 100644 src/main/java/org/decepticons/linkshortener/api/service/impl/UserServiceImpl.java
delete mode 100644 src/test/java/org/decepticons/linkshortener/api/controller/CacheControllerTest.java
diff --git a/src/main/java/org/decepticons/linkshortener/api/service/impl/LinkServiceImpl.java b/src/main/java/org/decepticons/linkshortener/api/service/impl/LinkServiceImpl.java
new file mode 100644
index 0000000..7d200e9
--- /dev/null
+++ b/src/main/java/org/decepticons/linkshortener/api/service/impl/LinkServiceImpl.java
@@ -0,0 +1,295 @@
+package org.decepticons.linkshortener.api.service.impl;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Random;
+import java.util.UUID;
+import org.decepticons.linkshortener.api.dto.LinkResponseDto;
+import org.decepticons.linkshortener.api.dto.UrlRequestDto;
+import org.decepticons.linkshortener.api.exception.InvalidExpirationDateException;
+import org.decepticons.linkshortener.api.exception.NoSuchShortLinkFoundInTheSystemException;
+import org.decepticons.linkshortener.api.model.Link;
+import org.decepticons.linkshortener.api.model.LinkStatus;
+import org.decepticons.linkshortener.api.repository.LinkRepository;
+import org.decepticons.linkshortener.api.service.LinkService;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Application service responsible for creating and maintaining {@link Link} entities.
+ * Provides operations for link creation, click tracking, simple DTO mapping,
+ * and basic liveness checks (active + not expired).
+ *
+ */
+
+
+@Service
+public class LinkServiceImpl implements LinkService {
+
+ @Value("${link.expiration-days}")
+ private long linkExpirationDays;
+
+ private final LinkRepository linkRepository;
+ private final UserServiceImpl userServiceImpl;
+ private final Random random = new Random();
+
+
+ /**
+ * Creates a new {@code LinkService}.
+ *
+ * @param linkRepository repository used to persist and load {@link Link} entities
+ */
+
+ public LinkServiceImpl(LinkRepository linkRepository,
+ UserServiceImpl userServiceImpl) {
+ this.linkRepository = linkRepository;
+ this.userServiceImpl = userServiceImpl;
+ }
+
+
+ /**
+ * Creates and persists a new {@link Link}.
+ *
+ * @param originalUrl the original long URL to be shortened
+ * @return a {@link LinkResponseDto} representing the newly created link
+ */
+ @Override
+ @Transactional
+ public LinkResponseDto createLink(UrlRequestDto originalUrl) {
+ Link link = new Link();
+ link.setOriginalUrl(originalUrl.getUrl());
+ link.setOwner(userServiceImpl.getCurrentUser());
+
+ link.setCode(generateRandomCode());
+ link.setExpiresAt(Instant.now().plus(linkExpirationDays, ChronoUnit.DAYS));
+ link.setStatus(LinkStatus.ACTIVE);
+
+ Link saved = linkRepository.save(link);
+
+ return mapToResponse(saved);
+ }
+
+
+ /**
+ * Increments the click counter of the given link and persists the change.
+ * This should be invoked whenever the shortened URL is accessed.
+ * Internally, the entity updates its {@code lastAccessedAt} timestamp.
+ *
+ *
+ * @param link the link whose click counter should be incremented
+ */
+ @Override
+ @Transactional
+ public LinkResponseDto incrementClicks(LinkResponseDto link) {
+
+ int affectedRows = linkRepository.incrementClicksByCodeNative(link.code());
+
+ if (affectedRows == 0) {
+ throw new NoSuchShortLinkFoundInTheSystemException(
+ "No such short link found in the system: " + link.code(),
+ link.code()
+ );
+ }
+ Link updatedLink = linkRepository.findByCode(link.code())
+ .orElseThrow(() -> new NoSuchShortLinkFoundInTheSystemException(
+ "No such short link found in the system: " + link.code(),
+ link.code()
+ ));
+
+ return mapToResponse(updatedLink);
+ }
+
+
+ /**
+ * Maps a {@link Link} JPA entity to a transport-friendly {@link LinkResponseDto}.
+ *
+ * @param link the entity to map
+ * @return a response DTO with the most relevant fields
+ */
+ public LinkResponseDto mapToResponse(Link link) {
+ return new LinkResponseDto(
+
+ link.getId(),
+ link.getCode(),
+ link.getOriginalUrl(),
+ link.getCreatedAt(),
+ link.getExpiresAt(),
+ link.getClicks(),
+ link.getStatus().name(),
+ link.getOwner().getId()
+ );
+ }
+
+
+ /**
+ * Generates a pseudo-random short code of fixed length .
+ *
+ * @return a new short code (e.g., {@code "aZ3fQ1"})
+ */
+ private String generateRandomCode() {
+ String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 6; i++) {
+ sb.append(chars.charAt(random.nextInt(chars.length())));
+ }
+ return sb.toString();
+ }
+
+
+ /**
+ * Retrieves a {@link Link} entity by its short code.
+ *
+ * @param code short link code.
+ * @return Optional
+ */
+ @Override
+ @Cacheable(value = "shortLinksCache", key = "#code")
+ public LinkResponseDto getLinkByCode(String code) {
+ Link link = linkRepository.findByCode(code)
+ .orElseThrow(() -> new NoSuchShortLinkFoundInTheSystemException(
+ "No such short link found in the system: " + code,
+ code
+ ));
+
+ return mapToResponse(link);
+
+ }
+
+
+ /**
+ * Deactivates a link by setting its status to INACTIVE.
+ *
+ * @param link the link to deactivate
+ * @return the updated {@link LinkResponseDto} with status set to INACTIVE
+ */
+
+ @Override
+ @CacheEvict(value = "shortLinksCache", key = "#result.code")
+ public LinkResponseDto deactivateLink(LinkResponseDto link) {
+ Link linkByCode = linkRepository.findByCode(link.code())
+ .orElseThrow(() -> new NoSuchShortLinkFoundInTheSystemException(
+ "No such short link found in the system: " + link.code(),
+ link.code()
+ ));
+ linkByCode.setStatus(LinkStatus.INACTIVE);
+ Link saved = linkRepository.save(linkByCode);
+ return mapToResponse(saved);
+ }
+
+
+ /**
+ * Validates if a link is active and not expired.
+ *
+ * @param link the link to validate
+ * @return {@code true} if the link is active and not expired; {@code false} otherwise
+ */
+ @Override
+ public boolean validateLink(LinkResponseDto link) {
+ return link.status().equalsIgnoreCase(LinkStatus.ACTIVE.toString())
+ && (link.expiresAt() == null || link.expiresAt().isAfter(Instant.now()));
+ }
+
+
+ /**
+ * Retrieves all links of the currently authenticated user with pagination.
+ *
+ * @param page the page number to retrieve (0-based)
+ * @param size the number of records per page
+ * @return a {@link Page} of {@link LinkResponseDto} objects representing all user's links
+ */
+ @Override
+ public Page getAllMyLinks(int page, int size) {
+ UUID userId = userServiceImpl.getCurrentUserId();
+ Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
+ return linkRepository.findAllByOwnerId(userId, pageable)
+ .map(this::mapToResponse);
+ }
+
+ /**
+ * Retrieves all active links of the currently authenticated user with pagination.
+ *
+ * @param page the page number to retrieve (0-based)
+ * @param size the number of records per page
+ * @return a {@link Page} of {@link LinkResponseDto} objects representing active user's links
+ */
+ @Override
+ public Page getAllMyActiveLinks(int page, int size) {
+ UUID userId = userServiceImpl.getCurrentUserId();
+ Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
+ return linkRepository.findAllByOwnerIdAndStatus(userId, LinkStatus.ACTIVE, pageable)
+ .map(this::mapToResponse);
+ }
+
+ /**
+ * Deletes a link from the database if it belongs to the currently authenticated user.
+ *
+ * @param linkId the unique identifier of the link to delete
+ */
+ @Transactional
+ @Override
+ @CacheEvict(value = "shortLinksCache", key = "#result")
+ public String deleteLink(UUID linkId) {
+ UUID currentUserId = userServiceImpl.getCurrentUserId();
+
+ Link link = linkRepository.findById(linkId)
+ .orElseThrow(() -> new NoSuchShortLinkFoundInTheSystemException(
+ "No such short link found in the system", linkId.toString()
+ ));
+
+
+ if (!link.getOwner().getId().equals(currentUserId)) {
+ throw new AccessDeniedException("You are not allowed to delete this link");
+ }
+
+ linkRepository.delete(link);
+
+ return link.getCode();
+ }
+
+ /**
+ * Retrieves the UUID of the currently authenticated user from the security context.
+ *
+ * @return the UUID of the authenticated user
+ */
+
+
+
+
+ @CachePut(value = "shortLinksCache", key = "#code")
+ @Override
+ public LinkResponseDto updateLinkExpiration(String code, Instant newExpirationDate) {
+ Link link = linkRepository.findByCode(code)
+ .orElseThrow(() -> new NoSuchShortLinkFoundInTheSystemException(
+ "No such short link found in the system: " + code,
+ code
+ ));
+
+ if (!link.getOwner().getId().equals(userServiceImpl.getCurrentUserId())) {
+ throw new AccessDeniedException("You are not allowed to update this link");
+ }
+
+ if (newExpirationDate.isBefore(Instant.now())) {
+ throw new InvalidExpirationDateException(
+ "Expiration date must be in the future",
+ newExpirationDate
+ );
+ }
+
+ link.setExpiresAt(newExpirationDate);
+ Link saved = linkRepository.save(link);
+
+ return mapToResponse(saved);
+ }
+
+
+}
+
diff --git a/src/main/java/org/decepticons/linkshortener/api/service/impl/UserServiceImpl.java b/src/main/java/org/decepticons/linkshortener/api/service/impl/UserServiceImpl.java
new file mode 100644
index 0000000..e2ea2f1
--- /dev/null
+++ b/src/main/java/org/decepticons/linkshortener/api/service/impl/UserServiceImpl.java
@@ -0,0 +1,53 @@
+package org.decepticons.linkshortener.api.service.impl;
+
+import java.util.UUID;
+import org.decepticons.linkshortener.api.exception.NoSuchUserFoundInTheSystemException;
+import org.decepticons.linkshortener.api.model.User;
+import org.decepticons.linkshortener.api.repository.UserRepository;
+import org.decepticons.linkshortener.api.service.UserService;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+
+
+/**
+ * Service implementation for retrieving information about the currently authenticated user.
+ */
+@Service
+public class UserServiceImpl implements UserService {
+
+ public UserRepository userRepository;
+
+ /**
+ * Constructs a new UserServiceImpl with the given UserRepository.
+ *
+ * @param userRepository the repository used to access user data
+ */
+ public UserServiceImpl(UserRepository userRepository) {
+ this.userRepository = userRepository;
+ }
+
+ @Override
+ public UUID getCurrentUserId() {
+ String username = SecurityContextHolder.getContext().getAuthentication().getName();
+ User user = userRepository.findByUsername(username)
+ .orElseThrow(() -> new NoSuchUserFoundInTheSystemException(
+ "No such user found in the system: " + username,
+ username
+ ));
+ return user.getId();
+
+ }
+
+ @Override
+ public User getCurrentUser() {
+ String username = SecurityContextHolder.getContext().getAuthentication().getName();
+ return userRepository.findByUsername(username)
+ .orElseThrow(() -> new NoSuchUserFoundInTheSystemException(
+ "No such user found in the system: " + username,
+ username
+ ));
+ }
+
+
+
+}
diff --git a/src/test/java/org/decepticons/linkshortener/api/controller/CacheControllerTest.java b/src/test/java/org/decepticons/linkshortener/api/controller/CacheControllerTest.java
deleted file mode 100644
index e92a970..0000000
--- a/src/test/java/org/decepticons/linkshortener/api/controller/CacheControllerTest.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.decepticons.linkshortener.api.controller;
-
-import org.decepticons.linkshortener.api.service.CacheInspectionService;
-import org.decepticons.linkshortener.api.v1.controller.CacheController;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class)
- class CacheControllerTest {
- @Mock
- private CacheInspectionService cacheInspectionService;
-
- @InjectMocks
- private CacheController cacheController;
-
- @Test
- @DisplayName("Inspect Cache - Service Called")
- void inspectCache_SUCCESS() {
-
- cacheController.inspectCache();
-
- verify(cacheInspectionService, times(1)).printCache("shortLinksCache");
- }
-}
From 222d2859ab5610a8d4cc518ddcebbc5af388a113 Mon Sep 17 00:00:00 2001
From: zkhr
Date: Mon, 15 Sep 2025 21:19:47 +0200
Subject: [PATCH 08/11] logical update
---
.../api/security/service/impl/AuthServiceImpl.java | 2 +-
.../api/security/service/impl/UserAuthServiceImpl.java | 6 +++---
src/main/resources/application.yaml | 6 ------
.../api/exceptions/GlobalExceptionHandlerTest.java | 8 ++++----
.../api/security/service/impl/AuthServiceImplTest.java | 5 +++--
.../security/service/impl/UserAuthServiceImplTest.java | 7 ++++---
6 files changed, 15 insertions(+), 19 deletions(-)
diff --git a/src/main/java/org/decepticons/linkshortener/api/security/service/impl/AuthServiceImpl.java b/src/main/java/org/decepticons/linkshortener/api/security/service/impl/AuthServiceImpl.java
index 40548bc..4fae884 100644
--- a/src/main/java/org/decepticons/linkshortener/api/security/service/impl/AuthServiceImpl.java
+++ b/src/main/java/org/decepticons/linkshortener/api/security/service/impl/AuthServiceImpl.java
@@ -3,7 +3,7 @@
import java.util.Collections;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
-import org.decepticons.linkshortener.api.exceptions.InvalidTokenException;
+import org.decepticons.linkshortener.api.exception.InvalidTokenException;
import org.decepticons.linkshortener.api.model.Role;
import org.decepticons.linkshortener.api.model.User;
import org.decepticons.linkshortener.api.model.UserStatus;
diff --git a/src/main/java/org/decepticons/linkshortener/api/security/service/impl/UserAuthServiceImpl.java b/src/main/java/org/decepticons/linkshortener/api/security/service/impl/UserAuthServiceImpl.java
index 62005e5..fb448d9 100644
--- a/src/main/java/org/decepticons/linkshortener/api/security/service/impl/UserAuthServiceImpl.java
+++ b/src/main/java/org/decepticons/linkshortener/api/security/service/impl/UserAuthServiceImpl.java
@@ -2,9 +2,9 @@
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
-import org.decepticons.linkshortener.api.exceptions.InvalidPasswordException;
-import org.decepticons.linkshortener.api.exceptions.UserAlreadyExistsException;
-import org.decepticons.linkshortener.api.exceptions.UserNotFoundException;
+import org.decepticons.linkshortener.api.exception.InvalidPasswordException;
+import org.decepticons.linkshortener.api.exception.UserAlreadyExistsException;
+import org.decepticons.linkshortener.api.exception.UserNotFoundException;
import org.decepticons.linkshortener.api.model.User;
import org.decepticons.linkshortener.api.repository.UserRepository;
import org.decepticons.linkshortener.api.security.service.UserAuthService;
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index 0593788..8947d45 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -8,12 +8,6 @@
# ====================================================
logging:
- level:
- org.springdoc: DEBUG
- root: DEBUG # set global logging to DEBUG
- org.springframework: INFO # optional: keep Spring framework logs at INFO to reduce noise
- com.yourpackage: DEBUG # set your own package to DEBUG
-
springdoc:
api-docs:
path: /v3/api-docs
diff --git a/src/test/java/org/decepticons/linkshortener/api/exceptions/GlobalExceptionHandlerTest.java b/src/test/java/org/decepticons/linkshortener/api/exceptions/GlobalExceptionHandlerTest.java
index 9c8bdab..925b84c 100644
--- a/src/test/java/org/decepticons/linkshortener/api/exceptions/GlobalExceptionHandlerTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/exceptions/GlobalExceptionHandlerTest.java
@@ -1,11 +1,13 @@
package org.decepticons.linkshortener.api.exceptions;
+import io.jsonwebtoken.JwtException;
import org.decepticons.linkshortener.api.controller.GlobalExceptionHandlerController;
import org.decepticons.linkshortener.api.exception.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
+import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.GetMapping;
@@ -135,10 +137,8 @@ void givenGenericException_whenThrown_thenReturnInternalServerError() throws Exc
*/
@RestController
private static class TestController {
- @GetMapping("/test-user-exists")
- public void testUserExists() {
- throw new UserAlreadyExistsException("testuser");
- }
+
+
@GetMapping("/test-authentication-exception")
public void testAuthenticationException() {
throw new BadCredentialsException("Invalid username or password");
diff --git a/src/test/java/org/decepticons/linkshortener/api/security/service/impl/AuthServiceImplTest.java b/src/test/java/org/decepticons/linkshortener/api/security/service/impl/AuthServiceImplTest.java
index f444def..d7204fa 100644
--- a/src/test/java/org/decepticons/linkshortener/api/security/service/impl/AuthServiceImplTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/security/service/impl/AuthServiceImplTest.java
@@ -1,7 +1,8 @@
package org.decepticons.linkshortener.api.security.service.impl;
-import org.decepticons.linkshortener.api.exceptions.InvalidTokenException;
-import org.decepticons.linkshortener.api.exceptions.UserAlreadyExistsException;
+
+import org.decepticons.linkshortener.api.exception.InvalidTokenException;
+import org.decepticons.linkshortener.api.exception.UserAlreadyExistsException;
import org.decepticons.linkshortener.api.model.User;
import org.decepticons.linkshortener.api.model.Role;
import org.decepticons.linkshortener.api.model.UserStatus;
diff --git a/src/test/java/org/decepticons/linkshortener/api/security/service/impl/UserAuthServiceImplTest.java b/src/test/java/org/decepticons/linkshortener/api/security/service/impl/UserAuthServiceImplTest.java
index d427044..0340dd3 100644
--- a/src/test/java/org/decepticons/linkshortener/api/security/service/impl/UserAuthServiceImplTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/security/service/impl/UserAuthServiceImplTest.java
@@ -1,8 +1,9 @@
package org.decepticons.linkshortener.api.security.service.impl;
-import org.decepticons.linkshortener.api.exceptions.InvalidPasswordException;
-import org.decepticons.linkshortener.api.exceptions.UserAlreadyExistsException;
-import org.decepticons.linkshortener.api.exceptions.UserNotFoundException;
+
+import org.decepticons.linkshortener.api.exception.InvalidPasswordException;
+import org.decepticons.linkshortener.api.exception.UserAlreadyExistsException;
+import org.decepticons.linkshortener.api.exception.UserNotFoundException;
import org.decepticons.linkshortener.api.model.User;
import org.decepticons.linkshortener.api.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
From 998445b4338d8a0cc572722201201db7ef8cd0f8 Mon Sep 17 00:00:00 2001
From: zkhr
Date: Mon, 15 Sep 2025 21:49:39 +0200
Subject: [PATCH 09/11] removed setter
---
.../org/decepticons/linkshortener/api/model/User.java | 2 +-
.../api/controller/LinkCrudControllerTest.java | 2 +-
.../api/controller/RedirectControllerTest.java | 4 ++--
.../api/service/LinkServiceImplMethodsTest.java | 4 ++--
.../linkshortener/api/service/LinkServiceImplTest.java | 8 ++++----
.../linkshortener/api/service/UserServiceImplTest.java | 4 ++--
6 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/src/main/java/org/decepticons/linkshortener/api/model/User.java b/src/main/java/org/decepticons/linkshortener/api/model/User.java
index afc30b0..c904221 100644
--- a/src/main/java/org/decepticons/linkshortener/api/model/User.java
+++ b/src/main/java/org/decepticons/linkshortener/api/model/User.java
@@ -52,7 +52,7 @@ public class User {
@Id
@GeneratedValue
- @Setter
+// @Setter
@Column(name = "id", nullable = false, updatable = false)
private UUID id;
diff --git a/src/test/java/org/decepticons/linkshortener/api/controller/LinkCrudControllerTest.java b/src/test/java/org/decepticons/linkshortener/api/controller/LinkCrudControllerTest.java
index 5cf5f5f..af33ade 100644
--- a/src/test/java/org/decepticons/linkshortener/api/controller/LinkCrudControllerTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/controller/LinkCrudControllerTest.java
@@ -64,7 +64,7 @@ void testCreateShortLink_Success() {
urlRequestDto.setUrl("https://example.com/some/long/url");
User fakeUser = new User();
- fakeUser.setId(UUID.randomUUID());
+// fakeUser.setId(UUID.randomUUID());
fakeUser.setUsername("someName");
diff --git a/src/test/java/org/decepticons/linkshortener/api/controller/RedirectControllerTest.java b/src/test/java/org/decepticons/linkshortener/api/controller/RedirectControllerTest.java
index 122da62..720d60f 100644
--- a/src/test/java/org/decepticons/linkshortener/api/controller/RedirectControllerTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/controller/RedirectControllerTest.java
@@ -31,7 +31,7 @@ class RedirectControllerTest {
void verifyRedirectToOriginalUrl_SUCCESS() throws IOException {
String code = "abc123";
User owner = new User();
- owner.setId(java.util.UUID.randomUUID());
+// owner.setId(java.util.UUID.randomUUID());
LinkResponseDto responseDto = new LinkResponseDto(
java.util.UUID.randomUUID(),
@@ -61,7 +61,7 @@ void verifyRedirectToOriginalUrl_SUCCESS() throws IOException {
void verifyExceptionThrownWhenLinkNotValid_SUCCESS() throws IOException {
String code = "abc123";
User owner = new User();
- owner.setId(java.util.UUID.randomUUID());
+// owner.setId(java.util.UUID.randomUUID());
LinkResponseDto responseDto = new LinkResponseDto(
java.util.UUID.randomUUID(),
diff --git a/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplMethodsTest.java b/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplMethodsTest.java
index 86e0e1f..958cf84 100644
--- a/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplMethodsTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplMethodsTest.java
@@ -54,7 +54,7 @@ void setUpSecurityContext() {
User testUser = new User();
- testUser.setId(testUserId);
+// testUser.setId(testUserId);
testUser.setUsername(testUsername);
}
@@ -108,7 +108,7 @@ void getAllMyActiveLinks_SUCCESS() {
void deleteLink_SUCCESS() {
UUID linkId = UUID.randomUUID();
User owner = new User();
- owner.setId(testUserId);
+// owner.setId(testUserId);
owner.setUsername(testUsername);
Link link = new Link();
link.setCode("abc123");
diff --git a/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplTest.java b/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplTest.java
index 7c54024..d7d2de2 100644
--- a/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplTest.java
@@ -52,7 +52,7 @@ class LinkServiceImplTest {
void testLinkCreation_Success() {
UrlRequestDto urlRequestDto = new UrlRequestDto();
User user = new User();
- user.setId(UUID.randomUUID());
+// user.setId(UUID.randomUUID());
urlRequestDto.setUrl("https://www.example.com");
@@ -98,7 +98,7 @@ void incrementOfClicks_Success() {
void testMapToResponse_Success() {
UUID userId = UUID.randomUUID();
User owner = new User();
- owner.setId(userId);
+// owner.setId(userId);
Link link = new Link();
link.setCode("abc123");
@@ -125,7 +125,7 @@ void testMapToResponse_Success() {
void getLinkByCode_Success() {
User owner = new User();
- owner.setId(UUID.randomUUID());
+// owner.setId(UUID.randomUUID());
Link link = new Link();
link.setOwner(owner);
@@ -205,7 +205,7 @@ void testUpdateLinkExpiration(){
String code = "abc123";
User owner = new User();
owner.setUsername("testUser");
- owner.setId(UUID.randomUUID());
+// owner.setId(UUID.randomUUID());
Link link = new Link();
link.setCode(code);
diff --git a/src/test/java/org/decepticons/linkshortener/api/service/UserServiceImplTest.java b/src/test/java/org/decepticons/linkshortener/api/service/UserServiceImplTest.java
index 74bbba0..c0f47be 100644
--- a/src/test/java/org/decepticons/linkshortener/api/service/UserServiceImplTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/service/UserServiceImplTest.java
@@ -56,7 +56,7 @@ void tearDown() {
@DisplayName("Test getCurrentUser returns correct User")
void testGetCurrentUser_Success() {
User fakeUser = new User();
- fakeUser.setId(UUID.randomUUID());
+// fakeUser.setId(UUID.randomUUID());
fakeUser.setUsername("testuser");
when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(fakeUser));
@@ -72,7 +72,7 @@ void testGetCurrentUser_Success() {
void testGetCurrentUserId_Success() {
UUID fakeId = UUID.randomUUID();
User fakeUser = new User();
- fakeUser.setId(fakeId);
+// fakeUser.setId(fakeId);
fakeUser.setUsername("testuser");
when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(fakeUser));
From 7ec5918fb7e567984680102762566058715de2a5 Mon Sep 17 00:00:00 2001
From: zkhr
Date: Tue, 16 Sep 2025 10:50:20 +0200
Subject: [PATCH 10/11] removed setter
---
src/main/java/org/decepticons/linkshortener/api/model/User.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/main/java/org/decepticons/linkshortener/api/model/User.java b/src/main/java/org/decepticons/linkshortener/api/model/User.java
index c904221..65ad14b 100644
--- a/src/main/java/org/decepticons/linkshortener/api/model/User.java
+++ b/src/main/java/org/decepticons/linkshortener/api/model/User.java
@@ -52,7 +52,6 @@ public class User {
@Id
@GeneratedValue
-// @Setter
@Column(name = "id", nullable = false, updatable = false)
private UUID id;
From 1b2feff04d804fe581e454e2bb07d403d9ae6021 Mon Sep 17 00:00:00 2001
From: zkhr
Date: Tue, 16 Sep 2025 12:22:00 +0200
Subject: [PATCH 11/11] fixed checkstyle
---
.../exceptions/GlobalExceptionHandler.java | 0
.../linkshortener/api/model/Link.java | 8 +-
.../linkshortener/api/model/User.java | 4 +-
.../api/repository/LinkRepository.java | 16 +-
.../GlobalExceptionHandlerController.java | 2 +-
.../controller/LinkCrudControllerTest.java | 35 +-
.../GlobalExceptionHandlerTest.java | 19 +-
.../linkshortener/api/model/RoleTest.java | 5 +-
.../controller/AuthControllerTest.java | 331 +++++++++---------
.../jwt/JwtAuthenticationFilterTest.java | 36 +-
.../api/security/jwt/JwtTokenUtilTest.java | 14 +-
.../security/model/CustomUserDetailsTest.java | 10 +-
.../service/impl/AuthServiceImplTest.java | 27 +-
.../service/impl/UserAuthServiceImplTest.java | 25 +-
.../service/LinkServiceImplMethodsTest.java | 38 +-
.../api/service/LinkServiceImplTest.java | 65 ++--
.../api/service/UserServiceImplTest.java | 32 +-
.../api/util/AuthMapperTest.java | 15 +-
.../api/util/PasswordValidatorTest.java | 6 +-
.../v1/controller/CacheControllerTest.java | 254 --------------
.../v1/controller/LinkCrudControllerTest.java | 22 +-
.../LinkShortenerApplicationTests.java | 30 +-
.../unversioned/HealthControllerTest.java | 29 +-
.../unversioned/RedirectControllerTest.java | 30 +-
24 files changed, 446 insertions(+), 607 deletions(-)
delete mode 100644 src/main/java/org/decepticons/linkshortener/api/exceptions/GlobalExceptionHandler.java
delete mode 100644 src/test/java/org/decepticons/linkshortener/api/v1/controller/CacheControllerTest.java
diff --git a/src/main/java/org/decepticons/linkshortener/api/exceptions/GlobalExceptionHandler.java b/src/main/java/org/decepticons/linkshortener/api/exceptions/GlobalExceptionHandler.java
deleted file mode 100644
index e69de29..0000000
diff --git a/src/main/java/org/decepticons/linkshortener/api/model/Link.java b/src/main/java/org/decepticons/linkshortener/api/model/Link.java
index fabaf9b..4383a39 100644
--- a/src/main/java/org/decepticons/linkshortener/api/model/Link.java
+++ b/src/main/java/org/decepticons/linkshortener/api/model/Link.java
@@ -110,8 +110,12 @@ public void incrementClicks() {
@Override
public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof Link that)) return false;
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Link that)) {
+ return false;
+ }
return Objects.equals(id, that.id);
}
diff --git a/src/main/java/org/decepticons/linkshortener/api/model/User.java b/src/main/java/org/decepticons/linkshortener/api/model/User.java
index 561c51e..b9446cc 100644
--- a/src/main/java/org/decepticons/linkshortener/api/model/User.java
+++ b/src/main/java/org/decepticons/linkshortener/api/model/User.java
@@ -41,8 +41,8 @@
* {@code updatedAt} – Timestamp of last update.
*
*
- * @since 1.0
- * author Ruslan Lomaka
+ * @since 1.0
+ * author Ruslan Lomaka
*/
@Getter
diff --git a/src/main/java/org/decepticons/linkshortener/api/repository/LinkRepository.java b/src/main/java/org/decepticons/linkshortener/api/repository/LinkRepository.java
index 534a75c..aed11f6 100644
--- a/src/main/java/org/decepticons/linkshortener/api/repository/LinkRepository.java
+++ b/src/main/java/org/decepticons/linkshortener/api/repository/LinkRepository.java
@@ -56,11 +56,19 @@ public interface LinkRepository extends JpaRepository {
Page findAllByOwnerIdAndStatus(UUID ownerId, LinkStatus status, Pageable pageable);
+ /**
+ * Increments the click count and updates the last accessed timestamp for a link
+ * identified by its short code using a native SQL query.
+ *
+ * @param code the short code of the link to update
+ * @return the number of rows affected (should be 1 if the link exists, 0 otherwise)
+ */
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query(value = """
- UPDATE links
- SET clicks = clicks + 1, last_accessed_at = now()
- WHERE code = :code
-""", nativeQuery = true)
+ UPDATE links
+ SET clicks = clicks + 1, last_accessed_at = now()
+ WHERE code = :code
+ """, nativeQuery = true)
int incrementClicksByCodeNative(@Param("code") String code);
+
}
\ No newline at end of file
diff --git a/src/main/java/org/decepticons/linkshortener/api/v1/controller/unversioned/GlobalExceptionHandlerController.java b/src/main/java/org/decepticons/linkshortener/api/v1/controller/unversioned/GlobalExceptionHandlerController.java
index 949062f..0c61b9b 100644
--- a/src/main/java/org/decepticons/linkshortener/api/v1/controller/unversioned/GlobalExceptionHandlerController.java
+++ b/src/main/java/org/decepticons/linkshortener/api/v1/controller/unversioned/GlobalExceptionHandlerController.java
@@ -1,4 +1,4 @@
-package org.decepticons.linkshortener.api.controller;
+package org.decepticons.linkshortener.api.v1.controller.unversioned;
import java.time.Instant;
import java.util.Map;
diff --git a/src/test/java/org/decepticons/linkshortener/api/controller/LinkCrudControllerTest.java b/src/test/java/org/decepticons/linkshortener/api/controller/LinkCrudControllerTest.java
index af33ade..475783a 100644
--- a/src/test/java/org/decepticons/linkshortener/api/controller/LinkCrudControllerTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/controller/LinkCrudControllerTest.java
@@ -1,6 +1,18 @@
package org.decepticons.linkshortener.api.controller;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+import java.util.UUID;
import org.decepticons.linkshortener.api.dto.LinkResponseDto;
import org.decepticons.linkshortener.api.dto.UpdateLinkExpirationRequestDto;
import org.decepticons.linkshortener.api.dto.UrlRequestDto;
@@ -24,13 +36,6 @@
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.List;
-import java.util.UUID;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@@ -59,12 +64,11 @@ void setUp() {
@Test
@DisplayName("Create Short Link - Success")
- void testCreateShortLink_Success() {
+ void testCreateShortLinkSuccess() {
UrlRequestDto urlRequestDto = new UrlRequestDto();
urlRequestDto.setUrl("https://example.com/some/long/url");
User fakeUser = new User();
-// fakeUser.setId(UUID.randomUUID());
fakeUser.setUsername("someName");
@@ -99,7 +103,7 @@ void testCreateShortLink_Success() {
@Test
@DisplayName("Create Short Link - User Not Found")
- void testCreateShortLink_UserNotFound_Controller() {
+ void testCreateShortLinkUserNotFoundController() {
UrlRequestDto urlRequestDto = new UrlRequestDto();
urlRequestDto.setUrl("https://example.com/some/long/url");
@@ -121,7 +125,7 @@ void testCreateShortLink_UserNotFound_Controller() {
@Test
@DisplayName("Get All My Links - Success")
- void getAllMyLinks_SUCCESS() {
+ void getAllMyLinksSuccess() {
int page = 0;
int size = 10;
@@ -165,7 +169,7 @@ void getAllMyLinks_SUCCESS() {
@Test
@DisplayName("Get All My Active Links - Success")
- void getAllMyActiveLinks_SUCCESS() {
+ void getAllMyActiveLinksSuccess() {
int page = 0;
int size = 10;
@@ -214,7 +218,7 @@ void getAllMyActiveLinks_SUCCESS() {
@Test
@DisplayName("Delete Link - Success")
- void testDeleteLink_SUCCESS() {
+ void testDeleteLinkSuccess() {
UUID linkId = UUID.randomUUID();
String mockCode = "abc123";
@@ -233,7 +237,7 @@ void testDeleteLink_SUCCESS() {
@Test
@DisplayName("Update Link Expiration Date - Success")
- void testUpdateLinkExpirationDate(){
+ void testUpdateLinkExpirationDate() {
String code = "abc123";
@@ -253,7 +257,8 @@ void testUpdateLinkExpirationDate(){
UUID.randomUUID()
));
- ResponseEntity response = linkController.updateLinkExpiration(requestDto, code);
+ ResponseEntity response =
+ linkController.updateLinkExpiration(requestDto, code);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
diff --git a/src/test/java/org/decepticons/linkshortener/api/exceptions/GlobalExceptionHandlerTest.java b/src/test/java/org/decepticons/linkshortener/api/exceptions/GlobalExceptionHandlerTest.java
index 925b84c..50ded9a 100644
--- a/src/test/java/org/decepticons/linkshortener/api/exceptions/GlobalExceptionHandlerTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/exceptions/GlobalExceptionHandlerTest.java
@@ -1,8 +1,17 @@
package org.decepticons.linkshortener.api.exceptions;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
import io.jsonwebtoken.JwtException;
-import org.decepticons.linkshortener.api.controller.GlobalExceptionHandlerController;
-import org.decepticons.linkshortener.api.exception.*;
+import org.decepticons.linkshortener.api.exception.ExpiredTokenException;
+import org.decepticons.linkshortener.api.exception.InvalidPasswordException;
+import org.decepticons.linkshortener.api.exception.InvalidTokenException;
+import org.decepticons.linkshortener.api.exception.UserAlreadyExistsException;
+import org.decepticons.linkshortener.api.exception.UserNotFoundException;
+import org.decepticons.linkshortener.api.v1.controller.unversioned.GlobalExceptionHandlerController;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -13,10 +22,6 @@
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
/**
* Unit tests for the GlobalExceptionHandler.
* These tests ensure that all custom exceptions are handled correctly
@@ -25,7 +30,7 @@
@DisplayName("Global Exception Handler Tests")
class GlobalExceptionHandlerTest {
- private MockMvc mockMvc;
+ private MockMvc mockMvc;
@BeforeEach
void setup() {
diff --git a/src/test/java/org/decepticons/linkshortener/api/model/RoleTest.java b/src/test/java/org/decepticons/linkshortener/api/model/RoleTest.java
index 050b6d1..a1511fa 100644
--- a/src/test/java/org/decepticons/linkshortener/api/model/RoleTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/model/RoleTest.java
@@ -1,9 +1,12 @@
package org.decepticons.linkshortener.api.model;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
@DisplayName("Role Class Unit Tests")
class RoleTest {
diff --git a/src/test/java/org/decepticons/linkshortener/api/security/controller/AuthControllerTest.java b/src/test/java/org/decepticons/linkshortener/api/security/controller/AuthControllerTest.java
index c830d4b..21f1bef 100644
--- a/src/test/java/org/decepticons/linkshortener/api/security/controller/AuthControllerTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/security/controller/AuthControllerTest.java
@@ -1,5 +1,10 @@
package org.decepticons.linkshortener.api.security.controller;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
import com.fasterxml.jackson.databind.ObjectMapper;
import org.decepticons.linkshortener.api.dto.AuthRequestDto;
import org.decepticons.linkshortener.api.dto.RegistrationRequestDto;
@@ -22,14 +27,6 @@
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
-import java.util.UUID;
-
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
/**
* Integration tests for the AuthController using Testcontainers.
* These tests verify the full application stack's behavior for authentication
@@ -40,174 +37,176 @@
@DisplayName("Auth Controller Integration Tests with Testcontainers")
class AuthControllerTest {
- private MockMvc mockMvc;
+ private MockMvc mockMvc;
- @Autowired
- private WebApplicationContext webApplicationContext;
+ @Autowired
+ private WebApplicationContext webApplicationContext;
- @Autowired
- private UserRepository userRepository;
+ @Autowired
+ private UserRepository userRepository;
- @Autowired
- private PasswordEncoder passwordEncoder;
+ @Autowired
+ private PasswordEncoder passwordEncoder;
- private final ObjectMapper objectMapper = new ObjectMapper();
+ private final ObjectMapper objectMapper = new ObjectMapper();
- // Use a PostgreSQL container for the tests
- @Container
- public static PostgreSQLContainer> postgresContainer = new PostgreSQLContainer<>("postgres:16-alpine")
- .withDatabaseName("testdb")
- .withUsername("testuser")
- .withPassword("testpass");
+ // Use a PostgreSQL container for the tests
+ @Container
+ public static PostgreSQLContainer> postgresContainer = new PostgreSQLContainer<>(
+ "postgres:16-alpine")
+ .withDatabaseName("testdb")
+ .withUsername("testuser")
+ .withPassword("testpass");
- // Dynamically set the data source properties using the running container's details
- @DynamicPropertySource
+ // Dynamically set the data source properties using the running container's details
+ @DynamicPropertySource
static void setDatasourceProperties(DynamicPropertyRegistry registry) {
- registry.add("spring.datasource.url", postgresContainer::getJdbcUrl);
- registry.add("spring.datasource.username", postgresContainer::getUsername);
- registry.add("spring.datasource.password", postgresContainer::getPassword);
- registry.add("spring.datasource.driver-class-name", () -> "org.postgresql.Driver");
- registry.add("spring.flyway.enabled", () -> "true");
- registry.add("spring.flyway.locations", () -> "classpath:db/migration/postgresql");
- }
-
- @BeforeEach
- void setup() {
- mockMvc = MockMvcBuilders
- .webAppContextSetup(webApplicationContext)
- .build();
- userRepository.deleteAll(); // Clean up before each test
- }
-
- @Test
- @DisplayName("given a new user, when registering, then the user is successfully created and returns 200 OK")
- void givenNewUser_whenRegistering_thenUserIsCreated() throws Exception {
- // Given a new registration request
- RegistrationRequestDto requestDto = new RegistrationRequestDto();
- requestDto.setUsername("testuser_new");
- requestDto.setPassword("Password123!");
-
- // When the registration endpoint is called
- mockMvc.perform(post("/api/v1/auth/register")
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(requestDto)))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$").value("testuser_new"));
-
- // Then verify the user exists in the database
- assertTrue(userRepository.findByUsername("testuser_new").isPresent());
- }
-
- @Test
- @DisplayName("given an existing user, when registering, then returns 409 Conflict")
- void givenExistingUser_whenRegistering_thenReturnsConflict() throws Exception {
- // Given an existing user in the database
- User existingUser = new User();
- existingUser.setUsername("existinguser");
- existingUser.setPasswordHash(passwordEncoder.encode("Password123!"));
- userRepository.save(existingUser);
-
- // When attempting to register the same user again
- RegistrationRequestDto requestDto = new RegistrationRequestDto();
- requestDto.setUsername("existinguser");
- requestDto.setPassword("Password123!");
-
- // Then an exception should be thrown
- mockMvc.perform(post("/api/v1/auth/register")
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(requestDto)))
- .andExpect(status().isConflict())
- .andExpect(jsonPath("$.message").value("User with username 'existinguser' already exists"));
- }
-
- @Test
- @DisplayName("given valid credentials, when logging in, then returns JWT tokens")
- void givenValidCredentials_whenLoggingIn_thenReturnsJwtTokens() throws Exception {
- // Given a registered user
- RegistrationRequestDto registrationRequestDto = new RegistrationRequestDto();
- registrationRequestDto.setUsername("loginuser");
- registrationRequestDto.setPassword("Password123!");
- mockMvc.perform(post("/api/v1/auth/register")
+ registry.add("spring.datasource.url", postgresContainer::getJdbcUrl);
+ registry.add("spring.datasource.username", postgresContainer::getUsername);
+ registry.add("spring.datasource.password", postgresContainer::getPassword);
+ registry.add("spring.datasource.driver-class-name", () -> "org.postgresql.Driver");
+ registry.add("spring.flyway.enabled", () -> "true");
+ registry.add("spring.flyway.locations", () -> "classpath:db/migration/postgresql");
+ }
+
+ @BeforeEach
+ void setup() {
+ mockMvc = MockMvcBuilders
+ .webAppContextSetup(webApplicationContext)
+ .build();
+ userRepository.deleteAll(); // Clean up before each test
+ }
+
+ @Test
+ @DisplayName("given a new user, when registering, then the user "
+ + "is successfully created and returns 200 OK")
+ void givenNewUser_whenRegistering_thenUserIsCreated() throws Exception {
+ // Given a new registration request
+ RegistrationRequestDto requestDto = new RegistrationRequestDto();
+ requestDto.setUsername("testuser_new");
+ requestDto.setPassword("Password123!");
+
+ // When the registration endpoint is called
+ mockMvc.perform(post("/api/v1/auth/register")
.contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(registrationRequestDto)));
-
- // When logging in with valid credentials
- AuthRequestDto loginRequestDto = new AuthRequestDto();
- loginRequestDto.setUsername("loginuser");
- loginRequestDto.setPassword("Password123!");
-
- // Then the response should contain access and refresh tokens
- mockMvc.perform(post("/api/v1/auth/login")
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(loginRequestDto)))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.username").value("loginuser"))
- .andExpect(jsonPath("$.accessToken").exists())
- .andExpect(jsonPath("$.refreshToken").exists());
- }
-
- @Test
- @DisplayName("given invalid password, when logging in, then returns 401 Unauthorized")
- void givenInvalidPassword_whenLoggingIn_thenReturnsUnauthorized() throws Exception {
- // Given a registered user
- RegistrationRequestDto registrationRequestDto = new RegistrationRequestDto();
- registrationRequestDto.setUsername("badpassuser");
- registrationRequestDto.setPassword("Password123!");
- mockMvc.perform(post("/api/v1/auth/register")
+ .content(objectMapper.writeValueAsString(requestDto)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$").value("testuser_new"));
+
+ // Then verify the user exists in the database
+ assertTrue(userRepository.findByUsername("testuser_new").isPresent());
+ }
+
+ @Test
+ @DisplayName("given an existing user, when registering, then returns 409 Conflict")
+ void givenExistingUser_whenRegistering_thenReturnsConflict() throws Exception {
+ // Given an existing user in the database
+ User existingUser = new User();
+ existingUser.setUsername("existinguser");
+ existingUser.setPasswordHash(passwordEncoder.encode("Password123!"));
+ userRepository.save(existingUser);
+
+ // When attempting to register the same user again
+ RegistrationRequestDto requestDto = new RegistrationRequestDto();
+ requestDto.setUsername("existinguser");
+ requestDto.setPassword("Password123!");
+
+ // Then an exception should be thrown
+ mockMvc.perform(post("/api/v1/auth/register")
.contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(registrationRequestDto)));
-
- // When logging in with an invalid password
- AuthRequestDto loginRequestDto = new AuthRequestDto();
- loginRequestDto.setUsername("badpassuser");
- loginRequestDto.setPassword("WrongPassword");
-
- // Then the response should be 401 Unauthorized
- mockMvc.perform(post("/api/v1/auth/login")
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(loginRequestDto)))
- .andExpect(status().isUnauthorized());
- }
-
- @Test
- @DisplayName("given a valid refresh token, when refreshing, then returns new JWT tokens")
- void givenValidRefreshToken_whenRefreshing_thenReturnsNewTokens() throws Exception {
- // Given a registered user with a valid refresh token
- RegistrationRequestDto registrationRequestDto = new RegistrationRequestDto();
- registrationRequestDto.setUsername("refreshuser");
- registrationRequestDto.setPassword("Password123!");
- mockMvc.perform(post("/api/v1/auth/register")
+ .content(objectMapper.writeValueAsString(requestDto)))
+ .andExpect(status().isConflict())
+ .andExpect(jsonPath("$.message").value("User with username 'existinguser' already exists"));
+ }
+
+ @Test
+ @DisplayName("given valid credentials, when logging in, then returns JWT tokens")
+ void givenValidCredentials_whenLoggingIn_thenReturnsJwtTokens() throws Exception {
+ // Given a registered user
+ RegistrationRequestDto registrationRequestDto = new RegistrationRequestDto();
+ registrationRequestDto.setUsername("loginuser");
+ registrationRequestDto.setPassword("Password123!");
+ mockMvc.perform(post("/api/v1/auth/register")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(registrationRequestDto)));
+
+ // When logging in with valid credentials
+ AuthRequestDto loginRequestDto = new AuthRequestDto();
+ loginRequestDto.setUsername("loginuser");
+ loginRequestDto.setPassword("Password123!");
+
+ // Then the response should contain access and refresh tokens
+ mockMvc.perform(post("/api/v1/auth/login")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(loginRequestDto)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.username").value("loginuser"))
+ .andExpect(jsonPath("$.accessToken").exists())
+ .andExpect(jsonPath("$.refreshToken").exists());
+ }
+
+ @Test
+ @DisplayName("given invalid password, when logging in, then returns 401 Unauthorized")
+ void givenInvalidPassword_whenLoggingIn_thenReturnsUnauthorized() throws Exception {
+ // Given a registered user
+ RegistrationRequestDto registrationRequestDto = new RegistrationRequestDto();
+ registrationRequestDto.setUsername("badpassuser");
+ registrationRequestDto.setPassword("Password123!");
+ mockMvc.perform(post("/api/v1/auth/register")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(registrationRequestDto)));
+
+ // When logging in with an invalid password
+ AuthRequestDto loginRequestDto = new AuthRequestDto();
+ loginRequestDto.setUsername("badpassuser");
+ loginRequestDto.setPassword("WrongPassword");
+
+ // Then the response should be 401 Unauthorized
+ mockMvc.perform(post("/api/v1/auth/login")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(loginRequestDto)))
+ .andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ @DisplayName("given a valid refresh token, when refreshing, then returns new JWT tokens")
+ void givenValidRefreshToken_whenRefreshing_thenReturnsNewTokens() throws Exception {
+ // Given a registered user with a valid refresh token
+ RegistrationRequestDto registrationRequestDto = new RegistrationRequestDto();
+ registrationRequestDto.setUsername("refreshuser");
+ registrationRequestDto.setPassword("Password123!");
+ mockMvc.perform(post("/api/v1/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(registrationRequestDto)));
- AuthRequestDto loginRequestDto = new AuthRequestDto();
- loginRequestDto.setUsername("refreshuser");
- loginRequestDto.setPassword("Password123!");
- MvcResult result = mockMvc.perform(post("/api/v1/auth/login")
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(loginRequestDto)))
- .andReturn();
-
- String response = result.getResponse().getContentAsString();
- String refreshToken = objectMapper.readTree(response).get("refreshToken").asText();
-
- // When refreshing the token
- mockMvc.perform(post("/api/v1/auth/refresh")
- .header("Authorization", "Bearer " + refreshToken))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.accessToken").exists())
- .andExpect(jsonPath("$.refreshToken").exists());
- }
-
- @Test
- @DisplayName("given an invalid refresh token, when refreshing, then returns 401 Unauthorized")
- void givenInvalidRefreshToken_whenRefreshing_thenReturnsUnauthorized() throws Exception {
- // Given an invalid token
- String invalidToken = "invalid.token.123";
-
- // When refreshing with the invalid token
- mockMvc.perform(post("/api/v1/auth/refresh")
- .header("Authorization", "Bearer " + invalidToken))
- .andExpect(status().isUnauthorized());
- }
+ AuthRequestDto loginRequestDto = new AuthRequestDto();
+ loginRequestDto.setUsername("refreshuser");
+ loginRequestDto.setPassword("Password123!");
+ MvcResult result = mockMvc.perform(post("/api/v1/auth/login")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(loginRequestDto)))
+ .andReturn();
+
+ String response = result.getResponse().getContentAsString();
+ String refreshToken = objectMapper.readTree(response).get("refreshToken").asText();
+
+ // When refreshing the token
+ mockMvc.perform(post("/api/v1/auth/refresh")
+ .header("Authorization", "Bearer " + refreshToken))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.accessToken").exists())
+ .andExpect(jsonPath("$.refreshToken").exists());
+ }
+
+ @Test
+ @DisplayName("given an invalid refresh token, when refreshing, then returns 401 Unauthorized")
+ void givenInvalidRefreshToken_whenRefreshing_thenReturnsUnauthorized() throws Exception {
+ // Given an invalid token
+ String invalidToken = "invalid.token.123";
+
+ // When refreshing with the invalid token
+ mockMvc.perform(post("/api/v1/auth/refresh")
+ .header("Authorization", "Bearer " + invalidToken))
+ .andExpect(status().isUnauthorized());
+ }
}
\ No newline at end of file
diff --git a/src/test/java/org/decepticons/linkshortener/api/security/jwt/JwtAuthenticationFilterTest.java b/src/test/java/org/decepticons/linkshortener/api/security/jwt/JwtAuthenticationFilterTest.java
index 06787a3..e1c8d2d 100644
--- a/src/test/java/org/decepticons/linkshortener/api/security/jwt/JwtAuthenticationFilterTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/security/jwt/JwtAuthenticationFilterTest.java
@@ -1,9 +1,20 @@
package org.decepticons.linkshortener.api.security.jwt;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.ArrayList;
import org.decepticons.linkshortener.api.exception.InvalidTokenException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@@ -17,13 +28,6 @@
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
-import java.io.IOException;
-import java.util.ArrayList;
-
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.mockito.Mockito.*;
/**
* Unit tests for the JwtAuthenticationFilter.
@@ -52,7 +56,8 @@ class JwtAuthenticationFilterTest {
@InjectMocks
private JwtAuthenticationFilter jwtAuthenticationFilter;
- private final String authHeader = "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0dXNlciJ9.invalid-signature";
+ private final String authHeader
+ = "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0dXNlciJ9.invalid-signature";
private UserDetails userDetails;
@BeforeEach
@@ -64,7 +69,8 @@ void setUp() {
@Test
@DisplayName("given a valid token, when filtering, then authenticates the user successfully")
- void givenValidToken_whenFiltering_thenAuthenticatesSuccessfully() throws ServletException, IOException {
+ void givenValidToken_whenFiltering_thenAuthenticatesSuccessfully()
+ throws ServletException, IOException {
// Given
when(request.getHeader("Authorization")).thenReturn(authHeader);
when(jwtTokenUtil.extractUsername(anyString())).thenReturn("testuser");
@@ -96,7 +102,8 @@ void givenNoHeader_whenFiltering_thenSkipsAuthentication() throws ServletExcepti
@Test
@DisplayName("given a malformed header, when filtering, then throws InvalidTokenException")
- void givenMalformedHeader_whenFiltering_thenThrowsInvalidTokenException() throws ServletException, IOException {
+ void givenMalformedHeader_whenFiltering_thenThrowsInvalidTokenException()
+ throws ServletException, IOException {
// Given
when(request.getHeader("Authorization")).thenReturn("MalformedToken");
@@ -110,7 +117,8 @@ void givenMalformedHeader_whenFiltering_thenThrowsInvalidTokenException() throws
@Test
@DisplayName("given an invalid token, when filtering, then throws InvalidTokenException")
- void givenInvalidToken_whenFiltering_thenThrowsInvalidTokenException() throws ServletException, IOException {
+ void givenInvalidToken_whenFiltering_thenThrowsInvalidTokenException()
+ throws ServletException, IOException {
// Given
when(request.getHeader("Authorization")).thenReturn(authHeader);
when(jwtTokenUtil.extractUsername(anyString())).thenReturn("testuser");
@@ -125,8 +133,10 @@ void givenInvalidToken_whenFiltering_thenThrowsInvalidTokenException() throws Se
}
@Test
- @DisplayName("given a valid token but a nonexistent user, when filtering, then skips authentication")
- void givenTokenForNonexistentUser_whenFiltering_thenSkipsAuthentication() throws ServletException, IOException {
+ @DisplayName("given a valid token but a nonexistent user, "
+ + "when filtering, then skips authentication")
+ void givenTokenForNonexistentUser_whenFiltering_thenSkipsAuthentication()
+ throws ServletException, IOException {
// Given
when(request.getHeader("Authorization")).thenReturn(authHeader);
when(jwtTokenUtil.extractUsername(anyString())).thenReturn("nonexistentuser");
diff --git a/src/test/java/org/decepticons/linkshortener/api/security/jwt/JwtTokenUtilTest.java b/src/test/java/org/decepticons/linkshortener/api/security/jwt/JwtTokenUtilTest.java
index 77e15d8..642d18b 100644
--- a/src/test/java/org/decepticons/linkshortener/api/security/jwt/JwtTokenUtilTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/security/jwt/JwtTokenUtilTest.java
@@ -1,19 +1,23 @@
package org.decepticons.linkshortener.api.security.jwt;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
+import java.util.Date;
+import javax.crypto.SecretKey;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.security.core.userdetails.UserDetails;
-import java.util.Date;
-import javax.crypto.SecretKey;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
/**
* Unit tests for the JwtTokenUtil class.
diff --git a/src/test/java/org/decepticons/linkshortener/api/security/model/CustomUserDetailsTest.java b/src/test/java/org/decepticons/linkshortener/api/security/model/CustomUserDetailsTest.java
index 2c93b10..6ebb2f1 100644
--- a/src/test/java/org/decepticons/linkshortener/api/security/model/CustomUserDetailsTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/security/model/CustomUserDetailsTest.java
@@ -1,12 +1,16 @@
package org.decepticons.linkshortener.api.security.model;
-import org.decepticons.linkshortener.api.model.User;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Collections;
import org.decepticons.linkshortener.api.model.Role;
+import org.decepticons.linkshortener.api.model.User;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-import java.util.Collections;
-import static org.junit.jupiter.api.Assertions.*;
@DisplayName("CustomUserDetails Unit Tests")
class CustomUserDetailsTest {
diff --git a/src/test/java/org/decepticons/linkshortener/api/security/service/impl/AuthServiceImplTest.java b/src/test/java/org/decepticons/linkshortener/api/security/service/impl/AuthServiceImplTest.java
index d7204fa..41bf74d 100644
--- a/src/test/java/org/decepticons/linkshortener/api/security/service/impl/AuthServiceImplTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/security/service/impl/AuthServiceImplTest.java
@@ -1,10 +1,22 @@
package org.decepticons.linkshortener.api.security.service.impl;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import java.util.Collections;
+import java.util.Optional;
import org.decepticons.linkshortener.api.exception.InvalidTokenException;
import org.decepticons.linkshortener.api.exception.UserAlreadyExistsException;
-import org.decepticons.linkshortener.api.model.User;
import org.decepticons.linkshortener.api.model.Role;
+import org.decepticons.linkshortener.api.model.User;
import org.decepticons.linkshortener.api.model.UserStatus;
import org.decepticons.linkshortener.api.repository.RoleRepository;
import org.decepticons.linkshortener.api.repository.UserRepository;
@@ -21,15 +33,10 @@
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
-import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.crypto.password.PasswordEncoder;
-import java.util.Collections;
-import java.util.Optional;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.*;
/**
* Unit tests for the AuthServiceImpl class.
@@ -111,7 +118,8 @@ void givenInvalidCredentials_whenLogin_thenThrowsBadCredentialsException() {
.thenThrow(new BadCredentialsException("Invalid credentials"));
// When & Then
- assertThrows(BadCredentialsException.class, () -> authService.login("testuser", "wrongpassword"));
+ assertThrows(BadCredentialsException.class,
+ () -> authService.login("testuser", "wrongpassword"));
verify(authenticationManager).authenticate(any(UsernamePasswordAuthenticationToken.class));
verify(userAuthService, never()).findByUsername(anyString());
}
@@ -135,7 +143,8 @@ void givenValidRefreshToken_whenRefreshing_thenReturnsUser() {
}
@Test
- @DisplayName("given a null or malformed header, when refreshing, then throws InvalidTokenException")
+ @DisplayName("given a null or malformed header, "
+ + "when refreshing, then throws InvalidTokenException")
void givenMalformedHeader_whenRefreshing_thenThrowsInvalidTokenException() {
// When & Then
assertThrows(InvalidTokenException.class, () -> authService.refreshToken(null));
diff --git a/src/test/java/org/decepticons/linkshortener/api/security/service/impl/UserAuthServiceImplTest.java b/src/test/java/org/decepticons/linkshortener/api/security/service/impl/UserAuthServiceImplTest.java
index 0340dd3..f142b4d 100644
--- a/src/test/java/org/decepticons/linkshortener/api/security/service/impl/UserAuthServiceImplTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/security/service/impl/UserAuthServiceImplTest.java
@@ -1,6 +1,15 @@
package org.decepticons.linkshortener.api.security.service.impl;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import java.util.Optional;
import org.decepticons.linkshortener.api.exception.InvalidPasswordException;
import org.decepticons.linkshortener.api.exception.UserAlreadyExistsException;
import org.decepticons.linkshortener.api.exception.UserNotFoundException;
@@ -15,13 +24,6 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.crypto.password.PasswordEncoder;
-import java.util.Optional;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.*;
-
/**
* Unit tests for the UserAuthServiceImpl class.
* These tests focus on the user registration and retrieval logic using mocks for dependencies.
@@ -95,7 +97,8 @@ void givenInvalidPassword_whenRegistering_thenThrowsInvalidPasswordException() {
when(userRepository.existsByUsername(anyString())).thenReturn(false);
// When & Then
- assertThrows(InvalidPasswordException.class, () -> userAuthService.registerUser(invalidPasswordUser));
+ assertThrows(InvalidPasswordException.class,
+ () -> userAuthService.registerUser(invalidPasswordUser));
verify(userRepository, never()).save(any(User.class));
}
@@ -115,13 +118,15 @@ void givenExistingUsername_whenFindingByUsername_thenReturnsUser() {
}
@Test
- @DisplayName("given a nonexistent username, when finding by username, then throws UserNotFoundException")
+ @DisplayName("given a nonexistent username, "
+ + "when finding by username, then throws UserNotFoundException")
void givenNonexistentUsername_whenFindingByUsername_thenThrowsUserNotFoundException() {
// Given
when(userRepository.findByUsername("nonexistentuser")).thenReturn(Optional.empty());
// When & Then
- assertThrows(UserNotFoundException.class, () -> userAuthService.findByUsername("nonexistentuser"));
+ assertThrows(UserNotFoundException.class,
+ () -> userAuthService.findByUsername("nonexistentuser"));
verify(userRepository).findByUsername("nonexistentuser");
}
}
\ No newline at end of file
diff --git a/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplMethodsTest.java b/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplMethodsTest.java
index 958cf84..72edfb9 100644
--- a/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplMethodsTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplMethodsTest.java
@@ -1,5 +1,16 @@
package org.decepticons.linkshortener.api.service;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
import org.decepticons.linkshortener.api.dto.LinkResponseDto;
import org.decepticons.linkshortener.api.model.Link;
import org.decepticons.linkshortener.api.model.LinkStatus;
@@ -11,25 +22,22 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.*;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
-import org.junit.jupiter.api.extension.ExtendWith;
-
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
-
-import java.util.List;
-import java.util.Optional;
-import java.util.UUID;
+import org.springframework.test.util.ReflectionTestUtils;
+/** * Unit tests for the LinkServiceImpl class.
+ * These tests focus on the link management logic using mocks for dependencies.
+ */
@ExtendWith(MockitoExtension.class)
public class LinkServiceImplMethodsTest {
@Mock
@@ -54,7 +62,6 @@ void setUpSecurityContext() {
User testUser = new User();
-// testUser.setId(testUserId);
testUser.setUsername(testUsername);
}
@@ -65,7 +72,7 @@ void clearSecurityContext() {
@Test
@DisplayName("Get All My Links - Success")
- void getAllMyLinks_SUCCESS() {
+ void getAllMyLinksSuccess() {
Link link1 = new Link();
link1.setOwner(new User());
@@ -85,7 +92,7 @@ void getAllMyLinks_SUCCESS() {
@Test
@DisplayName("Get All My Active Links - Success")
- void getAllMyActiveLinks_SUCCESS() {
+ void getAllMyActiveLinksSuccess() {
Link link1 = new Link();
link1.setStatus(LinkStatus.ACTIVE);
link1.setOwner(new User());
@@ -93,7 +100,8 @@ void getAllMyActiveLinks_SUCCESS() {
Page mockPage = new PageImpl<>(List.of(link1));
when(userServiceImpl.getCurrentUserId()).thenReturn(testUserId);
- when(linkRepository.findAllByOwnerIdAndStatus(eq(testUserId), eq(LinkStatus.ACTIVE), any(Pageable.class)))
+ when(linkRepository.findAllByOwnerIdAndStatus(eq(testUserId),
+ eq(LinkStatus.ACTIVE), any(Pageable.class)))
.thenReturn(mockPage);
Page result = linkService.getAllMyActiveLinks(0, 10);
@@ -105,14 +113,14 @@ void getAllMyActiveLinks_SUCCESS() {
@Test
@DisplayName("Delete Link - Success")
- void deleteLink_SUCCESS() {
- UUID linkId = UUID.randomUUID();
+ void deleteLinkSuccess() {
User owner = new User();
-// owner.setId(testUserId);
+ ReflectionTestUtils.setField(owner, "id", testUserId);
owner.setUsername(testUsername);
Link link = new Link();
link.setCode("abc123");
link.setOwner(owner);
+ UUID linkId = UUID.randomUUID();
when(userServiceImpl.getCurrentUserId()).thenReturn(testUserId);
when(linkRepository.findById(linkId)).thenReturn(Optional.of(link));
diff --git a/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplTest.java b/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplTest.java
index d7d2de2..e6b5956 100644
--- a/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/service/LinkServiceImplTest.java
@@ -1,5 +1,19 @@
package org.decepticons.linkshortener.api.service;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Optional;
+import java.util.UUID;
import org.decepticons.linkshortener.api.dto.LinkResponseDto;
import org.decepticons.linkshortener.api.dto.UrlRequestDto;
import org.decepticons.linkshortener.api.model.Link;
@@ -17,18 +31,9 @@
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.test.util.ReflectionTestUtils;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.Optional;
-import java.util.UUID;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.*;
-
@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class)
class LinkServiceImplTest {
@@ -49,10 +54,9 @@ class LinkServiceImplTest {
@Test
@DisplayName("Link Creation - Success")
- void testLinkCreation_Success() {
+ void testLinkCreationSuccess() {
UrlRequestDto urlRequestDto = new UrlRequestDto();
User user = new User();
-// user.setId(UUID.randomUUID());
urlRequestDto.setUrl("https://www.example.com");
@@ -70,7 +74,12 @@ void testLinkCreation_Success() {
@Test
@DisplayName("Increment of Clicks - Success")
- void incrementOfClicks_Success() {
+ void incrementOfClicksSuccess() {
+ Link link = new Link();
+ link.setOwner(new User());
+ link.setCode("abc123");
+ link.setClicks(0);
+
LinkResponseDto linkResponseDto = new LinkResponseDto(
UUID.randomUUID(),
"abc123",
@@ -81,10 +90,6 @@ void incrementOfClicks_Success() {
"ACTIVE",
UUID.randomUUID()
);
- Link link = new Link();
- link.setOwner(new User());
- link.setCode("abc123");
- link.setClicks(0);
when(linkRepository.incrementClicksByCodeNative("abc123")).thenReturn(1);
when(linkRepository.findByCode("abc123")).thenReturn(Optional.of(link));
@@ -95,10 +100,9 @@ void incrementOfClicks_Success() {
@Test
@DisplayName("Mapping to Response DTO - Success")
- void testMapToResponse_Success() {
+ void testMapToResponseSuccess() {
UUID userId = UUID.randomUUID();
User owner = new User();
-// owner.setId(userId);
Link link = new Link();
link.setCode("abc123");
@@ -122,10 +126,9 @@ void testMapToResponse_Success() {
@Test
@DisplayName("Get Link By Code - Success")
- void getLinkByCode_Success() {
+ void getLinkByCodeSuccess() {
User owner = new User();
-// owner.setId(UUID.randomUUID());
Link link = new Link();
link.setOwner(owner);
@@ -151,9 +154,15 @@ void getLinkByCode_Success() {
@Test
@DisplayName("Deactivate Link - Success")
- void deactivateLink_Success() {
+ void deactivateLinkSuccess() {
User owner = new User();
+
+ Link link = new Link();
+ link.setOwner(owner);
+ link.setCode("abc123");
+ link.setStatus(LinkStatus.ACTIVE);
+
LinkResponseDto linkResponseDto = new LinkResponseDto(
UUID.randomUUID(),
"abc123",
@@ -165,12 +174,6 @@ void deactivateLink_Success() {
UUID.randomUUID()
);
- Link link = new Link();
- link.setOwner(owner);
- link.setCode("abc123");
- link.setStatus(LinkStatus.ACTIVE);
-
-
when(linkRepository.findByCode("abc123")).thenReturn(Optional.of(link));
when(linkRepository.save(any(Link.class))).thenAnswer(i -> i.getArguments()[0]);
@@ -182,7 +185,7 @@ void deactivateLink_Success() {
@Test
@DisplayName("Validate Link - Success")
- void validateLink_Success() {
+ void validateLinkSuccess() {
LinkResponseDto linkResponseDto = new LinkResponseDto(
UUID.randomUUID(),
"abc123",
@@ -201,11 +204,11 @@ void validateLink_Success() {
@Test
@DisplayName("Update Link Expiration - Success")
- void testUpdateLinkExpiration(){
+ void testUpdateLinkExpiration() {
String code = "abc123";
User owner = new User();
owner.setUsername("testUser");
-// owner.setId(UUID.randomUUID());
+ ReflectionTestUtils.setField(owner, "id", UUID.randomUUID());
Link link = new Link();
link.setCode(code);
diff --git a/src/test/java/org/decepticons/linkshortener/api/service/UserServiceImplTest.java b/src/test/java/org/decepticons/linkshortener/api/service/UserServiceImplTest.java
index c0f47be..5003d0f 100644
--- a/src/test/java/org/decepticons/linkshortener/api/service/UserServiceImplTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/service/UserServiceImplTest.java
@@ -1,5 +1,14 @@
package org.decepticons.linkshortener.api.service;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.when;
+
+import java.util.Optional;
+import java.util.UUID;
import org.decepticons.linkshortener.api.exception.NoSuchUserFoundInTheSystemException;
import org.decepticons.linkshortener.api.model.User;
import org.decepticons.linkshortener.api.repository.UserRepository;
@@ -12,18 +21,14 @@
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
-import org.springframework.security.core.context.SecurityContextHolder;
-
-import java.util.List;
-import java.util.Optional;
-import java.util.UUID;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.when;
+import org.springframework.test.util.ReflectionTestUtils;
+/**
+ * Unit tests for the UserServiceImpl class.
+ * These tests focus on the user retrieval logic using mocks for dependencies.
+ */
@ExtendWith(MockitoExtension.class)
public class UserServiceImplTest {
@Mock
@@ -54,9 +59,8 @@ void tearDown() {
@Test
@DisplayName("Test getCurrentUser returns correct User")
- void testGetCurrentUser_Success() {
+ void testGetCurrentUserSuccess() {
User fakeUser = new User();
-// fakeUser.setId(UUID.randomUUID());
fakeUser.setUsername("testuser");
when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(fakeUser));
@@ -69,10 +73,10 @@ void testGetCurrentUser_Success() {
@Test
@DisplayName("Test getCurrentUserId returns correct UUID")
- void testGetCurrentUserId_Success() {
+ void testGetCurrentUserIdSuccess() {
UUID fakeId = UUID.randomUUID();
User fakeUser = new User();
-// fakeUser.setId(fakeId);
+ ReflectionTestUtils.setField(fakeUser, "id", fakeId);
fakeUser.setUsername("testuser");
when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(fakeUser));
@@ -84,7 +88,7 @@ void testGetCurrentUserId_Success() {
@Test
@DisplayName("Test getCurrentUser throws exception when user not found")
- void testGetCurrentUser_UserNotFound() {
+ void testGetCurrentUserUserNotFound() {
when(userRepository.findByUsername("testuser")).thenReturn(Optional.empty());
NoSuchUserFoundInTheSystemException ex = assertThrows(
diff --git a/src/test/java/org/decepticons/linkshortener/api/util/AuthMapperTest.java b/src/test/java/org/decepticons/linkshortener/api/util/AuthMapperTest.java
index 70503ba..178e322 100644
--- a/src/test/java/org/decepticons/linkshortener/api/util/AuthMapperTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/util/AuthMapperTest.java
@@ -1,17 +1,19 @@
package org.decepticons.linkshortener.api.util;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.util.Collections;
+import java.util.List;
import org.decepticons.linkshortener.api.dto.AuthResponseDto;
import org.decepticons.linkshortener.api.dto.RegistrationRequestDto;
-import org.decepticons.linkshortener.api.model.User;
import org.decepticons.linkshortener.api.model.Role;
+import org.decepticons.linkshortener.api.model.User;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.security.core.GrantedAuthority;
-import java.util.Collections;
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.*;
@DisplayName("AuthMapper Unit Tests")
class AuthMapperTest {
@@ -47,7 +49,8 @@ void shouldMapUserAndTokensToAuthResponseDto() {
String refreshToken = "mocked.refresh.token";
// When
- AuthResponseDto responseDto = AuthMapper.toAuthResponseDto(user, authorities, accessToken, refreshToken);
+ AuthResponseDto responseDto
+ = AuthMapper.toAuthResponseDto(user, authorities, accessToken, refreshToken);
// Then
assertNotNull(responseDto);
diff --git a/src/test/java/org/decepticons/linkshortener/api/util/PasswordValidatorTest.java b/src/test/java/org/decepticons/linkshortener/api/util/PasswordValidatorTest.java
index 61ac15a..e5074e2 100644
--- a/src/test/java/org/decepticons/linkshortener/api/util/PasswordValidatorTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/util/PasswordValidatorTest.java
@@ -1,10 +1,12 @@
package org.decepticons.linkshortener.api.util;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+
/**
* Unit tests for the PasswordValidator class.
* These tests ensure that the password complexity rules are correctly enforced.
diff --git a/src/test/java/org/decepticons/linkshortener/api/v1/controller/CacheControllerTest.java b/src/test/java/org/decepticons/linkshortener/api/v1/controller/CacheControllerTest.java
deleted file mode 100644
index 17cd19d..0000000
--- a/src/test/java/org/decepticons/linkshortener/api/v1/controller/CacheControllerTest.java
+++ /dev/null
@@ -1,254 +0,0 @@
-package org.decepticons.linkshortener.api.v1.controller;
-
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.List;
-import java.util.UUID;
-import org.decepticons.linkshortener.api.dto.LinkResponseDto;
-import org.decepticons.linkshortener.api.dto.UrlRequestDto;
-import org.decepticons.linkshortener.api.exception.NoSuchUserFoundInTheSystemException;
-import org.decepticons.linkshortener.api.model.User;
-import org.decepticons.linkshortener.api.service.CacheInspectionService;
-import org.decepticons.linkshortener.api.service.LinkService;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageImpl;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.context.SecurityContextHolder;
-
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertInstanceOf;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class)
- class CacheControllerTest {
- @Mock
- private CacheInspectionService cacheInspectionService;
-
- @InjectMocks
- private CacheController cacheController;
-
- @Test
- @DisplayName("Inspect Cache - Service Called")
- void inspectCache_SUCCESS() {
-
- cacheController.inspectCache();
-
- verify(cacheInspectionService, times(1)).printCache("shortLinksCache");
- }
-
- @ExtendWith(MockitoExtension.class)
- @DisplayName("Short Link Creation Tests")
- static
- class LinkCrudControllerTest {
-
- @Mock
- private LinkService linkService;
-
-
- @InjectMocks
- private LinkCrudController linkController;
-
-
- @BeforeEach
- void setUp() {
-
- SecurityContextHolder.getContext().setAuthentication(
- new UsernamePasswordAuthenticationToken("someName", null, null)
- );
-
- }
-
- @Test
- @DisplayName("Create Short Link - Success")
- void testCreateShortLink_Success() {
- UrlRequestDto urlRequestDto = new UrlRequestDto();
- urlRequestDto.setUrl("https://example.com/some/long/url");
-
- User fakeUser = new User();
- fakeUser.setId(UUID.randomUUID());
- fakeUser.setUsername("someName");
-
-
- LinkResponseDto fakeResponse = new LinkResponseDto(
- UUID.randomUUID(),
- "abc123",
- urlRequestDto.getUrl(),
- Instant.now(),
- Instant.now().plus(2, ChronoUnit.DAYS),
- 0,
- "ACTIVE",
- fakeUser.getId()
- );
-
- when(linkService.getCurrentUser()).thenReturn(fakeUser);
-
-
- when(linkService.createLink(urlRequestDto, fakeUser)).thenReturn(fakeResponse);
-
-
- ResponseEntity response = linkController.createLink(urlRequestDto);
-
-
- assertEquals(HttpStatus.CREATED, response.getStatusCode());
- assertEquals("abc123", response.getBody().code());
- assertEquals("https://example.com/some/long/url", response.getBody().originalUrl());
-
-
- verify(linkService).getCurrentUser();
- verify(linkService).createLink(urlRequestDto, fakeUser);
-
- }
-
-
- @Test
- @DisplayName("Create Short Link - User Not Found")
- void testCreateShortLink_UserNotFound() {
-
- UrlRequestDto urlRequestDto = new UrlRequestDto();
- urlRequestDto.setUrl("https://example.com/some/long/url");
-
-
- when(linkService.getCurrentUser())
- .thenThrow(new NoSuchUserFoundInTheSystemException("No such user", "someName"));
-
-
- Exception ex = null;
- try {
- linkController.createLink(urlRequestDto);
- }catch (Exception exception){
- ex = exception;
- }
-
- assertNotNull(ex);
- assertInstanceOf(NoSuchUserFoundInTheSystemException.class, ex);
-
- verify(linkService, Mockito.never()).createLink(Mockito.any(), Mockito.any());
- }
-
- @Test
- @DisplayName("Get All My Links - Success")
- void getAllMyLinks_SUCCESS() {
- int page = 0;
- int size = 10;
-
- LinkResponseDto link1 = new LinkResponseDto(
- UUID.randomUUID(),
- "code1",
- "https://example.com/1",
- Instant.now(),
- Instant.now().plusSeconds(3600),
- 0,
- "ACTIVE",
- UUID.randomUUID()
- );
-
- LinkResponseDto link2 = new LinkResponseDto(
- UUID.randomUUID(),
- "code2",
- "https://example.com/2",
- Instant.now(),
- Instant.now().plusSeconds(3600),
- 0,
- "ACTIVE",
- UUID.randomUUID()
- );
-
- Page mockPage = new PageImpl<>(List.of(link1, link2));
-
- when(linkService.getAllMyLinks(page, size)).thenReturn(mockPage);
-
- ResponseEntity> response = linkController.getAllMyLinks(page, size);
-
- assertNotNull(response.getBody());
- assertEquals(2, response.getBody().getContent().size());
- assertEquals("code1", response.getBody().getContent().get(0).code());
- assertEquals("code2", response.getBody().getContent().get(1).code());
-
-
- verify(linkService, times(1)).getAllMyLinks(page, size);
-
- }
-
- @Test
- @DisplayName("Get All My Active Links - Success")
- void getAllMyActiveLinks_SUCCESS() {
- int page = 0;
- int size = 10;
-
-
- LinkResponseDto link1 = new LinkResponseDto(
- UUID.randomUUID(),
- "code1",
- "https://example.com/1",
- Instant.now(),
- Instant.now().plusSeconds(3600),
- 0,
- "ACTIVE",
- UUID.randomUUID()
- );
-
- LinkResponseDto link2 = new LinkResponseDto(
- UUID.randomUUID(),
- "code2",
- "https://example.com/2",
- Instant.now(),
- Instant.now().plusSeconds(3600),
- 0,
- "ACTIVE",
- UUID.randomUUID()
- );
-
- Page mockPage = new PageImpl<>(List.of(link1, link2));
-
-
- when(linkService.getAllMyActiveLinks(page, size)).thenReturn(mockPage);
-
-
- ResponseEntity> response = linkController.getAllMyActiveLinks(page, size);
-
-
- assertNotNull(response.getBody());
- assertEquals(2, response.getBody().getContent().size());
- assertEquals("code1", response.getBody().getContent().get(0).code());
- assertEquals("code2", response.getBody().getContent().get(1).code());
-
-
- verify(linkService, times(1)).getAllMyActiveLinks(page, size);
- }
-
-
-
- @Test
- @DisplayName("Delete Link - Success")
- void testDeleteLink_SUCCESS() {
-
- UUID linkId = UUID.randomUUID();
-
- doNothing().when(linkService).deleteLink(linkId);
-
- ResponseEntity response = linkController.deleteLink(linkId);
-
- assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode());
- assertNull(response.getBody());
-
- verify(linkService, times(1)).deleteLink(linkId);
- }
-
- }
-}
diff --git a/src/test/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudControllerTest.java b/src/test/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudControllerTest.java
index 957385f..0cce143 100644
--- a/src/test/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudControllerTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/v1/controller/LinkCrudControllerTest.java
@@ -1,5 +1,18 @@
package org.decepticons.linkshortener.api.v1.controller;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.time.Instant;
+import java.util.Collections;
+import java.util.UUID;
import org.decepticons.linkshortener.api.dto.LinkResponseDto;
import org.decepticons.linkshortener.api.service.impl.LinkServiceImpl;
import org.junit.jupiter.api.Test;
@@ -15,15 +28,6 @@
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
-import java.time.Instant;
-import java.util.Collections;
-import java.util.UUID;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.*;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
-
@SpringBootTest
@AutoConfigureMockMvc
class LinkCrudControllerTest {
diff --git a/src/test/java/org/decepticons/linkshortener/api/v1/controller/LinkShortenerApplicationTests.java b/src/test/java/org/decepticons/linkshortener/api/v1/controller/LinkShortenerApplicationTests.java
index 1023eed..bc3020c 100644
--- a/src/test/java/org/decepticons/linkshortener/api/v1/controller/LinkShortenerApplicationTests.java
+++ b/src/test/java/org/decepticons/linkshortener/api/v1/controller/LinkShortenerApplicationTests.java
@@ -1,5 +1,9 @@
package org.decepticons.linkshortener.api.v1.controller;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.decepticons.linkshortener.api.security.jwt.JwtAuthenticationFilter;
+import org.decepticons.linkshortener.api.security.jwt.JwtTokenUtil;
import org.decepticons.linkshortener.api.v1.controller.unversioned.HealthController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -7,25 +11,27 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
-import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
@AutoConfigureMockMvc(addFilters = false)
class LinkShortenerApplicationTests {
- @Autowired ApplicationContext context;
+ @Autowired ApplicationContext context;
- @MockitoBean
- org.decepticons.linkshortener.api.security.jwt.JwtAuthenticationFilter jwtFilter;
- @MockitoBean org.decepticons.linkshortener.api.security.jwt.JwtTokenUtil jwtTokenUtil;
+ @MockitoBean
+ JwtAuthenticationFilter jwtFilter;
+ @MockitoBean
+ JwtTokenUtil jwtTokenUtil;
- @Test
- void contextLoads() { assertThat(context).isNotNull(); }
+ @Test
+ void contextLoads() {
+ assertThat(context).isNotNull();
+ }
- @Test
- void healthControllerIsLoaded() {
- assertThat(context.getBean(
- HealthController.class)).isNotNull();
- }
+ @Test
+void healthControllerIsLoaded() {
+ assertThat(context.getBean(
+ HealthController.class)).isNotNull();
+ }
}
diff --git a/src/test/java/org/decepticons/linkshortener/api/v1/controller/unversioned/HealthControllerTest.java b/src/test/java/org/decepticons/linkshortener/api/v1/controller/unversioned/HealthControllerTest.java
index bcbc391..17bd01e 100644
--- a/src/test/java/org/decepticons/linkshortener/api/v1/controller/unversioned/HealthControllerTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/v1/controller/unversioned/HealthControllerTest.java
@@ -1,5 +1,10 @@
package org.decepticons.linkshortener.api.v1.controller.unversioned;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
import org.decepticons.linkshortener.api.security.jwt.JwtAuthenticationFilter;
import org.decepticons.linkshortener.api.security.jwt.JwtTokenUtil;
import org.junit.jupiter.api.Test;
@@ -9,24 +14,22 @@
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(controllers = HealthController.class)
@AutoConfigureMockMvc(addFilters = false) // disable Spring Security filters
class HealthControllerTest {
- @Autowired
- private MockMvc mvc;
+ @Autowired
+ private MockMvc mvc;
- @MockitoBean private JwtAuthenticationFilter jwtAuthenticationFilter;
- @MockitoBean private JwtTokenUtil jwtTokenUtil;
+ @MockitoBean private JwtAuthenticationFilter jwtAuthenticationFilter;
+ @MockitoBean private JwtTokenUtil jwtTokenUtil;
- @Test
- void health_returnsUp() throws Exception {
- mvc.perform(get("/health"))
- .andExpect(status().isOk())
- .andExpect(content().contentType("application/json"))
- .andExpect(jsonPath("$.status").value("UP"));
- }
+ @Test
+ void health_returnsUp() throws Exception {
+ mvc.perform(get("/health"))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType("application/json"))
+ .andExpect(jsonPath("$.status").value("UP"));
+ }
}
diff --git a/src/test/java/org/decepticons/linkshortener/api/v1/controller/unversioned/RedirectControllerTest.java b/src/test/java/org/decepticons/linkshortener/api/v1/controller/unversioned/RedirectControllerTest.java
index f53ed60..0ec74ab 100644
--- a/src/test/java/org/decepticons/linkshortener/api/v1/controller/unversioned/RedirectControllerTest.java
+++ b/src/test/java/org/decepticons/linkshortener/api/v1/controller/unversioned/RedirectControllerTest.java
@@ -1,8 +1,18 @@
package org.decepticons.linkshortener.api.v1.controller.unversioned;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.time.Instant;
import org.decepticons.linkshortener.api.dto.LinkResponseDto;
+import org.decepticons.linkshortener.api.exception.ShortLinkIsOutOfDateException;
import org.decepticons.linkshortener.api.model.User;
import org.decepticons.linkshortener.api.service.impl.LinkServiceImpl;
import org.junit.jupiter.api.Test;
@@ -11,12 +21,6 @@
import org.mockito.Mock;
-import java.io.IOException;
-import java.time.Instant;
-
-import static org.junit.jupiter.api.Assertions.assertInstanceOf;
-import static org.mockito.Mockito.*;
-
@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class)
class RedirectControllerTest {
@@ -28,10 +32,10 @@ class RedirectControllerTest {
private HttpServletResponse httpServletResponse;
@Test
- void verifyRedirectToOriginalUrl_SUCCESS() throws IOException {
+ void verifyRedirectToOriginalUrlSuccess() throws IOException {
String code = "abc123";
User owner = new User();
-// owner.setId(java.util.UUID.randomUUID());
+
LinkResponseDto responseDto = new LinkResponseDto(
java.util.UUID.randomUUID(),
@@ -58,10 +62,10 @@ void verifyRedirectToOriginalUrl_SUCCESS() throws IOException {
}
@Test
- void verifyExceptionThrownWhenLinkNotValid_SUCCESS() throws IOException {
+ void verifyExceptionThrownWhenLinkNotValidSuccess() throws IOException {
String code = "abc123";
User owner = new User();
-// owner.setId(java.util.UUID.randomUUID());
+
LinkResponseDto responseDto = new LinkResponseDto(
java.util.UUID.randomUUID(),
@@ -75,18 +79,18 @@ void verifyExceptionThrownWhenLinkNotValid_SUCCESS() throws IOException {
);
when(linkServiceImpl.getLinkByCode(code)).thenReturn(responseDto);
- when(linkServiceImpl.validateLink(responseDto)).thenReturn(false );
+ when(linkServiceImpl.validateLink(responseDto)).thenReturn(false);
Exception ex = null;
try {
redirectController.redirect(code, httpServletResponse);
- }catch (Exception e) {
+ } catch (Exception e) {
ex = e;
}
verify(httpServletResponse, never()).sendRedirect("https://www.example.com");
- assertInstanceOf(org.decepticons.linkshortener.api.exception.ShortLinkIsOutOfDateException.class, ex);
+ assertInstanceOf(ShortLinkIsOutOfDateException.class, ex);
}
}