.
This commit is contained in:
+18
-3
@@ -1,5 +1,6 @@
|
||||
# CI: Maven build + unit tests on merge requests to main/master and on push to those branches.
|
||||
# CD: Build Docker image and push to GitHub Container Registry (ghcr.io) on push to main/master only.
|
||||
# GitLab CI pipeline for marketing-parser.
|
||||
# CI: runs Maven unit tests, enforces JaCoCo coverage, and publishes reports on merge requests and protected-branch pushes.
|
||||
# CD: existing publish job pushes the Docker image to ghcr.io on push to main/master.
|
||||
# Optional: SSH deploy when CI/CD variable ENABLE_SSH_DEPLOY=true and deploy secrets are set.
|
||||
#
|
||||
# CI/CD variables (Settings → CI/CD → Variables):
|
||||
@@ -33,7 +34,21 @@ build-and-test:
|
||||
stage: test
|
||||
image: maven:3.9.9-eclipse-temurin-21
|
||||
script:
|
||||
- mvn -B -ntp verify
|
||||
- mvn -B -ntp clean verify
|
||||
after_script:
|
||||
- |
|
||||
if [ "$CI_JOB_STATUS" != "success" ]; then
|
||||
echo "ALERT: marketing-parser unit tests or JaCoCo coverage threshold failed."
|
||||
fi
|
||||
artifacts:
|
||||
when: always
|
||||
expire_in: 7 days
|
||||
paths:
|
||||
- target/site/jacoco
|
||||
- target/surefire-reports
|
||||
reports:
|
||||
junit:
|
||||
- target/surefire-reports/TEST-*.xml
|
||||
|
||||
docker-build-push:
|
||||
stage: publish
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
</scm>
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
<jacoco.version>0.8.12</jacoco.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@@ -202,8 +203,59 @@
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludedGroups>integration</excludedGroups>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>${jacoco.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare-agent</id>
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>report</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>check</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<rule>
|
||||
<element>CLASS</element>
|
||||
<includes>
|
||||
<include>kz.konturai.parser.service.PostingTaskService</include>
|
||||
<include>kz.konturai.parser.service.JwtService</include>
|
||||
<include>kz.konturai.parser.service.SocialMediaCredentialsService</include>
|
||||
</includes>
|
||||
<limits>
|
||||
<limit>
|
||||
<counter>LINE</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>0.80</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
package kz.konturai.parser.service;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.io.Decoders;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class JwtServiceTest {
|
||||
|
||||
private static final String TEST_SECRET = Base64.getEncoder().encodeToString(
|
||||
"0123456789abcdef0123456789abcdef".getBytes(StandardCharsets.UTF_8));
|
||||
private static final String DEFAULT_SECRET =
|
||||
"dGVzdC1zZWNyZXQta2V5LWZvci1kZXZlbG9wbWVudC1vbmx5LWRvLW5vdC11c2UtaW4tcHJvZHVjdGlvbg==";
|
||||
|
||||
private JwtService jwtService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
jwtService = new JwtService(TEST_SECRET);
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseAndValidateShouldReturnClaimsForValidToken() {
|
||||
String token = createToken(TEST_SECRET, "qa@example.com", Instant.now().plusSeconds(3600), Map.of(
|
||||
"uid", 42L,
|
||||
"roles", "ADMIN, USER"));
|
||||
|
||||
Claims claims = jwtService.parseAndValidate(token);
|
||||
|
||||
assertEquals("qa@example.com", claims.getSubject());
|
||||
assertEquals(42L, claims.get("uid", Long.class));
|
||||
assertEquals("ADMIN, USER", claims.get("roles", String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseAndValidateShouldThrowForInvalidSignature() {
|
||||
String token = createToken(
|
||||
Base64.getEncoder().encodeToString("another-valid-secret-key-1234567".getBytes(StandardCharsets.UTF_8)),
|
||||
"qa@example.com",
|
||||
Instant.now().plusSeconds(3600),
|
||||
Map.of("uid", 99L));
|
||||
|
||||
assertThrows(JwtException.class, () -> jwtService.parseAndValidate(token));
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorWithoutConfiguredSecretShouldUseDefaultSigningKey() {
|
||||
JwtService serviceWithDefaultKey = new JwtService("");
|
||||
String token = createToken(DEFAULT_SECRET, "fallback@example.com", Instant.now().plusSeconds(3600), Map.of("uid", 7L));
|
||||
|
||||
assertEquals("7", serviceWithDefaultKey.extractUserIdFromToken(token));
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractTokenFromHeaderShouldHandleBearerPrefixBlankAndRawToken() {
|
||||
assertNull(jwtService.extractTokenFromHeader(null));
|
||||
assertNull(jwtService.extractTokenFromHeader(" "));
|
||||
assertEquals("sample-token", jwtService.extractTokenFromHeader("Bearer sample-token"));
|
||||
assertEquals("raw-token", jwtService.extractTokenFromHeader("raw-token"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractUserIdFromTokenShouldPreferUidClaim() {
|
||||
String token = createToken(TEST_SECRET, "qa@example.com", Instant.now().plusSeconds(3600), Map.of("uid", 12345L));
|
||||
|
||||
assertEquals("12345", jwtService.extractUserIdFromToken(token));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("legacyUserIdClaims")
|
||||
void extractUserIdFromTokenShouldUseLegacyClaims(String claimName, Object claimValue, String expectedValue) {
|
||||
String token = createToken(TEST_SECRET, "qa@example.com", Instant.now().plusSeconds(3600), Map.of(claimName, claimValue));
|
||||
|
||||
assertEquals(expectedValue, jwtService.extractUserIdFromToken(token));
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractUserIdFromTokenShouldUseSubjectAsLastFallback() {
|
||||
String token = createToken(TEST_SECRET, "subject@example.com", Instant.now().plusSeconds(3600), Map.of());
|
||||
|
||||
assertEquals("subject@example.com", jwtService.extractUserIdFromToken(token));
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractUserIdFromTokenShouldReturnNullForExpiredToken() {
|
||||
String token = createToken(TEST_SECRET, "qa@example.com", Instant.now().minusSeconds(60), Map.of("uid", 10L));
|
||||
|
||||
assertNull(jwtService.extractUserIdFromToken(token));
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractUserIdFromTokenShouldReturnNullForMalformedToken() {
|
||||
assertNull(jwtService.extractUserIdFromToken("not-a-jwt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractUserIdFromHeaderShouldExtractFromBearerToken() {
|
||||
String token = createToken(TEST_SECRET, "qa@example.com", Instant.now().plusSeconds(3600), Map.of("uid", 501L));
|
||||
|
||||
assertEquals("501", jwtService.extractUserIdFromHeader("Bearer " + token));
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractEmailFromTokenShouldReturnSubject() {
|
||||
String token = createToken(TEST_SECRET, "qa@example.com", Instant.now().plusSeconds(3600), Map.of("uid", 12L));
|
||||
|
||||
assertEquals("qa@example.com", jwtService.extractEmailFromToken(token));
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractEmailFromTokenShouldReturnNullForInvalidToken() {
|
||||
assertNull(jwtService.extractEmailFromToken("broken-token"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractRolesFromTokenShouldSplitAndTrimRoleList() {
|
||||
String token = createToken(TEST_SECRET, "qa@example.com", Instant.now().plusSeconds(3600), Map.of(
|
||||
"uid", 1L,
|
||||
"roles", "ADMIN, USER , ,MANAGER"));
|
||||
|
||||
assertEquals(List.of("ADMIN", "USER", "MANAGER"), jwtService.extractRolesFromToken(token));
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractRolesFromTokenShouldReturnEmptyListWhenRolesAreMissingOrTokenInvalid() {
|
||||
String tokenWithoutRoles = createToken(TEST_SECRET, "qa@example.com", Instant.now().plusSeconds(3600), Map.of("uid", 1L));
|
||||
|
||||
assertTrue(jwtService.extractRolesFromToken(tokenWithoutRoles).isEmpty());
|
||||
assertTrue(jwtService.extractRolesFromToken("invalid").isEmpty());
|
||||
assertTrue(jwtService.extractRolesFromToken(null).isEmpty());
|
||||
}
|
||||
|
||||
private static Stream<Arguments> legacyUserIdClaims() {
|
||||
return Stream.of(
|
||||
Arguments.of("userId", "legacy-user", "legacy-user"),
|
||||
Arguments.of("id", 55L, "55"),
|
||||
Arguments.of("user_id", "external-user", "external-user"));
|
||||
}
|
||||
|
||||
private String createToken(String base64Secret, String subject, Instant expiration, Map<String, Object> customClaims) {
|
||||
SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(base64Secret));
|
||||
Map<String, Object> claims = new HashMap<>(customClaims);
|
||||
|
||||
var builder = Jwts.builder()
|
||||
.subject(subject)
|
||||
.issuedAt(Date.from(Instant.now().minusSeconds(30)))
|
||||
.expiration(Date.from(expiration))
|
||||
.signWith(key);
|
||||
|
||||
claims.forEach(builder::claim);
|
||||
return builder.compact();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,538 @@
|
||||
package kz.konturai.parser.service;
|
||||
|
||||
import kz.konturai.parser.exception.FacebookTokenExpiredException;
|
||||
import kz.konturai.parser.exception.LinkedInTokenExpiredException;
|
||||
import kz.konturai.parser.exception.TelegramTokenExpiredException;
|
||||
import kz.konturai.parser.model.MarketingStrategy;
|
||||
import kz.konturai.parser.model.PostingTask;
|
||||
import kz.konturai.parser.repository.MarketingStrategyRepository;
|
||||
import kz.konturai.parser.repository.PostingTaskRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
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.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.aryEq;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PostingTaskServiceTest {
|
||||
|
||||
private static final String TEST_RECIPIENT_ID = "25769470256007187";
|
||||
|
||||
@Mock
|
||||
private PostingTaskRepository taskRepository;
|
||||
|
||||
@Mock
|
||||
private MarketingStrategyRepository strategyRepository;
|
||||
|
||||
@Mock
|
||||
private SocialMediaCredentialsService credentialsService;
|
||||
|
||||
@Mock
|
||||
private FacebookPostingService facebookPostingService;
|
||||
|
||||
@Mock
|
||||
private LinkedInPostingService linkedInPostingService;
|
||||
|
||||
@Mock
|
||||
private TelegramPostingService telegramPostingService;
|
||||
|
||||
@Mock
|
||||
private MinIOService minIOService;
|
||||
|
||||
private PostingTaskService postingTaskService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
postingTaskService = spy(new PostingTaskService(
|
||||
taskRepository,
|
||||
strategyRepository,
|
||||
credentialsService,
|
||||
facebookPostingService,
|
||||
linkedInPostingService,
|
||||
telegramPostingService,
|
||||
minIOService));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createTasksFromStrategyShouldThrowWhenStrategyIsMissing() {
|
||||
when(strategyRepository.findById("strategy-1")).thenReturn(Optional.empty());
|
||||
|
||||
IllegalArgumentException exception = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> postingTaskService.createTasksFromStrategy("strategy-1"));
|
||||
|
||||
assertEquals("Strategy not found: strategy-1", exception.getMessage());
|
||||
verifyNoInteractions(taskRepository, credentialsService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createTasksFromStrategyShouldThrowWhenStrategyIsNotCompleted() {
|
||||
MarketingStrategy strategy = new MarketingStrategy();
|
||||
strategy.setStatus("processing");
|
||||
when(strategyRepository.findById("strategy-1")).thenReturn(Optional.of(strategy));
|
||||
|
||||
IllegalStateException exception = assertThrows(
|
||||
IllegalStateException.class,
|
||||
() -> postingTaskService.createTasksFromStrategy("strategy-1"));
|
||||
|
||||
assertEquals("Strategy is not completed yet. Status: processing", exception.getMessage());
|
||||
verifyNoInteractions(credentialsService, taskRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createTasksFromStrategyShouldReturnEmptyListWhenCalendarIsMissing() {
|
||||
MarketingStrategy strategy = new MarketingStrategy();
|
||||
strategy.setStatus("completed");
|
||||
strategy.setPostCalendar(List.of());
|
||||
when(strategyRepository.findById("strategy-1")).thenReturn(Optional.of(strategy));
|
||||
|
||||
List<PostingTask> tasks = postingTaskService.createTasksFromStrategy("strategy-1");
|
||||
|
||||
assertTrue(tasks.isEmpty());
|
||||
verifyNoInteractions(credentialsService);
|
||||
verify(taskRepository, never()).saveAll(anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createTasksFromStrategyShouldThrowWhenCredentialsAreMissing() {
|
||||
MarketingStrategy strategy = completedStrategy("user-1", List.of(
|
||||
calendarItem("facebook", LocalDateTime.now().plusDays(1), "Post", List.of("#marketing"))));
|
||||
|
||||
when(strategyRepository.findById("strategy-1")).thenReturn(Optional.of(strategy));
|
||||
when(credentialsService.hasCredentials("user-1", "facebook")).thenReturn(false);
|
||||
|
||||
IllegalStateException exception = assertThrows(
|
||||
IllegalStateException.class,
|
||||
() -> postingTaskService.createTasksFromStrategy("strategy-1"));
|
||||
|
||||
assertEquals(
|
||||
"Credentials not found for platform: facebook. Please configure credentials first.",
|
||||
exception.getMessage());
|
||||
verify(taskRepository, never()).saveAll(anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createTasksFromStrategyShouldReturnExistingTasksWhenAlreadyCreated() {
|
||||
MarketingStrategy strategy = completedStrategy("user-1", List.of(
|
||||
calendarItem("facebook", LocalDateTime.now().plusDays(1), "Post", List.of("#marketing"))));
|
||||
List<PostingTask> existingTasks = List.of(task("task-1", "user-1", "facebook", "pending"));
|
||||
|
||||
when(strategyRepository.findById("strategy-1")).thenReturn(Optional.of(strategy));
|
||||
when(credentialsService.hasCredentials("user-1", "facebook")).thenReturn(true);
|
||||
when(taskRepository.findByStrategyId("strategy-1")).thenReturn(existingTasks);
|
||||
|
||||
List<PostingTask> result = postingTaskService.createTasksFromStrategy("strategy-1");
|
||||
|
||||
assertSame(existingTasks, result);
|
||||
verify(taskRepository, never()).saveAll(anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createTasksFromStrategyShouldCreateTasksAndTriggerImmediateExecutionForPastDates() {
|
||||
MarketingStrategy.PostCalendarItem pastItem = calendarItem(
|
||||
"facebook",
|
||||
LocalDateTime.now().minusHours(1),
|
||||
"Past post",
|
||||
List.of("#past"));
|
||||
pastItem.setImageUrl("https://cdn.example.com/post.png");
|
||||
pastItem.setImageFilename("post.png");
|
||||
|
||||
MarketingStrategy.PostCalendarItem futureItem = calendarItem(
|
||||
"telegram",
|
||||
LocalDateTime.now().plusHours(2),
|
||||
"Future post",
|
||||
List.of("#future"));
|
||||
|
||||
MarketingStrategy strategy = completedStrategy("user-1", List.of(pastItem, futureItem));
|
||||
|
||||
when(strategyRepository.findById("strategy-1")).thenReturn(Optional.of(strategy));
|
||||
when(credentialsService.hasCredentials("user-1", "facebook")).thenReturn(true);
|
||||
when(credentialsService.hasCredentials("user-1", "telegram")).thenReturn(true);
|
||||
when(taskRepository.findByStrategyId("strategy-1")).thenReturn(List.of());
|
||||
when(taskRepository.saveAll(anyList())).thenAnswer(invocation -> {
|
||||
List<PostingTask> tasks = invocation.getArgument(0);
|
||||
tasks.get(0).setId("past-task");
|
||||
tasks.get(1).setId("future-task");
|
||||
return tasks;
|
||||
});
|
||||
doNothing().when(postingTaskService).executeTaskAsync(anyString());
|
||||
|
||||
List<PostingTask> savedTasks = postingTaskService.createTasksFromStrategy("strategy-1");
|
||||
|
||||
assertEquals(2, savedTasks.size());
|
||||
assertEquals("post.png", savedTasks.get(0).getImageFilename());
|
||||
assertEquals("https://cdn.example.com/post.png", savedTasks.get(0).getImageUrl());
|
||||
assertEquals("telegram", savedTasks.get(1).getPlatform());
|
||||
verify(postingTaskService).executeTaskAsync("past-task");
|
||||
verify(postingTaskService, never()).executeTaskAsync("future-task");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPendingTasksShouldDelegateToRepository() {
|
||||
LocalDateTime beforeDate = LocalDateTime.now();
|
||||
List<PostingTask> expectedTasks = List.of(task("task-1", "user-1", "facebook", "pending"));
|
||||
when(taskRepository.findByStatusAndPublishDateLessThanEqual("pending", beforeDate)).thenReturn(expectedTasks);
|
||||
|
||||
List<PostingTask> actualTasks = postingTaskService.getPendingTasks(beforeDate);
|
||||
|
||||
assertSame(expectedTasks, actualTasks);
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeTaskShouldIgnoreMissingTask() {
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.empty());
|
||||
|
||||
postingTaskService.executeTask("task-1");
|
||||
|
||||
verify(taskRepository, never()).save(any());
|
||||
verifyNoInteractions(credentialsService, facebookPostingService, linkedInPostingService, telegramPostingService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeTaskShouldSkipTaskWhenStatusIsNotPending() {
|
||||
PostingTask task = task("task-1", "user-1", "facebook", "completed");
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.of(task));
|
||||
|
||||
postingTaskService.executeTask("task-1");
|
||||
|
||||
verify(taskRepository, never()).save(any());
|
||||
verifyNoInteractions(credentialsService, facebookPostingService, linkedInPostingService, telegramPostingService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeTaskShouldCompleteFacebookTaskWithoutImage() throws Exception {
|
||||
PostingTask task = task("task-1", "user-1", "facebook", "pending");
|
||||
task.setPostText("Product launch");
|
||||
task.setHashtags(List.of("#launch"));
|
||||
|
||||
List<String> savedStatuses = new ArrayList<>();
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.of(task));
|
||||
when(taskRepository.save(any(PostingTask.class))).thenAnswer(invocation -> {
|
||||
PostingTask savedTask = invocation.getArgument(0);
|
||||
savedStatuses.add(savedTask.getStatus());
|
||||
return savedTask;
|
||||
});
|
||||
when(credentialsService.getCredentials("user-1", "facebook")).thenReturn("fb-token");
|
||||
when(facebookPostingService.postToPage("fb-token", "Product launch", List.of("#launch")))
|
||||
.thenReturn("fb-post-1");
|
||||
|
||||
postingTaskService.executeTask("task-1");
|
||||
|
||||
assertEquals(List.of("processing", "completed"), savedStatuses);
|
||||
assertEquals("completed", task.getStatus());
|
||||
assertNull(task.getErrorMessage());
|
||||
assertNotNull(task.getExecutedAt());
|
||||
verify(facebookPostingService).postToPage("fb-token", "Product launch", List.of("#launch"));
|
||||
verify(facebookPostingService).sendPrivateMessage(
|
||||
eq("fb-token"),
|
||||
eq(TEST_RECIPIENT_ID),
|
||||
argThat(message -> message.contains("Product launch") && message.contains("https://facebook.com/fb-post-1")));
|
||||
verify(facebookPostingService, never()).sendPrivateImageMessage(anyString(), anyString(), any());
|
||||
verify(minIOService, never()).downloadFile(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeTaskShouldCompleteFacebookTaskWithImageAndIgnoreNotificationFailure() throws Exception {
|
||||
PostingTask task = task("task-1", "user-1", "facebook", "pending");
|
||||
task.setPostText("Image post");
|
||||
task.setHashtags(List.of("#image"));
|
||||
task.setImageFilename("banner.png");
|
||||
byte[] imageBytes = "binary-image".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.of(task));
|
||||
when(taskRepository.save(any(PostingTask.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(credentialsService.getCredentials("user-1", "facebook")).thenReturn("fb-token");
|
||||
when(minIOService.downloadFile("banner.png")).thenReturn(streamOf(imageBytes));
|
||||
when(facebookPostingService.postToPageWithImage(
|
||||
eq("fb-token"),
|
||||
eq("Image post"),
|
||||
eq(List.of("#image")),
|
||||
aryEq(imageBytes)))
|
||||
.thenReturn("fb-post-2");
|
||||
doThrow(new RuntimeException("Messenger unavailable"))
|
||||
.when(facebookPostingService)
|
||||
.sendPrivateMessage(eq("fb-token"), eq(TEST_RECIPIENT_ID), anyString());
|
||||
|
||||
postingTaskService.executeTask("task-1");
|
||||
|
||||
assertEquals("completed", task.getStatus());
|
||||
assertNull(task.getErrorMessage());
|
||||
verify(facebookPostingService).postToPageWithImage(
|
||||
eq("fb-token"),
|
||||
eq("Image post"),
|
||||
eq(List.of("#image")),
|
||||
aryEq(imageBytes));
|
||||
verify(facebookPostingService, never()).sendPrivateImageMessage(anyString(), anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeTaskShouldCompleteLinkedInTaskWithImage() {
|
||||
PostingTask task = task("task-1", "user-1", "linkedin", "pending");
|
||||
task.setPostText("B2B update");
|
||||
task.setHashtags(List.of("#b2b"));
|
||||
task.setImageFilename("banner.png");
|
||||
byte[] imageBytes = "binary-image".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.of(task));
|
||||
when(taskRepository.save(any(PostingTask.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(credentialsService.getCredentials("user-1", "linkedin")).thenReturn("linkedin-token");
|
||||
when(minIOService.downloadFile("banner.png")).thenReturn(streamOf(imageBytes));
|
||||
when(linkedInPostingService.postToLinkedInWithImage(
|
||||
eq("linkedin-token"),
|
||||
eq("B2B update"),
|
||||
eq(List.of("#b2b")),
|
||||
aryEq(imageBytes),
|
||||
eq("image/png")))
|
||||
.thenReturn("linkedin-post-1");
|
||||
|
||||
postingTaskService.executeTask("task-1");
|
||||
|
||||
assertEquals("completed", task.getStatus());
|
||||
assertNull(task.getErrorMessage());
|
||||
verify(linkedInPostingService).postToLinkedInWithImage(
|
||||
eq("linkedin-token"),
|
||||
eq("B2B update"),
|
||||
eq(List.of("#b2b")),
|
||||
aryEq(imageBytes),
|
||||
eq("image/png"));
|
||||
verify(linkedInPostingService, never()).postToLinkedIn(anyString(), anyString(), anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeTaskShouldContinueWithoutImageWhenImageDownloadFails() {
|
||||
PostingTask task = task("task-1", "user-1", "telegram", "pending");
|
||||
task.setPostText("Telegram post");
|
||||
task.setHashtags(List.of("#telegram"));
|
||||
task.setImageFilename("missing.png");
|
||||
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.of(task));
|
||||
when(taskRepository.save(any(PostingTask.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(credentialsService.getCredentials("user-1", "telegram")).thenReturn("{\"botToken\":\"t\",\"chatId\":\"1\"}");
|
||||
when(minIOService.downloadFile("missing.png")).thenThrow(new RuntimeException("Missing object"));
|
||||
when(telegramPostingService.postToTelegram(
|
||||
"{\"botToken\":\"t\",\"chatId\":\"1\"}",
|
||||
"Telegram post",
|
||||
List.of("#telegram")))
|
||||
.thenReturn("telegram-message-1");
|
||||
|
||||
postingTaskService.executeTask("task-1");
|
||||
|
||||
assertEquals("completed", task.getStatus());
|
||||
verify(telegramPostingService).postToTelegram(
|
||||
"{\"botToken\":\"t\",\"chatId\":\"1\"}",
|
||||
"Telegram post",
|
||||
List.of("#telegram"));
|
||||
verify(telegramPostingService, never()).postToTelegramWithImage(anyString(), anyString(), anyList(), any(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeTaskShouldFailWhenCredentialsAreMissing() {
|
||||
PostingTask task = task("task-1", "user-1", "facebook", "pending");
|
||||
List<String> savedStatuses = new ArrayList<>();
|
||||
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.of(task));
|
||||
when(taskRepository.save(any(PostingTask.class))).thenAnswer(invocation -> {
|
||||
PostingTask savedTask = invocation.getArgument(0);
|
||||
savedStatuses.add(savedTask.getStatus());
|
||||
return savedTask;
|
||||
});
|
||||
when(credentialsService.getCredentials("user-1", "facebook")).thenReturn(null);
|
||||
|
||||
postingTaskService.executeTask("task-1");
|
||||
|
||||
assertEquals(List.of("processing", "failed"), savedStatuses);
|
||||
assertEquals("failed", task.getStatus());
|
||||
assertEquals("Credentials not found for platform: facebook", task.getErrorMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeTaskShouldFailWhenPlatformIsUnsupported() {
|
||||
PostingTask task = task("task-1", "user-1", "instagram", "pending");
|
||||
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.of(task));
|
||||
when(taskRepository.save(any(PostingTask.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(credentialsService.getCredentials("user-1", "instagram")).thenReturn("ig-token");
|
||||
|
||||
postingTaskService.executeTask("task-1");
|
||||
|
||||
assertEquals("failed", task.getStatus());
|
||||
assertEquals("Platform not supported: instagram", task.getErrorMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeTaskShouldHandleFacebookExpiredToken() throws Exception {
|
||||
PostingTask task = task("task-1", "user-1", "facebook", "pending");
|
||||
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.of(task));
|
||||
when(taskRepository.save(any(PostingTask.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(credentialsService.getCredentials("user-1", "facebook")).thenReturn("fb-token");
|
||||
when(facebookPostingService.postToPage("fb-token", task.getPostText(), task.getHashtags()))
|
||||
.thenThrow(new FacebookTokenExpiredException("Expired", "code 190", 190, 463));
|
||||
|
||||
postingTaskService.executeTask("task-1");
|
||||
|
||||
assertEquals("failed", task.getStatus());
|
||||
assertEquals("Facebook token expired: code 190", task.getErrorMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeTaskShouldHandleLinkedInExpiredToken() {
|
||||
PostingTask task = task("task-1", "user-1", "linkedin", "pending");
|
||||
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.of(task));
|
||||
when(taskRepository.save(any(PostingTask.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(credentialsService.getCredentials("user-1", "linkedin")).thenReturn("linkedin-token");
|
||||
when(linkedInPostingService.postToLinkedIn("linkedin-token", task.getPostText(), task.getHashtags()))
|
||||
.thenThrow(new LinkedInTokenExpiredException("Expired", "status 401", 401));
|
||||
|
||||
postingTaskService.executeTask("task-1");
|
||||
|
||||
assertEquals("failed", task.getStatus());
|
||||
assertEquals("LinkedIn token expired: status 401", task.getErrorMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeTaskShouldHandleTelegramExpiredToken() {
|
||||
PostingTask task = task("task-1", "user-1", "telegram", "pending");
|
||||
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.of(task));
|
||||
when(taskRepository.save(any(PostingTask.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(credentialsService.getCredentials("user-1", "telegram")).thenReturn("{\"botToken\":\"t\",\"chatId\":\"1\"}");
|
||||
when(telegramPostingService.postToTelegram("{\"botToken\":\"t\",\"chatId\":\"1\"}", task.getPostText(), task.getHashtags()))
|
||||
.thenThrow(new TelegramTokenExpiredException("Expired", "status 401", 401));
|
||||
|
||||
postingTaskService.executeTask("task-1");
|
||||
|
||||
assertEquals("failed", task.getStatus());
|
||||
assertEquals("Telegram token expired: status 401", task.getErrorMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeTaskManuallyShouldThrowWhenTaskIsMissing() {
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.empty());
|
||||
|
||||
IllegalArgumentException exception = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> postingTaskService.executeTaskManually("task-1"));
|
||||
|
||||
assertEquals("Task not found: task-1", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeTaskManuallyShouldThrowWhenTaskStatusIsNotAllowed() {
|
||||
PostingTask task = task("task-1", "user-1", "facebook", "completed");
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.of(task));
|
||||
|
||||
IllegalStateException exception = assertThrows(
|
||||
IllegalStateException.class,
|
||||
() -> postingTaskService.executeTaskManually("task-1"));
|
||||
|
||||
assertEquals("Task cannot be executed manually. Current status: completed", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeTaskManuallyShouldResetFailedTaskBeforeExecution() {
|
||||
PostingTask task = task("task-1", "user-1", "facebook", "failed");
|
||||
task.setErrorMessage("Old error");
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.of(task));
|
||||
when(taskRepository.save(any(PostingTask.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
doNothing().when(postingTaskService).executeTask("task-1");
|
||||
|
||||
postingTaskService.executeTaskManually("task-1");
|
||||
|
||||
assertEquals("pending", task.getStatus());
|
||||
assertNull(task.getErrorMessage());
|
||||
verify(taskRepository).save(task);
|
||||
verify(postingTaskService).executeTask("task-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeTaskManuallyShouldExecutePendingTaskWithoutReset() {
|
||||
PostingTask task = task("task-1", "user-1", "facebook", "pending");
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.of(task));
|
||||
doNothing().when(postingTaskService).executeTask("task-1");
|
||||
|
||||
postingTaskService.executeTaskManually("task-1");
|
||||
|
||||
verify(taskRepository, never()).save(any());
|
||||
verify(postingTaskService).executeTask("task-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryMethodsShouldDelegateToRepository() {
|
||||
PostingTask task = task("task-1", "user-1", "facebook", "pending");
|
||||
when(taskRepository.findByUserId("user-1")).thenReturn(List.of(task));
|
||||
when(taskRepository.findByStrategyId("strategy-1")).thenReturn(List.of(task));
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.of(task));
|
||||
|
||||
assertEquals(List.of(task), postingTaskService.getUserTasks("user-1"));
|
||||
assertEquals(List.of(task), postingTaskService.getStrategyTasks("strategy-1"));
|
||||
assertEquals(Optional.of(task), postingTaskService.getTaskById("task-1"));
|
||||
}
|
||||
|
||||
private MarketingStrategy completedStrategy(String userId, List<MarketingStrategy.PostCalendarItem> calendarItems) {
|
||||
MarketingStrategy strategy = new MarketingStrategy();
|
||||
strategy.setStatus("completed");
|
||||
strategy.setUserId(userId);
|
||||
strategy.setPostCalendar(calendarItems);
|
||||
return strategy;
|
||||
}
|
||||
|
||||
private MarketingStrategy.PostCalendarItem calendarItem(
|
||||
String platform,
|
||||
LocalDateTime publishDate,
|
||||
String postText,
|
||||
List<String> hashtags) {
|
||||
MarketingStrategy.PostCalendarItem item = new MarketingStrategy.PostCalendarItem();
|
||||
item.setPlatform(platform);
|
||||
item.setPublishDate(publishDate);
|
||||
item.setPostText(postText);
|
||||
item.setHashtags(hashtags);
|
||||
return item;
|
||||
}
|
||||
|
||||
private PostingTask task(String id, String userId, String platform, String status) {
|
||||
PostingTask task = new PostingTask();
|
||||
task.setId(id);
|
||||
task.setUserId(userId);
|
||||
task.setStrategyId("strategy-1");
|
||||
task.setPlatform(platform);
|
||||
task.setPostText("Default post");
|
||||
task.setHashtags(List.of("#default"));
|
||||
task.setPublishDate(LocalDateTime.now().plusMinutes(5));
|
||||
task.setStatus(status);
|
||||
return task;
|
||||
}
|
||||
|
||||
private InputStream streamOf(byte[] data) {
|
||||
return new ByteArrayInputStream(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package kz.konturai.parser.service;
|
||||
|
||||
import kz.konturai.parser.model.SocialMediaCredentials;
|
||||
import kz.konturai.parser.repository.SocialMediaCredentialsRepository;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
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.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class SocialMediaCredentialsServiceTest {
|
||||
|
||||
@Mock
|
||||
private SocialMediaCredentialsRepository repository;
|
||||
|
||||
@Mock
|
||||
private EncryptionService encryptionService;
|
||||
|
||||
@InjectMocks
|
||||
private SocialMediaCredentialsService service;
|
||||
|
||||
@Test
|
||||
void saveCredentialsShouldThrowWhenInputIsInvalid() {
|
||||
assertThrows(IllegalArgumentException.class, () -> service.saveCredentials(null, "facebook", "token"));
|
||||
assertThrows(IllegalArgumentException.class, () -> service.saveCredentials("user-1", null, "token"));
|
||||
assertThrows(IllegalArgumentException.class, () -> service.saveCredentials("user-1", "facebook", ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveCredentialsShouldCreateNewEntityAndEncryptCredentials() {
|
||||
when(repository.findByUserIdAndPlatform("user-1", "Facebook")).thenReturn(Optional.empty());
|
||||
when(encryptionService.encrypt("plain-token")).thenReturn("encrypted-token");
|
||||
when(repository.save(any(SocialMediaCredentials.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
SocialMediaCredentials saved = service.saveCredentials("user-1", "Facebook", "plain-token");
|
||||
|
||||
ArgumentCaptor<SocialMediaCredentials> captor = ArgumentCaptor.forClass(SocialMediaCredentials.class);
|
||||
verify(repository).save(captor.capture());
|
||||
SocialMediaCredentials persisted = captor.getValue();
|
||||
assertEquals("user-1", persisted.getUserId());
|
||||
assertEquals("facebook", persisted.getPlatform());
|
||||
assertEquals("encrypted-token", persisted.getEncryptedCredentials());
|
||||
assertSame(persisted, saved);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveCredentialsShouldUpdateExistingEntity() {
|
||||
SocialMediaCredentials existing = new SocialMediaCredentials("user-1", "facebook", "old-encrypted");
|
||||
when(repository.findByUserIdAndPlatform("user-1", "facebook")).thenReturn(Optional.of(existing));
|
||||
when(encryptionService.encrypt("new-token")).thenReturn("new-encrypted");
|
||||
when(repository.save(any(SocialMediaCredentials.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
SocialMediaCredentials saved = service.saveCredentials("user-1", "facebook", "new-token");
|
||||
|
||||
assertSame(existing, saved);
|
||||
assertEquals("new-encrypted", existing.getEncryptedCredentials());
|
||||
assertEquals("facebook", existing.getPlatform());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCredentialsShouldReturnNullWhenInputIsInvalid() {
|
||||
assertNull(service.getCredentials(null, "facebook"));
|
||||
assertNull(service.getCredentials("user-1", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCredentialsShouldReturnNullWhenCredentialsAreMissing() {
|
||||
when(repository.findByUserIdAndPlatform("user-1", "facebook")).thenReturn(Optional.empty());
|
||||
|
||||
assertNull(service.getCredentials("user-1", "FACEBOOK"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCredentialsShouldDecryptStringCredentials() {
|
||||
SocialMediaCredentials entity = new SocialMediaCredentials("user-1", "facebook", "encrypted-token");
|
||||
when(repository.findByUserIdAndPlatform("user-1", "facebook")).thenReturn(Optional.of(entity));
|
||||
when(encryptionService.decrypt("encrypted-token")).thenReturn("plain-token");
|
||||
|
||||
assertEquals("plain-token", service.getCredentials("user-1", "FACEBOOK"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCredentialsShouldDecryptNonStringCredentialsViaToString() {
|
||||
SocialMediaCredentials entity = new SocialMediaCredentials("user-1", "telegram", new StringBuilder("encrypted-json"));
|
||||
when(repository.findByUserIdAndPlatform("user-1", "telegram")).thenReturn(Optional.of(entity));
|
||||
when(encryptionService.decrypt("encrypted-json")).thenReturn("{\"botToken\":\"t\"}");
|
||||
|
||||
assertEquals("{\"botToken\":\"t\"}", service.getCredentials("user-1", "telegram"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCredentialsShouldWrapDecryptionFailures() {
|
||||
SocialMediaCredentials entity = new SocialMediaCredentials("user-1", "facebook", "encrypted-token");
|
||||
when(repository.findByUserIdAndPlatform("user-1", "facebook")).thenReturn(Optional.of(entity));
|
||||
when(encryptionService.decrypt("encrypted-token")).thenThrow(new RuntimeException("decrypt failed"));
|
||||
|
||||
RuntimeException exception = assertThrows(
|
||||
RuntimeException.class,
|
||||
() -> service.getCredentials("user-1", "facebook"));
|
||||
|
||||
assertEquals("Failed to decrypt credentials", exception.getMessage());
|
||||
assertNotNull(exception.getCause());
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasCredentialsShouldReturnFalseForInvalidInput() {
|
||||
assertTrue(!service.hasCredentials(null, "facebook"));
|
||||
assertTrue(!service.hasCredentials("user-1", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasCredentialsShouldCheckRepositoryUsingLowercasePlatform() {
|
||||
when(repository.findByUserIdAndPlatform("user-1", "facebook"))
|
||||
.thenReturn(Optional.of(new SocialMediaCredentials("user-1", "facebook", "encrypted")));
|
||||
|
||||
assertTrue(service.hasCredentials("user-1", "FACEBOOK"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteCredentialsShouldThrowForInvalidInput() {
|
||||
assertThrows(IllegalArgumentException.class, () -> service.deleteCredentials(null, "facebook"));
|
||||
assertThrows(IllegalArgumentException.class, () -> service.deleteCredentials("user-1", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteCredentialsShouldUseLowercasePlatform() {
|
||||
service.deleteCredentials("user-1", "FACEBOOK");
|
||||
|
||||
verify(repository).deleteByUserIdAndPlatform("user-1", "facebook");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUserPlatformsShouldReturnEmptyListForNullUserId() {
|
||||
assertEquals(List.of(), service.getUserPlatforms(null));
|
||||
verify(repository, never()).findByUserId(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUserPlatformsShouldReturnAllPlatforms() {
|
||||
when(repository.findByUserId("user-1")).thenReturn(List.of(
|
||||
new SocialMediaCredentials("user-1", "facebook", "e1"),
|
||||
new SocialMediaCredentials("user-1", "telegram", "e2")));
|
||||
|
||||
assertEquals(List.of("facebook", "telegram"), service.getUserPlatforms("user-1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUserCredentialsShouldReturnEmptyListForNullUserId() {
|
||||
assertEquals(List.of(), service.getUserCredentials(null));
|
||||
verify(repository, never()).findByUserId(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUserCredentialsShouldDelegateToRepository() {
|
||||
List<SocialMediaCredentials> credentials = List.of(new SocialMediaCredentials("user-1", "facebook", "encrypted"));
|
||||
when(repository.findByUserId("user-1")).thenReturn(credentials);
|
||||
|
||||
assertSame(credentials, service.getUserCredentials("user-1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateAdAccountIdShouldThrowForInvalidInput() {
|
||||
assertThrows(IllegalArgumentException.class, () -> service.updateAdAccountId(null, "facebook", "act-1"));
|
||||
assertThrows(IllegalArgumentException.class, () -> service.updateAdAccountId("user-1", null, "act-1"));
|
||||
assertThrows(IllegalArgumentException.class, () -> service.updateAdAccountId("user-1", "facebook", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateAdAccountIdShouldThrowWhenCredentialsAreMissing() {
|
||||
when(repository.findByUserIdAndPlatform("user-1", "facebook")).thenReturn(Optional.empty());
|
||||
|
||||
IllegalArgumentException exception = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> service.updateAdAccountId("user-1", "FACEBOOK", "act-1"));
|
||||
|
||||
assertEquals("Credentials not found for user user-1 and platform FACEBOOK", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateAdAccountIdShouldUpdateAndSaveCredentials() {
|
||||
SocialMediaCredentials credentials = new SocialMediaCredentials("user-1", "facebook", "encrypted");
|
||||
when(repository.findByUserIdAndPlatform("user-1", "facebook")).thenReturn(Optional.of(credentials));
|
||||
when(repository.save(any(SocialMediaCredentials.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
service.updateAdAccountId("user-1", "FACEBOOK", "act-123");
|
||||
|
||||
assertEquals("act-123", credentials.getAdAccountId());
|
||||
verify(repository).save(credentials);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user