Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions src/main/java/org/openpodcastapi/opa/helpers/UUIDHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,22 @@ private UUIDHelper() {
/// @param feedUrl the URL to sanitize
/// @return the sanitized URL
public static String sanitizeFeedUrl(String feedUrl) {
return feedUrl.replaceFirst("^[a-zA-Z]+://", "").replaceAll("/+$", "");
if (feedUrl == null || feedUrl.isBlank()) {
throw new IllegalArgumentException("Invalid feed URL passed to function");
}

// Reject unsupported schemes (e.g., ftp://)
if (feedUrl.matches("^[a-zA-Z]+://.*") && !feedUrl.startsWith("http://") && !feedUrl.startsWith("https://")) {
throw new IllegalArgumentException("Invalid feed URL passed to function");
}

String sanitized = feedUrl.replaceFirst("^(https?://)", "").replaceAll("/+$", "");

if (!sanitized.contains(".")) {
throw new IllegalArgumentException("Invalid feed URL passed to function");
}

return sanitized;
}

/// Calculates the UUID of a provided feed URL using Podcast index methodology.
Expand All @@ -34,8 +49,7 @@ public static String sanitizeFeedUrl(String feedUrl) {
/// @return the calculated UUID
public static UUID getFeedUUID(String feedUrl) {
final String sanitizedFeedUrl = sanitizeFeedUrl(feedUrl);
final UUID feedUUID = UUID.fromString(sanitizedFeedUrl);
return generator.generate(feedUUID.toString());
return generator.generate(sanitizedFeedUrl);
}

/// Validates that a supplied subscription UUID has been calculated properly
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package org.openpodcastapi.opa.subscription.model;

import jakarta.persistence.*;
import lombok.Generated;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.*;

import java.time.Instant;
import java.util.Set;
import java.util.UUID;

@Entity
@Table(name = "subscriptions")
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name = "subscriptions")
public class Subscription {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
package org.openpodcastapi.opa.subscription.model;

import jakarta.persistence.*;
import lombok.Generated;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.*;
import org.openpodcastapi.opa.user.model.User;

import java.time.Instant;
import java.util.UUID;

@Entity
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name = "user_subscription")
public class UserSubscription {
@Id
Expand Down
88 changes: 88 additions & 0 deletions src/test/java/org/openpodcastapi/opa/helpers/UUIDHelperTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package org.openpodcastapi.opa.helpers;

import org.junit.jupiter.api.Test;

import java.util.UUID;

import static org.junit.jupiter.api.Assertions.*;
import static org.openpodcastapi.opa.helpers.UUIDHelper.*;

class UUIDHelperTest {
@Test
void sanitizeFeedUrl_shouldSanitizeValidUrl() {
final String feedUrl = "https://test.com/feed1/";
final String expectedUrl = "test.com/feed1";
String cleanedUrl = sanitizeFeedUrl(feedUrl);

assertEquals(expectedUrl, cleanedUrl);
}

@Test
void sanitizeFeedUrl_shouldSanitizeUrlWithoutScheme() {
final String feedUrl = "test.com/feed1";
final String expectedUrl = "test.com/feed1";
String cleanedUrl = sanitizeFeedUrl(feedUrl);

assertEquals(expectedUrl, cleanedUrl);
}

@Test
void sanitizeFeedUrl_shouldThrowOnInvalidUrl() {
final String feedUrl = "ftp://test.com/feed1";
final String expectedMessage = "Invalid feed URL passed to function";

IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> sanitizeFeedUrl(feedUrl));

assertTrue(exception.getMessage().contains(expectedMessage));
}

@Test
void getFeedUUID_shouldReturnGeneratedUUID() {
final String feedUrl = "podnews.net/rss";
final UUID expectedUUID = UUID.fromString("9b024349-ccf0-5f69-a609-6b82873eab3c");

UUID calculatedUUID = getFeedUUID(feedUrl);

assertEquals(expectedUUID, calculatedUUID);
}

@Test
void getFeedUUID_shouldReturnDeterministicUUID() {
final String feedUrl = "podnews.net/rss";
final UUID incorrectUUID = UUID.fromString("d5d5520d-81da-474e-928b-5fa66233a1ac");

UUID calculatedUUID = getFeedUUID(feedUrl);

assertNotEquals(incorrectUUID, calculatedUUID);
}

@Test
void validateSubscriptionUUID_shouldReturnTrueWhenValid() {
final String feedUrl = "podnews.net/rss";
final UUID expectedUUID = UUID.fromString("9b024349-ccf0-5f69-a609-6b82873eab3c");

assertTrue(validateSubscriptionUUID(feedUrl, expectedUUID));
}

@Test
void validateSubscriptionUUID_shouldReturnFalseWhenInvalid() {
final String feedUrl = "podnews.net/rss";
final UUID incorrectUUID = UUID.fromString("d5d5520d-81da-474e-928b-5fa66233a1ac");

assertFalse(validateSubscriptionUUID(feedUrl, incorrectUUID));
}

@Test
void validateUUIDString_shouldReturnTrueWhenValid() {
final String validUUID = "d5d5520d-81da-474e-928b-5fa66233a1ac";

assertTrue(validateUUIDString(validUUID));
}

@Test
void validateUUIDString_shouldReturnFalseWhenInvalid() {
final String validUUID = "not-a-uuid";

assertFalse(validateUUIDString(validUUID));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.openpodcastapi.opa.subscriptions;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openpodcastapi.opa.subscription.dto.UserSubscriptionDto;
import org.openpodcastapi.opa.subscription.mapper.UserSubscriptionMapper;
import org.openpodcastapi.opa.subscription.mapper.UserSubscriptionMapperImpl;
import org.openpodcastapi.opa.subscription.model.Subscription;
import org.openpodcastapi.opa.subscription.model.UserSubscription;
import org.openpodcastapi.opa.subscription.repository.UserSubscriptionRepository;
import org.openpodcastapi.opa.user.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.time.Instant;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = UserSubscriptionMapperImpl.class)
class UserSubscriptionMapperTest {
@Autowired
private UserSubscriptionMapper mapper;

@MockitoBean
private UserSubscriptionRepository userSubscriptionRepository;

/// Tests that a [UserSubscription] entity maps to a [UserSubscriptionDto] representation
@Test
void testToDto() {
final Instant timestamp = Instant.now();
final UUID uuid = UUID.randomUUID();
User user = User.builder()
.uuid(UUID.randomUUID())
.username("test")
.email("test@test.test")
.createdAt(timestamp)
.updatedAt(timestamp)
.build();

Subscription subscription = Subscription.builder()
.uuid(UUID.randomUUID())
.feedUrl("test.com/feed1")
.createdAt(timestamp)
.updatedAt(timestamp)
.build();

UserSubscription userSubscription = UserSubscription.builder()
.uuid(uuid)
.user(user)
.subscription(subscription)
.isSubscribed(true)
.createdAt(timestamp)
.updatedAt(timestamp)
.build();

UserSubscriptionDto dto = mapper.toDto(userSubscription);
assertNotNull(dto);

// The DTO should inherit the feed URL from the Subscription
assertEquals(subscription.getFeedUrl(), dto.feedUrl());

// The DTO should use the Subscription's UUID rather than the UserSubscription's
assertEquals(subscription.getUuid(), dto.uuid());
assertTrue(dto.isSubscribed());
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
package org.openpodcastapi.opa.user;

import org.junit.jupiter.api.Test;
import org.mapstruct.factory.Mappers;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openpodcastapi.opa.user.dto.CreateUserDto;
import org.openpodcastapi.opa.user.dto.UserDto;
import org.openpodcastapi.opa.user.mapper.UserMapper;
import org.openpodcastapi.opa.user.mapper.UserMapperImpl;
import org.openpodcastapi.opa.user.model.User;
import org.openpodcastapi.opa.user.repository.UserRepository;
import org.openpodcastapi.opa.user.dto.UserDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.time.Instant;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = UserMapperImpl.class)
class UserMapperSpringTest {
private final UserMapper mapper = Mappers.getMapper(UserMapper.class);
@Autowired
private UserMapper mapper;

@MockitoBean
private UserRepository userRepository;

/// Tests that a [User] entity maps to a [UserDto] representation
@Test
void testToDto() {
final Instant timestamp = Instant.now();
Expand All @@ -41,6 +49,7 @@ void testToDto() {
assertEquals(user.getUpdatedAt(), dto.updatedAt());
}

/// Tests that a [CreateUserDto] maps to a [User] entity
@Test
void testToEntity() {
CreateUserDto dto = new CreateUserDto("test", "testPassword", "test@test.test");
Expand Down