diff --git a/.dockerignore b/.dockerignore index 65da4cb..2b119bd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,36 +1,36 @@ -# Maven -target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties - -# IDE -.idea/ -.vscode/ -*.iml -*.iws -*.ipr -.settings/ -.classpath -.project - -# OS -.DS_Store -Thumbs.db - -# Logs -*.log - -# Test -*.xlsx -equips*.xlsx - -# Git -.git/ -.gitignore -.gitattributes +# Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + +# IDE +.idea/ +.vscode/ +*.iml +*.iws +*.ipr +.settings/ +.classpath +.project + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log + +# Test +*.xlsx +equips*.xlsx + +# Git +.git/ +.gitignore +.gitattributes diff --git a/.gitattributes b/.gitattributes index 3b41682..0ac0d33 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,2 @@ -/mvnw text eol=lf -*.cmd text eol=crlf +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/.gitignore b/.gitignore index 667aaef..d3f8d6e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,33 +1,33 @@ -HELP.md -target/ -.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ - -### VS Code ### -.vscode/ +HELP.md +target/ +.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index c0bcafe..507bf28 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,3 +1,3 @@ -wrapperVersion=3.3.4 -distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip diff --git a/Dockerfile b/Dockerfile index 94b5b89..c91549a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,46 +1,46 @@ -# ============================================ -# Dockerfile para LD Admin Tool Backend -# ============================================ -# Aplicación Spring Boot con Java 17 - -FROM maven:3.9-eclipse-temurin-17 AS build - -WORKDIR /app - -# Copiar archivos de configuración de Maven -COPY pom.xml . -COPY mvnw . -COPY .mvn .mvn - -# Descargar dependencias (cache layer) -RUN mvn dependency:go-offline -B - -# Copiar código fuente -COPY src src - -# Compilar la aplicación -RUN mvn clean package -DskipTests - -# ============================================ -# Imagen de producción -# ============================================ -FROM eclipse-temurin:17-jre-alpine - -WORKDIR /app - -# Copiar el JAR compilado -COPY --from=build /app/target/*.jar app.jar - -# Exponer puerto -EXPOSE 8080 - -# Healthcheck -HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \ - CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1 - -# Variables de entorno por defecto (se pueden sobrescribir en docker-compose) -ENV SPRING_PROFILES_ACTIVE=prod -ENV SERVER_PORT=8080 - -# Ejecutar la aplicación -ENTRYPOINT ["java", "-jar", "app.jar"] +# ============================================ +# Dockerfile para LD Admin Tool Backend +# ============================================ +# Aplicación Spring Boot con Java 17 + +FROM maven:3.9-eclipse-temurin-17 AS build + +WORKDIR /app + +# Copiar archivos de configuración de Maven +COPY pom.xml . +COPY mvnw . +COPY .mvn .mvn + +# Descargar dependencias (cache layer) +RUN mvn dependency:go-offline -B + +# Copiar código fuente +COPY src src + +# Compilar la aplicación +RUN mvn clean package -DskipTests + +# ============================================ +# Imagen de producción +# ============================================ +FROM eclipse-temurin:17-jre-alpine + +WORKDIR /app + +# Copiar el JAR compilado +COPY --from=build /app/target/*.jar app.jar + +# Exponer puerto +EXPOSE 8080 + +# Healthcheck +HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:8080/api/wizard/status || exit 1 + +# Variables de entorno por defecto (se pueden sobrescribir en docker-compose) +ENV SPRING_PROFILES_ACTIVE=prod +ENV SERVER_PORT=8080 + +# Ejecutar la aplicación +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/amep.xlsx b/amep.xlsx new file mode 100644 index 0000000..2225e1e Binary files /dev/null and b/amep.xlsx differ diff --git a/equips.xlsx b/equips.xlsx deleted file mode 100644 index 8d6de0b..0000000 Binary files a/equips.xlsx and /dev/null differ diff --git a/mytest.xlsx b/mytest.xlsx new file mode 100644 index 0000000..72ca0c6 Binary files /dev/null and b/mytest.xlsx differ diff --git a/pom.xml b/pom.xml index 8014c27..10eaa37 100644 --- a/pom.xml +++ b/pom.xml @@ -1,144 +1,144 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.5.6 - - - com.upc.ld_admintool - ld_admintool - 0.0.1-SNAPSHOT - ld_admintool - Demo project for Spring Boot - - - - - - - - - - - - - - - 17 - - - - org.springframework.boot - spring-boot-starter-jdbc - - - org.springframework.boot - spring-boot-starter-web - - - - org.postgresql - postgresql - runtime - - - org.springframework.boot - spring-boot-starter-test - test - - - - - com.h2database - h2 - test - - - - - org.mockito - mockito-core - test - - - - org.apache.poi - poi-ooxml - 5.2.5 - - - org.projectlombok - lombok - 1.18.30 - provided - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - org.jacoco - jacoco-maven-plugin - 0.8.11 - - - - prepare-agent - - - - report - test - - report - - - - jacoco-check - - check - - - - - PACKAGE - - - LINE - COVEREDRATIO - 0.50 - - - - - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.2.5 - - - **/*Test.java - **/*Tests.java - - - - - - - + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.5.6 + + + com.upc.ld_admintool + ld_admintool + 0.0.1-SNAPSHOT + ld_admintool + Demo project for Spring Boot + + + + + + + + + + + + + + + 17 + + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-web + + + + org.postgresql + postgresql + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + + com.h2database + h2 + test + + + + + org.mockito + mockito-core + test + + + + org.apache.poi + poi-ooxml + 5.2.5 + + + org.projectlombok + lombok + 1.18.30 + provided + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + org.jacoco + jacoco-maven-plugin + 0.8.11 + + + + prepare-agent + + + + report + test + + report + + + + jacoco-check + + check + + + + + PACKAGE + + + LINE + COVEREDRATIO + 0.50 + + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + **/*Test.java + **/*Tests.java + + + + + + + diff --git a/src/main/java/com/upc/ld_admintool/LdAdmintoolApplication.java b/src/main/java/com/upc/ld_admintool/LdAdmintoolApplication.java index bb87617..4eec406 100644 --- a/src/main/java/com/upc/ld_admintool/LdAdmintoolApplication.java +++ b/src/main/java/com/upc/ld_admintool/LdAdmintoolApplication.java @@ -1,13 +1,13 @@ -package com.upc.ld_admintool; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class LdAdmintoolApplication { - - public static void main(String[] args) { - SpringApplication.run(LdAdmintoolApplication.class, args); - } - -} +package com.upc.ld_admintool; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class LdAdmintoolApplication { + + public static void main(String[] args) { + SpringApplication.run(LdAdmintoolApplication.class, args); + } + +} diff --git a/src/main/java/com/upc/ld_admintool/domain/services/CategoriesService.java b/src/main/java/com/upc/ld_admintool/domain/services/CategoriesService.java index be7121d..02ce9b9 100644 --- a/src/main/java/com/upc/ld_admintool/domain/services/CategoriesService.java +++ b/src/main/java/com/upc/ld_admintool/domain/services/CategoriesService.java @@ -1,28 +1,28 @@ -package com.upc.ld_admintool.domain.services; -import com.upc.ld_admintool.rest.DTO.CategoryDTO; -import com.upc.ld_admintool.rest.DTO.IntervalDTO; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import java.util.List; -@Service -public class CategoriesService { - - @Autowired - private LDService ldService; - - - ////// METRICS ///////// - public void importarCategoriesMetriques(List categories) { - ldService.importarCategoriesMetriques(categories); - } - - ////// FACTORS ///////// - public void importarCategoriesFactors(List categories) { - ldService.importarCategoriesFactors(categories); - } - - ////// STRATEGIC INDICATORS ///////// - public void importarCategoriesStrategicIndicators(List categories) { - ldService.importarCategoriesStrategicIndicators(categories); - } -} +package com.upc.ld_admintool.domain.services; +import com.upc.ld_admintool.rest.DTO.CategoryDTO; +import com.upc.ld_admintool.rest.DTO.IntervalDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.List; +@Service +public class CategoriesService { + + @Autowired + private LDService ldService; + + + ////// METRICS ///////// + public void importarCategoriesMetriques(List categories) { + ldService.importarCategoriesMetriques(categories); + } + + ////// FACTORS ///////// + public void importarCategoriesFactors(List categories) { + ldService.importarCategoriesFactors(categories); + } + + ////// STRATEGIC INDICATORS ///////// + public void importarCategoriesStrategicIndicators(List categories) { + ldService.importarCategoriesStrategicIndicators(categories); + } +} diff --git a/src/main/java/com/upc/ld_admintool/domain/services/FactorsService.java b/src/main/java/com/upc/ld_admintool/domain/services/FactorsService.java index 7addede..d09275c 100644 --- a/src/main/java/com/upc/ld_admintool/domain/services/FactorsService.java +++ b/src/main/java/com/upc/ld_admintool/domain/services/FactorsService.java @@ -1,34 +1,34 @@ -package com.upc.ld_admintool.domain.services; - -import org.springframework.beans.factory.annotation.Autowired; -import com.upc.ld_admintool.rest.DTO.FactorDTO; -import org.springframework.stereotype.Service; -import java.util.List; -import java.util.Map; - -@Service -public class FactorsService { - - @Autowired - private LDService ldService; - - public List getFactorsByProject(String projectId) { - return ldService.getFactorsByProject(projectId); - } - - public List getFactorsCategoriesList() { - return ldService.getFactorsCategoriesList(); - } - - public List> getAllFactorsCategories() { - return ldService.getAllFactorsCategories(); - } - - public void updateFactorCategory(Long id, String category, String project) { - ldService.updateFactorCategory(id, category, project); - } - - public void importQualityFactors() { - ldService.importQualityFactors(); - } +package com.upc.ld_admintool.domain.services; + +import org.springframework.beans.factory.annotation.Autowired; +import com.upc.ld_admintool.rest.DTO.FactorDTO; +import org.springframework.stereotype.Service; +import java.util.List; +import java.util.Map; + +@Service +public class FactorsService { + + @Autowired + private LDService ldService; + + public List getFactorsByProject(String projectId) { + return ldService.getFactorsByProject(projectId); + } + + public List getFactorsCategoriesList() { + return ldService.getFactorsCategoriesList(); + } + + public List> getAllFactorsCategories() { + return ldService.getAllFactorsCategories(); + } + + public void updateFactorCategory(Long id, String category, String project) { + ldService.updateFactorCategory(id, category, project); + } + + public void importQualityFactors() { + ldService.importQualityFactors(); + } } \ No newline at end of file diff --git a/src/main/java/com/upc/ld_admintool/domain/services/LDEvalService.java b/src/main/java/com/upc/ld_admintool/domain/services/LDEvalService.java index cdd87af..5f878fa 100644 --- a/src/main/java/com/upc/ld_admintool/domain/services/LDEvalService.java +++ b/src/main/java/com/upc/ld_admintool/domain/services/LDEvalService.java @@ -1,35 +1,35 @@ -package com.upc.ld_admintool.domain.services; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.*; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.client.HttpClientErrorException; - -@Service -public class LDEvalService { - - @Value("${ld.eval.url}") //http://learning-dashboard:5000/api - private String ldEvalUrl; - - private final RestTemplate restTemplate = new RestTemplate(); - - // ------------------------------- - // Trigger refresh del LDEval - // ------------------------------- - public boolean triggerRefresh() { - String url = ldEvalUrl + "/api/refresh"; - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - - HttpEntity request = new HttpEntity<>(headers); - - try { - restTemplate.postForEntity(url, request, Void.class); - return true; - } catch (HttpClientErrorException e) { - System.err.println("Error triggering LDEval refresh: " + e.getMessage()); - return false; - } - } -} +package com.upc.ld_admintool.domain.services; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.client.HttpClientErrorException; + +@Service +public class LDEvalService { + + @Value("${ld.eval.url}") //http://learning-dashboard:5000/api + private String ldEvalUrl; + + private final RestTemplate restTemplate = new RestTemplate(); + + // ------------------------------- + // Trigger refresh del LDEval + // ------------------------------- + public boolean triggerRefresh() { + String url = ldEvalUrl + "/api/refresh"; + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity request = new HttpEntity<>(headers); + + try { + restTemplate.postForEntity(url, request, Void.class); + return true; + } catch (HttpClientErrorException e) { + System.err.println("Error triggering LDEval refresh: " + e.getMessage()); + return false; + } + } +} diff --git a/src/main/java/com/upc/ld_admintool/domain/services/LDService.java b/src/main/java/com/upc/ld_admintool/domain/services/LDService.java index 6d8d070..5643873 100644 --- a/src/main/java/com/upc/ld_admintool/domain/services/LDService.java +++ b/src/main/java/com/upc/ld_admintool/domain/services/LDService.java @@ -1,399 +1,399 @@ -package com.upc.ld_admintool.domain.services; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.*; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.client.HttpClientErrorException; -import com.upc.ld_admintool.rest.DTO.*; -import com.upc.ld_admintool.domain.utils.DataSource; - -import java.util.*; -import java.util.stream.Collectors; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.core.JsonProcessingException; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; - -@Service -public class LDService { - - @Value("${ld.api.url}") - private String ldApiUrl; // http://localhost:8888/api - - private final RestTemplate restTemplate = new RestTemplate(); - - // ------------------------------- - // Crear projecte al Learning Dashboard - // ------------------------------- - public Long createProject(ProjectDTO project) { - try { - String url = ldApiUrl + "/projects"; - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - - HttpEntity request = new HttpEntity<>(project, headers); - ResponseEntity response = restTemplate.postForEntity(url, request, ProjectDTO.class); - - return Objects.requireNonNull(response.getBody()).getId(); - } catch (HttpClientErrorException e) { - System.err.println("Error creating project: " + e.getMessage()); - return null; - } - } - - // ------------------------------- - // Crear estudiant dins un projecte - // ------------------------------- - public void createStudent(Long projectId, StudentDTO student) { - String url = ldApiUrl + "/projects/" + projectId + "/students"; - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - - HttpEntity request = new HttpEntity<>(student, headers); - try { - restTemplate.postForEntity(url, request, StudentDTO.class); - } catch (HttpClientErrorException e) { - System.err.println("Error creating student: " + e.getMessage()); - } - } - - // ------------------------------- - // Obtenir tots els projectes - // ------------------------------- - public List getAllProjects() { - String url = ldApiUrl + "/projects"; - ResponseEntity response = restTemplate.getForEntity(url, ProjectDTO[].class); - return Arrays.asList(response.getBody()); - } - - // ------------------------------- - // Obtenir projecte per id - // ------------------------------- - public ProjectDTO getProjectById(Long id) { - String url = ldApiUrl + "/projects/" + id; - try { - ResponseEntity response = restTemplate.getForEntity(url, ProjectDTO.class); - return response.getBody(); - } catch (HttpClientErrorException e) { - System.err.println("Error fetching project by ID: " + e.getMessage()); - return null; - } - } - - // ------------------------------- - // Actualitzar projecte - // ------------------------------- - public void updateProject(Long id, ProjectDTO project) { - String url = ldApiUrl + "/projects/" + id; - ObjectMapper mapper = new ObjectMapper(); - MultiValueMap body = new LinkedMultiValueMap<>(); - try { - Map updateData = new HashMap<>(); - - // Mapping als camps esperats pel backend LD: - updateData.put("external_id", project.getExternalId()); - updateData.put("name", project.getName()); - updateData.put("description", project.getDescription()); - updateData.put("backlog_id", project.getBacklogId()); - // Atenció: adapta mapping de identities al format correcte - // S'ha d'enviar Map. Probablement necessites crear un Map a - // partir de project.getIdentities() - Map identities = new HashMap<>(); - if (project.getIdentities() != null) { - project.getIdentities().forEach((ds, pid) -> { - if (pid != null && pid.getUrl() != null) - identities.put(ds, pid.getUrl()); - }); - } - updateData.put("identities", identities); - updateData.put("global", project.getIsGlobal()); - if (project.getStudents() != null) { - updateData.put("students", project.getStudents()); - } - System.out.println("Update data prepared: " + updateData); - - HttpHeaders jsonHeaders = new HttpHeaders(); - jsonHeaders.setContentType(MediaType.APPLICATION_JSON); - HttpEntity jsonPart = new HttpEntity<>(mapper.writeValueAsString(updateData), jsonHeaders); - body.add("data", jsonPart); - - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.MULTIPART_FORM_DATA); - - HttpEntity> request = new HttpEntity<>(body, headers); - restTemplate.exchange(url, HttpMethod.PUT, request, Void.class); - } catch (JsonProcessingException e) { - throw new RuntimeException("Error serialitzant updateData a JSON!", e); - } catch (HttpClientErrorException e) { - System.err.println("Error updating project: " + e.getMessage()); - } - } - - // ------------------------------- - // Eliminar estudiant - // ------------------------------- - public void deleteStudent(Long studentId) { - String url = ldApiUrl + "/metrics/students/" + studentId; - try { - restTemplate.delete(url); - } catch (HttpClientErrorException e) { - System.err.println("Error deleting student: " + e.getMessage()); - } - } - - // ------------------------------- - // Eliminar projecte - // ------------------------------- - public void deleteProject(Long id) { - String url = ldApiUrl + "/projects/" + id; - try { - restTemplate.delete(url); - } catch (HttpClientErrorException e) { - System.err.println("Error deleting project: " + e.getMessage()); - } - } - - // ------------------------------- - // Obtenir mètriques d'un projecte - // ------------------------------- - public List getMetricsByProject(String projectId) { - - String url = ldApiUrl + "/metrics?prj=" + projectId; - try { - ResponseEntity response = restTemplate.getForEntity(url, List.class); - List> data = response.getBody(); - List metrics = new ArrayList<>(); - for (Map m : data) { - metrics.add(new MetricDTO( - String.valueOf(m.get("id")), - (String) m.get("externalId"), - (String) m.get("name"), - (String) m.get("description"), - (String) m.get("categoryName"), - (String) m.get("scope"))); - } - return metrics; - } catch (HttpClientErrorException e) { - System.err.println("Error fetching metrics: " + e.getMessage()); - return Collections.emptyList(); - } - } - - // ------------------------------- - // Obtenir categories de mètriques - // ------------------------------- - public List> getAllMetricsCategories() { - String url = ldApiUrl + "/metrics/categories"; - ResponseEntity response = restTemplate.getForEntity(url, List.class); - return response.getBody(); - } - - // ------------------------------- - // Obtenir llista de categories de mètriques - // ------------------------------- - public List getMetricsCategoriesList() { - String url = ldApiUrl + "/metrics/list"; - ResponseEntity response = restTemplate.getForEntity(url, List.class); - return response.getBody(); - } - - // ------------------------------- - // Editar mètrica - // ------------------------------- - public void editMetric(Long id, String threshold, String url, String categoryName, String scope, String project) { - String apiUrl = ldApiUrl + "/metrics/" + id + "?prj=" + URLEncoder.encode(project, StandardCharsets.UTF_8); - MultiValueMap formData = new LinkedMultiValueMap<>(); - formData.add("threshold", threshold != null ? threshold : ""); - formData.add("url", url != null ? url : ""); - formData.add("categoryName", categoryName != null ? categoryName : ""); - - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.MULTIPART_FORM_DATA); - - HttpEntity> request = new HttpEntity<>(formData, headers); - - try { - restTemplate.exchange(apiUrl, HttpMethod.PUT, request, Void.class); - } catch (HttpClientErrorException e) { - System.err.println(" ❌ Error editing metric: " + e.getMessage()); - throw e; - } - } - - // ------------------------------- - // Importar categories de mètriques - // ------------------------------- - public void importarCategoriesMetriques(List categories) { - System.out.println("Importing metric categories: " + categories); - for (CategoryDTO cat : categories) { - String url = ldApiUrl + "/metrics/categories?name=" + cat.getCategory() - + (cat.getPatternGroup() != null ? "&patternGroup=" + cat.getPatternGroup() : ""); - try { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - HttpEntity>> request = new HttpEntity<>(cat.getInterval(), headers); - restTemplate.postForEntity(url, request, Void.class); - } catch (Exception e) { - System.err.println("Error important categoria " + cat.getCategory() + ": " + e.getMessage()); - } - } - } - - // ------------------------------- - // Obtenir factors d'un projecte - // ------------------------------- - public List getFactorsByProject(String projectId) { - String url = ldApiUrl + "/qualityFactors?prj=" + projectId; - ResponseEntity response = restTemplate.getForEntity(url, List.class); - List> data = response.getBody(); - - List factors = new ArrayList<>(); - for (Map f : data) { - factors.add(new FactorDTO( - String.valueOf(f.get("id")), - (String) f.get("externalId"), - (String) f.get("name"), - (String) f.get("description"), - (String) f.get("categoryName"), - f.get("threshold") != null ? String.valueOf(f.get("threshold")) : null, - (String) f.get("type"), - (List) f.get("metrics"), - (List) f.get("metricsWeights"))); - } - return factors; - } - - // ------------------------------- - // Obtenir llista de categories de factors - // ------------------------------- - public List getFactorsCategoriesList() { - String url = ldApiUrl + "/factors/list"; - ResponseEntity response = restTemplate.getForEntity(url, List.class); - return response.getBody(); - } - - // ------------------------------- - // Obtenir categories de factors - // ------------------------------- - public List> getAllFactorsCategories() { - String url = ldApiUrl + "/factors/categories"; - ResponseEntity response = restTemplate.getForEntity(url, List.class); - return response.getBody(); - } - - // ------------------------------- - // Editar factor - // ------------------------------- - public void updateFactorCategory(Long id, String category, String project) { - String apiUrl = ldApiUrl + "/qualityFactors/" + id + - "/category?prj=" + URLEncoder.encode(project, StandardCharsets.UTF_8); - - MultiValueMap formData = new LinkedMultiValueMap<>(); - formData.add("category", category); - - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - - HttpEntity> request = new HttpEntity<>(formData, headers); - - restTemplate.exchange(apiUrl, HttpMethod.PUT, request, Void.class); - } - - // ------------------------------- - // Importar categories de factors - // ------------------------------- - public void importarCategoriesFactors(List categories) { - System.out.println("Importing factor categories: " + categories); - for (CategoryDTO cat : categories) { - String url = ldApiUrl + "/factors/categories?name=" + cat.getCategory() - + (cat.getPatternGroup() != null ? "&patternGroup=" + cat.getPatternGroup() : ""); - System.out.println("Importing category to URL: " + url); - try { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - HttpEntity>> request = new HttpEntity<>(cat.getInterval(), headers); - restTemplate.postForEntity(url, request, Void.class); - } catch (Exception e) { - System.err.println("Error important categoria " + cat.getCategory() + ": " + e.getMessage()); - } - } - } - - // ------------------------------- - // Obtenir categories d'indicadors estratègics - // ------------------------------- - public List> getAllStrategicIndicatorCategories() { - String url = ldApiUrl + "/strategicIndicators/categories"; - ResponseEntity response = restTemplate.getForEntity(url, List.class); - return response.getBody(); - } - - // ------------------------------- - // Importar categories d'indicadors estratègics - // ------------------------------- - public void importarCategoriesStrategicIndicators(List dtos) { - try { - List> categories = dtos.stream() - .map(dto -> { - Map map = new HashMap<>(); - map.put("name", dto.getName()); - map.put("color", dto.getColor()); - return map; - }) - .collect(Collectors.toList()); - System.out.println("Importing strategic indicator categories: " + categories); - String url = ldApiUrl + "/strategicIndicators/categories"; - restTemplate.postForEntity(url, categories, Void.class); - } catch (HttpClientErrorException e) { - System.err.println("Error importing strategic indicator categories: " + e.getMessage()); - } catch (Exception e) { - System.err.println("Unexpected error importing strategic indicator categories: " + e.getMessage()); - } - } - - // ------------------------------- - // Importar mètriques (cridar LD API /api/metrics/import) - // ------------------------------- - public void importMetrics() { - String url = ldApiUrl + "/metrics/import"; - try { - restTemplate.getForEntity(url, Void.class); - System.out.println("Metrics imported successfully"); - } catch (HttpClientErrorException e) { - System.err.println("Error importing metrics: " + e.getMessage()); - throw e; - } - } - - // ------------------------------- - // Importar quality factors (cridar LD API /api/qualityFactors/import) - // ------------------------------- - public void importQualityFactors() { - String url = ldApiUrl + "/qualityFactors/import"; - try { - restTemplate.getForEntity(url, Void.class); - System.out.println("Quality Factors imported successfully"); - } catch (HttpClientErrorException e) { - System.err.println("Error importing quality factors: " + e.getMessage()); - throw e; - } - } - - // ------------------------------- - // Fetch strategic indicators (cridar LD API /api/strategicIndicators/fetch) - // ------------------------------- - public void fetchStrategicIndicators() { - String url = ldApiUrl + "/strategicIndicators/fetch"; - try { - restTemplate.getForEntity(url, Void.class); - System.out.println("Strategic Indicators fetched successfully"); - } catch (HttpClientErrorException e) { - System.err.println("Error fetching strategic indicators: " + e.getMessage()); - throw e; - } - } - -} +package com.upc.ld_admintool.domain.services; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.client.HttpClientErrorException; +import com.upc.ld_admintool.rest.DTO.*; +import com.upc.ld_admintool.domain.utils.DataSource; + +import java.util.*; +import java.util.stream.Collectors; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +@Service +public class LDService { + + @Value("${ld.api.url}") + private String ldApiUrl; // http://localhost:8888/api + + private final RestTemplate restTemplate = new RestTemplate(); + + // ------------------------------- + // Crear projecte al Learning Dashboard + // ------------------------------- + public Long createProject(ProjectDTO project) { + try { + String url = ldApiUrl + "/projects"; + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity request = new HttpEntity<>(project, headers); + ResponseEntity response = restTemplate.postForEntity(url, request, ProjectDTO.class); + + return Objects.requireNonNull(response.getBody()).getId(); + } catch (HttpClientErrorException e) { + System.err.println("Error creating project: " + e.getMessage()); + return null; + } + } + + // ------------------------------- + // Crear estudiant dins un projecte + // ------------------------------- + public void createStudent(Long projectId, StudentDTO student) { + String url = ldApiUrl + "/projects/" + projectId + "/students"; + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity request = new HttpEntity<>(student, headers); + try { + restTemplate.postForEntity(url, request, StudentDTO.class); + } catch (HttpClientErrorException e) { + System.err.println("Error creating student: " + e.getMessage()); + } + } + + // ------------------------------- + // Obtenir tots els projectes + // ------------------------------- + public List getAllProjects() { + String url = ldApiUrl + "/projects"; + ResponseEntity response = restTemplate.getForEntity(url, ProjectDTO[].class); + return Arrays.asList(response.getBody()); + } + + // ------------------------------- + // Obtenir projecte per id + // ------------------------------- + public ProjectDTO getProjectById(Long id) { + String url = ldApiUrl + "/projects/" + id; + try { + ResponseEntity response = restTemplate.getForEntity(url, ProjectDTO.class); + return response.getBody(); + } catch (HttpClientErrorException e) { + System.err.println("Error fetching project by ID: " + e.getMessage()); + return null; + } + } + + // ------------------------------- + // Actualitzar projecte + // ------------------------------- + public void updateProject(Long id, ProjectDTO project) { + String url = ldApiUrl + "/projects/" + id; + ObjectMapper mapper = new ObjectMapper(); + MultiValueMap body = new LinkedMultiValueMap<>(); + try { + Map updateData = new HashMap<>(); + + // Mapping als camps esperats pel backend LD: + updateData.put("external_id", project.getExternalId()); + updateData.put("name", project.getName()); + updateData.put("description", project.getDescription()); + updateData.put("backlog_id", project.getBacklogId()); + // Atenció: adapta mapping de identities al format correcte + // S'ha d'enviar Map. Probablement necessites crear un Map a + // partir de project.getIdentities() + Map identities = new HashMap<>(); + if (project.getIdentities() != null) { + project.getIdentities().forEach((ds, pid) -> { + if (pid != null && pid.getUrl() != null) + identities.put(ds, pid.getUrl()); + }); + } + updateData.put("identities", identities); + updateData.put("global", project.getIsGlobal()); + if (project.getStudents() != null) { + updateData.put("students", project.getStudents()); + } + System.out.println("Update data prepared: " + updateData); + + HttpHeaders jsonHeaders = new HttpHeaders(); + jsonHeaders.setContentType(MediaType.APPLICATION_JSON); + HttpEntity jsonPart = new HttpEntity<>(mapper.writeValueAsString(updateData), jsonHeaders); + body.add("data", jsonPart); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + + HttpEntity> request = new HttpEntity<>(body, headers); + restTemplate.exchange(url, HttpMethod.PUT, request, Void.class); + } catch (JsonProcessingException e) { + throw new RuntimeException("Error serialitzant updateData a JSON!", e); + } catch (HttpClientErrorException e) { + System.err.println("Error updating project: " + e.getMessage()); + } + } + + // ------------------------------- + // Eliminar estudiant + // ------------------------------- + public void deleteStudent(Long studentId) { + String url = ldApiUrl + "/metrics/students/" + studentId; + try { + restTemplate.delete(url); + } catch (HttpClientErrorException e) { + System.err.println("Error deleting student: " + e.getMessage()); + } + } + + // ------------------------------- + // Eliminar projecte + // ------------------------------- + public void deleteProject(Long id) { + String url = ldApiUrl + "/projects/" + id; + try { + restTemplate.delete(url); + } catch (HttpClientErrorException e) { + System.err.println("Error deleting project: " + e.getMessage()); + } + } + + // ------------------------------- + // Obtenir mètriques d'un projecte + // ------------------------------- + public List getMetricsByProject(String projectId) { + + String url = ldApiUrl + "/metrics?prj=" + projectId; + try { + ResponseEntity response = restTemplate.getForEntity(url, List.class); + List> data = response.getBody(); + List metrics = new ArrayList<>(); + for (Map m : data) { + metrics.add(new MetricDTO( + String.valueOf(m.get("id")), + (String) m.get("externalId"), + (String) m.get("name"), + (String) m.get("description"), + (String) m.get("categoryName"), + (String) m.get("scope"))); + } + return metrics; + } catch (HttpClientErrorException e) { + System.err.println("Error fetching metrics: " + e.getMessage()); + return Collections.emptyList(); + } + } + + // ------------------------------- + // Obtenir categories de mètriques + // ------------------------------- + public List> getAllMetricsCategories() { + String url = ldApiUrl + "/metrics/categories"; + ResponseEntity response = restTemplate.getForEntity(url, List.class); + return response.getBody(); + } + + // ------------------------------- + // Obtenir llista de categories de mètriques + // ------------------------------- + public List getMetricsCategoriesList() { + String url = ldApiUrl + "/metrics/list"; + ResponseEntity response = restTemplate.getForEntity(url, List.class); + return response.getBody(); + } + + // ------------------------------- + // Editar mètrica + // ------------------------------- + public void editMetric(Long id, String threshold, String url, String categoryName, String scope, String project) { + String apiUrl = ldApiUrl + "/metrics/" + id + "?prj=" + URLEncoder.encode(project, StandardCharsets.UTF_8); + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("threshold", threshold != null ? threshold : ""); + formData.add("url", url != null ? url : ""); + formData.add("categoryName", categoryName != null ? categoryName : ""); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + + HttpEntity> request = new HttpEntity<>(formData, headers); + + try { + restTemplate.exchange(apiUrl, HttpMethod.PUT, request, Void.class); + } catch (HttpClientErrorException e) { + System.err.println(" ❌ Error editing metric: " + e.getMessage()); + throw e; + } + } + + // ------------------------------- + // Importar categories de mètriques + // ------------------------------- + public void importarCategoriesMetriques(List categories) { + System.out.println("Importing metric categories: " + categories); + for (CategoryDTO cat : categories) { + String url = ldApiUrl + "/metrics/categories?name=" + cat.getCategory() + + (cat.getPatternGroup() != null ? "&patternGroup=" + cat.getPatternGroup() : ""); + try { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity>> request = new HttpEntity<>(cat.getInterval(), headers); + restTemplate.postForEntity(url, request, Void.class); + } catch (Exception e) { + System.err.println("Error important categoria " + cat.getCategory() + ": " + e.getMessage()); + } + } + } + + // ------------------------------- + // Obtenir factors d'un projecte + // ------------------------------- + public List getFactorsByProject(String projectId) { + String url = ldApiUrl + "/qualityFactors?prj=" + projectId; + ResponseEntity response = restTemplate.getForEntity(url, List.class); + List> data = response.getBody(); + + List factors = new ArrayList<>(); + for (Map f : data) { + factors.add(new FactorDTO( + String.valueOf(f.get("id")), + (String) f.get("externalId"), + (String) f.get("name"), + (String) f.get("description"), + (String) f.get("categoryName"), + f.get("threshold") != null ? String.valueOf(f.get("threshold")) : null, + (String) f.get("type"), + (List) f.get("metrics"), + (List) f.get("metricsWeights"))); + } + return factors; + } + + // ------------------------------- + // Obtenir llista de categories de factors + // ------------------------------- + public List getFactorsCategoriesList() { + String url = ldApiUrl + "/factors/list"; + ResponseEntity response = restTemplate.getForEntity(url, List.class); + return response.getBody(); + } + + // ------------------------------- + // Obtenir categories de factors + // ------------------------------- + public List> getAllFactorsCategories() { + String url = ldApiUrl + "/factors/categories"; + ResponseEntity response = restTemplate.getForEntity(url, List.class); + return response.getBody(); + } + + // ------------------------------- + // Editar factor + // ------------------------------- + public void updateFactorCategory(Long id, String category, String project) { + String apiUrl = ldApiUrl + "/qualityFactors/" + id + + "/category?prj=" + URLEncoder.encode(project, StandardCharsets.UTF_8); + + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("category", category); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + HttpEntity> request = new HttpEntity<>(formData, headers); + + restTemplate.exchange(apiUrl, HttpMethod.PUT, request, Void.class); + } + + // ------------------------------- + // Importar categories de factors + // ------------------------------- + public void importarCategoriesFactors(List categories) { + System.out.println("Importing factor categories: " + categories); + for (CategoryDTO cat : categories) { + String url = ldApiUrl + "/factors/categories?name=" + cat.getCategory() + + (cat.getPatternGroup() != null ? "&patternGroup=" + cat.getPatternGroup() : ""); + System.out.println("Importing category to URL: " + url); + try { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity>> request = new HttpEntity<>(cat.getInterval(), headers); + restTemplate.postForEntity(url, request, Void.class); + } catch (Exception e) { + System.err.println("Error important categoria " + cat.getCategory() + ": " + e.getMessage()); + } + } + } + + // ------------------------------- + // Obtenir categories d'indicadors estratègics + // ------------------------------- + public List> getAllStrategicIndicatorCategories() { + String url = ldApiUrl + "/strategicIndicators/categories"; + ResponseEntity response = restTemplate.getForEntity(url, List.class); + return response.getBody(); + } + + // ------------------------------- + // Importar categories d'indicadors estratègics + // ------------------------------- + public void importarCategoriesStrategicIndicators(List dtos) { + try { + List> categories = dtos.stream() + .map(dto -> { + Map map = new HashMap<>(); + map.put("name", dto.getName()); + map.put("color", dto.getColor()); + return map; + }) + .collect(Collectors.toList()); + System.out.println("Importing strategic indicator categories: " + categories); + String url = ldApiUrl + "/strategicIndicators/categories"; + restTemplate.postForEntity(url, categories, Void.class); + } catch (HttpClientErrorException e) { + System.err.println("Error importing strategic indicator categories: " + e.getMessage()); + } catch (Exception e) { + System.err.println("Unexpected error importing strategic indicator categories: " + e.getMessage()); + } + } + + // ------------------------------- + // Importar mètriques (cridar LD API /api/metrics/import) + // ------------------------------- + public void importMetrics() { + String url = ldApiUrl + "/metrics/import"; + try { + restTemplate.getForEntity(url, Void.class); + System.out.println("Metrics imported successfully"); + } catch (HttpClientErrorException e) { + System.err.println("Error importing metrics: " + e.getMessage()); + throw e; + } + } + + // ------------------------------- + // Importar quality factors (cridar LD API /api/qualityFactors/import) + // ------------------------------- + public void importQualityFactors() { + String url = ldApiUrl + "/qualityFactors/import"; + try { + restTemplate.getForEntity(url, Void.class); + System.out.println("Quality Factors imported successfully"); + } catch (HttpClientErrorException e) { + System.err.println("Error importing quality factors: " + e.getMessage()); + throw e; + } + } + + // ------------------------------- + // Fetch strategic indicators (cridar LD API /api/strategicIndicators/fetch) + // ------------------------------- + public void fetchStrategicIndicators() { + String url = ldApiUrl + "/strategicIndicators/fetch"; + try { + restTemplate.getForEntity(url, Void.class); + System.out.println("Strategic Indicators fetched successfully"); + } catch (HttpClientErrorException e) { + System.err.println("Error fetching strategic indicators: " + e.getMessage()); + throw e; + } + } + +} diff --git a/src/main/java/com/upc/ld_admintool/domain/services/MetricsService.java b/src/main/java/com/upc/ld_admintool/domain/services/MetricsService.java index d16ef89..51161a7 100644 --- a/src/main/java/com/upc/ld_admintool/domain/services/MetricsService.java +++ b/src/main/java/com/upc/ld_admintool/domain/services/MetricsService.java @@ -1,35 +1,35 @@ -package com.upc.ld_admintool.domain.services; - -import org.springframework.beans.factory.annotation.Autowired; -import com.upc.ld_admintool.rest.DTO.MetricDTO; -import org.springframework.stereotype.Service; -import java.util.List; -import java.util.Map; - -@Service -public class MetricsService { - - @Autowired - private LDService ldService; - - public List getMetricsByProject(String projectId) { - return ldService.getMetricsByProject(projectId); - } - - public List getMetricsCategoriesList() { - return ldService.getMetricsCategoriesList(); - } - - public List> getAllMetricsCategories() { - return ldService.getAllMetricsCategories(); - } - - public void editMetric(Long id, String threshold, String url, String categoryName, String scope, String project) { - ldService.editMetric(id, threshold, url, categoryName, scope, project); - } - - public void importMetrics() { - ldService.importMetrics(); - } - +package com.upc.ld_admintool.domain.services; + +import org.springframework.beans.factory.annotation.Autowired; +import com.upc.ld_admintool.rest.DTO.MetricDTO; +import org.springframework.stereotype.Service; +import java.util.List; +import java.util.Map; + +@Service +public class MetricsService { + + @Autowired + private LDService ldService; + + public List getMetricsByProject(String projectId) { + return ldService.getMetricsByProject(projectId); + } + + public List getMetricsCategoriesList() { + return ldService.getMetricsCategoriesList(); + } + + public List> getAllMetricsCategories() { + return ldService.getAllMetricsCategories(); + } + + public void editMetric(Long id, String threshold, String url, String categoryName, String scope, String project) { + ldService.editMetric(id, threshold, url, categoryName, scope, project); + } + + public void importMetrics() { + ldService.importMetrics(); + } + } \ No newline at end of file diff --git a/src/main/java/com/upc/ld_admintool/domain/services/ProjectService.java b/src/main/java/com/upc/ld_admintool/domain/services/ProjectService.java index 710e356..f007f31 100644 --- a/src/main/java/com/upc/ld_admintool/domain/services/ProjectService.java +++ b/src/main/java/com/upc/ld_admintool/domain/services/ProjectService.java @@ -1,775 +1,775 @@ -package com.upc.ld_admintool.domain.services; - -import com.upc.ld_admintool.rest.DTO.ProjectDTO; -import com.upc.ld_admintool.rest.DTO.StudentDTO; -import com.upc.ld_admintool.rest.DTO.MetricDTO; -import com.upc.ld_admintool.rest.DTO.FactorDTO; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import java.text.Normalizer; -import java.util.List; -import java.util.ArrayList; -import java.util.Optional; -import java.util.Set; -import java.util.HashSet; -import java.util.HashMap; -import java.util.Objects; -import java.util.Map; -import java.util.stream.Collectors; - -import org.springframework.transaction.annotation.Transactional; - -import com.upc.ld_admintool.domain.services.exceptions.SaveSyncException; -import com.upc.ld_admintool.rest.DTO.SaveSyncResponseDTO; - -@Service -@Transactional -public class ProjectService { - - @Autowired - private LDService ldService; - - @Autowired - private LDEvalService ldEvalService; - - public List llistarProjectesAmbStudents() { - List rawProjects = ldService.getAllProjects(); - List result = new ArrayList<>(); - for (ProjectDTO p : rawProjects) { - ProjectDTO complet = ldService.getProjectById(p.getId()); - if (complet != null) { - result.add(complet); - } else { - result.add(p); - } - } - return result; - } - - public ProjectDTO getProjectById(Long id) { - return ldService.getProjectById(id); - } - - public void importProjects(List projects) { - boolean createdAny = false; - for (ProjectDTO project : projects) { - Long projectId = ldService.createProject(project); - if (projectId == null) { - continue; - } - createdAny = true; - - if (project.getStudents() != null) { - for (StudentDTO student : project.getStudents()) { - ldService.createStudent(projectId, student); - } - } - } - if (createdAny) { - ldEvalService.triggerRefresh(); - } - } - - public SaveSyncResponseDTO modificarProjecte(Long id, ProjectDTO projecte) { - SaveSyncResponseDTO workflow = new SaveSyncResponseDTO(); - int stepOrder = 1; - - ProjectDTO original = ldService.getProjectById(id); - if (original == null) { - throw new SaveSyncException("No s'ha trobat el projecte", workflow); - } - String projectExternalId = original != null ? original.getExternalId() : null; - - Set originalIds = Optional.ofNullable(original.getStudents()) - .orElse(List.of()).stream() - .map(StudentDTO::getId) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - - Set newIds = Optional.ofNullable(projecte.getStudents()) - .orElse(List.of()).stream() - .map(StudentDTO::getId) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - - List newStudents = Optional.ofNullable(projecte.getStudents()) - .orElse(List.of()).stream() - .filter(student -> student.getId() == null) - .collect(Collectors.toList()); - - Set studentsToDelete = new HashSet<>(originalIds); - studentsToDelete.removeAll(newIds); - - int futureSize = Optional.ofNullable(projecte.getStudents()).map(List::size).orElse(0); - workflow.setFinalTeamSize(futureSize); - - try { - validateCategoriesForNewTeamSize(id, futureSize); - workflow.addSuccessStep(stepOrder++, "Validate category availability", - "Category definitions available for " + futureSize + " member(s)."); - } catch (RuntimeException e) { - workflow.addFailureStep(stepOrder, "Validate category availability", - "Unable to find categories for " + futureSize + " member(s).", e.getMessage()); - throw new SaveSyncException("Category validation failed", e, workflow); - } - - int removed = studentsToDelete.size(); - int added = newStudents.size(); - - try { - if (!studentsToDelete.isEmpty()) { - for (Long removedId : studentsToDelete) { - ldService.deleteStudent(removedId); - } - } - - if (!newStudents.isEmpty()) { - for (StudentDTO student : newStudents) { - ldService.createStudent(id, student); - } - } - - ldService.updateProject(id, projecte); - - String detail = String.format("Team now has %d member(s). Added: %d, removed: %d.", - futureSize, added, removed); - workflow.addSuccessStep(stepOrder++, "Update LD team roster", detail); - } catch (RuntimeException e) { - workflow.addFailureStep(stepOrder, "Update LD team roster", - "Error updating roster in LD.", e.getMessage()); - throw new SaveSyncException("Roster update failed", e, workflow); - } - - boolean refreshOk = ldEvalService.triggerRefresh(); - if (!refreshOk) { - workflow.addFailureStep(stepOrder, "Refresh LDEval caches", - "LDEval refresh endpoint returned an error.", "LDEval refresh failed"); - throw new SaveSyncException("LDEval refresh failed", workflow); - } - workflow.addSuccessStep(stepOrder++, "Refresh LDEval caches", - "Learning Dashboard has been instructed to refresh its evaluation cache."); - - runDataImportSequenceWithRetry(projectExternalId, newStudents, workflow, stepOrder++); - - try { - updateCategoriesForProject(id, projecte, futureSize); - workflow.addSuccessStep(stepOrder++, "Reassign metric & factor categories", - "Categories recalculated for " + futureSize + " member(s)."); - } catch (RuntimeException e) { - workflow.addFailureStep(stepOrder, "Reassign metric & factor categories", - "Unable to update categories.", e.getMessage()); - throw new SaveSyncException("Category update failed", e, workflow); - } - - ldEvalService.triggerRefresh(); - return workflow; - } - - public void esborrarProjecte(Long id) { - ldService.deleteProject(id); - ldEvalService.triggerRefresh(); - } - - public void validateCategoriesForNewTeamSize(Long projectId, int numStudents) { - ProjectDTO project = ldService.getProjectById(projectId); - if (project == null) - return; - - String projectExternalId = project.getExternalId(); - List> metricCategories = ldService.getAllMetricsCategories(); - List> factorCategories = ldService.getAllFactorsCategories(); - - List metrics = ldService.getMetricsByProject(projectExternalId); - for (MetricDTO metric : metrics) { - String currentCategory = metric.getCategoryName(); - if (currentCategory == null || currentCategory.isEmpty()) - continue; - - String patternGroup = null; - for (Map cat : metricCategories) { - if (currentCategory.equals(cat.get("name"))) { - patternGroup = (String) cat.get("patternGroup"); - break; - } - } - - if (patternGroup != null && !patternGroup.isEmpty()) { - String newCategory = findCategoryForMembers(metricCategories, patternGroup, numStudents); - if (newCategory == null) { - throw new RuntimeException("Error: La categoria per a " + numStudents + - " membres no existeix al patró '" + patternGroup + "'"); - } - } - } - - List factors = ldService.getFactorsByProject(projectExternalId); - for (FactorDTO factor : factors) { - String currentCategory = factor.getCategory(); - if (currentCategory == null || currentCategory.isEmpty()) - continue; - - String patternGroup = null; - for (Map cat : factorCategories) { - if (currentCategory.equals(cat.get("name"))) { - patternGroup = (String) cat.get("patternGroup"); - break; - } - } - - if (patternGroup != null && !patternGroup.isEmpty()) { - String newCategory = findCategoryForMembers(factorCategories, patternGroup, numStudents); - if (newCategory == null) { - throw new RuntimeException("Error: La categoria per a " + numStudents + - " membres no existeix al patró '" + patternGroup); - } - } - } - } - - public void updateCategoriesForProject(Long projectId) { - updateCategoriesForProject(projectId, null, null); - } - - public void updateCategoriesForProject(Long projectId, ProjectDTO overrideProject, Integer overrideStudentCount) { - // Removed try-catch to allow exception propagation - ProjectDTO project = overrideProject != null ? overrideProject : ldService.getProjectById(projectId); - if (project == null) { - throw new RuntimeException("No s'ha trobat el projecte amb ID: " + projectId); - } - - String projectExternalId = project.getExternalId(); - int numStudents = overrideStudentCount != null ? overrideStudentCount - : (project.getStudents() != null ? project.getStudents().size() : 0); - - List> metricCategories = ldService.getAllMetricsCategories(); - List> factorCategories = ldService.getAllFactorsCategories(); - - // Actualitzar mètriques - List metrics = ldService.getMetricsByProject(projectExternalId); - Set projectAliases = overrideProject != null - ? buildStudentAliases(overrideProject.getStudents()) - : buildStudentAliases(project); - Map aliasCategoryLookup = buildMetricAliasCategoryLookup(metrics, projectAliases); - - for (MetricDTO metric : metrics) { - String currentCategory = metric.getCategoryName(); - if (needsAliasCategory(currentCategory)) { - String desired = resolveCategoryFromAlias(metric, projectAliases, aliasCategoryLookup); - if (desired != null && !desired.equalsIgnoreCase(currentCategory)) { - updateMetricCategory(metric, desired, projectExternalId); - } - } - - currentCategory = metric.getCategoryName(); - if (currentCategory == null || currentCategory.isEmpty()) { - continue; - } - - String patternGroup = null; - for (Map cat : metricCategories) { - if (currentCategory.equals(cat.get("name"))) { - patternGroup = (String) cat.get("patternGroup"); - break; - } - } - - if (patternGroup != null && !patternGroup.isEmpty()) { - String newCategory = findCategoryForMembers(metricCategories, patternGroup, numStudents); - - if (newCategory == null) { - throw new RuntimeException("Error: La categoria per a " + numStudents + - " membres no existeix al patró '" + patternGroup + "'"); - } - - if (!newCategory.equals(currentCategory)) { - updateMetricCategory(metric, newCategory, projectExternalId); - } - } - } - - // Actualitzar factors - List factors = ldService.getFactorsByProject(projectExternalId); - - for (FactorDTO factor : factors) { - String currentCategory = factor.getCategory(); - if (currentCategory == null || currentCategory.isEmpty()) { - continue; - } - - String patternGroup = null; - for (Map cat : factorCategories) { - if (currentCategory.equals(cat.get("name"))) { - patternGroup = (String) cat.get("patternGroup"); - break; - } - } - - if (patternGroup != null && !patternGroup.isEmpty()) { - String newCategory = findCategoryForMembers(factorCategories, patternGroup, numStudents); - - if (newCategory == null) { - throw new RuntimeException("Error: La categoria per a " + numStudents + - " membres no existeix al patró '" + patternGroup + "'"); - } - - if (!newCategory.equals(currentCategory)) { - Long factorId = Long.parseLong(factor.getId()); - ldService.updateFactorCategory(factorId, newCategory, projectExternalId); - } - } - } - } - - private String findCategoryForMembers(List> categories, String patternGroup, int numMembers) { - for (Map cat : categories) { - String catPatternGroup = (String) cat.get("patternGroup"); - String catName = (String) cat.get("name"); - - if (patternGroup.equals(catPatternGroup) && - catName != null && - catName.startsWith(numMembers + " members")) { - return catName; - } - } - return null; - } - - public void synchronizeCategoriesAfterDataImport() { - try { - List summaries = ldService.getAllProjects(); - Map> grouped = new HashMap<>(); - - for (ProjectDTO summary : summaries) { - ProjectDTO full = ldService.getProjectById(summary.getId()); - if (full == null) { - continue; - } - String subjectKey = resolveSubjectKey(full); - if (subjectKey == null) { - continue; - } - grouped.computeIfAbsent(subjectKey, key -> new ArrayList<>()).add(full); - } - - grouped.forEach((subject, projects) -> { - if (projects.size() < 2) { - return; - } - projects.sort((a, b) -> Long.compare(sortableId(a), sortableId(b))); - ProjectDTO reference = projects.get(0); - if (reference.getExternalId() == null) { - return; - } - - List referenceMetrics = ldService.getMetricsByProject(reference.getExternalId()); - List referenceFactors = ldService.getFactorsByProject(reference.getExternalId()); - - for (int i = 1; i < projects.size(); i++) { - ProjectDTO target = projects.get(i); - if (target.getExternalId() == null) { - continue; - } - try { - List targetMetrics = ldService.getMetricsByProject(target.getExternalId()); - applyMetricCategoriesFromReference(reference, referenceMetrics, target, targetMetrics); - - List targetFactors = ldService.getFactorsByProject(target.getExternalId()); - applyFactorCategoriesFromReference(referenceFactors, targetFactors, target.getExternalId()); - } catch (Exception inner) { - System.err.println("⚠ Error alineant categories per al projecte '" + target.getName() - + "' dins la matèria '" + subject + "': " + inner.getMessage()); - } - } - }); - - ldEvalService.triggerRefresh(); - } catch (Exception e) { - System.err.println("⚠ Error sincronitzant categories després d'importar dades: " + e.getMessage()); - } - } - - private void runDataImportSequenceWithRetry(String projectExternalId, List newStudents, - SaveSyncResponseDTO workflow, int stepOrder) { - boolean needVerification = projectExternalId != null && newStudents != null && !newStudents.isEmpty(); - int maxAttempts = needVerification ? 3 : 1; - - for (int attempt = 1; attempt <= maxAttempts; attempt++) { - try { - executeDataImportCycle(); - } catch (RuntimeException e) { - workflow.addFailureStep(stepOrder, "Re-import metrics, factors & indicators", - "Error while importing new data.", e.getMessage()); - throw new SaveSyncException("Data import failed", e, workflow); - } - - if (!needVerification || metricsAvailableForStudents(projectExternalId, newStudents)) { - String detail = attempt == 1 - ? "Metrics, quality factors and strategic indicators re-imported successfully." - : "Data re-import succeeded after retry #" + attempt + "."; - workflow.addSuccessStep(stepOrder, "Re-import metrics, factors & indicators", detail); - return; - } - - if (attempt < maxAttempts) { - try { - Thread.sleep(5000L); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - workflow.addFailureStep(stepOrder, "Re-import metrics, factors & indicators", - "Interrupted while waiting for metrics.", ie.getMessage()); - throw new SaveSyncException("Interrupted while waiting for metrics", ie, workflow); - } - } - } - - workflow.addFailureStep(stepOrder, "Re-import metrics, factors & indicators", - "Metrics for the new students were not detected after multiple import attempts.", - "New metrics not found after retries."); - throw new SaveSyncException("New metrics not detected after import retries", workflow); - } - - private void executeDataImportCycle() { - ldService.importMetrics(); - try { - Thread.sleep(2000L); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException("Interrupted while waiting between metric and factor imports", e); - } - ldService.importQualityFactors(); - ldService.fetchStrategicIndicators(); - } - - private boolean metricsAvailableForStudents(String projectExternalId, List students) { - if (projectExternalId == null || students == null || students.isEmpty()) { - return true; - } - List metrics = ldService.getMetricsByProject(projectExternalId); - if (metrics == null || metrics.isEmpty()) { - return false; - } - for (StudentDTO student : students) { - Set aliases = extractAliasesForStudent(student); - if (aliases.isEmpty()) { - continue; - } - boolean found = metrics.stream().anyMatch(metric -> metricMatchesAliases(metric, aliases)); - if (!found) { - return false; - } - } - return true; - } - - private boolean metricMatchesAliases(MetricDTO metric, Set aliases) { - if (metric == null || aliases.isEmpty()) { - return false; - } - String normalizedId = normalizeMetricId(metric.getExternalId()); - if (normalizedId == null) { - return false; - } - int idx = normalizedId.indexOf('_'); - if (idx <= 0 || idx >= normalizedId.length() - 1) { - return false; - } - String suffix = normalizedId.substring(idx + 1); - return matchesAlias(suffix, aliases); - } - - private void applyMetricCategoriesFromReference(ProjectDTO referenceProject, List referenceMetrics, - ProjectDTO newProject, List newMetrics) { - if (newProject == null || referenceMetrics == null || newMetrics == null) { - return; - } - - MetricCategoryLookup lookup = buildMetricCategoryLookup(referenceProject, referenceMetrics); - Set newAliases = buildStudentAliases(newProject); - - int updated = 0; - int matched = 0; - int aliasMatches = 0; - int noMatch = 0; - - for (MetricDTO metric : newMetrics) { - if (metric == null || metric.getId() == null) { - continue; - } - String normalizedId = normalizeMetricId(metric.getExternalId()); - if (normalizedId == null) { - continue; - } - - String desiredCategory = lookup.exactMatch.get(normalizedId); - if (desiredCategory == null) { - String baseKey = buildMetricBaseKey(normalizedId, metric.getScope(), newAliases); - if (baseKey != null) { - desiredCategory = lookup.byBase.get(baseKey); - if (desiredCategory != null) { - aliasMatches++; - } - } - } else { - matched++; - } - - if (desiredCategory != null - && (metric.getCategoryName() == null - || !metric.getCategoryName().equalsIgnoreCase(desiredCategory))) { - try { - updated++; - ldService.editMetric(Long.parseLong(metric.getId()), null, null, - desiredCategory, metric.getScope(), newProject.getExternalId()); - } catch (Exception e) { - System.err.println("⚠ Error actualitzant la mètrica " + metric.getExternalId() + ": " - + e.getMessage()); - } - } else if (desiredCategory == null) { - noMatch++; - } - } - } - - private void applyFactorCategoriesFromReference(List referenceFactors, - List newFactors, String projectExternalId) { - if (referenceFactors == null || newFactors == null || projectExternalId == null) { - return; - } - - Map factorCategories = referenceFactors.stream() - .filter(f -> f.getExternalId() != null && f.getCategory() != null) - .collect(Collectors.toMap(f -> normalizeMetricId(f.getExternalId()), FactorDTO::getCategory, - (first, second) -> first)); - - for (FactorDTO factor : newFactors) { - if (factor == null || factor.getId() == null || factor.getExternalId() == null) { - continue; - } - - String key = normalizeMetricId(factor.getExternalId()); - String desiredCategory = factorCategories.get(key); - if (desiredCategory != null - && (factor.getCategory() == null || !factor.getCategory().equalsIgnoreCase(desiredCategory))) { - try { - ldService.updateFactorCategory(Long.parseLong(factor.getId()), desiredCategory, projectExternalId); - } catch (Exception e) { - System.err.println("⚠ Error actualitzant el factor " + factor.getExternalId() + ": " - + e.getMessage()); - } - } - } - } - - private MetricCategoryLookup buildMetricCategoryLookup(ProjectDTO project, List metrics) { - MetricCategoryLookup lookup = new MetricCategoryLookup(); - if (metrics == null) { - return lookup; - } - - Set aliases = buildStudentAliases(project); - for (MetricDTO metric : metrics) { - if (metric == null || metric.getExternalId() == null || metric.getCategoryName() == null) { - continue; - } - String normalizedId = normalizeMetricId(metric.getExternalId()); - if (normalizedId == null) { - continue; - } - lookup.exactMatch.put(normalizedId, metric.getCategoryName()); - - String baseKey = buildMetricBaseKey(normalizedId, metric.getScope(), aliases); - if (baseKey != null && !lookup.byBase.containsKey(baseKey)) { - lookup.byBase.put(baseKey, metric.getCategoryName()); - } - } - return lookup; - } - - private Map buildMetricAliasCategoryLookup(List metrics, Set aliases) { - Map lookup = new HashMap<>(); - if (metrics == null || aliases == null || aliases.isEmpty()) { - return lookup; - } - for (MetricDTO metric : metrics) { - if (metric == null || needsAliasCategory(metric.getCategoryName())) { - continue; - } - String normalizedId = normalizeMetricId(metric.getExternalId()); - if (normalizedId == null) { - continue; - } - String baseKey = buildMetricBaseKey(normalizedId, metric.getScope(), aliases); - if (baseKey != null && !lookup.containsKey(baseKey)) { - lookup.put(baseKey, metric.getCategoryName()); - } - } - return lookup; - } - - private boolean needsAliasCategory(String category) { - return category == null || category.isBlank() || "default".equalsIgnoreCase(category); - } - - private String resolveCategoryFromAlias(MetricDTO metric, Set aliases, Map aliasLookup) { - if (metric == null || aliases == null || aliases.isEmpty() || aliasLookup == null || aliasLookup.isEmpty()) { - return null; - } - String normalizedId = normalizeMetricId(metric.getExternalId()); - if (normalizedId == null) { - return null; - } - String baseKey = buildMetricBaseKey(normalizedId, metric.getScope(), aliases); - if (baseKey == null) { - return null; - } - String resolved = aliasLookup.get(baseKey); - return resolved; - } - - private void updateMetricCategory(MetricDTO metric, String category, String projectExternalId) { - if (metric == null || metric.getId() == null || category == null || projectExternalId == null) { - return; - } - metric.setCategoryName(category); - ldService.editMetric( - Long.parseLong(metric.getId()), - null, - null, - category, - metric.getScope(), - projectExternalId); - } - - private Set buildStudentAliases(ProjectDTO project) { - if (project == null) { - return new HashSet<>(); - } - return buildStudentAliases(project.getStudents()); - } - - private Set buildStudentAliases(List students) { - Set aliases = new HashSet<>(); - if (students == null) { - return aliases; - } - students.forEach(student -> aliases.addAll(extractAliasesForStudent(student))); - return aliases; - } - - private Set extractAliasesForStudent(StudentDTO student) { - Set aliases = new HashSet<>(); - if (student == null) { - return aliases; - } - if (student.getName() != null) { - addAliasVariants(aliases, slugify(student.getName())); - } - if (student.getIdentities() != null) { - student.getIdentities().values().forEach(identity -> { - if (identity != null && identity.getUsername() != null) { - addAliasVariants(aliases, slugify(identity.getUsername())); - } - }); - } - return aliases; - } - - private void addAliasVariants(Set aliases, String base) { - if (base == null || base.isEmpty()) { - return; - } - aliases.add(base); - aliases.add(base.replace("_", "")); - } - - private String slugify(String value) { - if (value == null) { - return null; - } - String normalized = Normalizer.normalize(value, Normalizer.Form.NFD) - .replaceAll("\\p{InCombiningDiacriticalMarks}+", "") - .toLowerCase(); - normalized = normalized.replaceAll("[^a-z0-9]+", "_"); - return normalized.replaceAll("^_+|_+$", ""); - } - - private String normalizeMetricId(String value) { - return value == null ? null : value.trim().toLowerCase(); - } - - private String buildMetricBaseKey(String normalizedExternalId, String scope, Set aliases) { - if (normalizedExternalId == null || aliases.isEmpty()) { - return null; - } - int idx = normalizedExternalId.indexOf('_'); - if (idx <= 0 || idx >= normalizedExternalId.length() - 1) { - return null; - } - String suffix = normalizedExternalId.substring(idx + 1); - if (!matchesAlias(suffix, aliases)) { - return null; - } - String prefix = normalizedExternalId.substring(0, idx); - String scopeKey = scope != null ? scope.toLowerCase() : ""; - return prefix + "|" + scopeKey; - } - - private boolean matchesAlias(String candidate, Set aliases) { - if (candidate == null) { - return false; - } - String clean = candidate.replaceAll("^_+|_+$", ""); - return aliases.contains(clean) || aliases.contains(clean.replace("_", "")); - } - - private String resolveSubjectKey(ProjectDTO project) { - if (project == null) { - return null; - } - if (project.getSubject() != null && !project.getSubject().isBlank()) { - return normalizeSubjectKey(project.getSubject()); - } - String fromExternal = extractSubjectFromIdentifier(project.getExternalId()); - if (fromExternal != null) { - return fromExternal; - } - return extractSubjectFromIdentifier(project.getName()); - } - - private String normalizeSubjectKey(String value) { - if (value == null) { - return null; - } - String slug = slugify(value); - return slug == null ? null : slug.replace("_", ""); - } - - private String extractSubjectFromIdentifier(String identifier) { - if (identifier == null) { - return null; - } - String normalized = slugify(identifier); - if (normalized == null || normalized.isBlank()) { - return null; - } - StringBuilder letters = new StringBuilder(); - for (char ch : normalized.toCharArray()) { - if (Character.isLetter(ch)) { - letters.append(ch); - } else if (letters.length() > 0) { - break; - } - } - String result = letters.toString(); - return result.length() >= 3 ? result : null; - } - - private long sortableId(ProjectDTO project) { - return project != null && project.getId() != null ? project.getId() : Long.MAX_VALUE; - } - - private static class MetricCategoryLookup { - private final Map exactMatch = new HashMap<>(); - private final Map byBase = new HashMap<>(); - } -} +package com.upc.ld_admintool.domain.services; + +import com.upc.ld_admintool.rest.DTO.ProjectDTO; +import com.upc.ld_admintool.rest.DTO.StudentDTO; +import com.upc.ld_admintool.rest.DTO.MetricDTO; +import com.upc.ld_admintool.rest.DTO.FactorDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.text.Normalizer; +import java.util.List; +import java.util.ArrayList; +import java.util.Optional; +import java.util.Set; +import java.util.HashSet; +import java.util.HashMap; +import java.util.Objects; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.transaction.annotation.Transactional; + +import com.upc.ld_admintool.domain.services.exceptions.SaveSyncException; +import com.upc.ld_admintool.rest.DTO.SaveSyncResponseDTO; + +@Service +@Transactional +public class ProjectService { + + @Autowired + private LDService ldService; + + @Autowired + private LDEvalService ldEvalService; + + public List llistarProjectesAmbStudents() { + List rawProjects = ldService.getAllProjects(); + List result = new ArrayList<>(); + for (ProjectDTO p : rawProjects) { + ProjectDTO complet = ldService.getProjectById(p.getId()); + if (complet != null) { + result.add(complet); + } else { + result.add(p); + } + } + return result; + } + + public ProjectDTO getProjectById(Long id) { + return ldService.getProjectById(id); + } + + public void importProjects(List projects) { + boolean createdAny = false; + for (ProjectDTO project : projects) { + Long projectId = ldService.createProject(project); + if (projectId == null) { + continue; + } + createdAny = true; + + if (project.getStudents() != null) { + for (StudentDTO student : project.getStudents()) { + ldService.createStudent(projectId, student); + } + } + } + if (createdAny) { + ldEvalService.triggerRefresh(); + } + } + + public SaveSyncResponseDTO modificarProjecte(Long id, ProjectDTO projecte) { + SaveSyncResponseDTO workflow = new SaveSyncResponseDTO(); + int stepOrder = 1; + + ProjectDTO original = ldService.getProjectById(id); + if (original == null) { + throw new SaveSyncException("No s'ha trobat el projecte", workflow); + } + String projectExternalId = original != null ? original.getExternalId() : null; + + Set originalIds = Optional.ofNullable(original.getStudents()) + .orElse(List.of()).stream() + .map(StudentDTO::getId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + Set newIds = Optional.ofNullable(projecte.getStudents()) + .orElse(List.of()).stream() + .map(StudentDTO::getId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + List newStudents = Optional.ofNullable(projecte.getStudents()) + .orElse(List.of()).stream() + .filter(student -> student.getId() == null) + .collect(Collectors.toList()); + + Set studentsToDelete = new HashSet<>(originalIds); + studentsToDelete.removeAll(newIds); + + int futureSize = Optional.ofNullable(projecte.getStudents()).map(List::size).orElse(0); + workflow.setFinalTeamSize(futureSize); + + try { + validateCategoriesForNewTeamSize(id, futureSize); + workflow.addSuccessStep(stepOrder++, "Validate category availability", + "Category definitions available for " + futureSize + " member(s)."); + } catch (RuntimeException e) { + workflow.addFailureStep(stepOrder, "Validate category availability", + "Unable to find categories for " + futureSize + " member(s).", e.getMessage()); + throw new SaveSyncException("Category validation failed", e, workflow); + } + + int removed = studentsToDelete.size(); + int added = newStudents.size(); + + try { + if (!studentsToDelete.isEmpty()) { + for (Long removedId : studentsToDelete) { + ldService.deleteStudent(removedId); + } + } + + if (!newStudents.isEmpty()) { + for (StudentDTO student : newStudents) { + ldService.createStudent(id, student); + } + } + + ldService.updateProject(id, projecte); + + String detail = String.format("Team now has %d member(s). Added: %d, removed: %d.", + futureSize, added, removed); + workflow.addSuccessStep(stepOrder++, "Update LD team roster", detail); + } catch (RuntimeException e) { + workflow.addFailureStep(stepOrder, "Update LD team roster", + "Error updating roster in LD.", e.getMessage()); + throw new SaveSyncException("Roster update failed", e, workflow); + } + + boolean refreshOk = ldEvalService.triggerRefresh(); + if (!refreshOk) { + workflow.addFailureStep(stepOrder, "Refresh LDEval caches", + "LDEval refresh endpoint returned an error.", "LDEval refresh failed"); + throw new SaveSyncException("LDEval refresh failed", workflow); + } + workflow.addSuccessStep(stepOrder++, "Refresh LDEval caches", + "Learning Dashboard has been instructed to refresh its evaluation cache."); + + runDataImportSequenceWithRetry(projectExternalId, newStudents, workflow, stepOrder++); + + try { + updateCategoriesForProject(id, projecte, futureSize); + workflow.addSuccessStep(stepOrder++, "Reassign metric & factor categories", + "Categories recalculated for " + futureSize + " member(s)."); + } catch (RuntimeException e) { + workflow.addFailureStep(stepOrder, "Reassign metric & factor categories", + "Unable to update categories.", e.getMessage()); + throw new SaveSyncException("Category update failed", e, workflow); + } + + ldEvalService.triggerRefresh(); + return workflow; + } + + public void esborrarProjecte(Long id) { + ldService.deleteProject(id); + ldEvalService.triggerRefresh(); + } + + public void validateCategoriesForNewTeamSize(Long projectId, int numStudents) { + ProjectDTO project = ldService.getProjectById(projectId); + if (project == null) + return; + + String projectExternalId = project.getExternalId(); + List> metricCategories = ldService.getAllMetricsCategories(); + List> factorCategories = ldService.getAllFactorsCategories(); + + List metrics = ldService.getMetricsByProject(projectExternalId); + for (MetricDTO metric : metrics) { + String currentCategory = metric.getCategoryName(); + if (currentCategory == null || currentCategory.isEmpty()) + continue; + + String patternGroup = null; + for (Map cat : metricCategories) { + if (currentCategory.equals(cat.get("name"))) { + patternGroup = (String) cat.get("patternGroup"); + break; + } + } + + if (patternGroup != null && !patternGroup.isEmpty()) { + String newCategory = findCategoryForMembers(metricCategories, patternGroup, numStudents); + if (newCategory == null) { + throw new RuntimeException("Error: La categoria per a " + numStudents + + " membres no existeix al patró '" + patternGroup + "'"); + } + } + } + + List factors = ldService.getFactorsByProject(projectExternalId); + for (FactorDTO factor : factors) { + String currentCategory = factor.getCategory(); + if (currentCategory == null || currentCategory.isEmpty()) + continue; + + String patternGroup = null; + for (Map cat : factorCategories) { + if (currentCategory.equals(cat.get("name"))) { + patternGroup = (String) cat.get("patternGroup"); + break; + } + } + + if (patternGroup != null && !patternGroup.isEmpty()) { + String newCategory = findCategoryForMembers(factorCategories, patternGroup, numStudents); + if (newCategory == null) { + throw new RuntimeException("Error: La categoria per a " + numStudents + + " membres no existeix al patró '" + patternGroup); + } + } + } + } + + public void updateCategoriesForProject(Long projectId) { + updateCategoriesForProject(projectId, null, null); + } + + public void updateCategoriesForProject(Long projectId, ProjectDTO overrideProject, Integer overrideStudentCount) { + // Removed try-catch to allow exception propagation + ProjectDTO project = overrideProject != null ? overrideProject : ldService.getProjectById(projectId); + if (project == null) { + throw new RuntimeException("No s'ha trobat el projecte amb ID: " + projectId); + } + + String projectExternalId = project.getExternalId(); + int numStudents = overrideStudentCount != null ? overrideStudentCount + : (project.getStudents() != null ? project.getStudents().size() : 0); + + List> metricCategories = ldService.getAllMetricsCategories(); + List> factorCategories = ldService.getAllFactorsCategories(); + + // Actualitzar mètriques + List metrics = ldService.getMetricsByProject(projectExternalId); + Set projectAliases = overrideProject != null + ? buildStudentAliases(overrideProject.getStudents()) + : buildStudentAliases(project); + Map aliasCategoryLookup = buildMetricAliasCategoryLookup(metrics, projectAliases); + + for (MetricDTO metric : metrics) { + String currentCategory = metric.getCategoryName(); + if (needsAliasCategory(currentCategory)) { + String desired = resolveCategoryFromAlias(metric, projectAliases, aliasCategoryLookup); + if (desired != null && !desired.equalsIgnoreCase(currentCategory)) { + updateMetricCategory(metric, desired, projectExternalId); + } + } + + currentCategory = metric.getCategoryName(); + if (currentCategory == null || currentCategory.isEmpty()) { + continue; + } + + String patternGroup = null; + for (Map cat : metricCategories) { + if (currentCategory.equals(cat.get("name"))) { + patternGroup = (String) cat.get("patternGroup"); + break; + } + } + + if (patternGroup != null && !patternGroup.isEmpty()) { + String newCategory = findCategoryForMembers(metricCategories, patternGroup, numStudents); + + if (newCategory == null) { + throw new RuntimeException("Error: La categoria per a " + numStudents + + " membres no existeix al patró '" + patternGroup + "'"); + } + + if (!newCategory.equals(currentCategory)) { + updateMetricCategory(metric, newCategory, projectExternalId); + } + } + } + + // Actualitzar factors + List factors = ldService.getFactorsByProject(projectExternalId); + + for (FactorDTO factor : factors) { + String currentCategory = factor.getCategory(); + if (currentCategory == null || currentCategory.isEmpty()) { + continue; + } + + String patternGroup = null; + for (Map cat : factorCategories) { + if (currentCategory.equals(cat.get("name"))) { + patternGroup = (String) cat.get("patternGroup"); + break; + } + } + + if (patternGroup != null && !patternGroup.isEmpty()) { + String newCategory = findCategoryForMembers(factorCategories, patternGroup, numStudents); + + if (newCategory == null) { + throw new RuntimeException("Error: La categoria per a " + numStudents + + " membres no existeix al patró '" + patternGroup + "'"); + } + + if (!newCategory.equals(currentCategory)) { + Long factorId = Long.parseLong(factor.getId()); + ldService.updateFactorCategory(factorId, newCategory, projectExternalId); + } + } + } + } + + private String findCategoryForMembers(List> categories, String patternGroup, int numMembers) { + for (Map cat : categories) { + String catPatternGroup = (String) cat.get("patternGroup"); + String catName = (String) cat.get("name"); + + if (patternGroup.equals(catPatternGroup) && + catName != null && + catName.startsWith(numMembers + " members")) { + return catName; + } + } + return null; + } + + public void synchronizeCategoriesAfterDataImport() { + try { + List summaries = ldService.getAllProjects(); + Map> grouped = new HashMap<>(); + + for (ProjectDTO summary : summaries) { + ProjectDTO full = ldService.getProjectById(summary.getId()); + if (full == null) { + continue; + } + String subjectKey = resolveSubjectKey(full); + if (subjectKey == null) { + continue; + } + grouped.computeIfAbsent(subjectKey, key -> new ArrayList<>()).add(full); + } + + grouped.forEach((subject, projects) -> { + if (projects.size() < 2) { + return; + } + projects.sort((a, b) -> Long.compare(sortableId(a), sortableId(b))); + ProjectDTO reference = projects.get(0); + if (reference.getExternalId() == null) { + return; + } + + List referenceMetrics = ldService.getMetricsByProject(reference.getExternalId()); + List referenceFactors = ldService.getFactorsByProject(reference.getExternalId()); + + for (int i = 1; i < projects.size(); i++) { + ProjectDTO target = projects.get(i); + if (target.getExternalId() == null) { + continue; + } + try { + List targetMetrics = ldService.getMetricsByProject(target.getExternalId()); + applyMetricCategoriesFromReference(reference, referenceMetrics, target, targetMetrics); + + List targetFactors = ldService.getFactorsByProject(target.getExternalId()); + applyFactorCategoriesFromReference(referenceFactors, targetFactors, target.getExternalId()); + } catch (Exception inner) { + System.err.println("⚠ Error alineant categories per al projecte '" + target.getName() + + "' dins la matèria '" + subject + "': " + inner.getMessage()); + } + } + }); + + ldEvalService.triggerRefresh(); + } catch (Exception e) { + System.err.println("⚠ Error sincronitzant categories després d'importar dades: " + e.getMessage()); + } + } + + private void runDataImportSequenceWithRetry(String projectExternalId, List newStudents, + SaveSyncResponseDTO workflow, int stepOrder) { + boolean needVerification = projectExternalId != null && newStudents != null && !newStudents.isEmpty(); + int maxAttempts = needVerification ? 3 : 1; + + for (int attempt = 1; attempt <= maxAttempts; attempt++) { + try { + executeDataImportCycle(); + } catch (RuntimeException e) { + workflow.addFailureStep(stepOrder, "Re-import metrics, factors & indicators", + "Error while importing new data.", e.getMessage()); + throw new SaveSyncException("Data import failed", e, workflow); + } + + if (!needVerification || metricsAvailableForStudents(projectExternalId, newStudents)) { + String detail = attempt == 1 + ? "Metrics, quality factors and strategic indicators re-imported successfully." + : "Data re-import succeeded after retry #" + attempt + "."; + workflow.addSuccessStep(stepOrder, "Re-import metrics, factors & indicators", detail); + return; + } + + if (attempt < maxAttempts) { + try { + Thread.sleep(5000L); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + workflow.addFailureStep(stepOrder, "Re-import metrics, factors & indicators", + "Interrupted while waiting for metrics.", ie.getMessage()); + throw new SaveSyncException("Interrupted while waiting for metrics", ie, workflow); + } + } + } + + workflow.addFailureStep(stepOrder, "Re-import metrics, factors & indicators", + "Metrics for the new students were not detected after multiple import attempts.", + "New metrics not found after retries."); + throw new SaveSyncException("New metrics not detected after import retries", workflow); + } + + private void executeDataImportCycle() { + ldService.importMetrics(); + try { + Thread.sleep(2000L); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting between metric and factor imports", e); + } + ldService.importQualityFactors(); + ldService.fetchStrategicIndicators(); + } + + private boolean metricsAvailableForStudents(String projectExternalId, List students) { + if (projectExternalId == null || students == null || students.isEmpty()) { + return true; + } + List metrics = ldService.getMetricsByProject(projectExternalId); + if (metrics == null || metrics.isEmpty()) { + return false; + } + for (StudentDTO student : students) { + Set aliases = extractAliasesForStudent(student); + if (aliases.isEmpty()) { + continue; + } + boolean found = metrics.stream().anyMatch(metric -> metricMatchesAliases(metric, aliases)); + if (!found) { + return false; + } + } + return true; + } + + private boolean metricMatchesAliases(MetricDTO metric, Set aliases) { + if (metric == null || aliases.isEmpty()) { + return false; + } + String normalizedId = normalizeMetricId(metric.getExternalId()); + if (normalizedId == null) { + return false; + } + int idx = normalizedId.indexOf('_'); + if (idx <= 0 || idx >= normalizedId.length() - 1) { + return false; + } + String suffix = normalizedId.substring(idx + 1); + return matchesAlias(suffix, aliases); + } + + private void applyMetricCategoriesFromReference(ProjectDTO referenceProject, List referenceMetrics, + ProjectDTO newProject, List newMetrics) { + if (newProject == null || referenceMetrics == null || newMetrics == null) { + return; + } + + MetricCategoryLookup lookup = buildMetricCategoryLookup(referenceProject, referenceMetrics); + Set newAliases = buildStudentAliases(newProject); + + int updated = 0; + int matched = 0; + int aliasMatches = 0; + int noMatch = 0; + + for (MetricDTO metric : newMetrics) { + if (metric == null || metric.getId() == null) { + continue; + } + String normalizedId = normalizeMetricId(metric.getExternalId()); + if (normalizedId == null) { + continue; + } + + String desiredCategory = lookup.exactMatch.get(normalizedId); + if (desiredCategory == null) { + String baseKey = buildMetricBaseKey(normalizedId, metric.getScope(), newAliases); + if (baseKey != null) { + desiredCategory = lookup.byBase.get(baseKey); + if (desiredCategory != null) { + aliasMatches++; + } + } + } else { + matched++; + } + + if (desiredCategory != null + && (metric.getCategoryName() == null + || !metric.getCategoryName().equalsIgnoreCase(desiredCategory))) { + try { + updated++; + ldService.editMetric(Long.parseLong(metric.getId()), null, null, + desiredCategory, metric.getScope(), newProject.getExternalId()); + } catch (Exception e) { + System.err.println("⚠ Error actualitzant la mètrica " + metric.getExternalId() + ": " + + e.getMessage()); + } + } else if (desiredCategory == null) { + noMatch++; + } + } + } + + private void applyFactorCategoriesFromReference(List referenceFactors, + List newFactors, String projectExternalId) { + if (referenceFactors == null || newFactors == null || projectExternalId == null) { + return; + } + + Map factorCategories = referenceFactors.stream() + .filter(f -> f.getExternalId() != null && f.getCategory() != null) + .collect(Collectors.toMap(f -> normalizeMetricId(f.getExternalId()), FactorDTO::getCategory, + (first, second) -> first)); + + for (FactorDTO factor : newFactors) { + if (factor == null || factor.getId() == null || factor.getExternalId() == null) { + continue; + } + + String key = normalizeMetricId(factor.getExternalId()); + String desiredCategory = factorCategories.get(key); + if (desiredCategory != null + && (factor.getCategory() == null || !factor.getCategory().equalsIgnoreCase(desiredCategory))) { + try { + ldService.updateFactorCategory(Long.parseLong(factor.getId()), desiredCategory, projectExternalId); + } catch (Exception e) { + System.err.println("⚠ Error actualitzant el factor " + factor.getExternalId() + ": " + + e.getMessage()); + } + } + } + } + + private MetricCategoryLookup buildMetricCategoryLookup(ProjectDTO project, List metrics) { + MetricCategoryLookup lookup = new MetricCategoryLookup(); + if (metrics == null) { + return lookup; + } + + Set aliases = buildStudentAliases(project); + for (MetricDTO metric : metrics) { + if (metric == null || metric.getExternalId() == null || metric.getCategoryName() == null) { + continue; + } + String normalizedId = normalizeMetricId(metric.getExternalId()); + if (normalizedId == null) { + continue; + } + lookup.exactMatch.put(normalizedId, metric.getCategoryName()); + + String baseKey = buildMetricBaseKey(normalizedId, metric.getScope(), aliases); + if (baseKey != null && !lookup.byBase.containsKey(baseKey)) { + lookup.byBase.put(baseKey, metric.getCategoryName()); + } + } + return lookup; + } + + private Map buildMetricAliasCategoryLookup(List metrics, Set aliases) { + Map lookup = new HashMap<>(); + if (metrics == null || aliases == null || aliases.isEmpty()) { + return lookup; + } + for (MetricDTO metric : metrics) { + if (metric == null || needsAliasCategory(metric.getCategoryName())) { + continue; + } + String normalizedId = normalizeMetricId(metric.getExternalId()); + if (normalizedId == null) { + continue; + } + String baseKey = buildMetricBaseKey(normalizedId, metric.getScope(), aliases); + if (baseKey != null && !lookup.containsKey(baseKey)) { + lookup.put(baseKey, metric.getCategoryName()); + } + } + return lookup; + } + + private boolean needsAliasCategory(String category) { + return category == null || category.isBlank() || "default".equalsIgnoreCase(category); + } + + private String resolveCategoryFromAlias(MetricDTO metric, Set aliases, Map aliasLookup) { + if (metric == null || aliases == null || aliases.isEmpty() || aliasLookup == null || aliasLookup.isEmpty()) { + return null; + } + String normalizedId = normalizeMetricId(metric.getExternalId()); + if (normalizedId == null) { + return null; + } + String baseKey = buildMetricBaseKey(normalizedId, metric.getScope(), aliases); + if (baseKey == null) { + return null; + } + String resolved = aliasLookup.get(baseKey); + return resolved; + } + + private void updateMetricCategory(MetricDTO metric, String category, String projectExternalId) { + if (metric == null || metric.getId() == null || category == null || projectExternalId == null) { + return; + } + metric.setCategoryName(category); + ldService.editMetric( + Long.parseLong(metric.getId()), + null, + null, + category, + metric.getScope(), + projectExternalId); + } + + private Set buildStudentAliases(ProjectDTO project) { + if (project == null) { + return new HashSet<>(); + } + return buildStudentAliases(project.getStudents()); + } + + private Set buildStudentAliases(List students) { + Set aliases = new HashSet<>(); + if (students == null) { + return aliases; + } + students.forEach(student -> aliases.addAll(extractAliasesForStudent(student))); + return aliases; + } + + private Set extractAliasesForStudent(StudentDTO student) { + Set aliases = new HashSet<>(); + if (student == null) { + return aliases; + } + if (student.getName() != null) { + addAliasVariants(aliases, slugify(student.getName())); + } + if (student.getIdentities() != null) { + student.getIdentities().values().forEach(identity -> { + if (identity != null && identity.getUsername() != null) { + addAliasVariants(aliases, slugify(identity.getUsername())); + } + }); + } + return aliases; + } + + private void addAliasVariants(Set aliases, String base) { + if (base == null || base.isEmpty()) { + return; + } + aliases.add(base); + aliases.add(base.replace("_", "")); + } + + private String slugify(String value) { + if (value == null) { + return null; + } + String normalized = Normalizer.normalize(value, Normalizer.Form.NFD) + .replaceAll("\\p{InCombiningDiacriticalMarks}+", "") + .toLowerCase(); + normalized = normalized.replaceAll("[^a-z0-9]+", "_"); + return normalized.replaceAll("^_+|_+$", ""); + } + + private String normalizeMetricId(String value) { + return value == null ? null : value.trim().toLowerCase(); + } + + private String buildMetricBaseKey(String normalizedExternalId, String scope, Set aliases) { + if (normalizedExternalId == null || aliases.isEmpty()) { + return null; + } + int idx = normalizedExternalId.indexOf('_'); + if (idx <= 0 || idx >= normalizedExternalId.length() - 1) { + return null; + } + String suffix = normalizedExternalId.substring(idx + 1); + if (!matchesAlias(suffix, aliases)) { + return null; + } + String prefix = normalizedExternalId.substring(0, idx); + String scopeKey = scope != null ? scope.toLowerCase() : ""; + return prefix + "|" + scopeKey; + } + + private boolean matchesAlias(String candidate, Set aliases) { + if (candidate == null) { + return false; + } + String clean = candidate.replaceAll("^_+|_+$", ""); + return aliases.contains(clean) || aliases.contains(clean.replace("_", "")); + } + + private String resolveSubjectKey(ProjectDTO project) { + if (project == null) { + return null; + } + if (project.getSubject() != null && !project.getSubject().isBlank()) { + return normalizeSubjectKey(project.getSubject()); + } + String fromExternal = extractSubjectFromIdentifier(project.getExternalId()); + if (fromExternal != null) { + return fromExternal; + } + return extractSubjectFromIdentifier(project.getName()); + } + + private String normalizeSubjectKey(String value) { + if (value == null) { + return null; + } + String slug = slugify(value); + return slug == null ? null : slug.replace("_", ""); + } + + private String extractSubjectFromIdentifier(String identifier) { + if (identifier == null) { + return null; + } + String normalized = slugify(identifier); + if (normalized == null || normalized.isBlank()) { + return null; + } + StringBuilder letters = new StringBuilder(); + for (char ch : normalized.toCharArray()) { + if (Character.isLetter(ch)) { + letters.append(ch); + } else if (letters.length() > 0) { + break; + } + } + String result = letters.toString(); + return result.length() >= 3 ? result : null; + } + + private long sortableId(ProjectDTO project) { + return project != null && project.getId() != null ? project.getId() : Long.MAX_VALUE; + } + + private static class MetricCategoryLookup { + private final Map exactMatch = new HashMap<>(); + private final Map byBase = new HashMap<>(); + } +} diff --git a/src/main/java/com/upc/ld_admintool/domain/services/RecoveryService.java b/src/main/java/com/upc/ld_admintool/domain/services/RecoveryService.java new file mode 100644 index 0000000..3dc5f9c --- /dev/null +++ b/src/main/java/com/upc/ld_admintool/domain/services/RecoveryService.java @@ -0,0 +1,151 @@ +package com.upc.ld_admintool.domain.services; + +import com.upc.ld_admintool.domain.utils.DataSource; +import com.upc.ld_admintool.rest.DTO.ProjectDTO; +import com.upc.ld_admintool.rest.DTO.ProjectIdentityDTO; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.HashMap; +import java.util.Map; + +@Service +public class RecoveryService { + + @Value("${ld.connect.url}") + private String ldConnectUrl; + + private final LDService ldService; + private final RestTemplate restTemplate; + + public RecoveryService(LDService ldService) { + this.ldService = ldService; + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(5_000); + factory.setReadTimeout(15_000); + this.restTemplate = new RestTemplate(factory); + } + + @SuppressWarnings("unchecked") + public Map getRecoveryStatus(String jobId) { + String statusUrl = ldConnectUrl + "/admin/recovery/status/" + jobId; + ResponseEntity response = restTemplate.getForEntity(statusUrl, Map.class); + return response.getBody() != null ? response.getBody() : Map.of("status", "error"); + } + + public Map runTeamRecovery(Long projectId, Map tokenOverrides) { + ProjectDTO project = ldService.getProjectById(projectId); + if (project == null) throw new IllegalArgumentException("Project not found"); + + String prj = project.getExternalId(); + if (prj == null || prj.isBlank()) throw new IllegalArgumentException("Project externalId is required"); + + Map identities = project.getIdentities(); + String githubUrl = extractIdentityUrl(identities, DataSource.GITHUB); + String taigaUrl = extractIdentityUrl(identities, DataSource.TAIGA); + + if (githubUrl == null || githubUrl.isBlank()) throw new IllegalArgumentException("Project GitHub identity URL is required"); + if (taigaUrl == null || taigaUrl.isBlank()) throw new IllegalArgumentException("Project Taiga identity URL is required"); + + Map payload = new HashMap<>(); + payload.put("prj", prj); + payload.put("github_url", githubUrl); + payload.put("taiga_url", taigaUrl); + applyTokenOverrides(payload, tokenOverrides, true, true); + + String jobId = startJob(ldConnectUrl + "/admin/recovery/team", payload); + + Map result = new HashMap<>(); + result.put("projectId", projectId); + result.put("projectExternalId", prj); + result.put("job_id", jobId); + result.put("status", "running"); + return result; + } + + public Map runGithubRecovery(Long projectId, Map tokenOverrides) { + ProjectDTO project = ldService.getProjectById(projectId); + if (project == null) throw new IllegalArgumentException("Project not found"); + + String prj = project.getExternalId(); + if (prj == null || prj.isBlank()) throw new IllegalArgumentException("Project externalId is required"); + + Map identities = project.getIdentities(); + String githubUrl = extractIdentityUrl(identities, DataSource.GITHUB); + if (githubUrl == null || githubUrl.isBlank()) throw new IllegalArgumentException("Project GitHub identity URL is required"); + + Map payload = new HashMap<>(); + payload.put("prj", prj); + payload.put("github_url", githubUrl); + applyTokenOverrides(payload, tokenOverrides, true, false); + + String jobId = startJob(ldConnectUrl + "/admin/recovery/github", payload); + + Map result = new HashMap<>(); + result.put("projectId", projectId); + result.put("projectExternalId", prj); + result.put("job_id", jobId); + result.put("status", "running"); + return result; + } + + public Map runTaigaRecovery(Long projectId, Map tokenOverrides) { + ProjectDTO project = ldService.getProjectById(projectId); + if (project == null) throw new IllegalArgumentException("Project not found"); + + String prj = project.getExternalId(); + if (prj == null || prj.isBlank()) throw new IllegalArgumentException("Project externalId is required"); + + Map identities = project.getIdentities(); + String taigaUrl = extractIdentityUrl(identities, DataSource.TAIGA); + if (taigaUrl == null || taigaUrl.isBlank()) throw new IllegalArgumentException("Project Taiga identity URL is required"); + + Map payload = new HashMap<>(); + payload.put("prj", prj); + payload.put("taiga_url", taigaUrl); + applyTokenOverrides(payload, tokenOverrides, false, true); + + String jobId = startJob(ldConnectUrl + "/admin/recovery/taiga", payload); + + Map result = new HashMap<>(); + result.put("projectId", projectId); + result.put("projectExternalId", prj); + result.put("job_id", jobId); + result.put("status", "running"); + return result; + } + + @SuppressWarnings("unchecked") + private String startJob(String url, Map payload) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity> request = new HttpEntity<>(payload, headers); + ResponseEntity response = restTemplate.postForEntity(url, request, Map.class); + return (String) response.getBody().get("job_id"); + } + + private void applyTokenOverrides(Map payload, Map overrides, + boolean github, boolean taiga) { + if (overrides == null) return; + if (github) { + String t = overrides.get("githubToken"); + if (t != null && !t.isBlank()) payload.put("github_token", t); + } + if (taiga) { + String t = overrides.get("taigaToken"); + if (t != null && !t.isBlank()) payload.put("taiga_token", t); + } + } + + private String extractIdentityUrl(Map identities, DataSource source) { + if (identities == null) return null; + ProjectIdentityDTO identity = identities.get(source); + return identity != null ? identity.getUrl() : null; + } +} diff --git a/src/main/java/com/upc/ld_admintool/domain/services/StrategicIndicatorsService.java b/src/main/java/com/upc/ld_admintool/domain/services/StrategicIndicatorsService.java index 003e636..04d027e 100644 --- a/src/main/java/com/upc/ld_admintool/domain/services/StrategicIndicatorsService.java +++ b/src/main/java/com/upc/ld_admintool/domain/services/StrategicIndicatorsService.java @@ -1,25 +1,25 @@ -package com.upc.ld_admintool.domain.services; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import java.util.List; -import java.util.Map; - -@Service -public class StrategicIndicatorsService { - - @Autowired - private LDService ldService; - - @Autowired - private ProjectService projectService; - - public List> getAllStrategicIndicatorCategories() { - return ldService.getAllStrategicIndicatorCategories(); - } - - public void fetchStrategicIndicators() { - ldService.fetchStrategicIndicators(); - projectService.synchronizeCategoriesAfterDataImport(); - } -} +package com.upc.ld_admintool.domain.services; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.List; +import java.util.Map; + +@Service +public class StrategicIndicatorsService { + + @Autowired + private LDService ldService; + + @Autowired + private ProjectService projectService; + + public List> getAllStrategicIndicatorCategories() { + return ldService.getAllStrategicIndicatorCategories(); + } + + public void fetchStrategicIndicators() { + ldService.fetchStrategicIndicators(); + projectService.synchronizeCategoriesAfterDataImport(); + } +} diff --git a/src/main/java/com/upc/ld_admintool/domain/services/WizardService.java b/src/main/java/com/upc/ld_admintool/domain/services/WizardService.java index 8902c9d..a409507 100644 --- a/src/main/java/com/upc/ld_admintool/domain/services/WizardService.java +++ b/src/main/java/com/upc/ld_admintool/domain/services/WizardService.java @@ -1,59 +1,59 @@ -package com.upc.ld_admintool.domain.services; - -import com.upc.ld_admintool.rest.DTO.WizardStatusDTO; -import com.upc.ld_admintool.rest.DTO.ProjectDTO; -import com.upc.ld_admintool.rest.DTO.MetricDTO; -import com.upc.ld_admintool.rest.DTO.FactorDTO; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.Map; - -@Service -public class WizardService { - - @Autowired - private LDService ldService; - - public WizardStatusDTO getWizardStatus() { - boolean hasProjects = false; - boolean hasData = false; - boolean hasMetricsCategories = false; - boolean hasFactorsCategories = false; - boolean hasStrategicIndicatorCategories = false; - - boolean hasAssignments = false; - try { - // 1. Projects - List projects = ldService.getAllProjects(); - hasProjects = !projects.isEmpty(); - - // 2. Categories - List> metricsCats = ldService.getAllMetricsCategories(); - hasMetricsCategories = (metricsCats != null && !metricsCats.isEmpty()); - - List> factorsCats = ldService.getAllFactorsCategories(); - hasFactorsCategories = (factorsCats != null && !factorsCats.isEmpty()); - - List> siCats = ldService.getAllStrategicIndicatorCategories(); - hasStrategicIndicatorCategories = (siCats != null && !siCats.isEmpty()); - - // 3. Data - if (hasProjects) { - String externalId = projects.get(0).getExternalId(); - if (externalId != null) { - List metrics = ldService.getMetricsByProject(externalId); - List factors = ldService.getFactorsByProject(externalId); - hasData = !metrics.isEmpty() && !factors.isEmpty(); - } - } - - } catch (Exception e) { - System.err.println("Error calculating wizard status: " + e.getMessage()); - } - - return new WizardStatusDTO(hasProjects, hasData, hasMetricsCategories, hasFactorsCategories, - hasStrategicIndicatorCategories); - } -} +package com.upc.ld_admintool.domain.services; + +import com.upc.ld_admintool.rest.DTO.WizardStatusDTO; +import com.upc.ld_admintool.rest.DTO.ProjectDTO; +import com.upc.ld_admintool.rest.DTO.MetricDTO; +import com.upc.ld_admintool.rest.DTO.FactorDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +@Service +public class WizardService { + + @Autowired + private LDService ldService; + + public WizardStatusDTO getWizardStatus() { + boolean hasProjects = false; + boolean hasData = false; + boolean hasMetricsCategories = false; + boolean hasFactorsCategories = false; + boolean hasStrategicIndicatorCategories = false; + + boolean hasAssignments = false; + try { + // 1. Projects + List projects = ldService.getAllProjects(); + hasProjects = !projects.isEmpty(); + + // 2. Categories + List> metricsCats = ldService.getAllMetricsCategories(); + hasMetricsCategories = (metricsCats != null && !metricsCats.isEmpty()); + + List> factorsCats = ldService.getAllFactorsCategories(); + hasFactorsCategories = (factorsCats != null && !factorsCats.isEmpty()); + + List> siCats = ldService.getAllStrategicIndicatorCategories(); + hasStrategicIndicatorCategories = (siCats != null && !siCats.isEmpty()); + + // 3. Data + if (hasProjects) { + String externalId = projects.get(0).getExternalId(); + if (externalId != null) { + List metrics = ldService.getMetricsByProject(externalId); + List factors = ldService.getFactorsByProject(externalId); + hasData = !metrics.isEmpty() && !factors.isEmpty(); + } + } + + } catch (Exception e) { + System.err.println("Error calculating wizard status: " + e.getMessage()); + } + + return new WizardStatusDTO(hasProjects, hasData, hasMetricsCategories, hasFactorsCategories, + hasStrategicIndicatorCategories); + } +} diff --git a/src/main/java/com/upc/ld_admintool/domain/services/exceptions/SaveSyncException.java b/src/main/java/com/upc/ld_admintool/domain/services/exceptions/SaveSyncException.java index 83e8c7a..e6ffdd3 100644 --- a/src/main/java/com/upc/ld_admintool/domain/services/exceptions/SaveSyncException.java +++ b/src/main/java/com/upc/ld_admintool/domain/services/exceptions/SaveSyncException.java @@ -1,22 +1,22 @@ -package com.upc.ld_admintool.domain.services.exceptions; - -import com.upc.ld_admintool.rest.DTO.SaveSyncResponseDTO; - -public class SaveSyncException extends RuntimeException { - - private final SaveSyncResponseDTO response; - - public SaveSyncException(String message, SaveSyncResponseDTO response) { - super(message); - this.response = response; - } - - public SaveSyncException(String message, Throwable cause, SaveSyncResponseDTO response) { - super(message, cause); - this.response = response; - } - - public SaveSyncResponseDTO getResponse() { - return response; - } -} +package com.upc.ld_admintool.domain.services.exceptions; + +import com.upc.ld_admintool.rest.DTO.SaveSyncResponseDTO; + +public class SaveSyncException extends RuntimeException { + + private final SaveSyncResponseDTO response; + + public SaveSyncException(String message, SaveSyncResponseDTO response) { + super(message); + this.response = response; + } + + public SaveSyncException(String message, Throwable cause, SaveSyncResponseDTO response) { + super(message, cause); + this.response = response; + } + + public SaveSyncResponseDTO getResponse() { + return response; + } +} diff --git a/src/main/java/com/upc/ld_admintool/domain/services/validation/GitHubValidationService.java b/src/main/java/com/upc/ld_admintool/domain/services/validation/GitHubValidationService.java index ef40185..9207dcb 100644 --- a/src/main/java/com/upc/ld_admintool/domain/services/validation/GitHubValidationService.java +++ b/src/main/java/com/upc/ld_admintool/domain/services/validation/GitHubValidationService.java @@ -1,181 +1,181 @@ -package com.upc.ld_admintool.domain.services.validation; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.*; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.client.HttpClientErrorException; - -import java.util.List; -import java.util.Map; - -@Service -public class GitHubValidationService { - - @Value("${github.api.url:https://api.github.com}") - private String githubApiUrl; - - @Value("${github.token:}") - private String githubToken; - - private final RestTemplate restTemplate = new RestTemplate(); - - /** - * Valida si una organització de GitHub existeix. - * - * @param org Nom de l'organització - * @param projectToken Token específic per aquest projecte (pot ser null) - * @return ValidationResult amb errors si l'organització no existeix - */ - public ValidationResult validateOrganization(String org, String projectToken) { - ValidationResult result = new ValidationResult(true); - - if (org == null || org.trim().isEmpty()) { - result.addError("El nom de l'organització GitHub està buit"); - return result; - } - - try { - String url = githubApiUrl + "/orgs/" + org + "/members"; - HttpHeaders headers = createHeaders(projectToken); - HttpEntity entity = new HttpEntity<>(headers); - ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, Object[].class); - - if (response.getStatusCode() == HttpStatus.OK) { - Object[] members = response.getBody(); - if (members == null || members.length == 0) { - // Organització existeix però membres són privats - result.addWarning("L'organització '" + org + "' té membres privats (no es poden validar usuaris)"); - } - } - - } catch (HttpClientErrorException.NotFound e) { - result.addError("L'organització GitHub '" + org + "' no existeix"); - } catch (HttpClientErrorException.Unauthorized e) { - result.addError("No tens autorització per accedir a l'organització '" + org + "' (comprova el token)"); - } catch (Exception e) { - result.addError("Error validant l'organització GitHub '" + org + "': " + e.getMessage()); - } - - return result; - } - - /** - * Valida si múltiples usuaris són membres d'una organització. - * - * @param org Nom de l'organització - * @param usernames Llista de noms d'usuari - * @param projectToken Token específic per aquest projecte (pot ser null) - * @return ValidationResult amb errors per cada usuari que no és membre - */ - public ValidationResult validateUsersInOrganization(String org, List usernames, String projectToken) { - ValidationResult result = new ValidationResult(true); - - if (usernames == null || usernames.isEmpty()) { - return result; - } - - try { - String url = githubApiUrl + "/orgs/" + org + "/members"; - HttpHeaders headers = createHeaders(projectToken); - HttpEntity entity = new HttpEntity<>(headers); - - ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, Object[].class); - - Object[] members = response.getBody(); - if (members == null || members.length == 0) { - // No podem validar perquè membres són privats - això és un ERROR - result.addError("No es poden validar els usuaris perquè els membres de l'organització '" + org - + "' són privats. Proporciona un token amb permisos adequats."); - return result; - } - - // Comprovar cada usuari (com fa existsUsername) - for (String username : usernames) { - if (username == null || username.trim().isEmpty()) { - continue; - } - - boolean found = false; - for (Object memberObj : members) { - if (memberObj instanceof Map) { - @SuppressWarnings("unchecked") - Map member = (Map) memberObj; - String login = (String) member.get("login"); - - if (username.trim().equals(login != null ? login.trim() : "")) { - found = true; - break; - } - } - } - - if (!found) { - result.addError("L'usuari GitHub '" + username + "' no és membre de l'organització '" + org + "'"); - } - } - - } catch (HttpClientErrorException.NotFound e) { - result.addError("L'organització '" + org + "' no existeix"); - } catch (Exception e) { - result.addError("Error validant usuaris a l'organització: " + e.getMessage()); - } - - return result; - } - - /** - * Valida si un usuari de GitHub existeix. - * - * @param username Nom d'usuari - * @param projectToken Token específic per aquest projecte (pot ser null) - * @return ValidationResult amb errors si l'usuari no existeix - */ - public ValidationResult validateUser(String username, String projectToken) { - ValidationResult result = new ValidationResult(true); - - try { - String url = githubApiUrl + "/users/" + username; - HttpHeaders headers = createHeaders(projectToken); - HttpEntity entity = new HttpEntity<>(headers); - - ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, Map.class); - - if (response.getStatusCode() != HttpStatus.OK) { - result.addError("L'usuari GitHub '" + username + "' no existeix"); - } - } catch (HttpClientErrorException.NotFound e) { - result.addError("L'usuari GitHub '" + username + "' no existeix"); - } catch (Exception e) { - result.addError("Error validant l'usuari GitHub '" + username + "': " + e.getMessage()); - } - - return result; - } - - /** - * Crea headers HTTP per les peticions a GitHub API. - * - * @param projectToken Token específic del projecte (prioritat sobre el token - * global) - * @return HttpHeaders amb el token corresponent - */ - private HttpHeaders createHeaders(String projectToken) { - HttpHeaders headers = new HttpHeaders(); - headers.set("Accept", "application/vnd.github.v3+json"); - - // Prioritat: 1) Token del projecte, 2) Token global, 3) Sense token - String tokenToUse = null; - if (projectToken != null && !projectToken.trim().isEmpty()) { - tokenToUse = projectToken.trim(); - } else if (githubToken != null && !githubToken.isEmpty()) { - tokenToUse = githubToken; - } - - if (tokenToUse != null) { - headers.set("Authorization", "token " + tokenToUse); - } - - return headers; - } -} +package com.upc.ld_admintool.domain.services.validation; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.client.HttpClientErrorException; + +import java.util.List; +import java.util.Map; + +@Service +public class GitHubValidationService { + + @Value("${github.api.url:https://api.github.com}") + private String githubApiUrl; + + @Value("${github.token:}") + private String githubToken; + + private final RestTemplate restTemplate = new RestTemplate(); + + /** + * Valida si una organització de GitHub existeix. + * + * @param org Nom de l'organització + * @param projectToken Token específic per aquest projecte (pot ser null) + * @return ValidationResult amb errors si l'organització no existeix + */ + public ValidationResult validateOrganization(String org, String projectToken) { + ValidationResult result = new ValidationResult(true); + + if (org == null || org.trim().isEmpty()) { + result.addError("El nom de l'organització GitHub està buit"); + return result; + } + + try { + String url = githubApiUrl + "/orgs/" + org + "/members"; + HttpHeaders headers = createHeaders(projectToken); + HttpEntity entity = new HttpEntity<>(headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, Object[].class); + + if (response.getStatusCode() == HttpStatus.OK) { + Object[] members = response.getBody(); + if (members == null || members.length == 0) { + // Organització existeix però membres són privats + result.addWarning("L'organització '" + org + "' té membres privats (no es poden validar usuaris)"); + } + } + + } catch (HttpClientErrorException.NotFound e) { + result.addError("L'organització GitHub '" + org + "' no existeix"); + } catch (HttpClientErrorException.Unauthorized e) { + result.addError("No tens autorització per accedir a l'organització '" + org + "' (comprova el token)"); + } catch (Exception e) { + result.addError("Error validant l'organització GitHub '" + org + "': " + e.getMessage()); + } + + return result; + } + + /** + * Valida si múltiples usuaris són membres d'una organització. + * + * @param org Nom de l'organització + * @param usernames Llista de noms d'usuari + * @param projectToken Token específic per aquest projecte (pot ser null) + * @return ValidationResult amb errors per cada usuari que no és membre + */ + public ValidationResult validateUsersInOrganization(String org, List usernames, String projectToken) { + ValidationResult result = new ValidationResult(true); + + if (usernames == null || usernames.isEmpty()) { + return result; + } + + try { + String url = githubApiUrl + "/orgs/" + org + "/members"; + HttpHeaders headers = createHeaders(projectToken); + HttpEntity entity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, Object[].class); + + Object[] members = response.getBody(); + if (members == null || members.length == 0) { + // No podem validar perquè membres són privats - això és un ERROR + result.addError("No es poden validar els usuaris perquè els membres de l'organització '" + org + + "' són privats. Proporciona un token amb permisos adequats."); + return result; + } + + // Comprovar cada usuari (com fa existsUsername) + for (String username : usernames) { + if (username == null || username.trim().isEmpty()) { + continue; + } + + boolean found = false; + for (Object memberObj : members) { + if (memberObj instanceof Map) { + @SuppressWarnings("unchecked") + Map member = (Map) memberObj; + String login = (String) member.get("login"); + + if (username.trim().equals(login != null ? login.trim() : "")) { + found = true; + break; + } + } + } + + if (!found) { + result.addError("L'usuari GitHub '" + username + "' no és membre de l'organització '" + org + "'"); + } + } + + } catch (HttpClientErrorException.NotFound e) { + result.addError("L'organització '" + org + "' no existeix"); + } catch (Exception e) { + result.addError("Error validant usuaris a l'organització: " + e.getMessage()); + } + + return result; + } + + /** + * Valida si un usuari de GitHub existeix. + * + * @param username Nom d'usuari + * @param projectToken Token específic per aquest projecte (pot ser null) + * @return ValidationResult amb errors si l'usuari no existeix + */ + public ValidationResult validateUser(String username, String projectToken) { + ValidationResult result = new ValidationResult(true); + + try { + String url = githubApiUrl + "/users/" + username; + HttpHeaders headers = createHeaders(projectToken); + HttpEntity entity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, Map.class); + + if (response.getStatusCode() != HttpStatus.OK) { + result.addError("L'usuari GitHub '" + username + "' no existeix"); + } + } catch (HttpClientErrorException.NotFound e) { + result.addError("L'usuari GitHub '" + username + "' no existeix"); + } catch (Exception e) { + result.addError("Error validant l'usuari GitHub '" + username + "': " + e.getMessage()); + } + + return result; + } + + /** + * Crea headers HTTP per les peticions a GitHub API. + * + * @param projectToken Token específic del projecte (prioritat sobre el token + * global) + * @return HttpHeaders amb el token corresponent + */ + private HttpHeaders createHeaders(String projectToken) { + HttpHeaders headers = new HttpHeaders(); + headers.set("Accept", "application/vnd.github.v3+json"); + + // Prioritat: 1) Token del projecte, 2) Token global, 3) Sense token + String tokenToUse = null; + if (projectToken != null && !projectToken.trim().isEmpty()) { + tokenToUse = projectToken.trim(); + } else if (githubToken != null && !githubToken.isEmpty()) { + tokenToUse = githubToken; + } + + if (tokenToUse != null) { + headers.set("Authorization", "token " + tokenToUse); + } + + return headers; + } +} diff --git a/src/main/java/com/upc/ld_admintool/domain/services/validation/ProjectValidationService.java b/src/main/java/com/upc/ld_admintool/domain/services/validation/ProjectValidationService.java index d925e99..cc500cd 100644 --- a/src/main/java/com/upc/ld_admintool/domain/services/validation/ProjectValidationService.java +++ b/src/main/java/com/upc/ld_admintool/domain/services/validation/ProjectValidationService.java @@ -1,313 +1,313 @@ -package com.upc.ld_admintool.domain.services.validation; - -import com.upc.ld_admintool.domain.utils.DataSource; -import com.upc.ld_admintool.domain.services.LDService; -import com.upc.ld_admintool.rest.DTO.ProjectDTO; -import com.upc.ld_admintool.rest.DTO.ProjectIdentityDTO; -import com.upc.ld_admintool.rest.DTO.StudentDTO; -import com.upc.ld_admintool.rest.DTO.StudentIdentityDTO; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -@Service -public class ProjectValidationService { - - @Autowired - private GitHubValidationService githubValidationService; - - @Autowired - private TaigaValidationService taigaValidationService; - - @Autowired - private LDService ldService; - - /** - * Valida un projecte complet amb els seus estudiants. - * Utilitza el token de GitHub específic del projecte si està disponible. - */ - public ValidationResult validateProject(ProjectDTO project) { - ValidationResult result = new ValidationResult(true); - - // Validar identitats del projecte - if (project.getIdentities() == null || project.getIdentities().isEmpty()) { - result.addError("The project '" + project.getName() + "' does not have defined identities (GitHub/Taiga)"); - return result; - } - - // Obtenir el token de GitHub del projecte (pot ser null) - String projectGithubToken = project.getGithubToken(); - - // Validar GitHub (com ho feia la Nora) - ProjectIdentityDTO githubIdentity = project.getIdentities().get(DataSource.GITHUB); - if (githubIdentity != null && githubIdentity.getUrl() != null) { - String org = extractGitHubOrg(githubIdentity.getUrl()); - if (org != null) { - // Validar organització amb el token del projecte - ValidationResult orgResult = githubValidationService.validateOrganization(org, projectGithubToken); - result.addErrors(orgResult.getErrors()); - result.addWarnings(orgResult.getWarnings()); - - // Validar estudiants dins l'organització amb el token del projecte - if (project.getStudents() != null) { - List githubUsernames = new ArrayList<>(); - for (StudentDTO student : project.getStudents()) { - if (student.getIdentities() != null) { - StudentIdentityDTO studentGithub = student.getIdentities().get(DataSource.GITHUB); - if (studentGithub != null && studentGithub.getUsername() != null) { - githubUsernames.add(studentGithub.getUsername()); - } - } - } - - if (!githubUsernames.isEmpty()) { - ValidationResult usersResult = githubValidationService.validateUsersInOrganization( - org, githubUsernames, projectGithubToken); - result.addErrors(usersResult.getErrors()); - result.addWarnings(usersResult.getWarnings()); - } - } - } else { - result.addError("Invalid GitHub URL format for project '" + project.getName() + "': " - + githubIdentity.getUrl()); - } - } else { - result.addWarning("The project '" + project.getName() + "' does not have a defined GitHub URL"); - } - - // Validar Taiga - ProjectIdentityDTO taigaIdentity = project.getIdentities().get(DataSource.TAIGA); - if (taigaIdentity != null && taigaIdentity.getUrl() != null) { - String slug = extractTaigaSlug(taigaIdentity.getUrl()); - if (slug != null) { - ValidationResult taigaResult = taigaValidationService.validateProjectBySlug(slug); - result.getErrors().addAll(taigaResult.getErrors()); - result.getWarnings().addAll(taigaResult.getWarnings()); - - // Validar estudiants dins el projecte Taiga - if (project.getStudents() != null) { - List taigaUsernames = new ArrayList<>(); - for (StudentDTO student : project.getStudents()) { - if (student.getIdentities() != null) { - StudentIdentityDTO studentTaiga = student.getIdentities().get(DataSource.TAIGA); - if (studentTaiga != null && studentTaiga.getUsername() != null) { - taigaUsernames.add(studentTaiga.getUsername()); - } - } - } - - if (!taigaUsernames.isEmpty()) { - ValidationResult usersResult = taigaValidationService.validateUsersInProject(slug, - taigaUsernames); - result.addErrors(usersResult.getErrors()); - result.addWarnings(usersResult.getWarnings()); - } - } - } else { - result.addError("Invalid Taiga URL format for project '" + project.getName() + "': " - + taigaIdentity.getUrl()); - } - } else { - result.addWarning("The project '" + project.getName() + "' does not have a defined Taiga URL"); - } - - if (result.hasErrors()) { - result.setValid(false); - } - return result; - } - - /** - * Valida múltiples projectes i retorna quins són vàlids i quins no - */ - public Map validateProjectsWithDetails(List projects) { - Map response = new HashMap<>(); - List validProjects = new ArrayList<>(); - List> invalidProjects = new ArrayList<>(); - Set existingProjectKeys = loadExistingProjectKeys(); - Set seenProjectKeys = new HashSet<>(); - - for (ProjectDTO project : projects) { - String projectKey = buildProjectKey(project); - boolean alreadyExists = projectKey != null && existingProjectKeys.contains(projectKey); - boolean duplicatedInFile = projectKey != null && seenProjectKeys.contains(projectKey); - if (projectKey != null) { - seenProjectKeys.add(projectKey); - } - - ValidationResult projectResult; - if (alreadyExists || duplicatedInFile) { - projectResult = new ValidationResult(false); - if (alreadyExists) { - projectResult.addError("The project '" + project.getName() + "' already exists in the database."); - } - if (duplicatedInFile) { - projectResult - .addError("The project '" + project.getName() + "' is duplicated within the import file."); - } - } else { - projectResult = validateProject(project); - if (projectResult.isValid() && projectKey != null) { - existingProjectKeys.add(projectKey); - } - } - - if (projectResult.hasErrors()) { - // Projecte invàlid - Map invalidInfo = new HashMap<>(); - invalidInfo.put("project", project); - invalidInfo.put("errors", projectResult.getErrors()); - invalidInfo.put("warnings", projectResult.getWarnings()); - invalidProjects.add(invalidInfo); - } else { - // Projecte vàlid - validProjects.add(project); - } - } - - response.put("validProjects", validProjects); - response.put("invalidProjects", invalidProjects); - response.put("totalProjects", projects.size()); - response.put("validCount", validProjects.size()); - response.put("invalidCount", invalidProjects.size()); - - return response; - } - - private Set loadExistingProjectKeys() { - try { - return ldService.getAllProjects().stream() - .map(this::buildProjectKey) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - } catch (Exception e) { - System.err.println("⚠ Could not retrieve the list of existing projects: " + e.getMessage()); - return new HashSet<>(); - } - } - - private String buildProjectKey(ProjectDTO project) { - if (project == null) { - return null; - } - String key = normalize(project.getExternalId()); - if (key == null || key.isEmpty()) { - key = normalize(project.getName()); - } - return (key == null || key.isEmpty()) ? null : key; - } - - private String normalize(String value) { - return value == null ? null : value.trim().toLowerCase(); - } - - /** - * Valida un estudiant individualment. - */ - public ValidationResult validateStudent(String githubUrl, String taigaUrl, String githubToken, StudentDTO student) { - ValidationResult result = new ValidationResult(true); - - if (student == null || student.getIdentities() == null) { - result.addError("The student has no defined identities"); - result.setValid(false); - return result; - } - - // Validar GitHub - if (githubUrl != null) { - String org = extractGitHubOrg(githubUrl); - if (org != null) { - StudentIdentityDTO studentGithub = student.getIdentities().get(DataSource.GITHUB); - if (studentGithub != null && studentGithub.getUsername() != null) { - List usernames = List.of(studentGithub.getUsername()); - ValidationResult usersResult = githubValidationService.validateUsersInOrganization( - org, usernames, githubToken); - result.addErrors(usersResult.getErrors()); - result.addWarnings(usersResult.getWarnings()); - } - } - } - - // Validar Taiga - if (taigaUrl != null) { - String slug = extractTaigaSlug(taigaUrl); - if (slug != null) { - StudentIdentityDTO studentTaiga = student.getIdentities().get(DataSource.TAIGA); - if (studentTaiga != null && studentTaiga.getUsername() != null) { - List usernames = List.of(studentTaiga.getUsername()); - ValidationResult usersResult = taigaValidationService.validateUsersInProject(slug, usernames); - result.addErrors(usersResult.getErrors()); - result.addWarnings(usersResult.getWarnings()); - } - } - } - - if (result.hasErrors()) { - result.setValid(false); - } - - return result; - } - - /** - * Extreu el nom de l'organització d'una URL de GitHub - * Format: https://github.com/organization (com ho feia la Nora) - */ - private String extractGitHubOrg(String url) { - try { - // Trim whitespace first - url = url.trim(); - - // Eliminar .git i barres finals - url = url.replace(".git", "").replaceAll("/$", ""); - - // Dividir per / - String[] parts = url.split("/"); - - // Buscar "github.com" i agafar el següent element (l'organització) - for (int i = 0; i < parts.length; i++) { - if (parts[i].contains("github.com") && i + 1 < parts.length) { - String org = parts[i + 1].trim(); - // Skip "orgs" if it's part of the URL path - if (org.equals("orgs") && i + 2 < parts.length) { - return parts[i + 2].trim(); - } - return org; - } - } - } catch (Exception e) { - System.err.println("Error extracting GitHub organization: " + e.getMessage()); - } - return null; - } - - /** - * Extreu el slug d'una URL de Taiga - * Format: https://tree.taiga.io/project/owner-slug/ - */ - private String extractTaigaSlug(String url) { - try { - // Trim whitespace first - url = url.trim(); - - if (url.contains("/project/")) { - String[] parts = url.split("/project/"); - if (parts.length > 1) { - String slug = parts[1].replaceAll("/$", "").trim(); - return slug; - } - } - } catch (Exception e) { - System.err.println("Error extracting Taiga slug: " + e.getMessage()); - } - return null; - } -} +package com.upc.ld_admintool.domain.services.validation; + +import com.upc.ld_admintool.domain.utils.DataSource; +import com.upc.ld_admintool.domain.services.LDService; +import com.upc.ld_admintool.rest.DTO.ProjectDTO; +import com.upc.ld_admintool.rest.DTO.ProjectIdentityDTO; +import com.upc.ld_admintool.rest.DTO.StudentDTO; +import com.upc.ld_admintool.rest.DTO.StudentIdentityDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class ProjectValidationService { + + @Autowired + private GitHubValidationService githubValidationService; + + @Autowired + private TaigaValidationService taigaValidationService; + + @Autowired + private LDService ldService; + + /** + * Valida un projecte complet amb els seus estudiants. + * Utilitza el token de GitHub específic del projecte si està disponible. + */ + public ValidationResult validateProject(ProjectDTO project) { + ValidationResult result = new ValidationResult(true); + + // Validar identitats del projecte + if (project.getIdentities() == null || project.getIdentities().isEmpty()) { + result.addError("The project '" + project.getName() + "' does not have defined identities (GitHub/Taiga)"); + return result; + } + + // Obtenir el token de GitHub del projecte (pot ser null) + String projectGithubToken = project.getGithubToken(); + + // Validar GitHub (com ho feia la Nora) + ProjectIdentityDTO githubIdentity = project.getIdentities().get(DataSource.GITHUB); + if (githubIdentity != null && githubIdentity.getUrl() != null) { + String org = extractGitHubOrg(githubIdentity.getUrl()); + if (org != null) { + // Validar organització amb el token del projecte + ValidationResult orgResult = githubValidationService.validateOrganization(org, projectGithubToken); + result.addErrors(orgResult.getErrors()); + result.addWarnings(orgResult.getWarnings()); + + // Validar estudiants dins l'organització amb el token del projecte + if (project.getStudents() != null) { + List githubUsernames = new ArrayList<>(); + for (StudentDTO student : project.getStudents()) { + if (student.getIdentities() != null) { + StudentIdentityDTO studentGithub = student.getIdentities().get(DataSource.GITHUB); + if (studentGithub != null && studentGithub.getUsername() != null) { + githubUsernames.add(studentGithub.getUsername()); + } + } + } + + if (!githubUsernames.isEmpty()) { + ValidationResult usersResult = githubValidationService.validateUsersInOrganization( + org, githubUsernames, projectGithubToken); + result.addErrors(usersResult.getErrors()); + result.addWarnings(usersResult.getWarnings()); + } + } + } else { + result.addError("Invalid GitHub URL format for project '" + project.getName() + "': " + + githubIdentity.getUrl()); + } + } else { + result.addWarning("The project '" + project.getName() + "' does not have a defined GitHub URL"); + } + + // Validar Taiga + ProjectIdentityDTO taigaIdentity = project.getIdentities().get(DataSource.TAIGA); + if (taigaIdentity != null && taigaIdentity.getUrl() != null) { + String slug = extractTaigaSlug(taigaIdentity.getUrl()); + if (slug != null) { + ValidationResult taigaResult = taigaValidationService.validateProjectBySlug(slug); + result.getErrors().addAll(taigaResult.getErrors()); + result.getWarnings().addAll(taigaResult.getWarnings()); + + // Validar estudiants dins el projecte Taiga + if (project.getStudents() != null) { + List taigaUsernames = new ArrayList<>(); + for (StudentDTO student : project.getStudents()) { + if (student.getIdentities() != null) { + StudentIdentityDTO studentTaiga = student.getIdentities().get(DataSource.TAIGA); + if (studentTaiga != null && studentTaiga.getUsername() != null) { + taigaUsernames.add(studentTaiga.getUsername()); + } + } + } + + if (!taigaUsernames.isEmpty()) { + ValidationResult usersResult = taigaValidationService.validateUsersInProject(slug, + taigaUsernames); + result.addErrors(usersResult.getErrors()); + result.addWarnings(usersResult.getWarnings()); + } + } + } else { + result.addError("Invalid Taiga URL format for project '" + project.getName() + "': " + + taigaIdentity.getUrl()); + } + } else { + result.addWarning("The project '" + project.getName() + "' does not have a defined Taiga URL"); + } + + if (result.hasErrors()) { + result.setValid(false); + } + return result; + } + + /** + * Valida múltiples projectes i retorna quins són vàlids i quins no + */ + public Map validateProjectsWithDetails(List projects) { + Map response = new HashMap<>(); + List validProjects = new ArrayList<>(); + List> invalidProjects = new ArrayList<>(); + Set existingProjectKeys = loadExistingProjectKeys(); + Set seenProjectKeys = new HashSet<>(); + + for (ProjectDTO project : projects) { + String projectKey = buildProjectKey(project); + boolean alreadyExists = projectKey != null && existingProjectKeys.contains(projectKey); + boolean duplicatedInFile = projectKey != null && seenProjectKeys.contains(projectKey); + if (projectKey != null) { + seenProjectKeys.add(projectKey); + } + + ValidationResult projectResult; + if (alreadyExists || duplicatedInFile) { + projectResult = new ValidationResult(false); + if (alreadyExists) { + projectResult.addError("The project '" + project.getName() + "' already exists in the database."); + } + if (duplicatedInFile) { + projectResult + .addError("The project '" + project.getName() + "' is duplicated within the import file."); + } + } else { + projectResult = validateProject(project); + if (projectResult.isValid() && projectKey != null) { + existingProjectKeys.add(projectKey); + } + } + + if (projectResult.hasErrors()) { + // Projecte invàlid + Map invalidInfo = new HashMap<>(); + invalidInfo.put("project", project); + invalidInfo.put("errors", projectResult.getErrors()); + invalidInfo.put("warnings", projectResult.getWarnings()); + invalidProjects.add(invalidInfo); + } else { + // Projecte vàlid + validProjects.add(project); + } + } + + response.put("validProjects", validProjects); + response.put("invalidProjects", invalidProjects); + response.put("totalProjects", projects.size()); + response.put("validCount", validProjects.size()); + response.put("invalidCount", invalidProjects.size()); + + return response; + } + + private Set loadExistingProjectKeys() { + try { + return ldService.getAllProjects().stream() + .map(this::buildProjectKey) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } catch (Exception e) { + System.err.println("⚠ Could not retrieve the list of existing projects: " + e.getMessage()); + return new HashSet<>(); + } + } + + private String buildProjectKey(ProjectDTO project) { + if (project == null) { + return null; + } + String key = normalize(project.getExternalId()); + if (key == null || key.isEmpty()) { + key = normalize(project.getName()); + } + return (key == null || key.isEmpty()) ? null : key; + } + + private String normalize(String value) { + return value == null ? null : value.trim().toLowerCase(); + } + + /** + * Valida un estudiant individualment. + */ + public ValidationResult validateStudent(String githubUrl, String taigaUrl, String githubToken, StudentDTO student) { + ValidationResult result = new ValidationResult(true); + + if (student == null || student.getIdentities() == null) { + result.addError("The student has no defined identities"); + result.setValid(false); + return result; + } + + // Validar GitHub + if (githubUrl != null) { + String org = extractGitHubOrg(githubUrl); + if (org != null) { + StudentIdentityDTO studentGithub = student.getIdentities().get(DataSource.GITHUB); + if (studentGithub != null && studentGithub.getUsername() != null) { + List usernames = List.of(studentGithub.getUsername()); + ValidationResult usersResult = githubValidationService.validateUsersInOrganization( + org, usernames, githubToken); + result.addErrors(usersResult.getErrors()); + result.addWarnings(usersResult.getWarnings()); + } + } + } + + // Validar Taiga + if (taigaUrl != null) { + String slug = extractTaigaSlug(taigaUrl); + if (slug != null) { + StudentIdentityDTO studentTaiga = student.getIdentities().get(DataSource.TAIGA); + if (studentTaiga != null && studentTaiga.getUsername() != null) { + List usernames = List.of(studentTaiga.getUsername()); + ValidationResult usersResult = taigaValidationService.validateUsersInProject(slug, usernames); + result.addErrors(usersResult.getErrors()); + result.addWarnings(usersResult.getWarnings()); + } + } + } + + if (result.hasErrors()) { + result.setValid(false); + } + + return result; + } + + /** + * Extreu el nom de l'organització d'una URL de GitHub + * Format: https://github.com/organization (com ho feia la Nora) + */ + private String extractGitHubOrg(String url) { + try { + // Trim whitespace first + url = url.trim(); + + // Eliminar .git i barres finals + url = url.replace(".git", "").replaceAll("/$", ""); + + // Dividir per / + String[] parts = url.split("/"); + + // Buscar "github.com" i agafar el següent element (l'organització) + for (int i = 0; i < parts.length; i++) { + if (parts[i].contains("github.com") && i + 1 < parts.length) { + String org = parts[i + 1].trim(); + // Skip "orgs" if it's part of the URL path + if (org.equals("orgs") && i + 2 < parts.length) { + return parts[i + 2].trim(); + } + return org; + } + } + } catch (Exception e) { + System.err.println("Error extracting GitHub organization: " + e.getMessage()); + } + return null; + } + + /** + * Extreu el slug d'una URL de Taiga + * Format: https://tree.taiga.io/project/owner-slug/ + */ + private String extractTaigaSlug(String url) { + try { + // Trim whitespace first + url = url.trim(); + + if (url.contains("/project/")) { + String[] parts = url.split("/project/"); + if (parts.length > 1) { + String slug = parts[1].replaceAll("/$", "").trim(); + return slug; + } + } + } catch (Exception e) { + System.err.println("Error extracting Taiga slug: " + e.getMessage()); + } + return null; + } +} diff --git a/src/main/java/com/upc/ld_admintool/domain/services/validation/TaigaValidationService.java b/src/main/java/com/upc/ld_admintool/domain/services/validation/TaigaValidationService.java index 9bca6d5..f3a3a9e 100644 --- a/src/main/java/com/upc/ld_admintool/domain/services/validation/TaigaValidationService.java +++ b/src/main/java/com/upc/ld_admintool/domain/services/validation/TaigaValidationService.java @@ -1,169 +1,169 @@ -package com.upc.ld_admintool.domain.services.validation; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.*; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.client.HttpClientErrorException; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -@Service -public class TaigaValidationService { - - @Value("${taiga.api.url:https://api.taiga.io/api/v1}") - private String taigaApiUrl; - - @Value("${taiga.token:}") - private String taigaToken; - - private final RestTemplate restTemplate = new RestTemplate(); - private final ObjectMapper objectMapper = new ObjectMapper(); - - /** - * Valida si un projecte de Taiga existeix per slug - */ - public ValidationResult validateProjectBySlug(String projectSlug) { - ValidationResult result = new ValidationResult(true); - - if (projectSlug == null || projectSlug.trim().isEmpty()) { - result.addError("El slug del projecte Taiga està buit"); - return result; - } - - try { - String url = taigaApiUrl + "/projects/by_slug?slug=" + projectSlug; - System.out.println("🔍 Validating Taiga project: " + url); - - HttpHeaders headers = createHeaders(); - HttpEntity entity = new HttpEntity<>(headers); - ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); - - // Check if the response is successful - if (response.getStatusCode().is2xxSuccessful()) { - System.out.println("✅ Taiga project '" + projectSlug + "' exists and is accessible"); - } else { - result.addError( - "El projecte Taiga '" + projectSlug + "' retorna codi d'estat: " + response.getStatusCode()); - } - - } catch (HttpClientErrorException.NotFound e) { - System.err.println("❌ Taiga project '" + projectSlug + "' not found (404)"); - result.addError("El projecte Taiga '" + projectSlug + "' no existeix"); - } catch (HttpClientErrorException.Unauthorized e) { - System.err.println("❌ Taiga project '" + projectSlug + "' unauthorized (401)"); - result.addError("No tens autorització per accedir al projecte Taiga '" + projectSlug - + "' (comprova el token de Taiga)"); - } catch (HttpClientErrorException.Forbidden e) { - System.err.println("⚠️ Taiga project '" + projectSlug + "' forbidden (403)"); - result.addWarning("El projecte Taiga '" + projectSlug + "' és privat o no tens permisos"); - } catch (Exception e) { - System.err.println("❌ Error validating Taiga project '" + projectSlug + "': " + e.getClass().getName() - + " - " + e.getMessage()); - e.printStackTrace(); - result.addError("Error validant el projecte Taiga '" + projectSlug + "': " + e.getMessage()); - } - - return result; - } - - /** - * Valida si els usuaris existeixen com a membres del projecte Taiga - */ - public ValidationResult validateUsersInProject(String projectSlug, List usernames) { - ValidationResult result = new ValidationResult(true); - - if (usernames == null || usernames.isEmpty()) { - return result; - } - - try { - String url = taigaApiUrl + "/projects/by_slug?slug=" + projectSlug; - System.out.println("🔍 Validating users in Taiga project: " + url); - - HttpHeaders headers = createHeaders(); - HttpEntity entity = new HttpEntity<>(headers); - ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); - - if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { - // Parse JSON per extreure els membres - JsonNode projectData = objectMapper.readTree(response.getBody()); - JsonNode membersNode = projectData.get("members"); - - if (membersNode != null && membersNode.isArray()) { - // Extreure usernames dels membres - Set projectMembers = new HashSet<>(); - for (JsonNode memberNode : membersNode) { - JsonNode usernameNode = memberNode.get("username"); - if (usernameNode != null) { - projectMembers.add(usernameNode.asText()); // Validation is now strict case-sensitive - } - } - - System.out.println("📋 Project members: " + projectMembers); - System.out.println("🔍 Validating usernames: " + usernames); - - // Validar cada username - for (String username : usernames) { - if (username == null || username.trim().isEmpty()) { - result.addWarning("Hi ha un username buit a la llista"); - continue; - } - - if (projectMembers.contains(username.trim())) { - System.out.println( - "✅ Usuari '" + username + "' és membre del projecte Taiga '" + projectSlug + "'"); - } else { - System.err.println("❌ Usuari '" + username + "' NO és membre del projecte Taiga '" - + projectSlug + "'"); - result.addError("L'usuari '" + username + "' NO és membre del projecte Taiga '" - + projectSlug + "'"); - } - } - } else { - System.err.println("⚠️ No members node found in Taiga project '" + projectSlug + "'"); - result.addWarning("No es poden validar els membres del projecte Taiga '" + projectSlug - + "' (membres no disponibles)"); - } - } else { - System.err - .println("❌ Failed to access Taiga project '" + projectSlug + "': " + response.getStatusCode()); - result.addError("No es pot accedir al projecte Taiga '" + projectSlug + "' per validar usuaris"); - } - - } catch (HttpClientErrorException.NotFound e) { - System.err.println("❌ Taiga project '" + projectSlug + "' not found (404) during user validation"); - result.addError("El projecte Taiga '" + projectSlug + "' no existeix"); - } catch (HttpClientErrorException.Unauthorized e) { - System.err.println("❌ Taiga project '" + projectSlug + "' unauthorized (401) during user validation"); - result.addError("No tens autorització per accedir al projecte Taiga '" + projectSlug - + "' (comprova el token de Taiga)"); - } catch (HttpClientErrorException.Forbidden e) { - System.err.println("⚠️ Taiga project '" + projectSlug + "' forbidden (403) during user validation"); - result.addWarning( - "El projecte Taiga '" + projectSlug + "' és privat o no tens permisos per veure els membres"); - } catch (Exception e) { - System.err.println("❌ Error validating users in Taiga project '" + projectSlug + "': " - + e.getClass().getName() + " - " + e.getMessage()); - e.printStackTrace(); - result.addError("Error validant usuaris al projecte Taiga '" + projectSlug + "': " + e.getMessage()); - } - - return result; - } - - private HttpHeaders createHeaders() { - HttpHeaders headers = new HttpHeaders(); - headers.set("Content-Type", "application/json"); - - if (taigaToken != null && !taigaToken.isEmpty()) { - headers.set("Authorization", "Bearer " + taigaToken); - } - return headers; - } -} +package com.upc.ld_admintool.domain.services.validation; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.client.HttpClientErrorException; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Service +public class TaigaValidationService { + + @Value("${taiga.api.url:https://api.taiga.io/api/v1}") + private String taigaApiUrl; + + @Value("${taiga.token:}") + private String taigaToken; + + private final RestTemplate restTemplate = new RestTemplate(); + private final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * Valida si un projecte de Taiga existeix per slug + */ + public ValidationResult validateProjectBySlug(String projectSlug) { + ValidationResult result = new ValidationResult(true); + + if (projectSlug == null || projectSlug.trim().isEmpty()) { + result.addError("El slug del projecte Taiga està buit"); + return result; + } + + try { + String url = taigaApiUrl + "/projects/by_slug?slug=" + projectSlug; + System.out.println("🔍 Validating Taiga project: " + url); + + HttpHeaders headers = createHeaders(); + HttpEntity entity = new HttpEntity<>(headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); + + // Check if the response is successful + if (response.getStatusCode().is2xxSuccessful()) { + System.out.println("✅ Taiga project '" + projectSlug + "' exists and is accessible"); + } else { + result.addError( + "El projecte Taiga '" + projectSlug + "' retorna codi d'estat: " + response.getStatusCode()); + } + + } catch (HttpClientErrorException.NotFound e) { + System.err.println("❌ Taiga project '" + projectSlug + "' not found (404)"); + result.addError("El projecte Taiga '" + projectSlug + "' no existeix"); + } catch (HttpClientErrorException.Unauthorized e) { + System.err.println("❌ Taiga project '" + projectSlug + "' unauthorized (401)"); + result.addError("No tens autorització per accedir al projecte Taiga '" + projectSlug + + "' (comprova el token de Taiga)"); + } catch (HttpClientErrorException.Forbidden e) { + System.err.println("⚠️ Taiga project '" + projectSlug + "' forbidden (403)"); + result.addWarning("El projecte Taiga '" + projectSlug + "' és privat o no tens permisos"); + } catch (Exception e) { + System.err.println("❌ Error validating Taiga project '" + projectSlug + "': " + e.getClass().getName() + + " - " + e.getMessage()); + e.printStackTrace(); + result.addError("Error validant el projecte Taiga '" + projectSlug + "': " + e.getMessage()); + } + + return result; + } + + /** + * Valida si els usuaris existeixen com a membres del projecte Taiga + */ + public ValidationResult validateUsersInProject(String projectSlug, List usernames) { + ValidationResult result = new ValidationResult(true); + + if (usernames == null || usernames.isEmpty()) { + return result; + } + + try { + String url = taigaApiUrl + "/projects/by_slug?slug=" + projectSlug; + System.out.println("🔍 Validating users in Taiga project: " + url); + + HttpHeaders headers = createHeaders(); + HttpEntity entity = new HttpEntity<>(headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); + + if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { + // Parse JSON per extreure els membres + JsonNode projectData = objectMapper.readTree(response.getBody()); + JsonNode membersNode = projectData.get("members"); + + if (membersNode != null && membersNode.isArray()) { + // Extreure usernames dels membres + Set projectMembers = new HashSet<>(); + for (JsonNode memberNode : membersNode) { + JsonNode usernameNode = memberNode.get("username"); + if (usernameNode != null) { + projectMembers.add(usernameNode.asText()); // Validation is now strict case-sensitive + } + } + + System.out.println("📋 Project members: " + projectMembers); + System.out.println("🔍 Validating usernames: " + usernames); + + // Validar cada username + for (String username : usernames) { + if (username == null || username.trim().isEmpty()) { + result.addWarning("Hi ha un username buit a la llista"); + continue; + } + + if (projectMembers.contains(username.trim())) { + System.out.println( + "✅ Usuari '" + username + "' és membre del projecte Taiga '" + projectSlug + "'"); + } else { + System.err.println("❌ Usuari '" + username + "' NO és membre del projecte Taiga '" + + projectSlug + "'"); + result.addError("L'usuari '" + username + "' NO és membre del projecte Taiga '" + + projectSlug + "'"); + } + } + } else { + System.err.println("⚠️ No members node found in Taiga project '" + projectSlug + "'"); + result.addWarning("No es poden validar els membres del projecte Taiga '" + projectSlug + + "' (membres no disponibles)"); + } + } else { + System.err + .println("❌ Failed to access Taiga project '" + projectSlug + "': " + response.getStatusCode()); + result.addError("No es pot accedir al projecte Taiga '" + projectSlug + "' per validar usuaris"); + } + + } catch (HttpClientErrorException.NotFound e) { + System.err.println("❌ Taiga project '" + projectSlug + "' not found (404) during user validation"); + result.addError("El projecte Taiga '" + projectSlug + "' no existeix"); + } catch (HttpClientErrorException.Unauthorized e) { + System.err.println("❌ Taiga project '" + projectSlug + "' unauthorized (401) during user validation"); + result.addError("No tens autorització per accedir al projecte Taiga '" + projectSlug + + "' (comprova el token de Taiga)"); + } catch (HttpClientErrorException.Forbidden e) { + System.err.println("⚠️ Taiga project '" + projectSlug + "' forbidden (403) during user validation"); + result.addWarning( + "El projecte Taiga '" + projectSlug + "' és privat o no tens permisos per veure els membres"); + } catch (Exception e) { + System.err.println("❌ Error validating users in Taiga project '" + projectSlug + "': " + + e.getClass().getName() + " - " + e.getMessage()); + e.printStackTrace(); + result.addError("Error validant usuaris al projecte Taiga '" + projectSlug + "': " + e.getMessage()); + } + + return result; + } + + private HttpHeaders createHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.set("Content-Type", "application/json"); + + if (taigaToken != null && !taigaToken.isEmpty()) { + headers.set("Authorization", "Bearer " + taigaToken); + } + return headers; + } +} diff --git a/src/main/java/com/upc/ld_admintool/domain/services/validation/ValidationResult.java b/src/main/java/com/upc/ld_admintool/domain/services/validation/ValidationResult.java index 11a2caf..b952fd7 100644 --- a/src/main/java/com/upc/ld_admintool/domain/services/validation/ValidationResult.java +++ b/src/main/java/com/upc/ld_admintool/domain/services/validation/ValidationResult.java @@ -1,53 +1,53 @@ -package com.upc.ld_admintool.domain.services.validation; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.ArrayList; -import java.util.List; - -@Data -@NoArgsConstructor -@AllArgsConstructor -public class ValidationResult { - private boolean valid; - private List errors; - private List warnings; - - public ValidationResult(boolean valid) { - this.valid = valid; - this.errors = new ArrayList<>(); - this.warnings = new ArrayList<>(); - } - - public void addError(String error) { - this.errors.add(error); - this.valid = false; - } - - public void addWarning(String warning) { - this.warnings.add(warning); - } - - public void addErrors(List errors) { - if (errors != null && !errors.isEmpty()) { - this.errors.addAll(errors); - this.valid = false; - } - } - - public void addWarnings(List warnings) { - if (warnings != null) { - this.warnings.addAll(warnings); - } - } - - public boolean hasErrors() { - return !errors.isEmpty(); - } - - public boolean hasWarnings() { - return !warnings.isEmpty(); - } -} +package com.upc.ld_admintool.domain.services.validation; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ValidationResult { + private boolean valid; + private List errors; + private List warnings; + + public ValidationResult(boolean valid) { + this.valid = valid; + this.errors = new ArrayList<>(); + this.warnings = new ArrayList<>(); + } + + public void addError(String error) { + this.errors.add(error); + this.valid = false; + } + + public void addWarning(String warning) { + this.warnings.add(warning); + } + + public void addErrors(List errors) { + if (errors != null && !errors.isEmpty()) { + this.errors.addAll(errors); + this.valid = false; + } + } + + public void addWarnings(List warnings) { + if (warnings != null) { + this.warnings.addAll(warnings); + } + } + + public boolean hasErrors() { + return !errors.isEmpty(); + } + + public boolean hasWarnings() { + return !warnings.isEmpty(); + } +} diff --git a/src/main/java/com/upc/ld_admintool/domain/utils/DataSource.java b/src/main/java/com/upc/ld_admintool/domain/utils/DataSource.java index f756054..23c03e3 100644 --- a/src/main/java/com/upc/ld_admintool/domain/utils/DataSource.java +++ b/src/main/java/com/upc/ld_admintool/domain/utils/DataSource.java @@ -1,5 +1,5 @@ -package com.upc.ld_admintool.domain.utils; - -public enum DataSource { - GITHUB, TAIGA, SHEETS -} +package com.upc.ld_admintool.domain.utils; + +public enum DataSource { + GITHUB, TAIGA, SHEETS +} diff --git a/src/main/java/com/upc/ld_admintool/rest/DTO/CategoryDTO.java b/src/main/java/com/upc/ld_admintool/rest/DTO/CategoryDTO.java index 199bee3..6e928b5 100644 --- a/src/main/java/com/upc/ld_admintool/rest/DTO/CategoryDTO.java +++ b/src/main/java/com/upc/ld_admintool/rest/DTO/CategoryDTO.java @@ -1,12 +1,12 @@ -package com.upc.ld_admintool.rest.DTO; - -import java.util.List; -import java.util.Map; -import lombok.Data; - -@Data -public class CategoryDTO { - private String category; - private String patternGroup; - private List> interval; -} +package com.upc.ld_admintool.rest.DTO; + +import java.util.List; +import java.util.Map; +import lombok.Data; + +@Data +public class CategoryDTO { + private String category; + private String patternGroup; + private List> interval; +} diff --git a/src/main/java/com/upc/ld_admintool/rest/DTO/FactorDTO.java b/src/main/java/com/upc/ld_admintool/rest/DTO/FactorDTO.java index c0559d5..050fa28 100644 --- a/src/main/java/com/upc/ld_admintool/rest/DTO/FactorDTO.java +++ b/src/main/java/com/upc/ld_admintool/rest/DTO/FactorDTO.java @@ -1,20 +1,20 @@ -package com.upc.ld_admintool.rest.DTO; -import lombok.Data; -import lombok.AllArgsConstructor; -import lombok.NoArgsConstructor; -import java.util.List; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class FactorDTO { - private String id; - private String externalId; - private String name; - private String description; - private String category; - private String threshold; - private String type; - private List metrics; - private List metricsWeights; +package com.upc.ld_admintool.rest.DTO; +import lombok.Data; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class FactorDTO { + private String id; + private String externalId; + private String name; + private String description; + private String category; + private String threshold; + private String type; + private List metrics; + private List metricsWeights; } \ No newline at end of file diff --git a/src/main/java/com/upc/ld_admintool/rest/DTO/IntervalDTO.java b/src/main/java/com/upc/ld_admintool/rest/DTO/IntervalDTO.java index 54b9e7b..6d1f9e3 100644 --- a/src/main/java/com/upc/ld_admintool/rest/DTO/IntervalDTO.java +++ b/src/main/java/com/upc/ld_admintool/rest/DTO/IntervalDTO.java @@ -1,8 +1,8 @@ -package com.upc.ld_admintool.rest.DTO; -import lombok.Data; - -@Data -public class IntervalDTO { - private String name; - private String color; -} +package com.upc.ld_admintool.rest.DTO; +import lombok.Data; + +@Data +public class IntervalDTO { + private String name; + private String color; +} diff --git a/src/main/java/com/upc/ld_admintool/rest/DTO/MetricDTO.java b/src/main/java/com/upc/ld_admintool/rest/DTO/MetricDTO.java index c449089..198bc1f 100644 --- a/src/main/java/com/upc/ld_admintool/rest/DTO/MetricDTO.java +++ b/src/main/java/com/upc/ld_admintool/rest/DTO/MetricDTO.java @@ -1,16 +1,16 @@ -package com.upc.ld_admintool.rest.DTO; -import lombok.Data; -import lombok.AllArgsConstructor; -import lombok.NoArgsConstructor; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class MetricDTO { - private String id; - private String externalId; - private String name; - private String description; - private String categoryName; - private String scope; -} +package com.upc.ld_admintool.rest.DTO; +import lombok.Data; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class MetricDTO { + private String id; + private String externalId; + private String name; + private String description; + private String categoryName; + private String scope; +} diff --git a/src/main/java/com/upc/ld_admintool/rest/DTO/ProjectDTO.java b/src/main/java/com/upc/ld_admintool/rest/DTO/ProjectDTO.java index 59acd25..1ddacec 100644 --- a/src/main/java/com/upc/ld_admintool/rest/DTO/ProjectDTO.java +++ b/src/main/java/com/upc/ld_admintool/rest/DTO/ProjectDTO.java @@ -1,25 +1,25 @@ -package com.upc.ld_admintool.rest.DTO; - -import com.upc.ld_admintool.domain.utils.DataSource; -import java.util.List; -import java.util.Map; -import lombok.Data; - -@Data -public class ProjectDTO { - private Long id; - private String externalId; - private String name; - private String description; - private byte[] logo; - private boolean active; - private String backlogId; - private Boolean isGlobal; - private boolean anonymized; - private String subject; - - private String githubToken; - - private Map identities; - private List students; -} +package com.upc.ld_admintool.rest.DTO; + +import com.upc.ld_admintool.domain.utils.DataSource; +import java.util.List; +import java.util.Map; +import lombok.Data; + +@Data +public class ProjectDTO { + private Long id; + private String externalId; + private String name; + private String description; + private byte[] logo; + private boolean active; + private String backlogId; + private Boolean isGlobal; + private boolean anonymized; + private String subject; + + private String githubToken; + + private Map identities; + private List students; +} diff --git a/src/main/java/com/upc/ld_admintool/rest/DTO/ProjectIdentityDTO.java b/src/main/java/com/upc/ld_admintool/rest/DTO/ProjectIdentityDTO.java index c5a6688..40c0883 100644 --- a/src/main/java/com/upc/ld_admintool/rest/DTO/ProjectIdentityDTO.java +++ b/src/main/java/com/upc/ld_admintool/rest/DTO/ProjectIdentityDTO.java @@ -1,14 +1,14 @@ -package com.upc.ld_admintool.rest.DTO; -import com.upc.ld_admintool.domain.utils.DataSource; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class ProjectIdentityDTO { - private DataSource dataSource; - private String url; - private ProjectDTO project; -} +package com.upc.ld_admintool.rest.DTO; +import com.upc.ld_admintool.domain.utils.DataSource; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ProjectIdentityDTO { + private DataSource dataSource; + private String url; + private ProjectDTO project; +} diff --git a/src/main/java/com/upc/ld_admintool/rest/DTO/SaveSyncResponseDTO.java b/src/main/java/com/upc/ld_admintool/rest/DTO/SaveSyncResponseDTO.java index bc56db2..8dbc8ba 100644 --- a/src/main/java/com/upc/ld_admintool/rest/DTO/SaveSyncResponseDTO.java +++ b/src/main/java/com/upc/ld_admintool/rest/DTO/SaveSyncResponseDTO.java @@ -1,26 +1,26 @@ -package com.upc.ld_admintool.rest.DTO; - -import lombok.Data; -import java.util.ArrayList; -import java.util.List; - -@Data -public class SaveSyncResponseDTO { - - private boolean success = true; - private int finalTeamSize; - private List steps = new ArrayList<>(); - - public void addSuccessStep(int order, String name, String detail) { - steps.add(new SaveSyncStepDTO(order, name, detail, "SUCCESS", null)); - } - - public void addFailureStep(int order, String name, String detail, String error) { - steps.add(new SaveSyncStepDTO(order, name, detail, "FAILED", error)); - this.success = false; - } - - public void addSkippedStep(int order, String name, String detail) { - steps.add(new SaveSyncStepDTO(order, name, detail, "SKIPPED", null)); - } -} +package com.upc.ld_admintool.rest.DTO; + +import lombok.Data; +import java.util.ArrayList; +import java.util.List; + +@Data +public class SaveSyncResponseDTO { + + private boolean success = true; + private int finalTeamSize; + private List steps = new ArrayList<>(); + + public void addSuccessStep(int order, String name, String detail) { + steps.add(new SaveSyncStepDTO(order, name, detail, "SUCCESS", null)); + } + + public void addFailureStep(int order, String name, String detail, String error) { + steps.add(new SaveSyncStepDTO(order, name, detail, "FAILED", error)); + this.success = false; + } + + public void addSkippedStep(int order, String name, String detail) { + steps.add(new SaveSyncStepDTO(order, name, detail, "SKIPPED", null)); + } +} diff --git a/src/main/java/com/upc/ld_admintool/rest/DTO/SaveSyncStepDTO.java b/src/main/java/com/upc/ld_admintool/rest/DTO/SaveSyncStepDTO.java index cea455d..ea2b2cd 100644 --- a/src/main/java/com/upc/ld_admintool/rest/DTO/SaveSyncStepDTO.java +++ b/src/main/java/com/upc/ld_admintool/rest/DTO/SaveSyncStepDTO.java @@ -1,17 +1,17 @@ -package com.upc.ld_admintool.rest.DTO; - -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; - -@Data -@NoArgsConstructor -@AllArgsConstructor -public class SaveSyncStepDTO { - - private int order; - private String name; - private String detail; - private String status; // SUCCESS, FAILED, SKIPPED - private String error; -} +package com.upc.ld_admintool.rest.DTO; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SaveSyncStepDTO { + + private int order; + private String name; + private String detail; + private String status; // SUCCESS, FAILED, SKIPPED + private String error; +} diff --git a/src/main/java/com/upc/ld_admintool/rest/DTO/StudentDTO.java b/src/main/java/com/upc/ld_admintool/rest/DTO/StudentDTO.java index 9ee639b..38366cf 100644 --- a/src/main/java/com/upc/ld_admintool/rest/DTO/StudentDTO.java +++ b/src/main/java/com/upc/ld_admintool/rest/DTO/StudentDTO.java @@ -1,12 +1,12 @@ -package com.upc.ld_admintool.rest.DTO; -import java.util.Map; -import lombok.Data; -import com.upc.ld_admintool.domain.utils.DataSource; - -@Data -public class StudentDTO { - private Long id; - private String name; - private Map identities; - private ProjectDTO project; -} +package com.upc.ld_admintool.rest.DTO; +import java.util.Map; +import lombok.Data; +import com.upc.ld_admintool.domain.utils.DataSource; + +@Data +public class StudentDTO { + private Long id; + private String name; + private Map identities; + private ProjectDTO project; +} diff --git a/src/main/java/com/upc/ld_admintool/rest/DTO/StudentIdentityDTO.java b/src/main/java/com/upc/ld_admintool/rest/DTO/StudentIdentityDTO.java index 976097f..7dca9e8 100644 --- a/src/main/java/com/upc/ld_admintool/rest/DTO/StudentIdentityDTO.java +++ b/src/main/java/com/upc/ld_admintool/rest/DTO/StudentIdentityDTO.java @@ -1,13 +1,13 @@ -package com.upc.ld_admintool.rest.DTO; -import com.upc.ld_admintool.domain.utils.DataSource; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class StudentIdentityDTO { - private DataSource dataSource; - private String username; -} +package com.upc.ld_admintool.rest.DTO; +import com.upc.ld_admintool.domain.utils.DataSource; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class StudentIdentityDTO { + private DataSource dataSource; + private String username; +} diff --git a/src/main/java/com/upc/ld_admintool/rest/DTO/StudentValidationDTO.java b/src/main/java/com/upc/ld_admintool/rest/DTO/StudentValidationDTO.java index 52d9e54..6dc4ae0 100644 --- a/src/main/java/com/upc/ld_admintool/rest/DTO/StudentValidationDTO.java +++ b/src/main/java/com/upc/ld_admintool/rest/DTO/StudentValidationDTO.java @@ -1,12 +1,12 @@ -package com.upc.ld_admintool.rest.DTO; - -import lombok.Data; - -@Data -public class StudentValidationDTO { - private Long projectId; - private String githubUrl; - private String taigaUrl; - private String githubToken; - private StudentDTO student; -} +package com.upc.ld_admintool.rest.DTO; + +import lombok.Data; + +@Data +public class StudentValidationDTO { + private Long projectId; + private String githubUrl; + private String taigaUrl; + private String githubToken; + private StudentDTO student; +} diff --git a/src/main/java/com/upc/ld_admintool/rest/DTO/WizardStatusDTO.java b/src/main/java/com/upc/ld_admintool/rest/DTO/WizardStatusDTO.java index 6eff874..3f38767 100644 --- a/src/main/java/com/upc/ld_admintool/rest/DTO/WizardStatusDTO.java +++ b/src/main/java/com/upc/ld_admintool/rest/DTO/WizardStatusDTO.java @@ -1,15 +1,15 @@ -package com.upc.ld_admintool.rest.DTO; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@AllArgsConstructor -public class WizardStatusDTO { - private boolean hasProjects; - private boolean hasData; - private boolean hasMetricsCategories; - private boolean hasFactorsCategories; - private boolean hasStrategicIndicatorCategories; -} +package com.upc.ld_admintool.rest.DTO; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +public class WizardStatusDTO { + private boolean hasProjects; + private boolean hasData; + private boolean hasMetricsCategories; + private boolean hasFactorsCategories; + private boolean hasStrategicIndicatorCategories; +} diff --git a/src/main/java/com/upc/ld_admintool/rest/controllers/CategoriesController.java b/src/main/java/com/upc/ld_admintool/rest/controllers/CategoriesController.java index 824422e..e432fca 100644 --- a/src/main/java/com/upc/ld_admintool/rest/controllers/CategoriesController.java +++ b/src/main/java/com/upc/ld_admintool/rest/controllers/CategoriesController.java @@ -1,38 +1,38 @@ -package com.upc.ld_admintool.rest.controllers; -import com.upc.ld_admintool.domain.services.CategoriesService; -import com.upc.ld_admintool.rest.DTO.CategoryDTO; -import com.upc.ld_admintool.rest.DTO.IntervalDTO; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -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.RestController; -import java.util.List; - - -@RestController -@RequestMapping("/api/categories") -public class CategoriesController { - @Autowired - private CategoriesService categoriesService; - - - @PostMapping("/metrics") - public ResponseEntity importarCategoriesMetriques(@RequestBody List categories) { - categoriesService.importarCategoriesMetriques(categories); - return ResponseEntity.ok().build(); - } - - @PostMapping("/factors") - public ResponseEntity importarCategoriesFactors(@RequestBody List categories) { - categoriesService.importarCategoriesFactors(categories); - return ResponseEntity.ok().build(); - } - - @PostMapping("/strategicIndicators") - public ResponseEntity importarCategoriesStrategic(@RequestBody List intervals) { - categoriesService.importarCategoriesStrategicIndicators(intervals); - return ResponseEntity.ok().build(); - } -} +package com.upc.ld_admintool.rest.controllers; +import com.upc.ld_admintool.domain.services.CategoriesService; +import com.upc.ld_admintool.rest.DTO.CategoryDTO; +import com.upc.ld_admintool.rest.DTO.IntervalDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RestController; +import java.util.List; + + +@RestController +@RequestMapping("/api/categories") +public class CategoriesController { + @Autowired + private CategoriesService categoriesService; + + + @PostMapping("/metrics") + public ResponseEntity importarCategoriesMetriques(@RequestBody List categories) { + categoriesService.importarCategoriesMetriques(categories); + return ResponseEntity.ok().build(); + } + + @PostMapping("/factors") + public ResponseEntity importarCategoriesFactors(@RequestBody List categories) { + categoriesService.importarCategoriesFactors(categories); + return ResponseEntity.ok().build(); + } + + @PostMapping("/strategicIndicators") + public ResponseEntity importarCategoriesStrategic(@RequestBody List intervals) { + categoriesService.importarCategoriesStrategicIndicators(intervals); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/upc/ld_admintool/rest/controllers/FactorsController.java b/src/main/java/com/upc/ld_admintool/rest/controllers/FactorsController.java index e0d51a6..b3bcf0b 100644 --- a/src/main/java/com/upc/ld_admintool/rest/controllers/FactorsController.java +++ b/src/main/java/com/upc/ld_admintool/rest/controllers/FactorsController.java @@ -1,53 +1,53 @@ -package com.upc.ld_admintool.rest.controllers; - -import com.upc.ld_admintool.domain.services.FactorsService; -import com.upc.ld_admintool.rest.DTO.FactorDTO; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -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.PutMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import java.util.List; -import java.util.Map; - -@RestController -@RequestMapping("/api/factors") -public class FactorsController { - - @Autowired - private FactorsService factorsService; - - @GetMapping - public ResponseEntity> getFactorsByProject(@RequestParam("prj") String projectId) { - return ResponseEntity.ok(factorsService.getFactorsByProject(projectId)); - } - - @GetMapping("/list") - public ResponseEntity> getFactorsCategoriesList() { - return ResponseEntity.ok(factorsService.getFactorsCategoriesList()); - } - - @GetMapping("/categories") - public ResponseEntity>> getAllFactorsCategories() { - return ResponseEntity.ok(factorsService.getAllFactorsCategories()); - } - - @PutMapping("/{id}/category") - public ResponseEntity updateFactorCategory( - @PathVariable Long id, - @RequestParam("category") String category, - @RequestParam("prj") String project) { - factorsService.updateFactorCategory(id, category, project); - return ResponseEntity.ok().build(); - } - - @GetMapping("/import") - public ResponseEntity importQualityFactors() { - factorsService.importQualityFactors(); - return ResponseEntity.ok().build(); - } +package com.upc.ld_admintool.rest.controllers; + +import com.upc.ld_admintool.domain.services.FactorsService; +import com.upc.ld_admintool.rest.DTO.FactorDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +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.PutMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/factors") +public class FactorsController { + + @Autowired + private FactorsService factorsService; + + @GetMapping + public ResponseEntity> getFactorsByProject(@RequestParam("prj") String projectId) { + return ResponseEntity.ok(factorsService.getFactorsByProject(projectId)); + } + + @GetMapping("/list") + public ResponseEntity> getFactorsCategoriesList() { + return ResponseEntity.ok(factorsService.getFactorsCategoriesList()); + } + + @GetMapping("/categories") + public ResponseEntity>> getAllFactorsCategories() { + return ResponseEntity.ok(factorsService.getAllFactorsCategories()); + } + + @PutMapping("/{id}/category") + public ResponseEntity updateFactorCategory( + @PathVariable Long id, + @RequestParam("category") String category, + @RequestParam("prj") String project) { + factorsService.updateFactorCategory(id, category, project); + return ResponseEntity.ok().build(); + } + + @GetMapping("/import") + public ResponseEntity importQualityFactors() { + factorsService.importQualityFactors(); + return ResponseEntity.ok().build(); + } } \ No newline at end of file diff --git a/src/main/java/com/upc/ld_admintool/rest/controllers/MetricsController.java b/src/main/java/com/upc/ld_admintool/rest/controllers/MetricsController.java index 2b88383..1b04c74 100644 --- a/src/main/java/com/upc/ld_admintool/rest/controllers/MetricsController.java +++ b/src/main/java/com/upc/ld_admintool/rest/controllers/MetricsController.java @@ -1,56 +1,56 @@ -package com.upc.ld_admintool.rest.controllers; - -import com.upc.ld_admintool.domain.services.MetricsService; -import com.upc.ld_admintool.rest.DTO.MetricDTO; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -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.PutMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import java.util.List; -import java.util.Map; - -@RestController -@RequestMapping("/api/metrics") -public class MetricsController { - - @Autowired - private MetricsService metricsService; - - @GetMapping - public ResponseEntity> getMetricsByProject(@RequestParam("prj") String projectId) { - System.out.println("Fetching metrics for project ID: " + projectId); - return ResponseEntity.ok(metricsService.getMetricsByProject(projectId)); - } - - @GetMapping("/list") - public ResponseEntity> getMetricsCategoriesList() { - return ResponseEntity.ok(metricsService.getMetricsCategoriesList()); - } - - @GetMapping("/categories") - public ResponseEntity>> getAllMetricsCategories() { - return ResponseEntity.ok(metricsService.getAllMetricsCategories()); - } - - @PutMapping("/{id}") - public ResponseEntity editMetric(@PathVariable Long id, - @RequestParam(required = false) String threshold, - @RequestParam(required = false) String url, - @RequestParam(required = false) String categoryName, - @RequestParam(required = false) String scope, - @RequestParam("prj") String project) { - metricsService.editMetric(id, threshold, url, categoryName, scope, project); - return ResponseEntity.ok().build(); - } - - @GetMapping("/import") - public ResponseEntity importMetrics() { - metricsService.importMetrics(); - return ResponseEntity.ok().build(); - } +package com.upc.ld_admintool.rest.controllers; + +import com.upc.ld_admintool.domain.services.MetricsService; +import com.upc.ld_admintool.rest.DTO.MetricDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/metrics") +public class MetricsController { + + @Autowired + private MetricsService metricsService; + + @GetMapping + public ResponseEntity> getMetricsByProject(@RequestParam("prj") String projectId) { + System.out.println("Fetching metrics for project ID: " + projectId); + return ResponseEntity.ok(metricsService.getMetricsByProject(projectId)); + } + + @GetMapping("/list") + public ResponseEntity> getMetricsCategoriesList() { + return ResponseEntity.ok(metricsService.getMetricsCategoriesList()); + } + + @GetMapping("/categories") + public ResponseEntity>> getAllMetricsCategories() { + return ResponseEntity.ok(metricsService.getAllMetricsCategories()); + } + + @PutMapping("/{id}") + public ResponseEntity editMetric(@PathVariable Long id, + @RequestParam(required = false) String threshold, + @RequestParam(required = false) String url, + @RequestParam(required = false) String categoryName, + @RequestParam(required = false) String scope, + @RequestParam("prj") String project) { + metricsService.editMetric(id, threshold, url, categoryName, scope, project); + return ResponseEntity.ok().build(); + } + + @GetMapping("/import") + public ResponseEntity importMetrics() { + metricsService.importMetrics(); + return ResponseEntity.ok().build(); + } } \ No newline at end of file diff --git a/src/main/java/com/upc/ld_admintool/rest/controllers/ProjectController.java b/src/main/java/com/upc/ld_admintool/rest/controllers/ProjectController.java index 91d2a24..b8a0c98 100644 --- a/src/main/java/com/upc/ld_admintool/rest/controllers/ProjectController.java +++ b/src/main/java/com/upc/ld_admintool/rest/controllers/ProjectController.java @@ -1,102 +1,151 @@ -package com.upc.ld_admintool.rest.controllers; - -import java.util.List; -import java.util.Map; -import com.upc.ld_admintool.domain.services.ProjectService; -import com.upc.ld_admintool.domain.services.exceptions.SaveSyncException; -import com.upc.ld_admintool.domain.services.validation.ProjectValidationService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.bind.annotation.RequestBody; -import com.upc.ld_admintool.rest.DTO.ProjectDTO; -import com.upc.ld_admintool.rest.DTO.SaveSyncResponseDTO; -import com.upc.ld_admintool.rest.DTO.StudentValidationDTO; -import com.upc.ld_admintool.domain.services.validation.ValidationResult; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.PathVariable; - -@RestController -@RequestMapping("/api/projects") -public class ProjectController { - - @Autowired - private ProjectService projectService; - - @Autowired - private ProjectValidationService validationService; - - // Llista tots els projectes - @GetMapping - public List getAllProjects() { - return projectService.llistarProjectesAmbStudents(); - } - - // Obtenir un projecte per id - @GetMapping("/{id}") - public ResponseEntity getProjectById(@PathVariable Long id) { - ProjectDTO project = projectService.getProjectById(id); - return ResponseEntity.ok(project); - } - - // Valida i importa projectes (només els vàlids) - @PostMapping - public ResponseEntity> importProjectsExcel(@RequestBody List projects) { - Map validationResult = validationService.validateProjectsWithDetails(projects); - - @SuppressWarnings("unchecked") - List validProjects = (List) validationResult.get("validProjects"); - - if (!validProjects.isEmpty()) { - projectService.importProjects(validProjects); - } - return ResponseEntity.ok(validationResult); - } - - @PostMapping("/sync-categories") - public ResponseEntity syncCategoriesAfterImport() { - projectService.synchronizeCategoriesAfterDataImport(); - return ResponseEntity.ok().build(); - } - - @PostMapping("/validate-student") - public ResponseEntity validateStudent(@RequestBody StudentValidationDTO request) { - ValidationResult result = validationService.validateStudent( - request.getGithubUrl(), - request.getTaigaUrl(), - request.getGithubToken(), - request.getStudent()); - - return ResponseEntity.ok(result); - } - - // Modifica un projecte per id - @PutMapping("/{id}") - public ResponseEntity modificarProjecte(@PathVariable Long id, @RequestBody ProjectDTO projecte) { - if (projecte.getStudents() != null) { - projecte.getStudents().forEach(student -> { - System.out.println(" - " + student.getName() + " (ID: " + student.getId() + ")"); - }); - } - try { - SaveSyncResponseDTO response = projectService.modificarProjecte(id, projecte); - return ResponseEntity.ok(response); - } catch (SaveSyncException syncError) { - return ResponseEntity.badRequest().body(syncError.getResponse()); - } catch (Exception e) { - e.printStackTrace(); - return ResponseEntity.badRequest().body(e.getMessage()); - } - } - - // Elimina un projecte per id - @DeleteMapping("/{id}") - public ResponseEntity esborrarProjecte(@PathVariable Long id) { - projectService.esborrarProjecte(id); - return ResponseEntity.ok().build(); - } -} +package com.upc.ld_admintool.rest.controllers; + +import java.util.List; +import java.util.Map; +import com.upc.ld_admintool.domain.services.ProjectService; +import com.upc.ld_admintool.domain.services.RecoveryService; +import com.upc.ld_admintool.domain.services.exceptions.SaveSyncException; +import com.upc.ld_admintool.domain.services.validation.ProjectValidationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RequestBody; +import com.upc.ld_admintool.rest.DTO.ProjectDTO; +import com.upc.ld_admintool.rest.DTO.SaveSyncResponseDTO; +import com.upc.ld_admintool.rest.DTO.StudentValidationDTO; +import com.upc.ld_admintool.domain.services.validation.ValidationResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; + +@RestController +@RequestMapping("/api/projects") +public class ProjectController { + + @Autowired + private ProjectService projectService; + + @Autowired + private ProjectValidationService validationService; + + @Autowired + private RecoveryService recoveryService; + + // Llista tots els projectes + @GetMapping + public List getAllProjects() { + return projectService.llistarProjectesAmbStudents(); + } + + // Obtenir un projecte per id + @GetMapping("/{id}") + public ResponseEntity getProjectById(@PathVariable Long id) { + ProjectDTO project = projectService.getProjectById(id); + return ResponseEntity.ok(project); + } + + // Valida i importa projectes (només els vàlids) + @PostMapping + public ResponseEntity> importProjectsExcel(@RequestBody List projects) { + Map validationResult = validationService.validateProjectsWithDetails(projects); + + @SuppressWarnings("unchecked") + List validProjects = (List) validationResult.get("validProjects"); + + if (!validProjects.isEmpty()) { + projectService.importProjects(validProjects); + } + return ResponseEntity.ok(validationResult); + } + + @PostMapping("/sync-categories") + public ResponseEntity syncCategoriesAfterImport() { + projectService.synchronizeCategoriesAfterDataImport(); + return ResponseEntity.ok().build(); + } + + @PostMapping("/validate-student") + public ResponseEntity validateStudent(@RequestBody StudentValidationDTO request) { + ValidationResult result = validationService.validateStudent( + request.getGithubUrl(), + request.getTaigaUrl(), + request.getGithubToken(), + request.getStudent()); + + return ResponseEntity.ok(result); + } + + @PostMapping("/{id}/recover") + public ResponseEntity triggerRecovery(@PathVariable Long id, + @RequestBody(required = false) Map tokenOverrides) { + try { + return ResponseEntity.accepted().body(recoveryService.runTeamRecovery(id, tokenOverrides)); + } catch (IllegalArgumentException e) { + return ResponseEntity.badRequest().body(Map.of("error", e.getMessage())); + } catch (Exception e) { + return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage())); + } + } + + @PostMapping("/{id}/recover/github") + public ResponseEntity triggerGithubRecovery(@PathVariable Long id, + @RequestBody(required = false) Map tokenOverrides) { + try { + return ResponseEntity.accepted().body(recoveryService.runGithubRecovery(id, tokenOverrides)); + } catch (IllegalArgumentException e) { + return ResponseEntity.badRequest().body(Map.of("error", e.getMessage())); + } catch (Exception e) { + return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage())); + } + } + + @PostMapping("/{id}/recover/taiga") + public ResponseEntity triggerTaigaRecovery(@PathVariable Long id, + @RequestBody(required = false) Map tokenOverrides) { + try { + return ResponseEntity.accepted().body(recoveryService.runTaigaRecovery(id, tokenOverrides)); + } catch (IllegalArgumentException e) { + return ResponseEntity.badRequest().body(Map.of("error", e.getMessage())); + } catch (Exception e) { + return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage())); + } + } + + @GetMapping("/{id}/recover/status/{jobId}") + public ResponseEntity getRecoveryStatus(@PathVariable Long id, @PathVariable String jobId) { + try { + return ResponseEntity.ok(recoveryService.getRecoveryStatus(jobId)); + } catch (Exception e) { + return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage())); + } + } + + // Modifica un projecte per id + @PutMapping("/{id}") + public ResponseEntity modificarProjecte(@PathVariable Long id, @RequestBody ProjectDTO projecte) { + if (projecte.getStudents() != null) { + projecte.getStudents().forEach(student -> { + System.out.println(" - " + student.getName() + " (ID: " + student.getId() + ")"); + }); + } + try { + SaveSyncResponseDTO response = projectService.modificarProjecte(id, projecte); + return ResponseEntity.ok(response); + } catch (SaveSyncException syncError) { + return ResponseEntity.badRequest().body(syncError.getResponse()); + } catch (Exception e) { + e.printStackTrace(); + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + + // Elimina un projecte per id + @DeleteMapping("/{id}") + public ResponseEntity esborrarProjecte(@PathVariable Long id) { + projectService.esborrarProjecte(id); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/upc/ld_admintool/rest/controllers/StrategicIndicatorsController.java b/src/main/java/com/upc/ld_admintool/rest/controllers/StrategicIndicatorsController.java index 94d3bcc..cc07c9a 100644 --- a/src/main/java/com/upc/ld_admintool/rest/controllers/StrategicIndicatorsController.java +++ b/src/main/java/com/upc/ld_admintool/rest/controllers/StrategicIndicatorsController.java @@ -1,29 +1,29 @@ -package com.upc.ld_admintool.rest.controllers; - -import com.upc.ld_admintool.domain.services.StrategicIndicatorsService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import java.util.List; -import java.util.Map; - -@RestController -@RequestMapping("/api/strategicIndicators") -public class StrategicIndicatorsController { - - @Autowired - private StrategicIndicatorsService strategicIndicatorsService; - - @GetMapping("/categories") - public ResponseEntity>> getAllStrategicIndicatorCategories() { - return ResponseEntity.ok(strategicIndicatorsService.getAllStrategicIndicatorCategories()); - } - - @GetMapping("/fetch") - public ResponseEntity fetchStrategicIndicators() { - strategicIndicatorsService.fetchStrategicIndicators(); - return ResponseEntity.ok().build(); - } -} +package com.upc.ld_admintool.rest.controllers; + +import com.upc.ld_admintool.domain.services.StrategicIndicatorsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/strategicIndicators") +public class StrategicIndicatorsController { + + @Autowired + private StrategicIndicatorsService strategicIndicatorsService; + + @GetMapping("/categories") + public ResponseEntity>> getAllStrategicIndicatorCategories() { + return ResponseEntity.ok(strategicIndicatorsService.getAllStrategicIndicatorCategories()); + } + + @GetMapping("/fetch") + public ResponseEntity fetchStrategicIndicators() { + strategicIndicatorsService.fetchStrategicIndicators(); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/upc/ld_admintool/rest/controllers/WizardController.java b/src/main/java/com/upc/ld_admintool/rest/controllers/WizardController.java index 9716056..1e05206 100644 --- a/src/main/java/com/upc/ld_admintool/rest/controllers/WizardController.java +++ b/src/main/java/com/upc/ld_admintool/rest/controllers/WizardController.java @@ -1,22 +1,22 @@ -package com.upc.ld_admintool.rest.controllers; - -import com.upc.ld_admintool.domain.services.WizardService; -import com.upc.ld_admintool.rest.DTO.WizardStatusDTO; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/api/wizard") -public class WizardController { - - @Autowired - private WizardService wizardService; - - @GetMapping("/status") - public ResponseEntity getStatus() { - return ResponseEntity.ok(wizardService.getWizardStatus()); - } -} +package com.upc.ld_admintool.rest.controllers; + +import com.upc.ld_admintool.domain.services.WizardService; +import com.upc.ld_admintool.rest.DTO.WizardStatusDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/wizard") +public class WizardController { + + @Autowired + private WizardService wizardService; + + @GetMapping("/status") + public ResponseEntity getStatus() { + return ResponseEntity.ok(wizardService.getWizardStatus()); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 287af78..e2ad0f6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,23 +1,25 @@ -server: - port: ${ADMINTOOL_PORT:8080} - -ld: - api: - url: ${ADMINTOOL_LD_API_URL:https://eaa864cb69ae.ngrok-free.app/api} - eval: - url: ${ADMINTOOL_EVAL_URL:http://localhost:5001} - -spring: - datasource: - url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5433}/${DB_NAME:postgres} - username: ${DB_USER:postgres} - password: ${DB_PASSWORD:example} - driver-class-name: org.postgresql.Driver - -taiga: - api: - url: ${TAIGA_API_URL:https://api.taiga.io/api/v1} - token: ${TAIGA_TOKEN:} - -github: - token: ${GITHUB_TOKEN:} +server: + port: ${ADMINTOOL_PORT:8080} + +ld: + api: + url: ${ADMINTOOL_LD_API_URL:https://eaa864cb69ae.ngrok-free.app/api} + eval: + url: ${ADMINTOOL_EVAL_URL:http://localhost:5001} + connect: + url: ${ADMINTOOL_LD_CONNECT_URL:http://localhost:5000} + +spring: + datasource: + url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5433}/${DB_NAME:postgres} + username: ${DB_USER:postgres} + password: ${DB_PASSWORD:example} + driver-class-name: org.postgresql.Driver + +taiga: + api: + url: ${TAIGA_API_URL:https://api.taiga.io/api/v1} + token: ${TAIGA_TOKEN:} + +github: + token: ${GITHUB_TOKEN:} diff --git a/src/test/java/com/upc/ld_admintool/LdAdmintoolApplicationTests.java b/src/test/java/com/upc/ld_admintool/LdAdmintoolApplicationTests.java index 3706d6c..07b22e3 100644 --- a/src/test/java/com/upc/ld_admintool/LdAdmintoolApplicationTests.java +++ b/src/test/java/com/upc/ld_admintool/LdAdmintoolApplicationTests.java @@ -1,13 +1,13 @@ -package com.upc.ld_admintool; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class LdAdmintoolApplicationTests { - - @Test - void contextLoads() { - } - -} +package com.upc.ld_admintool; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class LdAdmintoolApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/src/test/java/com/upc/ld_admintool/domain/services/CategoriesServiceTest.java b/src/test/java/com/upc/ld_admintool/domain/services/CategoriesServiceTest.java index 9ee2213..a5b4c25 100644 --- a/src/test/java/com/upc/ld_admintool/domain/services/CategoriesServiceTest.java +++ b/src/test/java/com/upc/ld_admintool/domain/services/CategoriesServiceTest.java @@ -1,97 +1,97 @@ -package com.upc.ld_admintool.domain.services; - -import com.upc.ld_admintool.rest.DTO.CategoryDTO; -import com.upc.ld_admintool.rest.DTO.IntervalDTO; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.Arrays; -import java.util.List; - -import static org.mockito.Mockito.*; - -/** - * Tests unitarios para CategoriesService - * Valida la lógica de importación de categorías para métricas, factores e indicadores estratégicos - */ -@ExtendWith(MockitoExtension.class) -@DisplayName("CategoriesService - Tests Unitarios") -class CategoriesServiceTest { - - @Mock - private LDService ldService; - - @InjectMocks - private CategoriesService categoriesService; - - private List testCategories; - private List testIntervals; - - @BeforeEach - void setUp() { - // Preparar datos de prueba - CategoryDTO category1 = new CategoryDTO(); - category1.setCategory("Category 1"); - - CategoryDTO category2 = new CategoryDTO(); - category2.setCategory("Category 2"); - - testCategories = Arrays.asList(category1, category2); - - IntervalDTO interval1 = new IntervalDTO(); - interval1.setName("Interval 1"); - - testIntervals = Arrays.asList(interval1); - } - - @Test - @DisplayName("Debe importar categorías de métricas correctamente") - void testImportarCategoriesMetriques_Success() { - // Act - categoriesService.importarCategoriesMetriques(testCategories); - - // Assert - verify(ldService, times(1)).importarCategoriesMetriques(testCategories); - verifyNoMoreInteractions(ldService); - } - - @Test - @DisplayName("Debe importar categorías de factores correctamente") - void testImportarCategoriesFactors_Success() { - // Act - categoriesService.importarCategoriesFactors(testCategories); - - // Assert - verify(ldService, times(1)).importarCategoriesFactors(testCategories); - verifyNoMoreInteractions(ldService); - } - - @Test - @DisplayName("Debe importar categorías de indicadores estratégicos correctamente") - void testImportarCategoriesStrategicIndicators_Success() { - // Act - categoriesService.importarCategoriesStrategicIndicators(testIntervals); - - // Assert - verify(ldService, times(1)).importarCategoriesStrategicIndicators(testIntervals); - verifyNoMoreInteractions(ldService); - } - - @Test - @DisplayName("Debe manejar lista vacía de categorías de métricas") - void testImportarCategoriesMetriques_EmptyList() { - // Arrange - List emptyList = Arrays.asList(); - - // Act - categoriesService.importarCategoriesMetriques(emptyList); - - // Assert - verify(ldService, times(1)).importarCategoriesMetriques(emptyList); - } -} +package com.upc.ld_admintool.domain.services; + +import com.upc.ld_admintool.rest.DTO.CategoryDTO; +import com.upc.ld_admintool.rest.DTO.IntervalDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.List; + +import static org.mockito.Mockito.*; + +/** + * Tests unitarios para CategoriesService + * Valida la lógica de importación de categorías para métricas, factores e indicadores estratégicos + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("CategoriesService - Tests Unitarios") +class CategoriesServiceTest { + + @Mock + private LDService ldService; + + @InjectMocks + private CategoriesService categoriesService; + + private List testCategories; + private List testIntervals; + + @BeforeEach + void setUp() { + // Preparar datos de prueba + CategoryDTO category1 = new CategoryDTO(); + category1.setCategory("Category 1"); + + CategoryDTO category2 = new CategoryDTO(); + category2.setCategory("Category 2"); + + testCategories = Arrays.asList(category1, category2); + + IntervalDTO interval1 = new IntervalDTO(); + interval1.setName("Interval 1"); + + testIntervals = Arrays.asList(interval1); + } + + @Test + @DisplayName("Debe importar categorías de métricas correctamente") + void testImportarCategoriesMetriques_Success() { + // Act + categoriesService.importarCategoriesMetriques(testCategories); + + // Assert + verify(ldService, times(1)).importarCategoriesMetriques(testCategories); + verifyNoMoreInteractions(ldService); + } + + @Test + @DisplayName("Debe importar categorías de factores correctamente") + void testImportarCategoriesFactors_Success() { + // Act + categoriesService.importarCategoriesFactors(testCategories); + + // Assert + verify(ldService, times(1)).importarCategoriesFactors(testCategories); + verifyNoMoreInteractions(ldService); + } + + @Test + @DisplayName("Debe importar categorías de indicadores estratégicos correctamente") + void testImportarCategoriesStrategicIndicators_Success() { + // Act + categoriesService.importarCategoriesStrategicIndicators(testIntervals); + + // Assert + verify(ldService, times(1)).importarCategoriesStrategicIndicators(testIntervals); + verifyNoMoreInteractions(ldService); + } + + @Test + @DisplayName("Debe manejar lista vacía de categorías de métricas") + void testImportarCategoriesMetriques_EmptyList() { + // Arrange + List emptyList = Arrays.asList(); + + // Act + categoriesService.importarCategoriesMetriques(emptyList); + + // Assert + verify(ldService, times(1)).importarCategoriesMetriques(emptyList); + } +} diff --git a/src/test/java/com/upc/ld_admintool/domain/services/FactorsServiceTest.java b/src/test/java/com/upc/ld_admintool/domain/services/FactorsServiceTest.java index 4194bc0..4db967e 100644 --- a/src/test/java/com/upc/ld_admintool/domain/services/FactorsServiceTest.java +++ b/src/test/java/com/upc/ld_admintool/domain/services/FactorsServiceTest.java @@ -1,185 +1,185 @@ -package com.upc.ld_admintool.domain.services; - -import com.upc.ld_admintool.rest.DTO.FactorDTO; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -/** - * Tests unitarios para FactorsService - * Valida la gestión de factores de calidad y sus categorías - */ -@ExtendWith(MockitoExtension.class) -@DisplayName("FactorsService - Tests Unitarios") -class FactorsServiceTest { - - @Mock - private LDService ldService; - - @InjectMocks - private FactorsService factorsService; - - private List testFactors; - private List testCategoriesList; - private List> testCategoriesMap; - - @BeforeEach - void setUp() { - // Preparar factores de prueba - FactorDTO factor1 = new FactorDTO(); - factor1.setId("1"); - factor1.setName("Test Factor 1"); - - FactorDTO factor2 = new FactorDTO(); - factor2.setId("2"); - factor2.setName("Test Factor 2"); - - testFactors = Arrays.asList(factor1, factor2); - - // Preparar categorías - testCategoriesList = Arrays.asList("Category A", "Category B", "Category C"); - - // Preparar mapa de categorías - Map categoryMap1 = new HashMap<>(); - categoryMap1.put("name", "Category A"); - categoryMap1.put("count", 5); - - Map categoryMap2 = new HashMap<>(); - categoryMap2.put("name", "Category B"); - categoryMap2.put("count", 3); - - testCategoriesMap = Arrays.asList(categoryMap1, categoryMap2); - } - - @Test - @DisplayName("Debe obtener factores por proyecto correctamente") - void testGetFactorsByProject_Success() { - // Arrange - String projectId = "test-project-123"; - when(ldService.getFactorsByProject(projectId)).thenReturn(testFactors); - - // Act - List result = factorsService.getFactorsByProject(projectId); - - // Assert - assertNotNull(result); - assertEquals(2, result.size()); - assertEquals("Test Factor 1", result.get(0).getName()); - assertEquals("Test Factor 2", result.get(1).getName()); - verify(ldService, times(1)).getFactorsByProject(projectId); - } - - @Test - @DisplayName("Debe retornar lista vacía cuando no hay factores") - void testGetFactorsByProject_EmptyList() { - // Arrange - String projectId = "empty-project"; - when(ldService.getFactorsByProject(projectId)).thenReturn(Arrays.asList()); - - // Act - List result = factorsService.getFactorsByProject(projectId); - - // Assert - assertNotNull(result); - assertTrue(result.isEmpty()); - verify(ldService, times(1)).getFactorsByProject(projectId); - } - - @Test - @DisplayName("Debe obtener lista de categorías de factores") - void testGetFactorsCategoriesList_Success() { - // Arrange - when(ldService.getFactorsCategoriesList()).thenReturn(testCategoriesList); - - // Act - List result = factorsService.getFactorsCategoriesList(); - - // Assert - assertNotNull(result); - assertEquals(3, result.size()); - assertTrue(result.contains("Category A")); - assertTrue(result.contains("Category B")); - assertTrue(result.contains("Category C")); - verify(ldService, times(1)).getFactorsCategoriesList(); - } - - @Test - @DisplayName("Debe obtener todas las categorías de factores con detalles") - void testGetAllFactorsCategories_Success() { - // Arrange - when(ldService.getAllFactorsCategories()).thenReturn(testCategoriesMap); - - // Act - List> result = factorsService.getAllFactorsCategories(); - - // Assert - assertNotNull(result); - assertEquals(2, result.size()); - assertEquals("Category A", result.get(0).get("name")); - assertEquals(5, result.get(0).get("count")); - verify(ldService, times(1)).getAllFactorsCategories(); - } - - @Test - @DisplayName("Debe actualizar categoría de factor correctamente") - void testUpdateFactorCategory_Success() { - // Arrange - Long factorId = 1L; - String category = "New Category"; - String project = "project-123"; - doNothing().when(ldService).updateFactorCategory(factorId, category, project); - - // Act - factorsService.updateFactorCategory(factorId, category, project); - - // Assert - verify(ldService, times(1)).updateFactorCategory(factorId, category, project); - verifyNoMoreInteractions(ldService); - } - - @Test - @DisplayName("Debe importar factores de calidad correctamente") - void testImportQualityFactors_Success() { - // Arrange - doNothing().when(ldService).importQualityFactors(); - - // Act - factorsService.importQualityFactors(); - - // Assert - verify(ldService, times(1)).importQualityFactors(); - verifyNoMoreInteractions(ldService); - } - - @Test - @DisplayName("Debe delegar correctamente al LDService") - void testServiceDelegation() { - // Arrange - String projectId = "test-project"; - when(ldService.getFactorsByProject(projectId)).thenReturn(testFactors); - when(ldService.getFactorsCategoriesList()).thenReturn(testCategoriesList); - when(ldService.getAllFactorsCategories()).thenReturn(testCategoriesMap); - - // Act - factorsService.getFactorsByProject(projectId); - factorsService.getFactorsCategoriesList(); - factorsService.getAllFactorsCategories(); - - // Assert - verify(ldService, times(1)).getFactorsByProject(projectId); - verify(ldService, times(1)).getFactorsCategoriesList(); - verify(ldService, times(1)).getAllFactorsCategories(); - } -} +package com.upc.ld_admintool.domain.services; + +import com.upc.ld_admintool.rest.DTO.FactorDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Tests unitarios para FactorsService + * Valida la gestión de factores de calidad y sus categorías + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("FactorsService - Tests Unitarios") +class FactorsServiceTest { + + @Mock + private LDService ldService; + + @InjectMocks + private FactorsService factorsService; + + private List testFactors; + private List testCategoriesList; + private List> testCategoriesMap; + + @BeforeEach + void setUp() { + // Preparar factores de prueba + FactorDTO factor1 = new FactorDTO(); + factor1.setId("1"); + factor1.setName("Test Factor 1"); + + FactorDTO factor2 = new FactorDTO(); + factor2.setId("2"); + factor2.setName("Test Factor 2"); + + testFactors = Arrays.asList(factor1, factor2); + + // Preparar categorías + testCategoriesList = Arrays.asList("Category A", "Category B", "Category C"); + + // Preparar mapa de categorías + Map categoryMap1 = new HashMap<>(); + categoryMap1.put("name", "Category A"); + categoryMap1.put("count", 5); + + Map categoryMap2 = new HashMap<>(); + categoryMap2.put("name", "Category B"); + categoryMap2.put("count", 3); + + testCategoriesMap = Arrays.asList(categoryMap1, categoryMap2); + } + + @Test + @DisplayName("Debe obtener factores por proyecto correctamente") + void testGetFactorsByProject_Success() { + // Arrange + String projectId = "test-project-123"; + when(ldService.getFactorsByProject(projectId)).thenReturn(testFactors); + + // Act + List result = factorsService.getFactorsByProject(projectId); + + // Assert + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals("Test Factor 1", result.get(0).getName()); + assertEquals("Test Factor 2", result.get(1).getName()); + verify(ldService, times(1)).getFactorsByProject(projectId); + } + + @Test + @DisplayName("Debe retornar lista vacía cuando no hay factores") + void testGetFactorsByProject_EmptyList() { + // Arrange + String projectId = "empty-project"; + when(ldService.getFactorsByProject(projectId)).thenReturn(Arrays.asList()); + + // Act + List result = factorsService.getFactorsByProject(projectId); + + // Assert + assertNotNull(result); + assertTrue(result.isEmpty()); + verify(ldService, times(1)).getFactorsByProject(projectId); + } + + @Test + @DisplayName("Debe obtener lista de categorías de factores") + void testGetFactorsCategoriesList_Success() { + // Arrange + when(ldService.getFactorsCategoriesList()).thenReturn(testCategoriesList); + + // Act + List result = factorsService.getFactorsCategoriesList(); + + // Assert + assertNotNull(result); + assertEquals(3, result.size()); + assertTrue(result.contains("Category A")); + assertTrue(result.contains("Category B")); + assertTrue(result.contains("Category C")); + verify(ldService, times(1)).getFactorsCategoriesList(); + } + + @Test + @DisplayName("Debe obtener todas las categorías de factores con detalles") + void testGetAllFactorsCategories_Success() { + // Arrange + when(ldService.getAllFactorsCategories()).thenReturn(testCategoriesMap); + + // Act + List> result = factorsService.getAllFactorsCategories(); + + // Assert + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals("Category A", result.get(0).get("name")); + assertEquals(5, result.get(0).get("count")); + verify(ldService, times(1)).getAllFactorsCategories(); + } + + @Test + @DisplayName("Debe actualizar categoría de factor correctamente") + void testUpdateFactorCategory_Success() { + // Arrange + Long factorId = 1L; + String category = "New Category"; + String project = "project-123"; + doNothing().when(ldService).updateFactorCategory(factorId, category, project); + + // Act + factorsService.updateFactorCategory(factorId, category, project); + + // Assert + verify(ldService, times(1)).updateFactorCategory(factorId, category, project); + verifyNoMoreInteractions(ldService); + } + + @Test + @DisplayName("Debe importar factores de calidad correctamente") + void testImportQualityFactors_Success() { + // Arrange + doNothing().when(ldService).importQualityFactors(); + + // Act + factorsService.importQualityFactors(); + + // Assert + verify(ldService, times(1)).importQualityFactors(); + verifyNoMoreInteractions(ldService); + } + + @Test + @DisplayName("Debe delegar correctamente al LDService") + void testServiceDelegation() { + // Arrange + String projectId = "test-project"; + when(ldService.getFactorsByProject(projectId)).thenReturn(testFactors); + when(ldService.getFactorsCategoriesList()).thenReturn(testCategoriesList); + when(ldService.getAllFactorsCategories()).thenReturn(testCategoriesMap); + + // Act + factorsService.getFactorsByProject(projectId); + factorsService.getFactorsCategoriesList(); + factorsService.getAllFactorsCategories(); + + // Assert + verify(ldService, times(1)).getFactorsByProject(projectId); + verify(ldService, times(1)).getFactorsCategoriesList(); + verify(ldService, times(1)).getAllFactorsCategories(); + } +} diff --git a/src/test/java/com/upc/ld_admintool/domain/services/LDEvalServiceTest.java b/src/test/java/com/upc/ld_admintool/domain/services/LDEvalServiceTest.java index 41dd416..9c24019 100644 --- a/src/test/java/com/upc/ld_admintool/domain/services/LDEvalServiceTest.java +++ b/src/test/java/com/upc/ld_admintool/domain/services/LDEvalServiceTest.java @@ -1,220 +1,220 @@ -package com.upc.ld_admintool.domain.services; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.*; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.RestTemplate; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -/** - * Tests unitarios para LDEvalService - * Valida la comunicación con el servicio de evaluación del Learning Dashboard - */ -@ExtendWith(MockitoExtension.class) -@DisplayName("LDEvalService - Tests Unitarios") -class LDEvalServiceTest { - - @Mock - private RestTemplate restTemplate; - - @InjectMocks - private LDEvalService ldEvalService; - - private static final String LD_EVAL_URL = "http://learning-dashboard:5000/api"; - - @BeforeEach - void setUp() { - ReflectionTestUtils.setField(ldEvalService, "ldEvalUrl", LD_EVAL_URL); - ReflectionTestUtils.setField(ldEvalService, "restTemplate", restTemplate); - } - - @Test - @DisplayName("triggerRefresh debe retornar true cuando la llamada es exitosa") - void testTriggerRefresh_Success() { - // Arrange - ResponseEntity responseEntity = new ResponseEntity<>(HttpStatus.OK); - when(restTemplate.postForEntity( - anyString(), - any(HttpEntity.class), - eq(Void.class) - )).thenReturn(responseEntity); - - // Act - boolean result = ldEvalService.triggerRefresh(); - - // Assert - assertTrue(result); - verify(restTemplate, times(1)).postForEntity( - anyString(), - any(HttpEntity.class), - eq(Void.class) - ); - } - - @Test - @DisplayName("triggerRefresh debe retornar false cuando hay HttpClientErrorException") - void testTriggerRefresh_HttpClientErrorException() { - // Arrange - when(restTemplate.postForEntity( - anyString(), - any(HttpEntity.class), - eq(Void.class) - )).thenThrow(new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Bad Request")); - - // Act - boolean result = ldEvalService.triggerRefresh(); - - // Assert - assertFalse(result); - verify(restTemplate, times(1)).postForEntity( - anyString(), - any(HttpEntity.class), - eq(Void.class) - ); - } - - @Test - @DisplayName("triggerRefresh debe enviar headers correctos") - void testTriggerRefresh_CorrectHeaders() { - // Arrange - ResponseEntity responseEntity = new ResponseEntity<>(HttpStatus.OK); - - when(restTemplate.postForEntity( - anyString(), - argThat(entity -> { - HttpHeaders headers = ((HttpEntity) entity).getHeaders(); - return headers.getContentType() != null && - headers.getContentType().equals(MediaType.APPLICATION_JSON); - }), - eq(Void.class) - )).thenReturn(responseEntity); - - // Act - boolean result = ldEvalService.triggerRefresh(); - - // Assert - assertTrue(result); - } - - @Test - @DisplayName("triggerRefresh debe manejar diferentes códigos de error HTTP") - void testTriggerRefresh_DifferentErrorCodes() { - // Test con 404 Not Found - when(restTemplate.postForEntity( - anyString(), - any(HttpEntity.class), - eq(Void.class) - )).thenThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)); - - assertFalse(ldEvalService.triggerRefresh()); - - // Test con 500 Internal Server Error - when(restTemplate.postForEntity( - anyString(), - any(HttpEntity.class), - eq(Void.class) - )).thenThrow(new HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR)); - - assertFalse(ldEvalService.triggerRefresh()); - - // Test con 401 Unauthorized - when(restTemplate.postForEntity( - anyString(), - any(HttpEntity.class), - eq(Void.class) - )).thenThrow(new HttpClientErrorException(HttpStatus.UNAUTHORIZED)); - - assertFalse(ldEvalService.triggerRefresh()); - } - - @Test - @DisplayName("triggerRefresh debe usar la URL configurada correctamente") - void testTriggerRefresh_UsesConfiguredUrl() { - // Arrange - String customUrl = "http://custom-ld:8080/api"; - ReflectionTestUtils.setField(ldEvalService, "ldEvalUrl", customUrl); - - ResponseEntity responseEntity = new ResponseEntity<>(HttpStatus.OK); - when(restTemplate.postForEntity( - anyString(), - any(HttpEntity.class), - eq(Void.class) - )).thenReturn(responseEntity); - - // Act - boolean result = ldEvalService.triggerRefresh(); - - // Assert - assertTrue(result); - verify(restTemplate).postForEntity( - anyString(), - any(HttpEntity.class), - eq(Void.class) - ); - } - - @Test - @DisplayName("triggerRefresh debe crear HttpEntity con cuerpo vacío") - void testTriggerRefresh_EmptyBody() { - // Arrange - ResponseEntity responseEntity = new ResponseEntity<>(HttpStatus.OK); - - when(restTemplate.postForEntity( - anyString(), - argThat(entity -> ((HttpEntity) entity).getBody() == null), - eq(Void.class) - )).thenReturn(responseEntity); - - // Act - boolean result = ldEvalService.triggerRefresh(); - - // Assert - assertTrue(result); - } - - @Test - @DisplayName("triggerRefresh debe retornar true con status 201 CREATED") - void testTriggerRefresh_Created() { - // Arrange - ResponseEntity responseEntity = new ResponseEntity<>(HttpStatus.CREATED); - when(restTemplate.postForEntity( - anyString(), - any(HttpEntity.class), - eq(Void.class) - )).thenReturn(responseEntity); - - // Act - boolean result = ldEvalService.triggerRefresh(); - - // Assert - assertTrue(result); - } - - @Test - @DisplayName("triggerRefresh debe retornar true con status 202 ACCEPTED") - void testTriggerRefresh_Accepted() { - // Arrange - ResponseEntity responseEntity = new ResponseEntity<>(HttpStatus.ACCEPTED); - when(restTemplate.postForEntity( - anyString(), - any(HttpEntity.class), - eq(Void.class) - )).thenReturn(responseEntity); - - // Act - boolean result = ldEvalService.triggerRefresh(); - - // Assert - assertTrue(result); - } -} +package com.upc.ld_admintool.domain.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.*; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * Tests unitarios para LDEvalService + * Valida la comunicación con el servicio de evaluación del Learning Dashboard + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("LDEvalService - Tests Unitarios") +class LDEvalServiceTest { + + @Mock + private RestTemplate restTemplate; + + @InjectMocks + private LDEvalService ldEvalService; + + private static final String LD_EVAL_URL = "http://learning-dashboard:5000/api"; + + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(ldEvalService, "ldEvalUrl", LD_EVAL_URL); + ReflectionTestUtils.setField(ldEvalService, "restTemplate", restTemplate); + } + + @Test + @DisplayName("triggerRefresh debe retornar true cuando la llamada es exitosa") + void testTriggerRefresh_Success() { + // Arrange + ResponseEntity responseEntity = new ResponseEntity<>(HttpStatus.OK); + when(restTemplate.postForEntity( + anyString(), + any(HttpEntity.class), + eq(Void.class) + )).thenReturn(responseEntity); + + // Act + boolean result = ldEvalService.triggerRefresh(); + + // Assert + assertTrue(result); + verify(restTemplate, times(1)).postForEntity( + anyString(), + any(HttpEntity.class), + eq(Void.class) + ); + } + + @Test + @DisplayName("triggerRefresh debe retornar false cuando hay HttpClientErrorException") + void testTriggerRefresh_HttpClientErrorException() { + // Arrange + when(restTemplate.postForEntity( + anyString(), + any(HttpEntity.class), + eq(Void.class) + )).thenThrow(new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Bad Request")); + + // Act + boolean result = ldEvalService.triggerRefresh(); + + // Assert + assertFalse(result); + verify(restTemplate, times(1)).postForEntity( + anyString(), + any(HttpEntity.class), + eq(Void.class) + ); + } + + @Test + @DisplayName("triggerRefresh debe enviar headers correctos") + void testTriggerRefresh_CorrectHeaders() { + // Arrange + ResponseEntity responseEntity = new ResponseEntity<>(HttpStatus.OK); + + when(restTemplate.postForEntity( + anyString(), + argThat(entity -> { + HttpHeaders headers = ((HttpEntity) entity).getHeaders(); + return headers.getContentType() != null && + headers.getContentType().equals(MediaType.APPLICATION_JSON); + }), + eq(Void.class) + )).thenReturn(responseEntity); + + // Act + boolean result = ldEvalService.triggerRefresh(); + + // Assert + assertTrue(result); + } + + @Test + @DisplayName("triggerRefresh debe manejar diferentes códigos de error HTTP") + void testTriggerRefresh_DifferentErrorCodes() { + // Test con 404 Not Found + when(restTemplate.postForEntity( + anyString(), + any(HttpEntity.class), + eq(Void.class) + )).thenThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)); + + assertFalse(ldEvalService.triggerRefresh()); + + // Test con 500 Internal Server Error + when(restTemplate.postForEntity( + anyString(), + any(HttpEntity.class), + eq(Void.class) + )).thenThrow(new HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR)); + + assertFalse(ldEvalService.triggerRefresh()); + + // Test con 401 Unauthorized + when(restTemplate.postForEntity( + anyString(), + any(HttpEntity.class), + eq(Void.class) + )).thenThrow(new HttpClientErrorException(HttpStatus.UNAUTHORIZED)); + + assertFalse(ldEvalService.triggerRefresh()); + } + + @Test + @DisplayName("triggerRefresh debe usar la URL configurada correctamente") + void testTriggerRefresh_UsesConfiguredUrl() { + // Arrange + String customUrl = "http://custom-ld:8080/api"; + ReflectionTestUtils.setField(ldEvalService, "ldEvalUrl", customUrl); + + ResponseEntity responseEntity = new ResponseEntity<>(HttpStatus.OK); + when(restTemplate.postForEntity( + anyString(), + any(HttpEntity.class), + eq(Void.class) + )).thenReturn(responseEntity); + + // Act + boolean result = ldEvalService.triggerRefresh(); + + // Assert + assertTrue(result); + verify(restTemplate).postForEntity( + anyString(), + any(HttpEntity.class), + eq(Void.class) + ); + } + + @Test + @DisplayName("triggerRefresh debe crear HttpEntity con cuerpo vacío") + void testTriggerRefresh_EmptyBody() { + // Arrange + ResponseEntity responseEntity = new ResponseEntity<>(HttpStatus.OK); + + when(restTemplate.postForEntity( + anyString(), + argThat(entity -> ((HttpEntity) entity).getBody() == null), + eq(Void.class) + )).thenReturn(responseEntity); + + // Act + boolean result = ldEvalService.triggerRefresh(); + + // Assert + assertTrue(result); + } + + @Test + @DisplayName("triggerRefresh debe retornar true con status 201 CREATED") + void testTriggerRefresh_Created() { + // Arrange + ResponseEntity responseEntity = new ResponseEntity<>(HttpStatus.CREATED); + when(restTemplate.postForEntity( + anyString(), + any(HttpEntity.class), + eq(Void.class) + )).thenReturn(responseEntity); + + // Act + boolean result = ldEvalService.triggerRefresh(); + + // Assert + assertTrue(result); + } + + @Test + @DisplayName("triggerRefresh debe retornar true con status 202 ACCEPTED") + void testTriggerRefresh_Accepted() { + // Arrange + ResponseEntity responseEntity = new ResponseEntity<>(HttpStatus.ACCEPTED); + when(restTemplate.postForEntity( + anyString(), + any(HttpEntity.class), + eq(Void.class) + )).thenReturn(responseEntity); + + // Act + boolean result = ldEvalService.triggerRefresh(); + + // Assert + assertTrue(result); + } +} diff --git a/src/test/java/com/upc/ld_admintool/domain/services/LDServiceTest.java b/src/test/java/com/upc/ld_admintool/domain/services/LDServiceTest.java index ff46cd7..79b2b76 100644 --- a/src/test/java/com/upc/ld_admintool/domain/services/LDServiceTest.java +++ b/src/test/java/com/upc/ld_admintool/domain/services/LDServiceTest.java @@ -1,436 +1,436 @@ -package com.upc.ld_admintool.domain.services; - -import com.upc.ld_admintool.rest.DTO.*; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.*; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.RestTemplate; - -import java.util.*; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -/** - * Tests unitarios para LDService - * Valida la comunicación con la API del Learning Dashboard - */ -@ExtendWith(MockitoExtension.class) -@DisplayName("LDService - Tests Unitarios") -class LDServiceTest { - - @Mock - private RestTemplate restTemplate; - - @InjectMocks - private LDService ldService; - - private static final String LD_API_URL = "http://localhost:8888/api"; - - @BeforeEach - void setUp() { - ReflectionTestUtils.setField(ldService, "ldApiUrl", LD_API_URL); - ReflectionTestUtils.setField(ldService, "restTemplate", restTemplate); - } - - @Test - @DisplayName("createProject debe crear proyecto y retornar ID") - void testCreateProject_Success() { - // Arrange - ProjectDTO inputProject = new ProjectDTO(); - inputProject.setName("Test Project"); - - ProjectDTO responseProject = new ProjectDTO(); - responseProject.setId(1L); - responseProject.setName("Test Project"); - - ResponseEntity responseEntity = new ResponseEntity<>(responseProject, HttpStatus.OK); - when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(ProjectDTO.class))) - .thenReturn(responseEntity); - - // Act - Long projectId = ldService.createProject(inputProject); - - // Assert - assertNotNull(projectId); - assertEquals(1L, projectId); - verify(restTemplate, times(1)).postForEntity(anyString(), any(HttpEntity.class), eq(ProjectDTO.class)); - } - - @Test - @DisplayName("createProject debe retornar null en caso de error HTTP") - void testCreateProject_HttpError() { - // Arrange - ProjectDTO project = new ProjectDTO(); - project.setName("Test Project"); - - when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(ProjectDTO.class))) - .thenThrow(new HttpClientErrorException(HttpStatus.BAD_REQUEST)); - - // Act - Long projectId = ldService.createProject(project); - - // Assert - assertNull(projectId); - } - - @Test - @DisplayName("getAllProjects debe retornar lista de proyectos") - void testGetAllProjects_Success() { - // Arrange - ProjectDTO project1 = new ProjectDTO(); - project1.setId(1L); - project1.setName("Project 1"); - - ProjectDTO project2 = new ProjectDTO(); - project2.setId(2L); - project2.setName("Project 2"); - - ProjectDTO[] projects = {project1, project2}; - ResponseEntity responseEntity = new ResponseEntity<>(projects, HttpStatus.OK); - - when(restTemplate.getForEntity(anyString(), eq(ProjectDTO[].class))) - .thenReturn(responseEntity); - - // Act - List result = ldService.getAllProjects(); - - // Assert - assertNotNull(result); - assertEquals(2, result.size()); - assertEquals("Project 1", result.get(0).getName()); - assertEquals("Project 2", result.get(1).getName()); - } - - @Test - @DisplayName("getProjectById debe retornar proyecto por ID") - void testGetProjectById_Success() { - // Arrange - ProjectDTO project = new ProjectDTO(); - project.setId(1L); - project.setName("Test Project"); - - ResponseEntity responseEntity = new ResponseEntity<>(project, HttpStatus.OK); - when(restTemplate.getForEntity(anyString(), eq(ProjectDTO.class))) - .thenReturn(responseEntity); - - // Act - ProjectDTO result = ldService.getProjectById(1L); - - // Assert - assertNotNull(result); - assertEquals(1L, result.getId()); - assertEquals("Test Project", result.getName()); - } - - @Test - @DisplayName("getProjectById debe retornar null si proyecto no existe") - void testGetProjectById_NotFound() { - // Arrange - when(restTemplate.getForEntity(anyString(), eq(ProjectDTO.class))) - .thenThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)); - - // Act - ProjectDTO result = ldService.getProjectById(999L); - - // Assert - assertNull(result); - } - - @Test - @DisplayName("createStudent debe crear estudiante sin errores") - void testCreateStudent_Success() { - // Arrange - StudentDTO student = new StudentDTO(); - student.setName("John Doe"); - - ResponseEntity responseEntity = new ResponseEntity<>(student, HttpStatus.CREATED); - when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(StudentDTO.class))) - .thenReturn(responseEntity); - - // Act & Assert - No debe lanzar excepción - assertDoesNotThrow(() -> ldService.createStudent(1L, student)); - verify(restTemplate, times(1)).postForEntity(anyString(), any(HttpEntity.class), eq(StudentDTO.class)); - } - - @Test - @DisplayName("createStudent debe manejar error HTTP silenciosamente") - void testCreateStudent_HttpError() { - // Arrange - StudentDTO student = new StudentDTO(); - when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(StudentDTO.class))) - .thenThrow(new HttpClientErrorException(HttpStatus.BAD_REQUEST)); - - // Act & Assert - No debe lanzar excepción - assertDoesNotThrow(() -> ldService.createStudent(1L, student)); - } - - @Test - @DisplayName("deleteStudent debe eliminar estudiante") - void testDeleteStudent_Success() { - // Arrange - doNothing().when(restTemplate).delete(anyString()); - - // Act & Assert - assertDoesNotThrow(() -> ldService.deleteStudent(1L)); - verify(restTemplate, times(1)).delete(anyString()); - } - - @Test - @DisplayName("deleteStudent debe manejar error HTTP") - void testDeleteStudent_HttpError() { - // Arrange - doThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)) - .when(restTemplate).delete(anyString()); - - // Act & Assert - No debe lanzar excepción - assertDoesNotThrow(() -> ldService.deleteStudent(1L)); - } - - @Test - @DisplayName("getMetricsByProject debe retornar lista de métricas") - void testGetMetricsByProject_Success() { - // Arrange - Map metric1 = new HashMap<>(); - metric1.put("id", 1); - metric1.put("externalId", "ext-1"); - metric1.put("name", "Metric 1"); - metric1.put("description", "Description 1"); - metric1.put("categoryName", "Category 1"); - metric1.put("scope", "project"); - - List> metricsData = Arrays.asList(metric1); - ResponseEntity responseEntity = new ResponseEntity<>(metricsData, HttpStatus.OK); - - when(restTemplate.getForEntity(anyString(), eq(List.class))) - .thenReturn(responseEntity); - - // Act - List result = ldService.getMetricsByProject("test-project"); - - // Assert - assertNotNull(result); - assertEquals(1, result.size()); - assertEquals("Metric 1", result.get(0).getName()); - } - - @Test - @DisplayName("getMetricsByProject debe retornar lista vacía en caso de error") - void testGetMetricsByProject_HttpError() { - // Arrange - when(restTemplate.getForEntity(anyString(), eq(List.class))) - .thenThrow(new HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR)); - - // Act - List result = ldService.getMetricsByProject("test-project"); - - // Assert - assertNotNull(result); - assertTrue(result.isEmpty()); - } - - @Test - @DisplayName("getAllMetricsCategories debe retornar lista de categorías") - void testGetAllMetricsCategories_Success() { - // Arrange - List> categories = new ArrayList<>(); - Map category = new HashMap<>(); - category.put("name", "Performance"); - categories.add(category); - - ResponseEntity responseEntity = new ResponseEntity<>(categories, HttpStatus.OK); - when(restTemplate.getForEntity(anyString(), eq(List.class))) - .thenReturn(responseEntity); - - // Act - List> result = ldService.getAllMetricsCategories(); - - // Assert - assertNotNull(result); - assertEquals(1, result.size()); - } - - @Test - @DisplayName("getMetricsCategoriesList debe retornar lista de nombres") - void testGetMetricsCategoriesList_Success() { - // Arrange - List categoriesList = Arrays.asList("Category1", "Category2"); - ResponseEntity responseEntity = new ResponseEntity<>(categoriesList, HttpStatus.OK); - - when(restTemplate.getForEntity(anyString(), eq(List.class))) - .thenReturn(responseEntity); - - // Act - List result = ldService.getMetricsCategoriesList(); - - // Assert - assertNotNull(result); - assertEquals(2, result.size()); - } - - @Test - @DisplayName("importarCategoriesMetriques debe importar todas las categorías") - void testImportarCategoriesMetriques_Success() { - // Arrange - CategoryDTO category1 = new CategoryDTO(); - category1.setCategory("Category1"); - - CategoryDTO category2 = new CategoryDTO(); - category2.setCategory("Category2"); - category2.setPatternGroup("group1"); - - List categories = Arrays.asList(category1, category2); - - ResponseEntity responseEntity = new ResponseEntity<>(HttpStatus.OK); - when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(Void.class))) - .thenReturn(responseEntity); - - // Act & Assert - assertDoesNotThrow(() -> ldService.importarCategoriesMetriques(categories)); - verify(restTemplate, times(2)).postForEntity(anyString(), any(HttpEntity.class), eq(Void.class)); - } - - @Test - @DisplayName("importarCategoriesMetriques debe continuar si una categoría falla") - void testImportarCategoriesMetriques_PartialFailure() { - // Arrange - CategoryDTO category1 = new CategoryDTO(); - category1.setCategory("Category1"); - - CategoryDTO category2 = new CategoryDTO(); - category2.setCategory("Category2"); - - List categories = Arrays.asList(category1, category2); - - when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(Void.class))) - .thenThrow(new RuntimeException("Error")) - .thenReturn(new ResponseEntity<>(HttpStatus.OK)); - - // Act & Assert - No debe lanzar excepción - assertDoesNotThrow(() -> ldService.importarCategoriesMetriques(categories)); - } - - @Test - @DisplayName("createStudent debe manejar error HTTP BadRequest") - void testCreateStudent_HttpErrorBadRequest() { - // Arrange - StudentDTO student = new StudentDTO(); - student.setName("Test Student"); - - doThrow(new HttpClientErrorException(HttpStatus.BAD_REQUEST)) - .when(restTemplate).postForEntity(anyString(), any(HttpEntity.class), eq(StudentDTO.class)); - - // Act & Assert - No debe lanzar excepción - assertDoesNotThrow(() -> ldService.createStudent(1L, student)); - } - - @Test - @DisplayName("deleteStudent debe manejar error HTTP NotFound") - void testDeleteStudent_HttpErrorNotFound() { - // Arrange - doThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)) - .when(restTemplate).delete(anyString()); - - // Act & Assert - No debe lanzar excepción - assertDoesNotThrow(() -> ldService.deleteStudent(999L)); - } - - @Test - @DisplayName("updateProject debe actualizar proyecto correctamente") - void testUpdateProject_Success() { - // Arrange - ProjectDTO project = new ProjectDTO(); - project.setName("Updated Project"); - - ResponseEntity response = new ResponseEntity<>(HttpStatus.OK); - when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Void.class))) - .thenReturn(response); - - // Act & Assert - assertDoesNotThrow(() -> ldService.updateProject(1L, project)); - } - - - - @Test - @DisplayName("editMetric debe actualizar métrica") - void testEditMetric_Success() { - // Arrange - ResponseEntity response = new ResponseEntity<>(HttpStatus.OK); - when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Void.class))) - .thenReturn(response); - - // Act & Assert - assertDoesNotThrow(() -> ldService.editMetric(1L, "0.8", "http://url", "Coverage", "PROJECT", "project1")); - } - - @Test - @DisplayName("importarCategoriesFactors debe importar categorías") - void testImportarCategoriesFactors_Success() { - // Arrange - CategoryDTO category = new CategoryDTO(); - category.setCategory("Quality"); - - ResponseEntity response = new ResponseEntity<>(HttpStatus.OK); - when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(Void.class))) - .thenReturn(response); - - // Act & Assert - assertDoesNotThrow(() -> ldService.importarCategoriesFactors(Arrays.asList(category))); - } - - - - @Test - @DisplayName("importarCategoriesStrategicIndicators debe importar intervalos") - void testImportarCategoriesStrategicIndicators_Success() { - // Arrange - IntervalDTO interval = new IntervalDTO(); - interval.setName("Good"); - interval.setColor("#00FF00"); - - ResponseEntity response = new ResponseEntity<>(HttpStatus.OK); - when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(Void.class))) - .thenReturn(response); - - // Act & Assert - assertDoesNotThrow(() -> ldService.importarCategoriesStrategicIndicators(Arrays.asList(interval))); - } - - - - - - - - @Test - @DisplayName("updateFactorCategory debe actualizar categoría") - void testUpdateFactorCategory_Success() { - // Arrange - ResponseEntity response = new ResponseEntity<>(HttpStatus.OK); - when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Void.class))) - .thenReturn(response); - - // Act & Assert - assertDoesNotThrow(() -> ldService.updateFactorCategory(1L, "NewCategory", "project1")); - } - - @Test - @DisplayName("deleteProject debe eliminar proyecto") - void testDeleteProject_Success() { - // Arrange - doNothing().when(restTemplate).delete(anyString()); - - // Act & Assert - assertDoesNotThrow(() -> ldService.deleteProject(1L)); - verify(restTemplate, times(1)).delete(anyString()); - } -} +package com.upc.ld_admintool.domain.services; + +import com.upc.ld_admintool.rest.DTO.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.*; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * Tests unitarios para LDService + * Valida la comunicación con la API del Learning Dashboard + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("LDService - Tests Unitarios") +class LDServiceTest { + + @Mock + private RestTemplate restTemplate; + + @InjectMocks + private LDService ldService; + + private static final String LD_API_URL = "http://localhost:8888/api"; + + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(ldService, "ldApiUrl", LD_API_URL); + ReflectionTestUtils.setField(ldService, "restTemplate", restTemplate); + } + + @Test + @DisplayName("createProject debe crear proyecto y retornar ID") + void testCreateProject_Success() { + // Arrange + ProjectDTO inputProject = new ProjectDTO(); + inputProject.setName("Test Project"); + + ProjectDTO responseProject = new ProjectDTO(); + responseProject.setId(1L); + responseProject.setName("Test Project"); + + ResponseEntity responseEntity = new ResponseEntity<>(responseProject, HttpStatus.OK); + when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(ProjectDTO.class))) + .thenReturn(responseEntity); + + // Act + Long projectId = ldService.createProject(inputProject); + + // Assert + assertNotNull(projectId); + assertEquals(1L, projectId); + verify(restTemplate, times(1)).postForEntity(anyString(), any(HttpEntity.class), eq(ProjectDTO.class)); + } + + @Test + @DisplayName("createProject debe retornar null en caso de error HTTP") + void testCreateProject_HttpError() { + // Arrange + ProjectDTO project = new ProjectDTO(); + project.setName("Test Project"); + + when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(ProjectDTO.class))) + .thenThrow(new HttpClientErrorException(HttpStatus.BAD_REQUEST)); + + // Act + Long projectId = ldService.createProject(project); + + // Assert + assertNull(projectId); + } + + @Test + @DisplayName("getAllProjects debe retornar lista de proyectos") + void testGetAllProjects_Success() { + // Arrange + ProjectDTO project1 = new ProjectDTO(); + project1.setId(1L); + project1.setName("Project 1"); + + ProjectDTO project2 = new ProjectDTO(); + project2.setId(2L); + project2.setName("Project 2"); + + ProjectDTO[] projects = {project1, project2}; + ResponseEntity responseEntity = new ResponseEntity<>(projects, HttpStatus.OK); + + when(restTemplate.getForEntity(anyString(), eq(ProjectDTO[].class))) + .thenReturn(responseEntity); + + // Act + List result = ldService.getAllProjects(); + + // Assert + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals("Project 1", result.get(0).getName()); + assertEquals("Project 2", result.get(1).getName()); + } + + @Test + @DisplayName("getProjectById debe retornar proyecto por ID") + void testGetProjectById_Success() { + // Arrange + ProjectDTO project = new ProjectDTO(); + project.setId(1L); + project.setName("Test Project"); + + ResponseEntity responseEntity = new ResponseEntity<>(project, HttpStatus.OK); + when(restTemplate.getForEntity(anyString(), eq(ProjectDTO.class))) + .thenReturn(responseEntity); + + // Act + ProjectDTO result = ldService.getProjectById(1L); + + // Assert + assertNotNull(result); + assertEquals(1L, result.getId()); + assertEquals("Test Project", result.getName()); + } + + @Test + @DisplayName("getProjectById debe retornar null si proyecto no existe") + void testGetProjectById_NotFound() { + // Arrange + when(restTemplate.getForEntity(anyString(), eq(ProjectDTO.class))) + .thenThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)); + + // Act + ProjectDTO result = ldService.getProjectById(999L); + + // Assert + assertNull(result); + } + + @Test + @DisplayName("createStudent debe crear estudiante sin errores") + void testCreateStudent_Success() { + // Arrange + StudentDTO student = new StudentDTO(); + student.setName("John Doe"); + + ResponseEntity responseEntity = new ResponseEntity<>(student, HttpStatus.CREATED); + when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(StudentDTO.class))) + .thenReturn(responseEntity); + + // Act & Assert - No debe lanzar excepción + assertDoesNotThrow(() -> ldService.createStudent(1L, student)); + verify(restTemplate, times(1)).postForEntity(anyString(), any(HttpEntity.class), eq(StudentDTO.class)); + } + + @Test + @DisplayName("createStudent debe manejar error HTTP silenciosamente") + void testCreateStudent_HttpError() { + // Arrange + StudentDTO student = new StudentDTO(); + when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(StudentDTO.class))) + .thenThrow(new HttpClientErrorException(HttpStatus.BAD_REQUEST)); + + // Act & Assert - No debe lanzar excepción + assertDoesNotThrow(() -> ldService.createStudent(1L, student)); + } + + @Test + @DisplayName("deleteStudent debe eliminar estudiante") + void testDeleteStudent_Success() { + // Arrange + doNothing().when(restTemplate).delete(anyString()); + + // Act & Assert + assertDoesNotThrow(() -> ldService.deleteStudent(1L)); + verify(restTemplate, times(1)).delete(anyString()); + } + + @Test + @DisplayName("deleteStudent debe manejar error HTTP") + void testDeleteStudent_HttpError() { + // Arrange + doThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)) + .when(restTemplate).delete(anyString()); + + // Act & Assert - No debe lanzar excepción + assertDoesNotThrow(() -> ldService.deleteStudent(1L)); + } + + @Test + @DisplayName("getMetricsByProject debe retornar lista de métricas") + void testGetMetricsByProject_Success() { + // Arrange + Map metric1 = new HashMap<>(); + metric1.put("id", 1); + metric1.put("externalId", "ext-1"); + metric1.put("name", "Metric 1"); + metric1.put("description", "Description 1"); + metric1.put("categoryName", "Category 1"); + metric1.put("scope", "project"); + + List> metricsData = Arrays.asList(metric1); + ResponseEntity responseEntity = new ResponseEntity<>(metricsData, HttpStatus.OK); + + when(restTemplate.getForEntity(anyString(), eq(List.class))) + .thenReturn(responseEntity); + + // Act + List result = ldService.getMetricsByProject("test-project"); + + // Assert + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals("Metric 1", result.get(0).getName()); + } + + @Test + @DisplayName("getMetricsByProject debe retornar lista vacía en caso de error") + void testGetMetricsByProject_HttpError() { + // Arrange + when(restTemplate.getForEntity(anyString(), eq(List.class))) + .thenThrow(new HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR)); + + // Act + List result = ldService.getMetricsByProject("test-project"); + + // Assert + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + @DisplayName("getAllMetricsCategories debe retornar lista de categorías") + void testGetAllMetricsCategories_Success() { + // Arrange + List> categories = new ArrayList<>(); + Map category = new HashMap<>(); + category.put("name", "Performance"); + categories.add(category); + + ResponseEntity responseEntity = new ResponseEntity<>(categories, HttpStatus.OK); + when(restTemplate.getForEntity(anyString(), eq(List.class))) + .thenReturn(responseEntity); + + // Act + List> result = ldService.getAllMetricsCategories(); + + // Assert + assertNotNull(result); + assertEquals(1, result.size()); + } + + @Test + @DisplayName("getMetricsCategoriesList debe retornar lista de nombres") + void testGetMetricsCategoriesList_Success() { + // Arrange + List categoriesList = Arrays.asList("Category1", "Category2"); + ResponseEntity responseEntity = new ResponseEntity<>(categoriesList, HttpStatus.OK); + + when(restTemplate.getForEntity(anyString(), eq(List.class))) + .thenReturn(responseEntity); + + // Act + List result = ldService.getMetricsCategoriesList(); + + // Assert + assertNotNull(result); + assertEquals(2, result.size()); + } + + @Test + @DisplayName("importarCategoriesMetriques debe importar todas las categorías") + void testImportarCategoriesMetriques_Success() { + // Arrange + CategoryDTO category1 = new CategoryDTO(); + category1.setCategory("Category1"); + + CategoryDTO category2 = new CategoryDTO(); + category2.setCategory("Category2"); + category2.setPatternGroup("group1"); + + List categories = Arrays.asList(category1, category2); + + ResponseEntity responseEntity = new ResponseEntity<>(HttpStatus.OK); + when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(Void.class))) + .thenReturn(responseEntity); + + // Act & Assert + assertDoesNotThrow(() -> ldService.importarCategoriesMetriques(categories)); + verify(restTemplate, times(2)).postForEntity(anyString(), any(HttpEntity.class), eq(Void.class)); + } + + @Test + @DisplayName("importarCategoriesMetriques debe continuar si una categoría falla") + void testImportarCategoriesMetriques_PartialFailure() { + // Arrange + CategoryDTO category1 = new CategoryDTO(); + category1.setCategory("Category1"); + + CategoryDTO category2 = new CategoryDTO(); + category2.setCategory("Category2"); + + List categories = Arrays.asList(category1, category2); + + when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(Void.class))) + .thenThrow(new RuntimeException("Error")) + .thenReturn(new ResponseEntity<>(HttpStatus.OK)); + + // Act & Assert - No debe lanzar excepción + assertDoesNotThrow(() -> ldService.importarCategoriesMetriques(categories)); + } + + @Test + @DisplayName("createStudent debe manejar error HTTP BadRequest") + void testCreateStudent_HttpErrorBadRequest() { + // Arrange + StudentDTO student = new StudentDTO(); + student.setName("Test Student"); + + doThrow(new HttpClientErrorException(HttpStatus.BAD_REQUEST)) + .when(restTemplate).postForEntity(anyString(), any(HttpEntity.class), eq(StudentDTO.class)); + + // Act & Assert - No debe lanzar excepción + assertDoesNotThrow(() -> ldService.createStudent(1L, student)); + } + + @Test + @DisplayName("deleteStudent debe manejar error HTTP NotFound") + void testDeleteStudent_HttpErrorNotFound() { + // Arrange + doThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)) + .when(restTemplate).delete(anyString()); + + // Act & Assert - No debe lanzar excepción + assertDoesNotThrow(() -> ldService.deleteStudent(999L)); + } + + @Test + @DisplayName("updateProject debe actualizar proyecto correctamente") + void testUpdateProject_Success() { + // Arrange + ProjectDTO project = new ProjectDTO(); + project.setName("Updated Project"); + + ResponseEntity response = new ResponseEntity<>(HttpStatus.OK); + when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Void.class))) + .thenReturn(response); + + // Act & Assert + assertDoesNotThrow(() -> ldService.updateProject(1L, project)); + } + + + + @Test + @DisplayName("editMetric debe actualizar métrica") + void testEditMetric_Success() { + // Arrange + ResponseEntity response = new ResponseEntity<>(HttpStatus.OK); + when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Void.class))) + .thenReturn(response); + + // Act & Assert + assertDoesNotThrow(() -> ldService.editMetric(1L, "0.8", "http://url", "Coverage", "PROJECT", "project1")); + } + + @Test + @DisplayName("importarCategoriesFactors debe importar categorías") + void testImportarCategoriesFactors_Success() { + // Arrange + CategoryDTO category = new CategoryDTO(); + category.setCategory("Quality"); + + ResponseEntity response = new ResponseEntity<>(HttpStatus.OK); + when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(Void.class))) + .thenReturn(response); + + // Act & Assert + assertDoesNotThrow(() -> ldService.importarCategoriesFactors(Arrays.asList(category))); + } + + + + @Test + @DisplayName("importarCategoriesStrategicIndicators debe importar intervalos") + void testImportarCategoriesStrategicIndicators_Success() { + // Arrange + IntervalDTO interval = new IntervalDTO(); + interval.setName("Good"); + interval.setColor("#00FF00"); + + ResponseEntity response = new ResponseEntity<>(HttpStatus.OK); + when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(Void.class))) + .thenReturn(response); + + // Act & Assert + assertDoesNotThrow(() -> ldService.importarCategoriesStrategicIndicators(Arrays.asList(interval))); + } + + + + + + + + @Test + @DisplayName("updateFactorCategory debe actualizar categoría") + void testUpdateFactorCategory_Success() { + // Arrange + ResponseEntity response = new ResponseEntity<>(HttpStatus.OK); + when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Void.class))) + .thenReturn(response); + + // Act & Assert + assertDoesNotThrow(() -> ldService.updateFactorCategory(1L, "NewCategory", "project1")); + } + + @Test + @DisplayName("deleteProject debe eliminar proyecto") + void testDeleteProject_Success() { + // Arrange + doNothing().when(restTemplate).delete(anyString()); + + // Act & Assert + assertDoesNotThrow(() -> ldService.deleteProject(1L)); + verify(restTemplate, times(1)).delete(anyString()); + } +} diff --git a/src/test/java/com/upc/ld_admintool/domain/services/MetricsServiceTest.java b/src/test/java/com/upc/ld_admintool/domain/services/MetricsServiceTest.java index 357d285..9b21044 100644 --- a/src/test/java/com/upc/ld_admintool/domain/services/MetricsServiceTest.java +++ b/src/test/java/com/upc/ld_admintool/domain/services/MetricsServiceTest.java @@ -1,203 +1,203 @@ -package com.upc.ld_admintool.domain.services; - -import com.upc.ld_admintool.rest.DTO.MetricDTO; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -/** - * Tests unitarios para MetricsService - * Valida la gestión de métricas y sus categorías - */ -@ExtendWith(MockitoExtension.class) -@DisplayName("MetricsService - Tests Unitarios") -class MetricsServiceTest { - - @Mock - private LDService ldService; - - @InjectMocks - private MetricsService metricsService; - - private List testMetrics; - private List testCategoriesList; - private List> testCategoriesMap; - - @BeforeEach - void setUp() { - // Preparar métricas de prueba - MetricDTO metric1 = new MetricDTO(); - metric1.setId("1"); - metric1.setName("Test Metric 1"); - - MetricDTO metric2 = new MetricDTO(); - metric2.setId("2"); - metric2.setName("Test Metric 2"); - - testMetrics = Arrays.asList(metric1, metric2); - - // Preparar categorías - testCategoriesList = Arrays.asList("Performance", "Security", "Maintainability"); - - // Preparar mapa de categorías - Map categoryMap1 = new HashMap<>(); - categoryMap1.put("name", "Performance"); - categoryMap1.put("metrics", 10); - - Map categoryMap2 = new HashMap<>(); - categoryMap2.put("name", "Security"); - categoryMap2.put("metrics", 7); - - testCategoriesMap = Arrays.asList(categoryMap1, categoryMap2); - } - - @Test - @DisplayName("Debe obtener métricas por proyecto correctamente") - void testGetMetricsByProject_Success() { - // Arrange - String projectId = "test-project-123"; - when(ldService.getMetricsByProject(projectId)).thenReturn(testMetrics); - - // Act - List result = metricsService.getMetricsByProject(projectId); - - // Assert - assertNotNull(result); - assertEquals(2, result.size()); - assertEquals("Test Metric 1", result.get(0).getName()); - assertEquals("Test Metric 2", result.get(1).getName()); - verify(ldService, times(1)).getMetricsByProject(projectId); - } - - @Test - @DisplayName("Debe retornar lista vacía cuando no hay métricas") - void testGetMetricsByProject_EmptyList() { - // Arrange - String projectId = "empty-project"; - when(ldService.getMetricsByProject(projectId)).thenReturn(Arrays.asList()); - - // Act - List result = metricsService.getMetricsByProject(projectId); - - // Assert - assertNotNull(result); - assertTrue(result.isEmpty()); - verify(ldService, times(1)).getMetricsByProject(projectId); - } - - @Test - @DisplayName("Debe obtener lista de categorías de métricas") - void testGetMetricsCategoriesList_Success() { - // Arrange - when(ldService.getMetricsCategoriesList()).thenReturn(testCategoriesList); - - // Act - List result = metricsService.getMetricsCategoriesList(); - - // Assert - assertNotNull(result); - assertEquals(3, result.size()); - assertTrue(result.contains("Performance")); - assertTrue(result.contains("Security")); - assertTrue(result.contains("Maintainability")); - verify(ldService, times(1)).getMetricsCategoriesList(); - } - - @Test - @DisplayName("Debe obtener todas las categorías de métricas con detalles") - void testGetAllMetricsCategories_Success() { - // Arrange - when(ldService.getAllMetricsCategories()).thenReturn(testCategoriesMap); - - // Act - List> result = metricsService.getAllMetricsCategories(); - - // Assert - assertNotNull(result); - assertEquals(2, result.size()); - assertEquals("Performance", result.get(0).get("name")); - assertEquals(10, result.get(0).get("metrics")); - verify(ldService, times(1)).getAllMetricsCategories(); - } - - @Test - @DisplayName("Debe editar métrica correctamente con todos los parámetros") - void testEditMetric_Success() { - // Arrange - Long metricId = 1L; - String threshold = "0.7"; - String url = "http://example.com/metric"; - String categoryName = "Performance"; - String scope = "global"; - String project = "project-123"; - - doNothing().when(ldService).editMetric(metricId, threshold, url, categoryName, scope, project); - - // Act - metricsService.editMetric(metricId, threshold, url, categoryName, scope, project); - - // Assert - verify(ldService, times(1)).editMetric(metricId, threshold, url, categoryName, scope, project); - verifyNoMoreInteractions(ldService); - } - - @Test - @DisplayName("Debe importar métricas correctamente") - void testImportMetrics_Success() { - // Arrange - doNothing().when(ldService).importMetrics(); - - // Act - metricsService.importMetrics(); - - // Assert - verify(ldService, times(1)).importMetrics(); - verifyNoMoreInteractions(ldService); - } - - @Test - @DisplayName("Debe manejar valores null en editMetric") - void testEditMetric_WithNullValues() { - // Arrange - Long metricId = 1L; - doNothing().when(ldService).editMetric(metricId, null, null, null, null, null); - - // Act - metricsService.editMetric(metricId, null, null, null, null, null); - - // Assert - verify(ldService, times(1)).editMetric(metricId, null, null, null, null, null); - } - - @Test - @DisplayName("Debe delegar correctamente al LDService") - void testServiceDelegation() { - // Arrange - String projectId = "test-project"; - when(ldService.getMetricsByProject(projectId)).thenReturn(testMetrics); - when(ldService.getMetricsCategoriesList()).thenReturn(testCategoriesList); - when(ldService.getAllMetricsCategories()).thenReturn(testCategoriesMap); - - // Act - metricsService.getMetricsByProject(projectId); - metricsService.getMetricsCategoriesList(); - metricsService.getAllMetricsCategories(); - - // Assert - verify(ldService, times(1)).getMetricsByProject(projectId); - verify(ldService, times(1)).getMetricsCategoriesList(); - verify(ldService, times(1)).getAllMetricsCategories(); - } -} +package com.upc.ld_admintool.domain.services; + +import com.upc.ld_admintool.rest.DTO.MetricDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Tests unitarios para MetricsService + * Valida la gestión de métricas y sus categorías + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("MetricsService - Tests Unitarios") +class MetricsServiceTest { + + @Mock + private LDService ldService; + + @InjectMocks + private MetricsService metricsService; + + private List testMetrics; + private List testCategoriesList; + private List> testCategoriesMap; + + @BeforeEach + void setUp() { + // Preparar métricas de prueba + MetricDTO metric1 = new MetricDTO(); + metric1.setId("1"); + metric1.setName("Test Metric 1"); + + MetricDTO metric2 = new MetricDTO(); + metric2.setId("2"); + metric2.setName("Test Metric 2"); + + testMetrics = Arrays.asList(metric1, metric2); + + // Preparar categorías + testCategoriesList = Arrays.asList("Performance", "Security", "Maintainability"); + + // Preparar mapa de categorías + Map categoryMap1 = new HashMap<>(); + categoryMap1.put("name", "Performance"); + categoryMap1.put("metrics", 10); + + Map categoryMap2 = new HashMap<>(); + categoryMap2.put("name", "Security"); + categoryMap2.put("metrics", 7); + + testCategoriesMap = Arrays.asList(categoryMap1, categoryMap2); + } + + @Test + @DisplayName("Debe obtener métricas por proyecto correctamente") + void testGetMetricsByProject_Success() { + // Arrange + String projectId = "test-project-123"; + when(ldService.getMetricsByProject(projectId)).thenReturn(testMetrics); + + // Act + List result = metricsService.getMetricsByProject(projectId); + + // Assert + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals("Test Metric 1", result.get(0).getName()); + assertEquals("Test Metric 2", result.get(1).getName()); + verify(ldService, times(1)).getMetricsByProject(projectId); + } + + @Test + @DisplayName("Debe retornar lista vacía cuando no hay métricas") + void testGetMetricsByProject_EmptyList() { + // Arrange + String projectId = "empty-project"; + when(ldService.getMetricsByProject(projectId)).thenReturn(Arrays.asList()); + + // Act + List result = metricsService.getMetricsByProject(projectId); + + // Assert + assertNotNull(result); + assertTrue(result.isEmpty()); + verify(ldService, times(1)).getMetricsByProject(projectId); + } + + @Test + @DisplayName("Debe obtener lista de categorías de métricas") + void testGetMetricsCategoriesList_Success() { + // Arrange + when(ldService.getMetricsCategoriesList()).thenReturn(testCategoriesList); + + // Act + List result = metricsService.getMetricsCategoriesList(); + + // Assert + assertNotNull(result); + assertEquals(3, result.size()); + assertTrue(result.contains("Performance")); + assertTrue(result.contains("Security")); + assertTrue(result.contains("Maintainability")); + verify(ldService, times(1)).getMetricsCategoriesList(); + } + + @Test + @DisplayName("Debe obtener todas las categorías de métricas con detalles") + void testGetAllMetricsCategories_Success() { + // Arrange + when(ldService.getAllMetricsCategories()).thenReturn(testCategoriesMap); + + // Act + List> result = metricsService.getAllMetricsCategories(); + + // Assert + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals("Performance", result.get(0).get("name")); + assertEquals(10, result.get(0).get("metrics")); + verify(ldService, times(1)).getAllMetricsCategories(); + } + + @Test + @DisplayName("Debe editar métrica correctamente con todos los parámetros") + void testEditMetric_Success() { + // Arrange + Long metricId = 1L; + String threshold = "0.7"; + String url = "http://example.com/metric"; + String categoryName = "Performance"; + String scope = "global"; + String project = "project-123"; + + doNothing().when(ldService).editMetric(metricId, threshold, url, categoryName, scope, project); + + // Act + metricsService.editMetric(metricId, threshold, url, categoryName, scope, project); + + // Assert + verify(ldService, times(1)).editMetric(metricId, threshold, url, categoryName, scope, project); + verifyNoMoreInteractions(ldService); + } + + @Test + @DisplayName("Debe importar métricas correctamente") + void testImportMetrics_Success() { + // Arrange + doNothing().when(ldService).importMetrics(); + + // Act + metricsService.importMetrics(); + + // Assert + verify(ldService, times(1)).importMetrics(); + verifyNoMoreInteractions(ldService); + } + + @Test + @DisplayName("Debe manejar valores null en editMetric") + void testEditMetric_WithNullValues() { + // Arrange + Long metricId = 1L; + doNothing().when(ldService).editMetric(metricId, null, null, null, null, null); + + // Act + metricsService.editMetric(metricId, null, null, null, null, null); + + // Assert + verify(ldService, times(1)).editMetric(metricId, null, null, null, null, null); + } + + @Test + @DisplayName("Debe delegar correctamente al LDService") + void testServiceDelegation() { + // Arrange + String projectId = "test-project"; + when(ldService.getMetricsByProject(projectId)).thenReturn(testMetrics); + when(ldService.getMetricsCategoriesList()).thenReturn(testCategoriesList); + when(ldService.getAllMetricsCategories()).thenReturn(testCategoriesMap); + + // Act + metricsService.getMetricsByProject(projectId); + metricsService.getMetricsCategoriesList(); + metricsService.getAllMetricsCategories(); + + // Assert + verify(ldService, times(1)).getMetricsByProject(projectId); + verify(ldService, times(1)).getMetricsCategoriesList(); + verify(ldService, times(1)).getAllMetricsCategories(); + } +} diff --git a/src/test/java/com/upc/ld_admintool/domain/services/ProjectServiceTest.java b/src/test/java/com/upc/ld_admintool/domain/services/ProjectServiceTest.java index 7368740..fe6e9d3 100644 --- a/src/test/java/com/upc/ld_admintool/domain/services/ProjectServiceTest.java +++ b/src/test/java/com/upc/ld_admintool/domain/services/ProjectServiceTest.java @@ -1,698 +1,698 @@ -package com.upc.ld_admintool.domain.services; - -import com.upc.ld_admintool.domain.services.exceptions.SaveSyncException; -import com.upc.ld_admintool.rest.DTO.*; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.*; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -/** - * Tests unitarios para ProjectService - * Valida la lógica de gestión de proyectos y estudiantes - */ -@ExtendWith(MockitoExtension.class) -@DisplayName("ProjectService - Tests Unitarios") -class ProjectServiceTest { - - @Mock - private LDService ldService; - - @Mock - private LDEvalService ldEvalService; - - @InjectMocks - private ProjectService projectService; - - private ProjectDTO testProject; - private StudentDTO testStudent1; - private StudentDTO testStudent2; - private List> metricCategories; - private List> factorCategories; - - @BeforeEach - void setUp() { - // Preparar proyecto de prueba - testProject = new ProjectDTO(); - testProject.setId(1L); - testProject.setName("Test Project"); - testProject.setDescription("Test Description"); - testProject.setExternalId("test-external-id"); - - // Preparar estudiantes de prueba - testStudent1 = new StudentDTO(); - testStudent1.setId(1L); - testStudent1.setName("Student One"); - - testStudent2 = new StudentDTO(); - testStudent2.setId(2L); - testStudent2.setName("Student Two"); - - testProject.setStudents(Arrays.asList(testStudent1, testStudent2)); - - // Preparar categorías de métricas - metricCategories = new ArrayList<>(); - Map metricCat1 = new HashMap<>(); - metricCat1.put("name", "2 members - Quality"); - metricCat1.put("patternGroup", "quality"); - metricCategories.add(metricCat1); - - Map metricCat2 = new HashMap<>(); - metricCat2.put("name", "3 members - Quality"); - metricCat2.put("patternGroup", "quality"); - metricCategories.add(metricCat2); - - // Preparar categorías de factores - factorCategories = new ArrayList<>(); - Map factorCat1 = new HashMap<>(); - factorCat1.put("name", "2 members - Performance"); - factorCat1.put("patternGroup", "performance"); - factorCategories.add(factorCat1); - - Map factorCat2 = new HashMap<>(); - factorCat2.put("name", "3 members - Performance"); - factorCat2.put("patternGroup", "performance"); - factorCategories.add(factorCat2); - } - - @Test - @DisplayName("Debe listar proyectos con estudiantes correctamente") - void testLlistarProjectesAmbStudents_Success() { - // Arrange - List rawProjects = Arrays.asList(testProject); - when(ldService.getAllProjects()).thenReturn(rawProjects); - when(ldService.getProjectById(1L)).thenReturn(testProject); - - // Act - List result = projectService.llistarProjectesAmbStudents(); - - // Assert - assertNotNull(result); - assertEquals(1, result.size()); - assertEquals("Test Project", result.get(0).getName()); - verify(ldService, times(1)).getAllProjects(); - verify(ldService, times(1)).getProjectById(1L); - } - - @Test - @DisplayName("Debe obtener proyecto por ID correctamente") - void testGetProjectById_Success() { - // Arrange - when(ldService.getProjectById(1L)).thenReturn(testProject); - - // Act - ProjectDTO result = projectService.getProjectById(1L); - - // Assert - assertNotNull(result); - assertEquals(1L, result.getId()); - assertEquals("Test Project", result.getName()); - verify(ldService, times(1)).getProjectById(1L); - } - - @Test - @DisplayName("Debe importar proyectos con estudiantes y triggerar refresh") - void testImportProjects_WithStudents_Success() { - // Arrange - List projects = Arrays.asList(testProject); - when(ldService.createProject(any(ProjectDTO.class))).thenReturn(1L); - doNothing().when(ldService).createStudent(anyLong(), any(StudentDTO.class)); - when(ldEvalService.triggerRefresh()).thenReturn(true); - - // Act - projectService.importProjects(projects); - - // Assert - verify(ldService, times(1)).createProject(testProject); - verify(ldService, times(2)).createStudent(anyLong(), any(StudentDTO.class)); - verify(ldEvalService, times(1)).triggerRefresh(); - } - - @Test - @DisplayName("Debe manejar proyectos sin estudiantes") - void testImportProjects_WithoutStudents_Success() { - // Arrange - ProjectDTO projectWithoutStudents = new ProjectDTO(); - projectWithoutStudents.setName("Project Without Students"); - projectWithoutStudents.setStudents(null); - - List projects = Arrays.asList(projectWithoutStudents); - when(ldService.createProject(any(ProjectDTO.class))).thenReturn(1L); - when(ldEvalService.triggerRefresh()).thenReturn(true); - - // Act - projectService.importProjects(projects); - - // Assert - verify(ldService, times(1)).createProject(projectWithoutStudents); - verify(ldService, never()).createStudent(anyLong(), any(StudentDTO.class)); - verify(ldEvalService, times(1)).triggerRefresh(); - } - - @Test - @DisplayName("No debe triggerar refresh si no se crea ningún proyecto") - void testImportProjects_NoProjectsCreated_NoRefresh() { - // Arrange - List projects = Arrays.asList(testProject); - when(ldService.createProject(any(ProjectDTO.class))).thenReturn(null); - - // Act - projectService.importProjects(projects); - - // Assert - verify(ldService, times(1)).createProject(testProject); - verify(ldEvalService, never()).triggerRefresh(); - } - - @Test - @DisplayName("Debe retornar null cuando el proyecto no existe") - void testGetProjectById_NotFound() { - // Arrange - when(ldService.getProjectById(999L)).thenReturn(null); - - // Act - ProjectDTO result = projectService.getProjectById(999L); - - // Assert - assertNull(result); - verify(ldService, times(1)).getProjectById(999L); - } - - @Test - @DisplayName("Debe manejar lista vacía de proyectos") - void testLlistarProjectesAmbStudents_EmptyList() { - // Arrange - when(ldService.getAllProjects()).thenReturn(Arrays.asList()); - - // Act - List result = projectService.llistarProjectesAmbStudents(); - - // Assert - assertNotNull(result); - assertTrue(result.isEmpty()); - verify(ldService, times(1)).getAllProjects(); - verify(ldService, never()).getProjectById(anyLong()); - } - - @Test - @DisplayName("Debe manejar cuando getProjectById retorna null durante listado") - void testLlistarProjectesAmbStudents_NullProject() { - // Arrange - List rawProjects = Arrays.asList(testProject); - when(ldService.getAllProjects()).thenReturn(rawProjects); - when(ldService.getProjectById(1L)).thenReturn(null); - - // Act - List result = projectService.llistarProjectesAmbStudents(); - - // Assert - assertNotNull(result); - assertEquals(1, result.size()); - assertEquals(testProject, result.get(0)); // Should use original project - } - - @Test - @DisplayName("Debe manejar múltiples proyectos en importación") - void testImportProjects_MultipleProjects() { - // Arrange - ProjectDTO project2 = new ProjectDTO(); - project2.setName("Project 2"); - project2.setStudents(null); - - List projects = Arrays.asList(testProject, project2); - when(ldService.createProject(any(ProjectDTO.class))) - .thenReturn(1L) - .thenReturn(2L); - when(ldEvalService.triggerRefresh()).thenReturn(true); - - // Act - projectService.importProjects(projects); - - // Assert - verify(ldService, times(2)).createProject(any(ProjectDTO.class)); - verify(ldEvalService, times(1)).triggerRefresh(); - } - - @Test - @DisplayName("Debe manejar lista vacía en importación") - void testImportProjects_EmptyList() { - // Arrange - List emptyList = Arrays.asList(); - - // Act - projectService.importProjects(emptyList); - - // Assert - verify(ldService, never()).createProject(any(ProjectDTO.class)); - verify(ldEvalService, never()).triggerRefresh(); - } - - @Test - @DisplayName("Debe deletear proyecto correctamente") - void testEsborrarProjecte_Success() { - // Arrange - doNothing().when(ldService).deleteProject(1L); - when(ldEvalService.triggerRefresh()).thenReturn(true); - - // Act - projectService.esborrarProjecte(1L); - - // Assert - verify(ldService, times(1)).deleteProject(1L); - verify(ldEvalService, times(1)).triggerRefresh(); - } - - @Test - @DisplayName("Debe manejar error al obtener proyecto por ID") - void testGetProjectById_Exception() { - // Arrange - when(ldService.getProjectById(1L)).thenThrow(new RuntimeException("Database error")); - - // Act & Assert - assertThrows(RuntimeException.class, () -> { - projectService.getProjectById(1L); - }); - } - - @Test - @DisplayName("Debe continuar importando proyectos si uno falla") - void testImportProjects_PartialFailure() { - // Arrange - ProjectDTO project2 = new ProjectDTO(); - project2.setName("Project 2"); - - List projects = Arrays.asList(testProject, project2); - when(ldService.createProject(testProject)).thenReturn(null); // Falla el primero - when(ldService.createProject(project2)).thenReturn(2L); // Éxito el segundo - when(ldEvalService.triggerRefresh()).thenReturn(true); - - // Act - projectService.importProjects(projects); - - // Assert - verify(ldService, times(2)).createProject(any(ProjectDTO.class)); - verify(ldEvalService, times(1)).triggerRefresh(); // Se llama porque al menos uno tuvo éxito - } - - @Test - @DisplayName("modificarProjecte debe lanzar excepción si proyecto no existe") - void testModificarProjecte_ProjectNotFound() { - // Arrange - when(ldService.getProjectById(999L)).thenReturn(null); - ProjectDTO updatedProject = new ProjectDTO(); - - // Act & Assert - assertThrows(SaveSyncException.class, () -> { - projectService.modificarProjecte(999L, updatedProject); - }); - } - - @Test - @DisplayName("modificarProjecte debe lanzar excepción cuando categorías no están disponibles") - void testModificarProjecte_CategoryValidationFails() { - // Arrange - ProjectDTO original = new ProjectDTO(); - original.setId(1L); - original.setExternalId("test-ext"); - original.setStudents(Arrays.asList()); - - ProjectDTO updated = new ProjectDTO(); - updated.setExternalId("test-ext"); - updated.setStudents(Arrays.asList(testStudent1)); - - when(ldService.getProjectById(1L)).thenReturn(original); - when(ldService.getAllMetricsCategories()).thenReturn(new ArrayList<>()); - when(ldService.getAllFactorsCategories()).thenReturn(new ArrayList<>()); - - // Act & Assert - assertThrows(SaveSyncException.class, () -> { - projectService.modificarProjecte(1L, updated); - }); - } - - @Test - @DisplayName("modificarProjecte debe eliminar estudiantes") - void testModificarProjecte_RemoveStudents() { - // Arrange - StudentDTO existingStudent = new StudentDTO(); - existingStudent.setId(10L); - existingStudent.setName("Existing"); - - ProjectDTO original = new ProjectDTO(); - original.setId(1L); - original.setExternalId("test-ext"); - original.setStudents(Arrays.asList(existingStudent)); - - ProjectDTO updated = new ProjectDTO(); - updated.setExternalId("test-ext"); - updated.setStudents(Arrays.asList()); - - when(ldService.getProjectById(1L)).thenReturn(original); - when(ldService.getAllMetricsCategories()).thenReturn(new ArrayList<>()); - when(ldService.getAllFactorsCategories()).thenReturn(new ArrayList<>()); - - // Act & Assert - debería fallar por falta de categorías - assertThrows(SaveSyncException.class, () -> { - projectService.modificarProjecte(1L, updated); - }); - } - - @Test - @DisplayName("validateCategoriesForNewTeamSize debe lanzar excepción si categoría no existe") - void testValidateCategoriesForNewTeamSize_CategoryNotFound() { - // Arrange - MetricDTO metric = new MetricDTO(); - metric.setId("1"); - metric.setCategoryName("2 members - Quality"); - - Map category = new HashMap<>(); - category.put("name", "2 members - Quality"); - category.put("patternGroup", "quality-pattern"); - - List> categories = Arrays.asList(category); - - lenient().when(ldService.getProjectById(1L)).thenReturn(testProject); - lenient().when(ldService.getAllMetricsCategories()).thenReturn(categories); - lenient().when(ldService.getAllFactorsCategories()).thenReturn(new ArrayList<>()); - lenient().when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList(metric)); - lenient().when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList()); - - // Act & Assert - Pedir 5 miembros cuando solo hay categorías para 2 - assertThrows(RuntimeException.class, () -> { - projectService.validateCategoriesForNewTeamSize(1L, 5); - }); - } - - @Test - @DisplayName("validateCategoriesForNewTeamSize debe pasar si categoría existe") - void testValidateCategoriesForNewTeamSize_Success() { - // Arrange - MetricDTO metric = new MetricDTO(); - metric.setId("1"); - metric.setCategoryName("2 members - Quality"); - - when(ldService.getProjectById(1L)).thenReturn(testProject); - when(ldService.getAllMetricsCategories()).thenReturn(metricCategories); - when(ldService.getAllFactorsCategories()).thenReturn(factorCategories); - when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList(metric)); - when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList()); - - // Act & Assert - Pedir 3 miembros (existe la categoría) - assertDoesNotThrow(() -> { - projectService.validateCategoriesForNewTeamSize(1L, 3); - }); - } - - @Test - @DisplayName("updateCategoriesForProject debe actualizar métricas y factores") - void testUpdateCategoriesForProject_Success() { - // Arrange - MetricDTO metric = new MetricDTO(); - metric.setId("1"); - metric.setExternalId("metric_test"); - metric.setCategoryName("2 members - Quality"); - metric.setScope("project"); - - FactorDTO factor = new FactorDTO(); - factor.setId("1"); - factor.setExternalId("factor_test"); - factor.setCategory("2 members - Performance"); - - when(ldService.getProjectById(1L)).thenReturn(testProject); - when(ldService.getAllMetricsCategories()).thenReturn(metricCategories); - when(ldService.getAllFactorsCategories()).thenReturn(factorCategories); - when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList(metric)); - when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList(factor)); - - // Act - projectService.updateCategoriesForProject(1L); - - // Assert - verify(ldService).getMetricsByProject(testProject.getExternalId()); - verify(ldService).getFactorsByProject(testProject.getExternalId()); - } - - @Test - @DisplayName("synchronizeCategoriesAfterDataImport debe sincronizar categorías entre proyectos") - void testSynchronizeCategoriesAfterDataImport_Success() { - // Arrange - ProjectDTO project1 = new ProjectDTO(); - project1.setId(1L); - project1.setExternalId("proj1"); - project1.setSubject("Math"); - - ProjectDTO project2 = new ProjectDTO(); - project2.setId(2L); - project2.setExternalId("proj2"); - project2.setSubject("Math"); - - when(ldService.getAllProjects()).thenReturn(Arrays.asList(project1, project2)); - when(ldService.getProjectById(1L)).thenReturn(project1); - when(ldService.getProjectById(2L)).thenReturn(project2); - when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList()); - when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList()); - when(ldEvalService.triggerRefresh()).thenReturn(true); - - // Act - projectService.synchronizeCategoriesAfterDataImport(); - - // Assert - verify(ldEvalService).triggerRefresh(); - } - - @Test - @DisplayName("synchronizeCategoriesAfterDataImport debe manejar excepciones") - void testSynchronizeCategoriesAfterDataImport_Exception() { - // Arrange - when(ldService.getAllProjects()).thenThrow(new RuntimeException("Database error")); - - // Act & Assert - No debe lanzar excepción, solo logear - assertDoesNotThrow(() -> { - projectService.synchronizeCategoriesAfterDataImport(); - }); - } - - @Test - @DisplayName("esborrarProjecte debe deletear proyecto y triggerar refresh en LDEval") - void testEsborrarProjecte_SuccessWithRefresh() { - // Arrange - doNothing().when(ldService).deleteProject(1L); - when(ldEvalService.triggerRefresh()).thenReturn(true); - - // Act - projectService.esborrarProjecte(1L); - - // Assert - verify(ldService, times(1)).deleteProject(1L); - verify(ldEvalService, times(1)).triggerRefresh(); - } - - @Test - @DisplayName("updateCategoriesForProject debe lanzar excepción si proyecto no existe") - void testUpdateCategoriesForProject_ProjectNotFound() { - // Arrange - when(ldService.getProjectById(999L)).thenReturn(null); - - // Act & Assert - assertThrows(RuntimeException.class, () -> { - projectService.updateCategoriesForProject(999L); - }); - } - - @Test - @DisplayName("updateCategoriesForProject con override debe usar valores proporcionados") - void testUpdateCategoriesForProject_WithOverride() { - // Arrange - ProjectDTO overrideProject = new ProjectDTO(); - overrideProject.setExternalId("override-ext"); - overrideProject.setStudents(Arrays.asList(testStudent1, testStudent2, new StudentDTO())); - - when(ldService.getAllMetricsCategories()).thenReturn(metricCategories); - when(ldService.getAllFactorsCategories()).thenReturn(factorCategories); - when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList()); - when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList()); - - // Act - projectService.updateCategoriesForProject(1L, overrideProject, 3); - - // Assert - verify(ldService).getMetricsByProject("override-ext"); - verify(ldService, never()).getProjectById(1L); // No debe buscar porque se proporciona override - } - - @Test - @DisplayName("validateCategoriesForNewTeamSize debe retornar si proyecto no existe") - void testValidateCategoriesForNewTeamSize_ProjectNotFound() { - // Arrange - when(ldService.getProjectById(999L)).thenReturn(null); - - // Act & Assert - No debe lanzar excepción - assertDoesNotThrow(() -> { - projectService.validateCategoriesForNewTeamSize(999L, 2); - }); - } - - @Test - @DisplayName("validateCategoriesForNewTeamSize debe validar factores correctamente") - void testValidateCategoriesForNewTeamSize_FactorsValidation() { - // Arrange - FactorDTO factor = new FactorDTO(); - factor.setId("1"); - factor.setCategory("2 members - Performance"); - - when(ldService.getProjectById(1L)).thenReturn(testProject); - when(ldService.getAllMetricsCategories()).thenReturn(metricCategories); - when(ldService.getAllFactorsCategories()).thenReturn(factorCategories); - when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList()); - when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList(factor)); - - // Act & Assert - assertDoesNotThrow(() -> { - projectService.validateCategoriesForNewTeamSize(1L, 3); - }); - } - - @Test - @DisplayName("validateCategoriesForNewTeamSize debe lanzar excepción para factores sin categoría") - void testValidateCategoriesForNewTeamSize_FactorCategoryNotFound() { - // Arrange - FactorDTO factor = new FactorDTO(); - factor.setId("1"); - factor.setCategory("2 members - Performance"); - - when(ldService.getProjectById(1L)).thenReturn(testProject); - when(ldService.getAllMetricsCategories()).thenReturn(metricCategories); - when(ldService.getAllFactorsCategories()).thenReturn(factorCategories); - when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList()); - when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList(factor)); - - // Act & Assert - Pedir 5 miembros cuando solo hay categorías para 2 y 3 - assertThrows(RuntimeException.class, () -> { - projectService.validateCategoriesForNewTeamSize(1L, 5); - }); - } - - @Test - @DisplayName("modificarProjecte debe manejar estudiantes sin cambios") - void testModificarProjecte_NoStudentChanges() { - // Arrange - StudentDTO student = new StudentDTO(); - student.setId(10L); - student.setName("Student"); - - ProjectDTO original = new ProjectDTO(); - original.setId(1L); - original.setExternalId("test-ext"); - original.setStudents(Arrays.asList(student)); - - ProjectDTO updated = new ProjectDTO(); - updated.setExternalId("test-ext"); - updated.setStudents(Arrays.asList(student)); // Mismo estudiante - - when(ldService.getProjectById(1L)).thenReturn(original); - when(ldService.getAllMetricsCategories()).thenReturn(metricCategories); - when(ldService.getAllFactorsCategories()).thenReturn(factorCategories); - when(ldEvalService.triggerRefresh()).thenReturn(true); - when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList()); - when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList()); - doNothing().when(ldService).importMetrics(); - doNothing().when(ldService).importQualityFactors(); - doNothing().when(ldService).fetchStrategicIndicators(); - - // Act - SaveSyncResponseDTO result = projectService.modificarProjecte(1L, updated); - - // Assert - assertNotNull(result); - verify(ldService, never()).createStudent(anyLong(), any()); - verify(ldService, never()).deleteStudent(anyLong()); - } - - @Test - @DisplayName("modificarProjecte debe manejar fallo en LDEval refresh") - void testModificarProjecte_LDEvalRefreshFails() { - // Arrange - ProjectDTO original = new ProjectDTO(); - original.setId(1L); - original.setExternalId("test-ext"); - original.setStudents(Arrays.asList()); - - ProjectDTO updated = new ProjectDTO(); - updated.setExternalId("test-ext"); - updated.setStudents(Arrays.asList()); - - when(ldService.getProjectById(1L)).thenReturn(original); - when(ldService.getAllMetricsCategories()).thenReturn(metricCategories); - when(ldService.getAllFactorsCategories()).thenReturn(factorCategories); - when(ldEvalService.triggerRefresh()).thenReturn(false); // Falla el refresh - when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList()); - when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList()); - - // Act & Assert - assertThrows(SaveSyncException.class, () -> { - projectService.modificarProjecte(1L, updated); - }); - } - - @Test - @DisplayName("synchronizeCategoriesAfterDataImport debe manejar proyectos sin subject") - void testSynchronizeCategoriesAfterDataImport_NoSubject() { - // Arrange - ProjectDTO project = new ProjectDTO(); - project.setId(1L); - project.setExternalId("proj1"); - project.setSubject(null); - project.setName(null); - - when(ldService.getAllProjects()).thenReturn(Arrays.asList(project)); - when(ldService.getProjectById(1L)).thenReturn(project); - when(ldEvalService.triggerRefresh()).thenReturn(true); - - // Act - projectService.synchronizeCategoriesAfterDataImport(); - - // Assert - verify(ldEvalService).triggerRefresh(); - } - - @Test - @DisplayName("modificarProjecte debe manejar estudiantes null") - void testModificarProjecte_NullStudents() { - // Arrange - ProjectDTO original = new ProjectDTO(); - original.setId(1L); - original.setExternalId("test-ext"); - original.setStudents(null); - - ProjectDTO updated = new ProjectDTO(); - updated.setExternalId("test-ext"); - updated.setStudents(null); - - when(ldService.getProjectById(1L)).thenReturn(original); - when(ldService.getAllMetricsCategories()).thenReturn(metricCategories); - when(ldService.getAllFactorsCategories()).thenReturn(factorCategories); - when(ldEvalService.triggerRefresh()).thenReturn(true); - when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList()); - when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList()); - doNothing().when(ldService).importMetrics(); - doNothing().when(ldService).importQualityFactors(); - doNothing().when(ldService).fetchStrategicIndicators(); - - // Act - SaveSyncResponseDTO result = projectService.modificarProjecte(1L, updated); - - // Assert - assertNotNull(result); - assertEquals(0, result.getFinalTeamSize()); - } -} - +package com.upc.ld_admintool.domain.services; + +import com.upc.ld_admintool.domain.services.exceptions.SaveSyncException; +import com.upc.ld_admintool.rest.DTO.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * Tests unitarios para ProjectService + * Valida la lógica de gestión de proyectos y estudiantes + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("ProjectService - Tests Unitarios") +class ProjectServiceTest { + + @Mock + private LDService ldService; + + @Mock + private LDEvalService ldEvalService; + + @InjectMocks + private ProjectService projectService; + + private ProjectDTO testProject; + private StudentDTO testStudent1; + private StudentDTO testStudent2; + private List> metricCategories; + private List> factorCategories; + + @BeforeEach + void setUp() { + // Preparar proyecto de prueba + testProject = new ProjectDTO(); + testProject.setId(1L); + testProject.setName("Test Project"); + testProject.setDescription("Test Description"); + testProject.setExternalId("test-external-id"); + + // Preparar estudiantes de prueba + testStudent1 = new StudentDTO(); + testStudent1.setId(1L); + testStudent1.setName("Student One"); + + testStudent2 = new StudentDTO(); + testStudent2.setId(2L); + testStudent2.setName("Student Two"); + + testProject.setStudents(Arrays.asList(testStudent1, testStudent2)); + + // Preparar categorías de métricas + metricCategories = new ArrayList<>(); + Map metricCat1 = new HashMap<>(); + metricCat1.put("name", "2 members - Quality"); + metricCat1.put("patternGroup", "quality"); + metricCategories.add(metricCat1); + + Map metricCat2 = new HashMap<>(); + metricCat2.put("name", "3 members - Quality"); + metricCat2.put("patternGroup", "quality"); + metricCategories.add(metricCat2); + + // Preparar categorías de factores + factorCategories = new ArrayList<>(); + Map factorCat1 = new HashMap<>(); + factorCat1.put("name", "2 members - Performance"); + factorCat1.put("patternGroup", "performance"); + factorCategories.add(factorCat1); + + Map factorCat2 = new HashMap<>(); + factorCat2.put("name", "3 members - Performance"); + factorCat2.put("patternGroup", "performance"); + factorCategories.add(factorCat2); + } + + @Test + @DisplayName("Debe listar proyectos con estudiantes correctamente") + void testLlistarProjectesAmbStudents_Success() { + // Arrange + List rawProjects = Arrays.asList(testProject); + when(ldService.getAllProjects()).thenReturn(rawProjects); + when(ldService.getProjectById(1L)).thenReturn(testProject); + + // Act + List result = projectService.llistarProjectesAmbStudents(); + + // Assert + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals("Test Project", result.get(0).getName()); + verify(ldService, times(1)).getAllProjects(); + verify(ldService, times(1)).getProjectById(1L); + } + + @Test + @DisplayName("Debe obtener proyecto por ID correctamente") + void testGetProjectById_Success() { + // Arrange + when(ldService.getProjectById(1L)).thenReturn(testProject); + + // Act + ProjectDTO result = projectService.getProjectById(1L); + + // Assert + assertNotNull(result); + assertEquals(1L, result.getId()); + assertEquals("Test Project", result.getName()); + verify(ldService, times(1)).getProjectById(1L); + } + + @Test + @DisplayName("Debe importar proyectos con estudiantes y triggerar refresh") + void testImportProjects_WithStudents_Success() { + // Arrange + List projects = Arrays.asList(testProject); + when(ldService.createProject(any(ProjectDTO.class))).thenReturn(1L); + doNothing().when(ldService).createStudent(anyLong(), any(StudentDTO.class)); + when(ldEvalService.triggerRefresh()).thenReturn(true); + + // Act + projectService.importProjects(projects); + + // Assert + verify(ldService, times(1)).createProject(testProject); + verify(ldService, times(2)).createStudent(anyLong(), any(StudentDTO.class)); + verify(ldEvalService, times(1)).triggerRefresh(); + } + + @Test + @DisplayName("Debe manejar proyectos sin estudiantes") + void testImportProjects_WithoutStudents_Success() { + // Arrange + ProjectDTO projectWithoutStudents = new ProjectDTO(); + projectWithoutStudents.setName("Project Without Students"); + projectWithoutStudents.setStudents(null); + + List projects = Arrays.asList(projectWithoutStudents); + when(ldService.createProject(any(ProjectDTO.class))).thenReturn(1L); + when(ldEvalService.triggerRefresh()).thenReturn(true); + + // Act + projectService.importProjects(projects); + + // Assert + verify(ldService, times(1)).createProject(projectWithoutStudents); + verify(ldService, never()).createStudent(anyLong(), any(StudentDTO.class)); + verify(ldEvalService, times(1)).triggerRefresh(); + } + + @Test + @DisplayName("No debe triggerar refresh si no se crea ningún proyecto") + void testImportProjects_NoProjectsCreated_NoRefresh() { + // Arrange + List projects = Arrays.asList(testProject); + when(ldService.createProject(any(ProjectDTO.class))).thenReturn(null); + + // Act + projectService.importProjects(projects); + + // Assert + verify(ldService, times(1)).createProject(testProject); + verify(ldEvalService, never()).triggerRefresh(); + } + + @Test + @DisplayName("Debe retornar null cuando el proyecto no existe") + void testGetProjectById_NotFound() { + // Arrange + when(ldService.getProjectById(999L)).thenReturn(null); + + // Act + ProjectDTO result = projectService.getProjectById(999L); + + // Assert + assertNull(result); + verify(ldService, times(1)).getProjectById(999L); + } + + @Test + @DisplayName("Debe manejar lista vacía de proyectos") + void testLlistarProjectesAmbStudents_EmptyList() { + // Arrange + when(ldService.getAllProjects()).thenReturn(Arrays.asList()); + + // Act + List result = projectService.llistarProjectesAmbStudents(); + + // Assert + assertNotNull(result); + assertTrue(result.isEmpty()); + verify(ldService, times(1)).getAllProjects(); + verify(ldService, never()).getProjectById(anyLong()); + } + + @Test + @DisplayName("Debe manejar cuando getProjectById retorna null durante listado") + void testLlistarProjectesAmbStudents_NullProject() { + // Arrange + List rawProjects = Arrays.asList(testProject); + when(ldService.getAllProjects()).thenReturn(rawProjects); + when(ldService.getProjectById(1L)).thenReturn(null); + + // Act + List result = projectService.llistarProjectesAmbStudents(); + + // Assert + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(testProject, result.get(0)); // Should use original project + } + + @Test + @DisplayName("Debe manejar múltiples proyectos en importación") + void testImportProjects_MultipleProjects() { + // Arrange + ProjectDTO project2 = new ProjectDTO(); + project2.setName("Project 2"); + project2.setStudents(null); + + List projects = Arrays.asList(testProject, project2); + when(ldService.createProject(any(ProjectDTO.class))) + .thenReturn(1L) + .thenReturn(2L); + when(ldEvalService.triggerRefresh()).thenReturn(true); + + // Act + projectService.importProjects(projects); + + // Assert + verify(ldService, times(2)).createProject(any(ProjectDTO.class)); + verify(ldEvalService, times(1)).triggerRefresh(); + } + + @Test + @DisplayName("Debe manejar lista vacía en importación") + void testImportProjects_EmptyList() { + // Arrange + List emptyList = Arrays.asList(); + + // Act + projectService.importProjects(emptyList); + + // Assert + verify(ldService, never()).createProject(any(ProjectDTO.class)); + verify(ldEvalService, never()).triggerRefresh(); + } + + @Test + @DisplayName("Debe deletear proyecto correctamente") + void testEsborrarProjecte_Success() { + // Arrange + doNothing().when(ldService).deleteProject(1L); + when(ldEvalService.triggerRefresh()).thenReturn(true); + + // Act + projectService.esborrarProjecte(1L); + + // Assert + verify(ldService, times(1)).deleteProject(1L); + verify(ldEvalService, times(1)).triggerRefresh(); + } + + @Test + @DisplayName("Debe manejar error al obtener proyecto por ID") + void testGetProjectById_Exception() { + // Arrange + when(ldService.getProjectById(1L)).thenThrow(new RuntimeException("Database error")); + + // Act & Assert + assertThrows(RuntimeException.class, () -> { + projectService.getProjectById(1L); + }); + } + + @Test + @DisplayName("Debe continuar importando proyectos si uno falla") + void testImportProjects_PartialFailure() { + // Arrange + ProjectDTO project2 = new ProjectDTO(); + project2.setName("Project 2"); + + List projects = Arrays.asList(testProject, project2); + when(ldService.createProject(testProject)).thenReturn(null); // Falla el primero + when(ldService.createProject(project2)).thenReturn(2L); // Éxito el segundo + when(ldEvalService.triggerRefresh()).thenReturn(true); + + // Act + projectService.importProjects(projects); + + // Assert + verify(ldService, times(2)).createProject(any(ProjectDTO.class)); + verify(ldEvalService, times(1)).triggerRefresh(); // Se llama porque al menos uno tuvo éxito + } + + @Test + @DisplayName("modificarProjecte debe lanzar excepción si proyecto no existe") + void testModificarProjecte_ProjectNotFound() { + // Arrange + when(ldService.getProjectById(999L)).thenReturn(null); + ProjectDTO updatedProject = new ProjectDTO(); + + // Act & Assert + assertThrows(SaveSyncException.class, () -> { + projectService.modificarProjecte(999L, updatedProject); + }); + } + + @Test + @DisplayName("modificarProjecte debe lanzar excepción cuando categorías no están disponibles") + void testModificarProjecte_CategoryValidationFails() { + // Arrange + ProjectDTO original = new ProjectDTO(); + original.setId(1L); + original.setExternalId("test-ext"); + original.setStudents(Arrays.asList()); + + ProjectDTO updated = new ProjectDTO(); + updated.setExternalId("test-ext"); + updated.setStudents(Arrays.asList(testStudent1)); + + when(ldService.getProjectById(1L)).thenReturn(original); + when(ldService.getAllMetricsCategories()).thenReturn(new ArrayList<>()); + when(ldService.getAllFactorsCategories()).thenReturn(new ArrayList<>()); + + // Act & Assert + assertThrows(SaveSyncException.class, () -> { + projectService.modificarProjecte(1L, updated); + }); + } + + @Test + @DisplayName("modificarProjecte debe eliminar estudiantes") + void testModificarProjecte_RemoveStudents() { + // Arrange + StudentDTO existingStudent = new StudentDTO(); + existingStudent.setId(10L); + existingStudent.setName("Existing"); + + ProjectDTO original = new ProjectDTO(); + original.setId(1L); + original.setExternalId("test-ext"); + original.setStudents(Arrays.asList(existingStudent)); + + ProjectDTO updated = new ProjectDTO(); + updated.setExternalId("test-ext"); + updated.setStudents(Arrays.asList()); + + when(ldService.getProjectById(1L)).thenReturn(original); + when(ldService.getAllMetricsCategories()).thenReturn(new ArrayList<>()); + when(ldService.getAllFactorsCategories()).thenReturn(new ArrayList<>()); + + // Act & Assert - debería fallar por falta de categorías + assertThrows(SaveSyncException.class, () -> { + projectService.modificarProjecte(1L, updated); + }); + } + + @Test + @DisplayName("validateCategoriesForNewTeamSize debe lanzar excepción si categoría no existe") + void testValidateCategoriesForNewTeamSize_CategoryNotFound() { + // Arrange + MetricDTO metric = new MetricDTO(); + metric.setId("1"); + metric.setCategoryName("2 members - Quality"); + + Map category = new HashMap<>(); + category.put("name", "2 members - Quality"); + category.put("patternGroup", "quality-pattern"); + + List> categories = Arrays.asList(category); + + lenient().when(ldService.getProjectById(1L)).thenReturn(testProject); + lenient().when(ldService.getAllMetricsCategories()).thenReturn(categories); + lenient().when(ldService.getAllFactorsCategories()).thenReturn(new ArrayList<>()); + lenient().when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList(metric)); + lenient().when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList()); + + // Act & Assert - Pedir 5 miembros cuando solo hay categorías para 2 + assertThrows(RuntimeException.class, () -> { + projectService.validateCategoriesForNewTeamSize(1L, 5); + }); + } + + @Test + @DisplayName("validateCategoriesForNewTeamSize debe pasar si categoría existe") + void testValidateCategoriesForNewTeamSize_Success() { + // Arrange + MetricDTO metric = new MetricDTO(); + metric.setId("1"); + metric.setCategoryName("2 members - Quality"); + + when(ldService.getProjectById(1L)).thenReturn(testProject); + when(ldService.getAllMetricsCategories()).thenReturn(metricCategories); + when(ldService.getAllFactorsCategories()).thenReturn(factorCategories); + when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList(metric)); + when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList()); + + // Act & Assert - Pedir 3 miembros (existe la categoría) + assertDoesNotThrow(() -> { + projectService.validateCategoriesForNewTeamSize(1L, 3); + }); + } + + @Test + @DisplayName("updateCategoriesForProject debe actualizar métricas y factores") + void testUpdateCategoriesForProject_Success() { + // Arrange + MetricDTO metric = new MetricDTO(); + metric.setId("1"); + metric.setExternalId("metric_test"); + metric.setCategoryName("2 members - Quality"); + metric.setScope("project"); + + FactorDTO factor = new FactorDTO(); + factor.setId("1"); + factor.setExternalId("factor_test"); + factor.setCategory("2 members - Performance"); + + when(ldService.getProjectById(1L)).thenReturn(testProject); + when(ldService.getAllMetricsCategories()).thenReturn(metricCategories); + when(ldService.getAllFactorsCategories()).thenReturn(factorCategories); + when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList(metric)); + when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList(factor)); + + // Act + projectService.updateCategoriesForProject(1L); + + // Assert + verify(ldService).getMetricsByProject(testProject.getExternalId()); + verify(ldService).getFactorsByProject(testProject.getExternalId()); + } + + @Test + @DisplayName("synchronizeCategoriesAfterDataImport debe sincronizar categorías entre proyectos") + void testSynchronizeCategoriesAfterDataImport_Success() { + // Arrange + ProjectDTO project1 = new ProjectDTO(); + project1.setId(1L); + project1.setExternalId("proj1"); + project1.setSubject("Math"); + + ProjectDTO project2 = new ProjectDTO(); + project2.setId(2L); + project2.setExternalId("proj2"); + project2.setSubject("Math"); + + when(ldService.getAllProjects()).thenReturn(Arrays.asList(project1, project2)); + when(ldService.getProjectById(1L)).thenReturn(project1); + when(ldService.getProjectById(2L)).thenReturn(project2); + when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList()); + when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList()); + when(ldEvalService.triggerRefresh()).thenReturn(true); + + // Act + projectService.synchronizeCategoriesAfterDataImport(); + + // Assert + verify(ldEvalService).triggerRefresh(); + } + + @Test + @DisplayName("synchronizeCategoriesAfterDataImport debe manejar excepciones") + void testSynchronizeCategoriesAfterDataImport_Exception() { + // Arrange + when(ldService.getAllProjects()).thenThrow(new RuntimeException("Database error")); + + // Act & Assert - No debe lanzar excepción, solo logear + assertDoesNotThrow(() -> { + projectService.synchronizeCategoriesAfterDataImport(); + }); + } + + @Test + @DisplayName("esborrarProjecte debe deletear proyecto y triggerar refresh en LDEval") + void testEsborrarProjecte_SuccessWithRefresh() { + // Arrange + doNothing().when(ldService).deleteProject(1L); + when(ldEvalService.triggerRefresh()).thenReturn(true); + + // Act + projectService.esborrarProjecte(1L); + + // Assert + verify(ldService, times(1)).deleteProject(1L); + verify(ldEvalService, times(1)).triggerRefresh(); + } + + @Test + @DisplayName("updateCategoriesForProject debe lanzar excepción si proyecto no existe") + void testUpdateCategoriesForProject_ProjectNotFound() { + // Arrange + when(ldService.getProjectById(999L)).thenReturn(null); + + // Act & Assert + assertThrows(RuntimeException.class, () -> { + projectService.updateCategoriesForProject(999L); + }); + } + + @Test + @DisplayName("updateCategoriesForProject con override debe usar valores proporcionados") + void testUpdateCategoriesForProject_WithOverride() { + // Arrange + ProjectDTO overrideProject = new ProjectDTO(); + overrideProject.setExternalId("override-ext"); + overrideProject.setStudents(Arrays.asList(testStudent1, testStudent2, new StudentDTO())); + + when(ldService.getAllMetricsCategories()).thenReturn(metricCategories); + when(ldService.getAllFactorsCategories()).thenReturn(factorCategories); + when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList()); + when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList()); + + // Act + projectService.updateCategoriesForProject(1L, overrideProject, 3); + + // Assert + verify(ldService).getMetricsByProject("override-ext"); + verify(ldService, never()).getProjectById(1L); // No debe buscar porque se proporciona override + } + + @Test + @DisplayName("validateCategoriesForNewTeamSize debe retornar si proyecto no existe") + void testValidateCategoriesForNewTeamSize_ProjectNotFound() { + // Arrange + when(ldService.getProjectById(999L)).thenReturn(null); + + // Act & Assert - No debe lanzar excepción + assertDoesNotThrow(() -> { + projectService.validateCategoriesForNewTeamSize(999L, 2); + }); + } + + @Test + @DisplayName("validateCategoriesForNewTeamSize debe validar factores correctamente") + void testValidateCategoriesForNewTeamSize_FactorsValidation() { + // Arrange + FactorDTO factor = new FactorDTO(); + factor.setId("1"); + factor.setCategory("2 members - Performance"); + + when(ldService.getProjectById(1L)).thenReturn(testProject); + when(ldService.getAllMetricsCategories()).thenReturn(metricCategories); + when(ldService.getAllFactorsCategories()).thenReturn(factorCategories); + when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList()); + when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList(factor)); + + // Act & Assert + assertDoesNotThrow(() -> { + projectService.validateCategoriesForNewTeamSize(1L, 3); + }); + } + + @Test + @DisplayName("validateCategoriesForNewTeamSize debe lanzar excepción para factores sin categoría") + void testValidateCategoriesForNewTeamSize_FactorCategoryNotFound() { + // Arrange + FactorDTO factor = new FactorDTO(); + factor.setId("1"); + factor.setCategory("2 members - Performance"); + + when(ldService.getProjectById(1L)).thenReturn(testProject); + when(ldService.getAllMetricsCategories()).thenReturn(metricCategories); + when(ldService.getAllFactorsCategories()).thenReturn(factorCategories); + when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList()); + when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList(factor)); + + // Act & Assert - Pedir 5 miembros cuando solo hay categorías para 2 y 3 + assertThrows(RuntimeException.class, () -> { + projectService.validateCategoriesForNewTeamSize(1L, 5); + }); + } + + @Test + @DisplayName("modificarProjecte debe manejar estudiantes sin cambios") + void testModificarProjecte_NoStudentChanges() { + // Arrange + StudentDTO student = new StudentDTO(); + student.setId(10L); + student.setName("Student"); + + ProjectDTO original = new ProjectDTO(); + original.setId(1L); + original.setExternalId("test-ext"); + original.setStudents(Arrays.asList(student)); + + ProjectDTO updated = new ProjectDTO(); + updated.setExternalId("test-ext"); + updated.setStudents(Arrays.asList(student)); // Mismo estudiante + + when(ldService.getProjectById(1L)).thenReturn(original); + when(ldService.getAllMetricsCategories()).thenReturn(metricCategories); + when(ldService.getAllFactorsCategories()).thenReturn(factorCategories); + when(ldEvalService.triggerRefresh()).thenReturn(true); + when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList()); + when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList()); + doNothing().when(ldService).importMetrics(); + doNothing().when(ldService).importQualityFactors(); + doNothing().when(ldService).fetchStrategicIndicators(); + + // Act + SaveSyncResponseDTO result = projectService.modificarProjecte(1L, updated); + + // Assert + assertNotNull(result); + verify(ldService, never()).createStudent(anyLong(), any()); + verify(ldService, never()).deleteStudent(anyLong()); + } + + @Test + @DisplayName("modificarProjecte debe manejar fallo en LDEval refresh") + void testModificarProjecte_LDEvalRefreshFails() { + // Arrange + ProjectDTO original = new ProjectDTO(); + original.setId(1L); + original.setExternalId("test-ext"); + original.setStudents(Arrays.asList()); + + ProjectDTO updated = new ProjectDTO(); + updated.setExternalId("test-ext"); + updated.setStudents(Arrays.asList()); + + when(ldService.getProjectById(1L)).thenReturn(original); + when(ldService.getAllMetricsCategories()).thenReturn(metricCategories); + when(ldService.getAllFactorsCategories()).thenReturn(factorCategories); + when(ldEvalService.triggerRefresh()).thenReturn(false); // Falla el refresh + when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList()); + when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList()); + + // Act & Assert + assertThrows(SaveSyncException.class, () -> { + projectService.modificarProjecte(1L, updated); + }); + } + + @Test + @DisplayName("synchronizeCategoriesAfterDataImport debe manejar proyectos sin subject") + void testSynchronizeCategoriesAfterDataImport_NoSubject() { + // Arrange + ProjectDTO project = new ProjectDTO(); + project.setId(1L); + project.setExternalId("proj1"); + project.setSubject(null); + project.setName(null); + + when(ldService.getAllProjects()).thenReturn(Arrays.asList(project)); + when(ldService.getProjectById(1L)).thenReturn(project); + when(ldEvalService.triggerRefresh()).thenReturn(true); + + // Act + projectService.synchronizeCategoriesAfterDataImport(); + + // Assert + verify(ldEvalService).triggerRefresh(); + } + + @Test + @DisplayName("modificarProjecte debe manejar estudiantes null") + void testModificarProjecte_NullStudents() { + // Arrange + ProjectDTO original = new ProjectDTO(); + original.setId(1L); + original.setExternalId("test-ext"); + original.setStudents(null); + + ProjectDTO updated = new ProjectDTO(); + updated.setExternalId("test-ext"); + updated.setStudents(null); + + when(ldService.getProjectById(1L)).thenReturn(original); + when(ldService.getAllMetricsCategories()).thenReturn(metricCategories); + when(ldService.getAllFactorsCategories()).thenReturn(factorCategories); + when(ldEvalService.triggerRefresh()).thenReturn(true); + when(ldService.getMetricsByProject(anyString())).thenReturn(Arrays.asList()); + when(ldService.getFactorsByProject(anyString())).thenReturn(Arrays.asList()); + doNothing().when(ldService).importMetrics(); + doNothing().when(ldService).importQualityFactors(); + doNothing().when(ldService).fetchStrategicIndicators(); + + // Act + SaveSyncResponseDTO result = projectService.modificarProjecte(1L, updated); + + // Assert + assertNotNull(result); + assertEquals(0, result.getFinalTeamSize()); + } +} + diff --git a/src/test/java/com/upc/ld_admintool/domain/services/StrategicIndicatorsServiceTest.java b/src/test/java/com/upc/ld_admintool/domain/services/StrategicIndicatorsServiceTest.java index 42e0d9b..df9d4ab 100644 --- a/src/test/java/com/upc/ld_admintool/domain/services/StrategicIndicatorsServiceTest.java +++ b/src/test/java/com/upc/ld_admintool/domain/services/StrategicIndicatorsServiceTest.java @@ -1,168 +1,168 @@ -package com.upc.ld_admintool.domain.services; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -/** - * Tests unitarios para StrategicIndicatorsService - * Valida la gestión de indicadores estratégicos - */ -@ExtendWith(MockitoExtension.class) -@DisplayName("StrategicIndicatorsService - Tests Unitarios") -class StrategicIndicatorsServiceTest { - - @Mock - private LDService ldService; - - @Mock - private ProjectService projectService; - - @InjectMocks - private StrategicIndicatorsService strategicIndicatorsService; - - private List> testCategories; - - @BeforeEach - void setUp() { - // Preparar categorías de indicadores estratégicos - Map category1 = new HashMap<>(); - category1.put("id", 1L); - category1.put("name", "Quality Assurance"); - category1.put("color", "#FF5733"); - category1.put("indicators", 5); - - Map category2 = new HashMap<>(); - category2.put("id", 2L); - category2.put("name", "Product Quality"); - category2.put("color", "#33FF57"); - category2.put("indicators", 3); - - testCategories = Arrays.asList(category1, category2); - } - - @Test - @DisplayName("Debe obtener todas las categorías de indicadores estratégicos") - void testGetAllStrategicIndicatorCategories_Success() { - // Arrange - when(ldService.getAllStrategicIndicatorCategories()).thenReturn(testCategories); - - // Act - List> result = strategicIndicatorsService.getAllStrategicIndicatorCategories(); - - // Assert - assertNotNull(result); - assertEquals(2, result.size()); - assertEquals("Quality Assurance", result.get(0).get("name")); - assertEquals("#FF5733", result.get(0).get("color")); - assertEquals(5, result.get(0).get("indicators")); - verify(ldService, times(1)).getAllStrategicIndicatorCategories(); - } - - @Test - @DisplayName("Debe retornar lista vacía cuando no hay categorías") - void testGetAllStrategicIndicatorCategories_EmptyList() { - // Arrange - when(ldService.getAllStrategicIndicatorCategories()).thenReturn(Arrays.asList()); - - // Act - List> result = strategicIndicatorsService.getAllStrategicIndicatorCategories(); - - // Assert - assertNotNull(result); - assertTrue(result.isEmpty()); - verify(ldService, times(1)).getAllStrategicIndicatorCategories(); - } - - @Test - @DisplayName("Debe fetchear indicadores estratégicos y sincronizar categorías") - void testFetchStrategicIndicators_Success() { - // Arrange - doNothing().when(ldService).fetchStrategicIndicators(); - doNothing().when(projectService).synchronizeCategoriesAfterDataImport(); - - // Act - strategicIndicatorsService.fetchStrategicIndicators(); - - // Assert - verify(ldService, times(1)).fetchStrategicIndicators(); - verify(projectService, times(1)).synchronizeCategoriesAfterDataImport(); - } - - @Test - @DisplayName("Debe ejecutar fetchStrategicIndicators antes de sincronizar") - void testFetchStrategicIndicators_OrderOfExecution() { - // Arrange - doNothing().when(ldService).fetchStrategicIndicators(); - doNothing().when(projectService).synchronizeCategoriesAfterDataImport(); - - // Act - strategicIndicatorsService.fetchStrategicIndicators(); - - // Assert - Verificar orden de invocación - var inOrder = inOrder(ldService, projectService); - inOrder.verify(ldService).fetchStrategicIndicators(); - inOrder.verify(projectService).synchronizeCategoriesAfterDataImport(); - } - - @Test - @DisplayName("Debe llamar a synchronizeCategoriesAfterDataImport después de fetch") - void testFetchStrategicIndicators_CallsSynchronize() { - // Arrange - doNothing().when(ldService).fetchStrategicIndicators(); - doNothing().when(projectService).synchronizeCategoriesAfterDataImport(); - - // Act - strategicIndicatorsService.fetchStrategicIndicators(); - - // Assert - verify(projectService, times(1)).synchronizeCategoriesAfterDataImport(); - } - - @Test - @DisplayName("Debe delegar correctamente al LDService") - void testServiceDelegation() { - // Arrange - when(ldService.getAllStrategicIndicatorCategories()).thenReturn(testCategories); - - // Act - strategicIndicatorsService.getAllStrategicIndicatorCategories(); - - // Assert - verify(ldService, times(1)).getAllStrategicIndicatorCategories(); - verifyNoMoreInteractions(ldService); - } - - @Test - @DisplayName("Debe manejar categorías con diferentes estructuras") - void testGetAllStrategicIndicatorCategories_DifferentStructures() { - // Arrange - Map customCategory = new HashMap<>(); - customCategory.put("name", "Custom"); - customCategory.put("extraField", "value"); - - when(ldService.getAllStrategicIndicatorCategories()) - .thenReturn(Arrays.asList(customCategory)); - - // Act - List> result = strategicIndicatorsService.getAllStrategicIndicatorCategories(); - - // Assert - assertNotNull(result); - assertEquals(1, result.size()); - assertEquals("Custom", result.get(0).get("name")); - assertEquals("value", result.get(0).get("extraField")); - } -} +package com.upc.ld_admintool.domain.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Tests unitarios para StrategicIndicatorsService + * Valida la gestión de indicadores estratégicos + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("StrategicIndicatorsService - Tests Unitarios") +class StrategicIndicatorsServiceTest { + + @Mock + private LDService ldService; + + @Mock + private ProjectService projectService; + + @InjectMocks + private StrategicIndicatorsService strategicIndicatorsService; + + private List> testCategories; + + @BeforeEach + void setUp() { + // Preparar categorías de indicadores estratégicos + Map category1 = new HashMap<>(); + category1.put("id", 1L); + category1.put("name", "Quality Assurance"); + category1.put("color", "#FF5733"); + category1.put("indicators", 5); + + Map category2 = new HashMap<>(); + category2.put("id", 2L); + category2.put("name", "Product Quality"); + category2.put("color", "#33FF57"); + category2.put("indicators", 3); + + testCategories = Arrays.asList(category1, category2); + } + + @Test + @DisplayName("Debe obtener todas las categorías de indicadores estratégicos") + void testGetAllStrategicIndicatorCategories_Success() { + // Arrange + when(ldService.getAllStrategicIndicatorCategories()).thenReturn(testCategories); + + // Act + List> result = strategicIndicatorsService.getAllStrategicIndicatorCategories(); + + // Assert + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals("Quality Assurance", result.get(0).get("name")); + assertEquals("#FF5733", result.get(0).get("color")); + assertEquals(5, result.get(0).get("indicators")); + verify(ldService, times(1)).getAllStrategicIndicatorCategories(); + } + + @Test + @DisplayName("Debe retornar lista vacía cuando no hay categorías") + void testGetAllStrategicIndicatorCategories_EmptyList() { + // Arrange + when(ldService.getAllStrategicIndicatorCategories()).thenReturn(Arrays.asList()); + + // Act + List> result = strategicIndicatorsService.getAllStrategicIndicatorCategories(); + + // Assert + assertNotNull(result); + assertTrue(result.isEmpty()); + verify(ldService, times(1)).getAllStrategicIndicatorCategories(); + } + + @Test + @DisplayName("Debe fetchear indicadores estratégicos y sincronizar categorías") + void testFetchStrategicIndicators_Success() { + // Arrange + doNothing().when(ldService).fetchStrategicIndicators(); + doNothing().when(projectService).synchronizeCategoriesAfterDataImport(); + + // Act + strategicIndicatorsService.fetchStrategicIndicators(); + + // Assert + verify(ldService, times(1)).fetchStrategicIndicators(); + verify(projectService, times(1)).synchronizeCategoriesAfterDataImport(); + } + + @Test + @DisplayName("Debe ejecutar fetchStrategicIndicators antes de sincronizar") + void testFetchStrategicIndicators_OrderOfExecution() { + // Arrange + doNothing().when(ldService).fetchStrategicIndicators(); + doNothing().when(projectService).synchronizeCategoriesAfterDataImport(); + + // Act + strategicIndicatorsService.fetchStrategicIndicators(); + + // Assert - Verificar orden de invocación + var inOrder = inOrder(ldService, projectService); + inOrder.verify(ldService).fetchStrategicIndicators(); + inOrder.verify(projectService).synchronizeCategoriesAfterDataImport(); + } + + @Test + @DisplayName("Debe llamar a synchronizeCategoriesAfterDataImport después de fetch") + void testFetchStrategicIndicators_CallsSynchronize() { + // Arrange + doNothing().when(ldService).fetchStrategicIndicators(); + doNothing().when(projectService).synchronizeCategoriesAfterDataImport(); + + // Act + strategicIndicatorsService.fetchStrategicIndicators(); + + // Assert + verify(projectService, times(1)).synchronizeCategoriesAfterDataImport(); + } + + @Test + @DisplayName("Debe delegar correctamente al LDService") + void testServiceDelegation() { + // Arrange + when(ldService.getAllStrategicIndicatorCategories()).thenReturn(testCategories); + + // Act + strategicIndicatorsService.getAllStrategicIndicatorCategories(); + + // Assert + verify(ldService, times(1)).getAllStrategicIndicatorCategories(); + verifyNoMoreInteractions(ldService); + } + + @Test + @DisplayName("Debe manejar categorías con diferentes estructuras") + void testGetAllStrategicIndicatorCategories_DifferentStructures() { + // Arrange + Map customCategory = new HashMap<>(); + customCategory.put("name", "Custom"); + customCategory.put("extraField", "value"); + + when(ldService.getAllStrategicIndicatorCategories()) + .thenReturn(Arrays.asList(customCategory)); + + // Act + List> result = strategicIndicatorsService.getAllStrategicIndicatorCategories(); + + // Assert + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals("Custom", result.get(0).get("name")); + assertEquals("value", result.get(0).get("extraField")); + } +} diff --git a/src/test/java/com/upc/ld_admintool/domain/services/WizardServiceTest.java b/src/test/java/com/upc/ld_admintool/domain/services/WizardServiceTest.java index f4329bb..cb0f47e 100644 --- a/src/test/java/com/upc/ld_admintool/domain/services/WizardServiceTest.java +++ b/src/test/java/com/upc/ld_admintool/domain/services/WizardServiceTest.java @@ -1,247 +1,247 @@ -package com.upc.ld_admintool.domain.services; - -import com.upc.ld_admintool.rest.DTO.*; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -/** - * Tests unitarios para WizardService - * Valida la lógica del asistente de configuración - */ -@ExtendWith(MockitoExtension.class) -@DisplayName("WizardService - Tests Unitarios") -class WizardServiceTest { - - @Mock - private LDService ldService; - - @InjectMocks - private WizardService wizardService; - - private List testProjects; - private List testMetrics; - private List testFactors; - private List> testCategories; - - @BeforeEach - void setUp() { - ProjectDTO project = new ProjectDTO(); - project.setId(1L); - project.setName("Test Project"); - project.setExternalId("test-external-id"); - testProjects = Arrays.asList(project); - - MetricDTO metric = new MetricDTO(); - metric.setId("1"); - metric.setName("Test Metric"); - testMetrics = Arrays.asList(metric); - - FactorDTO factor = new FactorDTO(); - factor.setId("1"); - factor.setName("Test Factor"); - testFactors = Arrays.asList(factor); - - Map category = new HashMap<>(); - category.put("name", "Test Category"); - testCategories = Arrays.asList(category); - } - - @Test - @DisplayName("getWizardStatus debe retornar estado completo cuando todo está configurado") - void testGetWizardStatus_AllConfigured() { - // Arrange - when(ldService.getAllProjects()).thenReturn(testProjects); - when(ldService.getAllMetricsCategories()).thenReturn(testCategories); - when(ldService.getAllFactorsCategories()).thenReturn(testCategories); - when(ldService.getAllStrategicIndicatorCategories()).thenReturn(testCategories); - when(ldService.getMetricsByProject("test-external-id")).thenReturn(testMetrics); - when(ldService.getFactorsByProject("test-external-id")).thenReturn(testFactors); - - // Act - WizardStatusDTO status = wizardService.getWizardStatus(); - - // Assert - assertTrue(status.isHasProjects()); - assertTrue(status.isHasData()); - assertTrue(status.isHasMetricsCategories()); - assertTrue(status.isHasFactorsCategories()); - assertTrue(status.isHasStrategicIndicatorCategories()); - } - - @Test - @DisplayName("getWizardStatus debe indicar sin proyectos cuando la lista está vacía") - void testGetWizardStatus_NoProjects() { - // Arrange - when(ldService.getAllProjects()).thenReturn(Arrays.asList()); - when(ldService.getAllMetricsCategories()).thenReturn(testCategories); - when(ldService.getAllFactorsCategories()).thenReturn(testCategories); - when(ldService.getAllStrategicIndicatorCategories()).thenReturn(testCategories); - - // Act - WizardStatusDTO status = wizardService.getWizardStatus(); - - // Assert - assertFalse(status.isHasProjects()); - assertFalse(status.isHasData()); - assertTrue(status.isHasMetricsCategories()); - assertTrue(status.isHasFactorsCategories()); - assertTrue(status.isHasStrategicIndicatorCategories()); - } - - @Test - @DisplayName("getWizardStatus debe indicar sin categorías cuando están vacías") - void testGetWizardStatus_NoCategories() { - // Arrange - when(ldService.getAllProjects()).thenReturn(testProjects); - when(ldService.getAllMetricsCategories()).thenReturn(Arrays.asList()); - when(ldService.getAllFactorsCategories()).thenReturn(Arrays.asList()); - when(ldService.getAllStrategicIndicatorCategories()).thenReturn(Arrays.asList()); - when(ldService.getMetricsByProject("test-external-id")).thenReturn(testMetrics); - when(ldService.getFactorsByProject("test-external-id")).thenReturn(testFactors); - - // Act - WizardStatusDTO status = wizardService.getWizardStatus(); - - // Assert - assertTrue(status.isHasProjects()); - assertTrue(status.isHasData()); - assertFalse(status.isHasMetricsCategories()); - assertFalse(status.isHasFactorsCategories()); - assertFalse(status.isHasStrategicIndicatorCategories()); - } - - @Test - @DisplayName("getWizardStatus debe manejar categorías null") - void testGetWizardStatus_NullCategories() { - // Arrange - when(ldService.getAllProjects()).thenReturn(testProjects); - when(ldService.getAllMetricsCategories()).thenReturn(null); - when(ldService.getAllFactorsCategories()).thenReturn(null); - when(ldService.getAllStrategicIndicatorCategories()).thenReturn(null); - when(ldService.getMetricsByProject("test-external-id")).thenReturn(testMetrics); - when(ldService.getFactorsByProject("test-external-id")).thenReturn(testFactors); - - // Act - WizardStatusDTO status = wizardService.getWizardStatus(); - - // Assert - assertTrue(status.isHasProjects()); - assertTrue(status.isHasData()); - assertFalse(status.isHasMetricsCategories()); - assertFalse(status.isHasFactorsCategories()); - assertFalse(status.isHasStrategicIndicatorCategories()); - } - - @Test - @DisplayName("getWizardStatus debe indicar sin datos cuando métricas están vacías") - void testGetWizardStatus_NoMetrics() { - // Arrange - when(ldService.getAllProjects()).thenReturn(testProjects); - when(ldService.getAllMetricsCategories()).thenReturn(testCategories); - when(ldService.getAllFactorsCategories()).thenReturn(testCategories); - when(ldService.getAllStrategicIndicatorCategories()).thenReturn(testCategories); - when(ldService.getMetricsByProject("test-external-id")).thenReturn(Arrays.asList()); - when(ldService.getFactorsByProject("test-external-id")).thenReturn(testFactors); - - // Act - WizardStatusDTO status = wizardService.getWizardStatus(); - - // Assert - assertTrue(status.isHasProjects()); - assertFalse(status.isHasData()); - } - - @Test - @DisplayName("getWizardStatus debe indicar sin datos cuando factores están vacíos") - void testGetWizardStatus_NoFactors() { - // Arrange - when(ldService.getAllProjects()).thenReturn(testProjects); - when(ldService.getAllMetricsCategories()).thenReturn(testCategories); - when(ldService.getAllFactorsCategories()).thenReturn(testCategories); - when(ldService.getAllStrategicIndicatorCategories()).thenReturn(testCategories); - when(ldService.getMetricsByProject("test-external-id")).thenReturn(testMetrics); - when(ldService.getFactorsByProject("test-external-id")).thenReturn(Arrays.asList()); - - // Act - WizardStatusDTO status = wizardService.getWizardStatus(); - - // Assert - assertTrue(status.isHasProjects()); - assertFalse(status.isHasData()); - } - - @Test - @DisplayName("getWizardStatus debe manejar proyecto sin externalId") - void testGetWizardStatus_ProjectWithoutExternalId() { - // Arrange - ProjectDTO projectWithoutExternalId = new ProjectDTO(); - projectWithoutExternalId.setId(1L); - projectWithoutExternalId.setExternalId(null); - - when(ldService.getAllProjects()).thenReturn(Arrays.asList(projectWithoutExternalId)); - when(ldService.getAllMetricsCategories()).thenReturn(testCategories); - when(ldService.getAllFactorsCategories()).thenReturn(testCategories); - when(ldService.getAllStrategicIndicatorCategories()).thenReturn(testCategories); - - // Act - WizardStatusDTO status = wizardService.getWizardStatus(); - - // Assert - assertTrue(status.isHasProjects()); - assertFalse(status.isHasData()); - verify(ldService, never()).getMetricsByProject(anyString()); - verify(ldService, never()).getFactorsByProject(anyString()); - } - - @Test - @DisplayName("getWizardStatus debe manejar excepciones correctamente") - void testGetWizardStatus_HandlesException() { - // Arrange - when(ldService.getAllProjects()).thenThrow(new RuntimeException("Database error")); - - // Act - WizardStatusDTO status = wizardService.getWizardStatus(); - - // Assert - assertNotNull(status); - assertFalse(status.isHasProjects()); - assertFalse(status.isHasData()); - assertFalse(status.isHasMetricsCategories()); - assertFalse(status.isHasFactorsCategories()); - assertFalse(status.isHasStrategicIndicatorCategories()); - } - - @Test - @DisplayName("getWizardStatus debe manejar excepción al obtener categorías") - void testGetWizardStatus_ExceptionInCategories() { - // Arrange - when(ldService.getAllProjects()).thenReturn(testProjects); - when(ldService.getAllMetricsCategories()).thenThrow(new RuntimeException("Categories error")); - - // Act - WizardStatusDTO status = wizardService.getWizardStatus(); - - // Assert - assertNotNull(status); - // hasProjects se establece antes de la excepción, por lo que permanece true - assertTrue(status.isHasProjects()); - // Los demás flags se quedan en false porque no se ejecutaron después de la excepción - assertFalse(status.isHasData()); - assertFalse(status.isHasMetricsCategories()); - assertFalse(status.isHasFactorsCategories()); - assertFalse(status.isHasStrategicIndicatorCategories()); - } -} +package com.upc.ld_admintool.domain.services; + +import com.upc.ld_admintool.rest.DTO.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Tests unitarios para WizardService + * Valida la lógica del asistente de configuración + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("WizardService - Tests Unitarios") +class WizardServiceTest { + + @Mock + private LDService ldService; + + @InjectMocks + private WizardService wizardService; + + private List testProjects; + private List testMetrics; + private List testFactors; + private List> testCategories; + + @BeforeEach + void setUp() { + ProjectDTO project = new ProjectDTO(); + project.setId(1L); + project.setName("Test Project"); + project.setExternalId("test-external-id"); + testProjects = Arrays.asList(project); + + MetricDTO metric = new MetricDTO(); + metric.setId("1"); + metric.setName("Test Metric"); + testMetrics = Arrays.asList(metric); + + FactorDTO factor = new FactorDTO(); + factor.setId("1"); + factor.setName("Test Factor"); + testFactors = Arrays.asList(factor); + + Map category = new HashMap<>(); + category.put("name", "Test Category"); + testCategories = Arrays.asList(category); + } + + @Test + @DisplayName("getWizardStatus debe retornar estado completo cuando todo está configurado") + void testGetWizardStatus_AllConfigured() { + // Arrange + when(ldService.getAllProjects()).thenReturn(testProjects); + when(ldService.getAllMetricsCategories()).thenReturn(testCategories); + when(ldService.getAllFactorsCategories()).thenReturn(testCategories); + when(ldService.getAllStrategicIndicatorCategories()).thenReturn(testCategories); + when(ldService.getMetricsByProject("test-external-id")).thenReturn(testMetrics); + when(ldService.getFactorsByProject("test-external-id")).thenReturn(testFactors); + + // Act + WizardStatusDTO status = wizardService.getWizardStatus(); + + // Assert + assertTrue(status.isHasProjects()); + assertTrue(status.isHasData()); + assertTrue(status.isHasMetricsCategories()); + assertTrue(status.isHasFactorsCategories()); + assertTrue(status.isHasStrategicIndicatorCategories()); + } + + @Test + @DisplayName("getWizardStatus debe indicar sin proyectos cuando la lista está vacía") + void testGetWizardStatus_NoProjects() { + // Arrange + when(ldService.getAllProjects()).thenReturn(Arrays.asList()); + when(ldService.getAllMetricsCategories()).thenReturn(testCategories); + when(ldService.getAllFactorsCategories()).thenReturn(testCategories); + when(ldService.getAllStrategicIndicatorCategories()).thenReturn(testCategories); + + // Act + WizardStatusDTO status = wizardService.getWizardStatus(); + + // Assert + assertFalse(status.isHasProjects()); + assertFalse(status.isHasData()); + assertTrue(status.isHasMetricsCategories()); + assertTrue(status.isHasFactorsCategories()); + assertTrue(status.isHasStrategicIndicatorCategories()); + } + + @Test + @DisplayName("getWizardStatus debe indicar sin categorías cuando están vacías") + void testGetWizardStatus_NoCategories() { + // Arrange + when(ldService.getAllProjects()).thenReturn(testProjects); + when(ldService.getAllMetricsCategories()).thenReturn(Arrays.asList()); + when(ldService.getAllFactorsCategories()).thenReturn(Arrays.asList()); + when(ldService.getAllStrategicIndicatorCategories()).thenReturn(Arrays.asList()); + when(ldService.getMetricsByProject("test-external-id")).thenReturn(testMetrics); + when(ldService.getFactorsByProject("test-external-id")).thenReturn(testFactors); + + // Act + WizardStatusDTO status = wizardService.getWizardStatus(); + + // Assert + assertTrue(status.isHasProjects()); + assertTrue(status.isHasData()); + assertFalse(status.isHasMetricsCategories()); + assertFalse(status.isHasFactorsCategories()); + assertFalse(status.isHasStrategicIndicatorCategories()); + } + + @Test + @DisplayName("getWizardStatus debe manejar categorías null") + void testGetWizardStatus_NullCategories() { + // Arrange + when(ldService.getAllProjects()).thenReturn(testProjects); + when(ldService.getAllMetricsCategories()).thenReturn(null); + when(ldService.getAllFactorsCategories()).thenReturn(null); + when(ldService.getAllStrategicIndicatorCategories()).thenReturn(null); + when(ldService.getMetricsByProject("test-external-id")).thenReturn(testMetrics); + when(ldService.getFactorsByProject("test-external-id")).thenReturn(testFactors); + + // Act + WizardStatusDTO status = wizardService.getWizardStatus(); + + // Assert + assertTrue(status.isHasProjects()); + assertTrue(status.isHasData()); + assertFalse(status.isHasMetricsCategories()); + assertFalse(status.isHasFactorsCategories()); + assertFalse(status.isHasStrategicIndicatorCategories()); + } + + @Test + @DisplayName("getWizardStatus debe indicar sin datos cuando métricas están vacías") + void testGetWizardStatus_NoMetrics() { + // Arrange + when(ldService.getAllProjects()).thenReturn(testProjects); + when(ldService.getAllMetricsCategories()).thenReturn(testCategories); + when(ldService.getAllFactorsCategories()).thenReturn(testCategories); + when(ldService.getAllStrategicIndicatorCategories()).thenReturn(testCategories); + when(ldService.getMetricsByProject("test-external-id")).thenReturn(Arrays.asList()); + when(ldService.getFactorsByProject("test-external-id")).thenReturn(testFactors); + + // Act + WizardStatusDTO status = wizardService.getWizardStatus(); + + // Assert + assertTrue(status.isHasProjects()); + assertFalse(status.isHasData()); + } + + @Test + @DisplayName("getWizardStatus debe indicar sin datos cuando factores están vacíos") + void testGetWizardStatus_NoFactors() { + // Arrange + when(ldService.getAllProjects()).thenReturn(testProjects); + when(ldService.getAllMetricsCategories()).thenReturn(testCategories); + when(ldService.getAllFactorsCategories()).thenReturn(testCategories); + when(ldService.getAllStrategicIndicatorCategories()).thenReturn(testCategories); + when(ldService.getMetricsByProject("test-external-id")).thenReturn(testMetrics); + when(ldService.getFactorsByProject("test-external-id")).thenReturn(Arrays.asList()); + + // Act + WizardStatusDTO status = wizardService.getWizardStatus(); + + // Assert + assertTrue(status.isHasProjects()); + assertFalse(status.isHasData()); + } + + @Test + @DisplayName("getWizardStatus debe manejar proyecto sin externalId") + void testGetWizardStatus_ProjectWithoutExternalId() { + // Arrange + ProjectDTO projectWithoutExternalId = new ProjectDTO(); + projectWithoutExternalId.setId(1L); + projectWithoutExternalId.setExternalId(null); + + when(ldService.getAllProjects()).thenReturn(Arrays.asList(projectWithoutExternalId)); + when(ldService.getAllMetricsCategories()).thenReturn(testCategories); + when(ldService.getAllFactorsCategories()).thenReturn(testCategories); + when(ldService.getAllStrategicIndicatorCategories()).thenReturn(testCategories); + + // Act + WizardStatusDTO status = wizardService.getWizardStatus(); + + // Assert + assertTrue(status.isHasProjects()); + assertFalse(status.isHasData()); + verify(ldService, never()).getMetricsByProject(anyString()); + verify(ldService, never()).getFactorsByProject(anyString()); + } + + @Test + @DisplayName("getWizardStatus debe manejar excepciones correctamente") + void testGetWizardStatus_HandlesException() { + // Arrange + when(ldService.getAllProjects()).thenThrow(new RuntimeException("Database error")); + + // Act + WizardStatusDTO status = wizardService.getWizardStatus(); + + // Assert + assertNotNull(status); + assertFalse(status.isHasProjects()); + assertFalse(status.isHasData()); + assertFalse(status.isHasMetricsCategories()); + assertFalse(status.isHasFactorsCategories()); + assertFalse(status.isHasStrategicIndicatorCategories()); + } + + @Test + @DisplayName("getWizardStatus debe manejar excepción al obtener categorías") + void testGetWizardStatus_ExceptionInCategories() { + // Arrange + when(ldService.getAllProjects()).thenReturn(testProjects); + when(ldService.getAllMetricsCategories()).thenThrow(new RuntimeException("Categories error")); + + // Act + WizardStatusDTO status = wizardService.getWizardStatus(); + + // Assert + assertNotNull(status); + // hasProjects se establece antes de la excepción, por lo que permanece true + assertTrue(status.isHasProjects()); + // Los demás flags se quedan en false porque no se ejecutaron después de la excepción + assertFalse(status.isHasData()); + assertFalse(status.isHasMetricsCategories()); + assertFalse(status.isHasFactorsCategories()); + assertFalse(status.isHasStrategicIndicatorCategories()); + } +} diff --git a/src/test/java/com/upc/ld_admintool/domain/services/exceptions/SaveSyncExceptionTest.java b/src/test/java/com/upc/ld_admintool/domain/services/exceptions/SaveSyncExceptionTest.java index df3f195..b4cf684 100644 --- a/src/test/java/com/upc/ld_admintool/domain/services/exceptions/SaveSyncExceptionTest.java +++ b/src/test/java/com/upc/ld_admintool/domain/services/exceptions/SaveSyncExceptionTest.java @@ -1,141 +1,141 @@ -package com.upc.ld_admintool.domain.services.exceptions; - -import com.upc.ld_admintool.rest.DTO.SaveSyncResponseDTO; -import com.upc.ld_admintool.rest.DTO.SaveSyncStepDTO; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; - -import java.util.Arrays; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Tests unitarios para SaveSyncException - * Valida el manejo de excepciones personalizadas - */ -@DisplayName("SaveSyncException - Tests Unitarios") -class SaveSyncExceptionTest { - - private SaveSyncResponseDTO testResponse; - - @BeforeEach - void setUp() { - testResponse = new SaveSyncResponseDTO(); - testResponse.setSuccess(false); - - SaveSyncStepDTO step1 = new SaveSyncStepDTO(1, "step1", "Detail 1", "SUCCESS", null); - SaveSyncStepDTO step2 = new SaveSyncStepDTO(2, "step2", "Detail 2", "FAILED", "Step 2 failed"); - - testResponse.setSteps(Arrays.asList(step1, step2)); - } - - @Test - @DisplayName("Constructor con mensaje y response debe inicializar correctamente") - void testConstructorWithMessageAndResponse() { - String errorMessage = "Sync operation failed"; - - SaveSyncException exception = new SaveSyncException(errorMessage, testResponse); - - assertEquals(errorMessage, exception.getMessage()); - assertNotNull(exception.getResponse()); - assertEquals(testResponse, exception.getResponse()); - assertFalse(exception.getResponse().isSuccess()); - } - - @Test - @DisplayName("Constructor con mensaje, causa y response debe inicializar correctamente") - void testConstructorWithMessageCauseAndResponse() { - String errorMessage = "Sync operation failed with cause"; - Throwable cause = new RuntimeException("Root cause"); - - SaveSyncException exception = new SaveSyncException(errorMessage, cause, testResponse); - - assertEquals(errorMessage, exception.getMessage()); - assertEquals(cause, exception.getCause()); - assertNotNull(exception.getResponse()); - assertEquals(testResponse, exception.getResponse()); - } - - @Test - @DisplayName("getResponse debe retornar el response correcto") - void testGetResponse() { - SaveSyncException exception = new SaveSyncException("Error", testResponse); - - SaveSyncResponseDTO response = exception.getResponse(); - - assertNotNull(response); - assertFalse(response.isSuccess()); - assertEquals(2, response.getSteps().size()); - } - - @Test - @DisplayName("Exception debe ser instanceof RuntimeException") - void testIsRuntimeException() { - SaveSyncException exception = new SaveSyncException("Error", testResponse); - - assertTrue(exception instanceof RuntimeException); - } - - @Test - @DisplayName("Exception debe mantener la cadena de causas") - void testCauseChain() { - RuntimeException rootCause = new RuntimeException("Root"); - IllegalStateException middleCause = new IllegalStateException("Middle", rootCause); - SaveSyncException exception = new SaveSyncException("Top", middleCause, testResponse); - - assertEquals(middleCause, exception.getCause()); - assertEquals(rootCause, exception.getCause().getCause()); - } - - @Test - @DisplayName("Exception debe permitir response null") - void testNullResponse() { - SaveSyncException exception = new SaveSyncException("Error", (SaveSyncResponseDTO) null); - - assertNull(exception.getResponse()); - assertEquals("Error", exception.getMessage()); - } - - @Test - @DisplayName("Exception debe ser serializable para logging") - void testExceptionForLogging() { - SaveSyncException exception = new SaveSyncException("Sync failed", testResponse); - - String stackTrace = exception.toString(); - assertNotNull(stackTrace); - assertTrue(stackTrace.contains("SaveSyncException")); - } - - @Test - @DisplayName("Response debe contener información de pasos fallidos") - void testResponseWithFailedSteps() { - SaveSyncException exception = new SaveSyncException("Sync failed", testResponse); - - SaveSyncResponseDTO response = exception.getResponse(); - long failedSteps = response.getSteps().stream() - .filter(step -> "FAILED".equals(step.getStatus())) - .count(); - - assertEquals(1, failedSteps); - } - - @Test - @DisplayName("Exception debe manejar mensaje null") - void testNullMessage() { - SaveSyncException exception = new SaveSyncException(null, testResponse); - - assertNull(exception.getMessage()); - assertNotNull(exception.getResponse()); - } - - @Test - @DisplayName("Exception con response vacío debe funcionar correctamente") - void testEmptyResponse() { - SaveSyncResponseDTO emptyResponse = new SaveSyncResponseDTO(); - SaveSyncException exception = new SaveSyncException("Error", emptyResponse); - - assertNotNull(exception.getResponse()); - assertEquals(emptyResponse, exception.getResponse()); - } -} +package com.upc.ld_admintool.domain.services.exceptions; + +import com.upc.ld_admintool.rest.DTO.SaveSyncResponseDTO; +import com.upc.ld_admintool.rest.DTO.SaveSyncStepDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests unitarios para SaveSyncException + * Valida el manejo de excepciones personalizadas + */ +@DisplayName("SaveSyncException - Tests Unitarios") +class SaveSyncExceptionTest { + + private SaveSyncResponseDTO testResponse; + + @BeforeEach + void setUp() { + testResponse = new SaveSyncResponseDTO(); + testResponse.setSuccess(false); + + SaveSyncStepDTO step1 = new SaveSyncStepDTO(1, "step1", "Detail 1", "SUCCESS", null); + SaveSyncStepDTO step2 = new SaveSyncStepDTO(2, "step2", "Detail 2", "FAILED", "Step 2 failed"); + + testResponse.setSteps(Arrays.asList(step1, step2)); + } + + @Test + @DisplayName("Constructor con mensaje y response debe inicializar correctamente") + void testConstructorWithMessageAndResponse() { + String errorMessage = "Sync operation failed"; + + SaveSyncException exception = new SaveSyncException(errorMessage, testResponse); + + assertEquals(errorMessage, exception.getMessage()); + assertNotNull(exception.getResponse()); + assertEquals(testResponse, exception.getResponse()); + assertFalse(exception.getResponse().isSuccess()); + } + + @Test + @DisplayName("Constructor con mensaje, causa y response debe inicializar correctamente") + void testConstructorWithMessageCauseAndResponse() { + String errorMessage = "Sync operation failed with cause"; + Throwable cause = new RuntimeException("Root cause"); + + SaveSyncException exception = new SaveSyncException(errorMessage, cause, testResponse); + + assertEquals(errorMessage, exception.getMessage()); + assertEquals(cause, exception.getCause()); + assertNotNull(exception.getResponse()); + assertEquals(testResponse, exception.getResponse()); + } + + @Test + @DisplayName("getResponse debe retornar el response correcto") + void testGetResponse() { + SaveSyncException exception = new SaveSyncException("Error", testResponse); + + SaveSyncResponseDTO response = exception.getResponse(); + + assertNotNull(response); + assertFalse(response.isSuccess()); + assertEquals(2, response.getSteps().size()); + } + + @Test + @DisplayName("Exception debe ser instanceof RuntimeException") + void testIsRuntimeException() { + SaveSyncException exception = new SaveSyncException("Error", testResponse); + + assertTrue(exception instanceof RuntimeException); + } + + @Test + @DisplayName("Exception debe mantener la cadena de causas") + void testCauseChain() { + RuntimeException rootCause = new RuntimeException("Root"); + IllegalStateException middleCause = new IllegalStateException("Middle", rootCause); + SaveSyncException exception = new SaveSyncException("Top", middleCause, testResponse); + + assertEquals(middleCause, exception.getCause()); + assertEquals(rootCause, exception.getCause().getCause()); + } + + @Test + @DisplayName("Exception debe permitir response null") + void testNullResponse() { + SaveSyncException exception = new SaveSyncException("Error", (SaveSyncResponseDTO) null); + + assertNull(exception.getResponse()); + assertEquals("Error", exception.getMessage()); + } + + @Test + @DisplayName("Exception debe ser serializable para logging") + void testExceptionForLogging() { + SaveSyncException exception = new SaveSyncException("Sync failed", testResponse); + + String stackTrace = exception.toString(); + assertNotNull(stackTrace); + assertTrue(stackTrace.contains("SaveSyncException")); + } + + @Test + @DisplayName("Response debe contener información de pasos fallidos") + void testResponseWithFailedSteps() { + SaveSyncException exception = new SaveSyncException("Sync failed", testResponse); + + SaveSyncResponseDTO response = exception.getResponse(); + long failedSteps = response.getSteps().stream() + .filter(step -> "FAILED".equals(step.getStatus())) + .count(); + + assertEquals(1, failedSteps); + } + + @Test + @DisplayName("Exception debe manejar mensaje null") + void testNullMessage() { + SaveSyncException exception = new SaveSyncException(null, testResponse); + + assertNull(exception.getMessage()); + assertNotNull(exception.getResponse()); + } + + @Test + @DisplayName("Exception con response vacío debe funcionar correctamente") + void testEmptyResponse() { + SaveSyncResponseDTO emptyResponse = new SaveSyncResponseDTO(); + SaveSyncException exception = new SaveSyncException("Error", emptyResponse); + + assertNotNull(exception.getResponse()); + assertEquals(emptyResponse, exception.getResponse()); + } +} diff --git a/src/test/java/com/upc/ld_admintool/domain/services/validation/GitHubValidationServiceTest.java b/src/test/java/com/upc/ld_admintool/domain/services/validation/GitHubValidationServiceTest.java index a08abf1..59a5d97 100644 --- a/src/test/java/com/upc/ld_admintool/domain/services/validation/GitHubValidationServiceTest.java +++ b/src/test/java/com/upc/ld_admintool/domain/services/validation/GitHubValidationServiceTest.java @@ -1,407 +1,407 @@ -package com.upc.ld_admintool.domain.services.validation; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.*; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.RestTemplate; - -import java.util.Arrays; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -/** - * Tests unitarios para GitHubValidationService - * Valida la lógica de validación de organizaciones y usuarios en GitHub - */ -@ExtendWith(MockitoExtension.class) -@DisplayName("GitHubValidationService - Tests Unitarios") -class GitHubValidationServiceTest { - - @Mock - private RestTemplate restTemplate; - - @InjectMocks - private GitHubValidationService githubValidationService; - - private static final String GITHUB_API_URL = "https://api.github.com"; - private static final String GITHUB_TOKEN = "test-token"; - - @BeforeEach - void setUp() { - ReflectionTestUtils.setField(githubValidationService, "githubApiUrl", GITHUB_API_URL); - ReflectionTestUtils.setField(githubValidationService, "githubToken", GITHUB_TOKEN); - ReflectionTestUtils.setField(githubValidationService, "restTemplate", restTemplate); - } - - @Test - @DisplayName("validateOrganization debe validar organización existente correctamente") - void testValidateOrganization_Success() { - // Arrange - String org = "test-org"; - Object[] members = new Object[]{new Object(), new Object()}; - ResponseEntity responseEntity = new ResponseEntity<>(members, HttpStatus.OK); - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) - .thenReturn(responseEntity); - - // Act - ValidationResult result = githubValidationService.validateOrganization(org, null); - - // Assert - assertTrue(result.isValid()); - assertFalse(result.hasErrors()); - assertFalse(result.hasWarnings()); - } - - @Test - @DisplayName("validateOrganization debe retornar error si organización no existe") - void testValidateOrganization_NotFound() { - // Arrange - String org = "non-existent-org"; - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) - .thenThrow(HttpClientErrorException.NotFound.create(HttpStatus.NOT_FOUND, "Not Found", null, null, null)); - - // Act - ValidationResult result = githubValidationService.validateOrganization(org, null); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - } - - @Test - @DisplayName("validateOrganization debe retornar error si nombre está vacío") - void testValidateOrganization_EmptyName() { - // Act - ValidationResult result = githubValidationService.validateOrganization("", null); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - assertTrue(result.getErrors().get(0).contains("buit")); - } - - @Test - @DisplayName("validateOrganization debe retornar error si nombre es null") - void testValidateOrganization_NullName() { - // Act - ValidationResult result = githubValidationService.validateOrganization(null, null); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - } - - @Test - @DisplayName("validateOrganization debe retornar warning si organización tiene miembros privados") - void testValidateOrganization_PrivateMembers() { - // Arrange - String org = "private-org"; - Object[] emptyMembers = new Object[0]; - ResponseEntity responseEntity = new ResponseEntity<>(emptyMembers, HttpStatus.OK); - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) - .thenReturn(responseEntity); - - // Act - ValidationResult result = githubValidationService.validateOrganization(org, null); - - // Assert - assertTrue(result.isValid()); - assertFalse(result.hasErrors()); - assertTrue(result.hasWarnings()); - assertTrue(result.getWarnings().get(0).contains("membres privats")); - } - - @Test - @DisplayName("validateOrganization debe usar token del proyecto si está disponible") - void testValidateOrganization_WithProjectToken() { - // Arrange - String org = "test-org"; - String projectToken = "project-specific-token"; - Object[] members = new Object[]{new Object()}; - ResponseEntity responseEntity = new ResponseEntity<>(members, HttpStatus.OK); - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) - .thenReturn(responseEntity); - - // Act - ValidationResult result = githubValidationService.validateOrganization(org, projectToken); - - // Assert - assertTrue(result.isValid()); - verify(restTemplate, times(1)).exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class)); - } - - @Test - @DisplayName("validateOrganization debe manejar error de autorización") - void testValidateOrganization_Unauthorized() { - // Arrange - String org = "private-org"; - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) - .thenThrow(HttpClientErrorException.Unauthorized.create(HttpStatus.UNAUTHORIZED, "Unauthorized", null, null, null)); - - // Act - ValidationResult result = githubValidationService.validateOrganization(org, null); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - } - - @Test - @DisplayName("validateUsersInOrganization debe validar usuarios correctamente") - void testValidateUsersInOrganization_Success() { - // Arrange - String org = "test-org"; - List usernames = Arrays.asList("user1", "user2"); - - // Mock members response - Object[] members = new Object[]{ - createMockMember("user1"), - createMockMember("user2"), - createMockMember("user3") - }; - ResponseEntity responseEntity = new ResponseEntity<>(members, HttpStatus.OK); - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) - .thenReturn(responseEntity); - - // Act - ValidationResult result = githubValidationService.validateUsersInOrganization(org, usernames, null); - - // Assert - assertTrue(result.isValid()); - assertFalse(result.hasErrors()); - } - - @Test - @DisplayName("validateUsersInOrganization debe retornar error si usuario no es miembro") - void testValidateUsersInOrganization_UserNotMember() { - // Arrange - String org = "test-org"; - List usernames = Arrays.asList("user1", "nonmember"); - - Object[] members = new Object[]{createMockMember("user1")}; - ResponseEntity responseEntity = new ResponseEntity<>(members, HttpStatus.OK); - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) - .thenReturn(responseEntity); - - // Act - ValidationResult result = githubValidationService.validateUsersInOrganization(org, usernames, null); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - } - - @Test - @DisplayName("validateUsersInOrganization debe retornar válido si lista de usuarios está vacía") - void testValidateUsersInOrganization_EmptyList() { - // Act - ValidationResult result = githubValidationService.validateUsersInOrganization("test-org", Arrays.asList(), null); - - // Assert - assertTrue(result.isValid()); - assertFalse(result.hasErrors()); - } - - @Test - @DisplayName("validateUsersInOrganization debe retornar válido si lista es null") - void testValidateUsersInOrganization_NullList() { - // Act - ValidationResult result = githubValidationService.validateUsersInOrganization("test-org", null, null); - - // Assert - assertTrue(result.isValid()); - assertFalse(result.hasErrors()); - } - - @Test - @DisplayName("validateUsersInOrganization debe retornar error si miembros son privados") - void testValidateUsersInOrganization_PrivateMembers() { - // Arrange - String org = "private-org"; - List usernames = Arrays.asList("user1"); - - Object[] emptyMembers = new Object[0]; - ResponseEntity responseEntity = new ResponseEntity<>(emptyMembers, HttpStatus.OK); - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) - .thenReturn(responseEntity); - - // Act - ValidationResult result = githubValidationService.validateUsersInOrganization(org, usernames, null); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - } - - @Test - @DisplayName("validateUsersInOrganization debe manejar error de organización no encontrada") - void testValidateUsersInOrganization_OrgNotFound() { - // Arrange - String org = "non-existent-org"; - List usernames = Arrays.asList("user1"); - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) - .thenThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)); - - // Act - ValidationResult result = githubValidationService.validateUsersInOrganization(org, usernames, null); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - } - - - @Test - @DisplayName("validateOrganization debe capturar Exception genérica") - void testValidateOrganization_GenericException() { - // Arrange - String org = "test-org"; - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) - .thenThrow(new RuntimeException("Connection timeout")); - - // Act - ValidationResult result = githubValidationService.validateOrganization(org, null); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors(), "Should have errors: " + result.getErrors()); - assertTrue(result.getErrors().stream() - .anyMatch(e -> e.contains("Error validant") && e.contains("Connection timeout")), - "Error message should contain 'Error validant' and 'Connection timeout': " + result.getErrors()); - } - - @Test - @DisplayName("validateUser debe validar usuario existente correctamente") - void testValidateUser_Success() { - // Arrange - String username = "testuser"; - ResponseEntity responseEntity = new ResponseEntity<>(new java.util.HashMap<>(), HttpStatus.OK); - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(java.util.Map.class))) - .thenReturn(responseEntity); - - // Act - ValidationResult result = githubValidationService.validateUser(username, null); - - // Assert - assertTrue(result.isValid()); - assertFalse(result.hasErrors()); - verify(restTemplate).exchange( - eq(GITHUB_API_URL + "/users/" + username), - eq(HttpMethod.GET), - any(HttpEntity.class), - eq(java.util.Map.class) - ); - } - - @Test - @DisplayName("validateUser debe usar token del proyecto si se proporciona") - void testValidateUser_WithProjectToken() { - // Arrange - String username = "testuser"; - String projectToken = "project-specific-token"; - ResponseEntity responseEntity = new ResponseEntity<>(new java.util.HashMap<>(), HttpStatus.OK); - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(java.util.Map.class))) - .thenReturn(responseEntity); - - // Act - ValidationResult result = githubValidationService.validateUser(username, projectToken); - - // Assert - assertTrue(result.isValid()); - verify(restTemplate).exchange( - anyString(), - eq(HttpMethod.GET), - argThat(entity -> { - HttpHeaders headers = ((HttpEntity) entity).getHeaders(); - return headers.getFirst("Authorization") != null && - headers.getFirst("Authorization").equals("token " + projectToken); - }), - eq(java.util.Map.class) - ); - } - - @Test - @DisplayName("validateUser debe retornar error si usuario no existe (NotFound)") - void testValidateUser_NotFound() { - // Arrange - String username = "nonexistentuser"; - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(java.util.Map.class))) - .thenThrow(HttpClientErrorException.NotFound.create(HttpStatus.NOT_FOUND, "Not Found", null, null, null)); - - // Act - ValidationResult result = githubValidationService.validateUser(username, null); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors(), "Should have errors: " + result.getErrors()); - assertTrue(result.getErrors().stream() - .anyMatch(e -> e.contains(username) && e.contains("no existeix")), - "Error message should contain username and 'no existeix': " + result.getErrors()); - } - - @Test - @DisplayName("validateUser debe retornar error si respuesta no es OK") - void testValidateUser_NonOkStatus() { - // Arrange - String username = "testuser"; - ResponseEntity responseEntity = new ResponseEntity<>(new java.util.HashMap<>(), HttpStatus.FORBIDDEN); - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(java.util.Map.class))) - .thenReturn(responseEntity); - - // Act - ValidationResult result = githubValidationService.validateUser(username, null); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - assertTrue(result.getErrors().stream() - .anyMatch(e -> e.contains(username) && e.contains("no existeix"))); - } - - @Test - @DisplayName("validateUser debe capturar Exception genérica") - void testValidateUser_GenericException() { - // Arrange - String username = "testuser"; - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(java.util.Map.class))) - .thenThrow(new RuntimeException("Network error")); - - // Act - ValidationResult result = githubValidationService.validateUser(username, null); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - assertTrue(result.getErrors().stream() - .anyMatch(e -> e.contains("Error validant") && e.contains(username) && e.contains("Network error"))); - } - - private Object createMockMember(String login) { - return new java.util.HashMap() {{ - put("login", login); - }}; - } -} +package com.upc.ld_admintool.domain.services.validation; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.*; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * Tests unitarios para GitHubValidationService + * Valida la lógica de validación de organizaciones y usuarios en GitHub + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("GitHubValidationService - Tests Unitarios") +class GitHubValidationServiceTest { + + @Mock + private RestTemplate restTemplate; + + @InjectMocks + private GitHubValidationService githubValidationService; + + private static final String GITHUB_API_URL = "https://api.github.com"; + private static final String GITHUB_TOKEN = "test-token"; + + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(githubValidationService, "githubApiUrl", GITHUB_API_URL); + ReflectionTestUtils.setField(githubValidationService, "githubToken", GITHUB_TOKEN); + ReflectionTestUtils.setField(githubValidationService, "restTemplate", restTemplate); + } + + @Test + @DisplayName("validateOrganization debe validar organización existente correctamente") + void testValidateOrganization_Success() { + // Arrange + String org = "test-org"; + Object[] members = new Object[]{new Object(), new Object()}; + ResponseEntity responseEntity = new ResponseEntity<>(members, HttpStatus.OK); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) + .thenReturn(responseEntity); + + // Act + ValidationResult result = githubValidationService.validateOrganization(org, null); + + // Assert + assertTrue(result.isValid()); + assertFalse(result.hasErrors()); + assertFalse(result.hasWarnings()); + } + + @Test + @DisplayName("validateOrganization debe retornar error si organización no existe") + void testValidateOrganization_NotFound() { + // Arrange + String org = "non-existent-org"; + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) + .thenThrow(HttpClientErrorException.NotFound.create(HttpStatus.NOT_FOUND, "Not Found", null, null, null)); + + // Act + ValidationResult result = githubValidationService.validateOrganization(org, null); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + } + + @Test + @DisplayName("validateOrganization debe retornar error si nombre está vacío") + void testValidateOrganization_EmptyName() { + // Act + ValidationResult result = githubValidationService.validateOrganization("", null); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + assertTrue(result.getErrors().get(0).contains("buit")); + } + + @Test + @DisplayName("validateOrganization debe retornar error si nombre es null") + void testValidateOrganization_NullName() { + // Act + ValidationResult result = githubValidationService.validateOrganization(null, null); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + } + + @Test + @DisplayName("validateOrganization debe retornar warning si organización tiene miembros privados") + void testValidateOrganization_PrivateMembers() { + // Arrange + String org = "private-org"; + Object[] emptyMembers = new Object[0]; + ResponseEntity responseEntity = new ResponseEntity<>(emptyMembers, HttpStatus.OK); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) + .thenReturn(responseEntity); + + // Act + ValidationResult result = githubValidationService.validateOrganization(org, null); + + // Assert + assertTrue(result.isValid()); + assertFalse(result.hasErrors()); + assertTrue(result.hasWarnings()); + assertTrue(result.getWarnings().get(0).contains("membres privats")); + } + + @Test + @DisplayName("validateOrganization debe usar token del proyecto si está disponible") + void testValidateOrganization_WithProjectToken() { + // Arrange + String org = "test-org"; + String projectToken = "project-specific-token"; + Object[] members = new Object[]{new Object()}; + ResponseEntity responseEntity = new ResponseEntity<>(members, HttpStatus.OK); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) + .thenReturn(responseEntity); + + // Act + ValidationResult result = githubValidationService.validateOrganization(org, projectToken); + + // Assert + assertTrue(result.isValid()); + verify(restTemplate, times(1)).exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class)); + } + + @Test + @DisplayName("validateOrganization debe manejar error de autorización") + void testValidateOrganization_Unauthorized() { + // Arrange + String org = "private-org"; + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) + .thenThrow(HttpClientErrorException.Unauthorized.create(HttpStatus.UNAUTHORIZED, "Unauthorized", null, null, null)); + + // Act + ValidationResult result = githubValidationService.validateOrganization(org, null); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + } + + @Test + @DisplayName("validateUsersInOrganization debe validar usuarios correctamente") + void testValidateUsersInOrganization_Success() { + // Arrange + String org = "test-org"; + List usernames = Arrays.asList("user1", "user2"); + + // Mock members response + Object[] members = new Object[]{ + createMockMember("user1"), + createMockMember("user2"), + createMockMember("user3") + }; + ResponseEntity responseEntity = new ResponseEntity<>(members, HttpStatus.OK); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) + .thenReturn(responseEntity); + + // Act + ValidationResult result = githubValidationService.validateUsersInOrganization(org, usernames, null); + + // Assert + assertTrue(result.isValid()); + assertFalse(result.hasErrors()); + } + + @Test + @DisplayName("validateUsersInOrganization debe retornar error si usuario no es miembro") + void testValidateUsersInOrganization_UserNotMember() { + // Arrange + String org = "test-org"; + List usernames = Arrays.asList("user1", "nonmember"); + + Object[] members = new Object[]{createMockMember("user1")}; + ResponseEntity responseEntity = new ResponseEntity<>(members, HttpStatus.OK); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) + .thenReturn(responseEntity); + + // Act + ValidationResult result = githubValidationService.validateUsersInOrganization(org, usernames, null); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + } + + @Test + @DisplayName("validateUsersInOrganization debe retornar válido si lista de usuarios está vacía") + void testValidateUsersInOrganization_EmptyList() { + // Act + ValidationResult result = githubValidationService.validateUsersInOrganization("test-org", Arrays.asList(), null); + + // Assert + assertTrue(result.isValid()); + assertFalse(result.hasErrors()); + } + + @Test + @DisplayName("validateUsersInOrganization debe retornar válido si lista es null") + void testValidateUsersInOrganization_NullList() { + // Act + ValidationResult result = githubValidationService.validateUsersInOrganization("test-org", null, null); + + // Assert + assertTrue(result.isValid()); + assertFalse(result.hasErrors()); + } + + @Test + @DisplayName("validateUsersInOrganization debe retornar error si miembros son privados") + void testValidateUsersInOrganization_PrivateMembers() { + // Arrange + String org = "private-org"; + List usernames = Arrays.asList("user1"); + + Object[] emptyMembers = new Object[0]; + ResponseEntity responseEntity = new ResponseEntity<>(emptyMembers, HttpStatus.OK); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) + .thenReturn(responseEntity); + + // Act + ValidationResult result = githubValidationService.validateUsersInOrganization(org, usernames, null); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + } + + @Test + @DisplayName("validateUsersInOrganization debe manejar error de organización no encontrada") + void testValidateUsersInOrganization_OrgNotFound() { + // Arrange + String org = "non-existent-org"; + List usernames = Arrays.asList("user1"); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) + .thenThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)); + + // Act + ValidationResult result = githubValidationService.validateUsersInOrganization(org, usernames, null); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + } + + + @Test + @DisplayName("validateOrganization debe capturar Exception genérica") + void testValidateOrganization_GenericException() { + // Arrange + String org = "test-org"; + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object[].class))) + .thenThrow(new RuntimeException("Connection timeout")); + + // Act + ValidationResult result = githubValidationService.validateOrganization(org, null); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors(), "Should have errors: " + result.getErrors()); + assertTrue(result.getErrors().stream() + .anyMatch(e -> e.contains("Error validant") && e.contains("Connection timeout")), + "Error message should contain 'Error validant' and 'Connection timeout': " + result.getErrors()); + } + + @Test + @DisplayName("validateUser debe validar usuario existente correctamente") + void testValidateUser_Success() { + // Arrange + String username = "testuser"; + ResponseEntity responseEntity = new ResponseEntity<>(new java.util.HashMap<>(), HttpStatus.OK); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(java.util.Map.class))) + .thenReturn(responseEntity); + + // Act + ValidationResult result = githubValidationService.validateUser(username, null); + + // Assert + assertTrue(result.isValid()); + assertFalse(result.hasErrors()); + verify(restTemplate).exchange( + eq(GITHUB_API_URL + "/users/" + username), + eq(HttpMethod.GET), + any(HttpEntity.class), + eq(java.util.Map.class) + ); + } + + @Test + @DisplayName("validateUser debe usar token del proyecto si se proporciona") + void testValidateUser_WithProjectToken() { + // Arrange + String username = "testuser"; + String projectToken = "project-specific-token"; + ResponseEntity responseEntity = new ResponseEntity<>(new java.util.HashMap<>(), HttpStatus.OK); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(java.util.Map.class))) + .thenReturn(responseEntity); + + // Act + ValidationResult result = githubValidationService.validateUser(username, projectToken); + + // Assert + assertTrue(result.isValid()); + verify(restTemplate).exchange( + anyString(), + eq(HttpMethod.GET), + argThat(entity -> { + HttpHeaders headers = ((HttpEntity) entity).getHeaders(); + return headers.getFirst("Authorization") != null && + headers.getFirst("Authorization").equals("token " + projectToken); + }), + eq(java.util.Map.class) + ); + } + + @Test + @DisplayName("validateUser debe retornar error si usuario no existe (NotFound)") + void testValidateUser_NotFound() { + // Arrange + String username = "nonexistentuser"; + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(java.util.Map.class))) + .thenThrow(HttpClientErrorException.NotFound.create(HttpStatus.NOT_FOUND, "Not Found", null, null, null)); + + // Act + ValidationResult result = githubValidationService.validateUser(username, null); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors(), "Should have errors: " + result.getErrors()); + assertTrue(result.getErrors().stream() + .anyMatch(e -> e.contains(username) && e.contains("no existeix")), + "Error message should contain username and 'no existeix': " + result.getErrors()); + } + + @Test + @DisplayName("validateUser debe retornar error si respuesta no es OK") + void testValidateUser_NonOkStatus() { + // Arrange + String username = "testuser"; + ResponseEntity responseEntity = new ResponseEntity<>(new java.util.HashMap<>(), HttpStatus.FORBIDDEN); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(java.util.Map.class))) + .thenReturn(responseEntity); + + // Act + ValidationResult result = githubValidationService.validateUser(username, null); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + assertTrue(result.getErrors().stream() + .anyMatch(e -> e.contains(username) && e.contains("no existeix"))); + } + + @Test + @DisplayName("validateUser debe capturar Exception genérica") + void testValidateUser_GenericException() { + // Arrange + String username = "testuser"; + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(java.util.Map.class))) + .thenThrow(new RuntimeException("Network error")); + + // Act + ValidationResult result = githubValidationService.validateUser(username, null); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + assertTrue(result.getErrors().stream() + .anyMatch(e -> e.contains("Error validant") && e.contains(username) && e.contains("Network error"))); + } + + private Object createMockMember(String login) { + return new java.util.HashMap() {{ + put("login", login); + }}; + } +} diff --git a/src/test/java/com/upc/ld_admintool/domain/services/validation/ProjectValidationServiceTest.java b/src/test/java/com/upc/ld_admintool/domain/services/validation/ProjectValidationServiceTest.java index 2e68c68..713ccf9 100644 --- a/src/test/java/com/upc/ld_admintool/domain/services/validation/ProjectValidationServiceTest.java +++ b/src/test/java/com/upc/ld_admintool/domain/services/validation/ProjectValidationServiceTest.java @@ -1,521 +1,521 @@ -package com.upc.ld_admintool.domain.services.validation; - -import com.upc.ld_admintool.domain.services.LDService; -import com.upc.ld_admintool.domain.utils.DataSource; -import com.upc.ld_admintool.rest.DTO.*; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.*; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -/** - * Tests unitarios para ProjectValidationService - * Valida la funcionalidad de validación de proyectos y estudiantes - */ -@ExtendWith(MockitoExtension.class) -@DisplayName("ProjectValidationService - Tests Unitarios") -class ProjectValidationServiceTest { - - @Mock - private GitHubValidationService githubValidationService; - - @Mock - private TaigaValidationService taigaValidationService; - - @Mock - private LDService ldService; - - @InjectMocks - private ProjectValidationService projectValidationService; - - private ProjectDTO createValidProject() { - ProjectDTO project = new ProjectDTO(); - project.setId(1L); - project.setName("Test Project"); - project.setExternalId("test-project"); - project.setGithubToken("github-token"); - - Map identities = new HashMap<>(); - - ProjectIdentityDTO githubIdentity = new ProjectIdentityDTO(); - githubIdentity.setUrl("https://github.com/testorg"); - identities.put(DataSource.GITHUB, githubIdentity); - - ProjectIdentityDTO taigaIdentity = new ProjectIdentityDTO(); - taigaIdentity.setUrl("https://tree.taiga.io/project/testuser-testproject/"); - identities.put(DataSource.TAIGA, taigaIdentity); - - project.setIdentities(identities); - - return project; - } - - private StudentDTO createStudent(String name, String githubUsername, String taigaUsername) { - StudentDTO student = new StudentDTO(); - student.setName(name); - - Map identities = new HashMap<>(); - - if (githubUsername != null) { - StudentIdentityDTO githubIdentity = new StudentIdentityDTO(); - githubIdentity.setUsername(githubUsername); - identities.put(DataSource.GITHUB, githubIdentity); - } - - if (taigaUsername != null) { - StudentIdentityDTO taigaIdentity = new StudentIdentityDTO(); - taigaIdentity.setUsername(taigaUsername); - identities.put(DataSource.TAIGA, taigaIdentity); - } - - student.setIdentities(identities); - return student; - } - - @BeforeEach - void setUp() { - lenient().when(githubValidationService.validateOrganization(anyString(), anyString())) - .thenReturn(new ValidationResult(true)); - lenient().when(githubValidationService.validateUsersInOrganization(anyString(), anyList(), anyString())) - .thenReturn(new ValidationResult(true)); - lenient().when(taigaValidationService.validateProjectBySlug(anyString())) - .thenReturn(new ValidationResult(true)); - lenient().when(taigaValidationService.validateUsersInProject(anyString(), anyList())) - .thenReturn(new ValidationResult(true)); - } - - @Test - @DisplayName("validateProject debe validar un proyecto válido") - void testValidateProject_ValidProject() { - // Arrange - ProjectDTO project = createValidProject(); - - // Act - ValidationResult result = projectValidationService.validateProject(project); - - // Assert - assertTrue(result.isValid()); - assertFalse(result.hasErrors()); - verify(githubValidationService).validateOrganization("testorg", "github-token"); - verify(taigaValidationService).validateProjectBySlug("testuser-testproject"); - } - - @Test - @DisplayName("validateProject debe retornar error si no tiene identidades") - void testValidateProject_NoIdentities() { - // Arrange - ProjectDTO project = new ProjectDTO(); - project.setName("Test Project"); - project.setIdentities(null); - - // Act - ValidationResult result = projectValidationService.validateProject(project); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - assertTrue(result.getErrors().get(0).contains("does not have defined identities")); - } - - @Test - @DisplayName("validateProject debe retornar error si identidades está vacío") - void testValidateProject_EmptyIdentities() { - // Arrange - ProjectDTO project = new ProjectDTO(); - project.setName("Test Project"); - project.setIdentities(new HashMap<>()); - - // Act - ValidationResult result = projectValidationService.validateProject(project); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - } - - @Test - @DisplayName("validateProject debe agregar warning si no tiene URL de GitHub") - void testValidateProject_NoGitHubURL() { - // Arrange - ProjectDTO project = new ProjectDTO(); - project.setName("Test Project"); - - Map identities = new HashMap<>(); - ProjectIdentityDTO taigaIdentity = new ProjectIdentityDTO(); - taigaIdentity.setUrl("https://tree.taiga.io/project/test/"); - identities.put(DataSource.TAIGA, taigaIdentity); - - project.setIdentities(identities); - - // Act - ValidationResult result = projectValidationService.validateProject(project); - - // Assert - assertTrue(result.hasWarnings()); - assertTrue(result.getWarnings().stream() - .anyMatch(w -> w.contains("does not have a defined GitHub URL"))); - } - - @Test - @DisplayName("validateProject debe agregar warning si no tiene URL de Taiga") - void testValidateProject_NoTaigaURL() { - // Arrange - ProjectDTO project = new ProjectDTO(); - project.setName("Test Project"); - - Map identities = new HashMap<>(); - ProjectIdentityDTO githubIdentity = new ProjectIdentityDTO(); - githubIdentity.setUrl("https://github.com/testorg"); - identities.put(DataSource.GITHUB, githubIdentity); - - project.setIdentities(identities); - - // Mock explícito para este test - when(githubValidationService.validateOrganization("testorg", null)) - .thenReturn(new ValidationResult(true)); - - // Act - ValidationResult result = projectValidationService.validateProject(project); - - // Assert - assertTrue(result.hasWarnings()); - assertTrue(result.getWarnings().stream() - .anyMatch(w -> w.contains("does not have a defined Taiga URL"))); - } - - @Test - @DisplayName("validateProject debe validar estudiantes en GitHub") - void testValidateProject_WithGitHubStudents() { - // Arrange - ProjectDTO project = createValidProject(); - List students = Arrays.asList( - createStudent("Student 1", "github1", "taiga1"), - createStudent("Student 2", "github2", "taiga2") - ); - project.setStudents(students); - - // Act - ValidationResult result = projectValidationService.validateProject(project); - - // Assert - assertTrue(result.isValid()); - verify(githubValidationService).validateUsersInOrganization( - eq("testorg"), - argThat(list -> list.size() == 2 && list.contains("github1") && list.contains("github2")), - eq("github-token") - ); - } - - @Test - @DisplayName("validateProject debe validar estudiantes en Taiga") - void testValidateProject_WithTaigaStudents() { - // Arrange - ProjectDTO project = createValidProject(); - List students = Arrays.asList( - createStudent("Student 1", "github1", "taiga1"), - createStudent("Student 2", "github2", "taiga2") - ); - project.setStudents(students); - - // Act - ValidationResult result = projectValidationService.validateProject(project); - - // Assert - assertTrue(result.isValid()); - verify(taigaValidationService).validateUsersInProject( - eq("testuser-testproject"), - argThat(list -> list.size() == 2 && list.contains("taiga1") && list.contains("taiga2")) - ); - } - - @Test - @DisplayName("validateProject debe manejar URL de GitHub inválida") - void testValidateProject_InvalidGitHubURL() { - // Arrange - ProjectDTO project = new ProjectDTO(); - project.setName("Test Project"); - - Map identities = new HashMap<>(); - ProjectIdentityDTO githubIdentity = new ProjectIdentityDTO(); - githubIdentity.setUrl("invalid-url"); - identities.put(DataSource.GITHUB, githubIdentity); - - project.setIdentities(identities); - - // Act - ValidationResult result = projectValidationService.validateProject(project); - - // Assert - assertTrue(result.hasErrors()); - assertTrue(result.getErrors().stream() - .anyMatch(e -> e.contains("Invalid GitHub URL format"))); - } - - @Test - @DisplayName("validateProject debe manejar URL de Taiga inválida") - void testValidateProject_InvalidTaigaURL() { - // Arrange - ProjectDTO project = new ProjectDTO(); - project.setName("Test Project"); - - Map identities = new HashMap<>(); - ProjectIdentityDTO taigaIdentity = new ProjectIdentityDTO(); - taigaIdentity.setUrl("invalid-taiga-url"); - identities.put(DataSource.TAIGA, taigaIdentity); - - project.setIdentities(identities); - - // Act - ValidationResult result = projectValidationService.validateProject(project); - - // Assert - assertTrue(result.hasErrors()); - assertTrue(result.getErrors().stream() - .anyMatch(e -> e.contains("Invalid Taiga URL format"))); - } - - @Test - @DisplayName("validateProject debe propagar errores de GitHub validation") - void testValidateProject_GitHubValidationErrors() { - // Arrange - ProjectDTO project = createValidProject(); - ValidationResult githubError = new ValidationResult(false); - githubError.addError("GitHub organization not found"); - - when(githubValidationService.validateOrganization(anyString(), anyString())) - .thenReturn(githubError); - - // Act - ValidationResult result = projectValidationService.validateProject(project); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.getErrors().contains("GitHub organization not found")); - } - - @Test - @DisplayName("validateProject debe propagar errores de Taiga validation") - void testValidateProject_TaigaValidationErrors() { - // Arrange - ProjectDTO project = createValidProject(); - ValidationResult taigaError = new ValidationResult(false); - taigaError.addError("Taiga project not found"); - - when(taigaValidationService.validateProjectBySlug(anyString())) - .thenReturn(taigaError); - - // Act - ValidationResult result = projectValidationService.validateProject(project); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.getErrors().contains("Taiga project not found")); - } - - @Test - @DisplayName("validateProjectsWithDetails debe validar múltiples proyectos") - void testValidateProjectsWithDetails_MultipleProjects() { - // Arrange - when(ldService.getAllProjects()).thenReturn(new ArrayList<>()); - - List projects = Arrays.asList( - createValidProject(), - createValidProject() - ); - projects.get(1).setName("Project 2"); - projects.get(1).setExternalId("project-2"); - - // Act - Map result = projectValidationService.validateProjectsWithDetails(projects); - - // Assert - assertEquals(2, result.get("totalProjects")); - assertEquals(2, result.get("validCount")); - assertEquals(0, result.get("invalidCount")); - } - - @Test - @DisplayName("validateProjectsWithDetails debe detectar proyectos duplicados en archivo") - void testValidateProjectsWithDetails_DuplicatedInFile() { - // Arrange - when(ldService.getAllProjects()).thenReturn(new ArrayList<>()); - - ProjectDTO project1 = createValidProject(); - ProjectDTO project2 = createValidProject(); // Same externalId - - List projects = Arrays.asList(project1, project2); - - // Act - Map result = projectValidationService.validateProjectsWithDetails(projects); - - // Assert - assertEquals(2, result.get("totalProjects")); - assertEquals(1, result.get("validCount")); - assertEquals(1, result.get("invalidCount")); - - @SuppressWarnings("unchecked") - List> invalidProjects = (List>) result.get("invalidProjects"); - assertTrue(invalidProjects.get(0).get("errors").toString().contains("duplicated")); - } - - @Test - @DisplayName("validateProjectsWithDetails debe detectar proyectos que ya existen en BD") - void testValidateProjectsWithDetails_AlreadyExists() { - // Arrange - ProjectDTO existingProject = createValidProject(); - when(ldService.getAllProjects()).thenReturn(Arrays.asList(existingProject)); - - ProjectDTO newProject = createValidProject(); // Same externalId - List projects = Arrays.asList(newProject); - - // Act - Map result = projectValidationService.validateProjectsWithDetails(projects); - - // Assert - assertEquals(1, result.get("totalProjects")); - assertEquals(0, result.get("validCount")); - assertEquals(1, result.get("invalidCount")); - - @SuppressWarnings("unchecked") - List> invalidProjects = (List>) result.get("invalidProjects"); - assertTrue(invalidProjects.get(0).get("errors").toString().contains("already exists")); - } - - @Test - @DisplayName("validateStudent debe validar un estudiante correctamente") - void testValidateStudent_ValidStudent() { - // Arrange - StudentDTO student = createStudent("Test Student", "github1", "taiga1"); - String githubUrl = "https://github.com/testorg"; - String taigaUrl = "https://tree.taiga.io/project/testuser-testproject/"; - - // Act - ValidationResult result = projectValidationService.validateStudent( - githubUrl, taigaUrl, "token", student); - - // Assert - assertTrue(result.isValid()); - verify(githubValidationService).validateUsersInOrganization( - eq("testorg"), - argThat(list -> list.contains("github1")), - eq("token") - ); - verify(taigaValidationService).validateUsersInProject( - eq("testuser-testproject"), - argThat(list -> list.contains("taiga1")) - ); - } - - @Test - @DisplayName("validateStudent debe retornar error si estudiante es null") - void testValidateStudent_NullStudent() { - // Act - ValidationResult result = projectValidationService.validateStudent( - "url", "url", "token", null); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - } - - @Test - @DisplayName("validateStudent debe retornar error si no tiene identidades") - void testValidateStudent_NoIdentities() { - // Arrange - StudentDTO student = new StudentDTO(); - student.setName("Test"); - student.setIdentities(null); - - // Act - ValidationResult result = projectValidationService.validateStudent( - "url", "url", "token", student); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.getErrors().get(0).contains("has no defined identities")); - } - - @Test - @DisplayName("validateStudent debe manejar URLs nulas") - void testValidateStudent_NullURLs() { - // Arrange - StudentDTO student = createStudent("Test", "github1", "taiga1"); - - // Act - ValidationResult result = projectValidationService.validateStudent( - null, null, "token", student); - - // Assert - assertTrue(result.isValid()); - verify(githubValidationService, never()).validateUsersInOrganization(anyString(), anyList(), anyString()); - verify(taigaValidationService, never()).validateUsersInProject(anyString(), anyList()); - } - - @Test - @DisplayName("validateProjectsWithDetails debe manejar excepción al cargar proyectos existentes") - void testValidateProjectsWithDetails_ExceptionLoadingExisting() { - // Arrange - when(ldService.getAllProjects()).thenThrow(new RuntimeException("Database error")); - - List projects = Arrays.asList(createValidProject()); - - // Act - Map result = projectValidationService.validateProjectsWithDetails(projects); - - // Assert - assertEquals(1, result.get("totalProjects")); - assertEquals(1, result.get("validCount")); // Should continue with validation - } - - @Test - @DisplayName("validateProject debe manejar proyecto sin token GitHub") - void testValidateProject_NoGitHubToken() { - // Arrange - ProjectDTO project = createValidProject(); - project.setGithubToken(null); - - // Mock explícito para este test - when(githubValidationService.validateOrganization("testorg", null)) - .thenReturn(new ValidationResult(true)); - when(taigaValidationService.validateProjectBySlug("testuser-testproject")) - .thenReturn(new ValidationResult(true)); - - // Act - ValidationResult result = projectValidationService.validateProject(project); - - // Assert - assertTrue(result.isValid()); - verify(githubValidationService).validateOrganization("testorg", null); - } - - @Test - @DisplayName("validateProject debe manejar estudiantes sin identidades GitHub") - void testValidateProject_StudentsWithoutGitHub() { - // Arrange - ProjectDTO project = createValidProject(); - List students = Arrays.asList( - createStudent("Student 1", null, "taiga1") - ); - project.setStudents(students); - - // Act - ValidationResult result = projectValidationService.validateProject(project); - - // Assert - assertTrue(result.isValid()); - verify(githubValidationService, never()).validateUsersInOrganization( - anyString(), - argThat(list -> list.isEmpty()), - anyString() - ); - } -} +package com.upc.ld_admintool.domain.services.validation; + +import com.upc.ld_admintool.domain.services.LDService; +import com.upc.ld_admintool.domain.utils.DataSource; +import com.upc.ld_admintool.rest.DTO.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * Tests unitarios para ProjectValidationService + * Valida la funcionalidad de validación de proyectos y estudiantes + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("ProjectValidationService - Tests Unitarios") +class ProjectValidationServiceTest { + + @Mock + private GitHubValidationService githubValidationService; + + @Mock + private TaigaValidationService taigaValidationService; + + @Mock + private LDService ldService; + + @InjectMocks + private ProjectValidationService projectValidationService; + + private ProjectDTO createValidProject() { + ProjectDTO project = new ProjectDTO(); + project.setId(1L); + project.setName("Test Project"); + project.setExternalId("test-project"); + project.setGithubToken("github-token"); + + Map identities = new HashMap<>(); + + ProjectIdentityDTO githubIdentity = new ProjectIdentityDTO(); + githubIdentity.setUrl("https://github.com/testorg"); + identities.put(DataSource.GITHUB, githubIdentity); + + ProjectIdentityDTO taigaIdentity = new ProjectIdentityDTO(); + taigaIdentity.setUrl("https://tree.taiga.io/project/testuser-testproject/"); + identities.put(DataSource.TAIGA, taigaIdentity); + + project.setIdentities(identities); + + return project; + } + + private StudentDTO createStudent(String name, String githubUsername, String taigaUsername) { + StudentDTO student = new StudentDTO(); + student.setName(name); + + Map identities = new HashMap<>(); + + if (githubUsername != null) { + StudentIdentityDTO githubIdentity = new StudentIdentityDTO(); + githubIdentity.setUsername(githubUsername); + identities.put(DataSource.GITHUB, githubIdentity); + } + + if (taigaUsername != null) { + StudentIdentityDTO taigaIdentity = new StudentIdentityDTO(); + taigaIdentity.setUsername(taigaUsername); + identities.put(DataSource.TAIGA, taigaIdentity); + } + + student.setIdentities(identities); + return student; + } + + @BeforeEach + void setUp() { + lenient().when(githubValidationService.validateOrganization(anyString(), anyString())) + .thenReturn(new ValidationResult(true)); + lenient().when(githubValidationService.validateUsersInOrganization(anyString(), anyList(), anyString())) + .thenReturn(new ValidationResult(true)); + lenient().when(taigaValidationService.validateProjectBySlug(anyString())) + .thenReturn(new ValidationResult(true)); + lenient().when(taigaValidationService.validateUsersInProject(anyString(), anyList())) + .thenReturn(new ValidationResult(true)); + } + + @Test + @DisplayName("validateProject debe validar un proyecto válido") + void testValidateProject_ValidProject() { + // Arrange + ProjectDTO project = createValidProject(); + + // Act + ValidationResult result = projectValidationService.validateProject(project); + + // Assert + assertTrue(result.isValid()); + assertFalse(result.hasErrors()); + verify(githubValidationService).validateOrganization("testorg", "github-token"); + verify(taigaValidationService).validateProjectBySlug("testuser-testproject"); + } + + @Test + @DisplayName("validateProject debe retornar error si no tiene identidades") + void testValidateProject_NoIdentities() { + // Arrange + ProjectDTO project = new ProjectDTO(); + project.setName("Test Project"); + project.setIdentities(null); + + // Act + ValidationResult result = projectValidationService.validateProject(project); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + assertTrue(result.getErrors().get(0).contains("does not have defined identities")); + } + + @Test + @DisplayName("validateProject debe retornar error si identidades está vacío") + void testValidateProject_EmptyIdentities() { + // Arrange + ProjectDTO project = new ProjectDTO(); + project.setName("Test Project"); + project.setIdentities(new HashMap<>()); + + // Act + ValidationResult result = projectValidationService.validateProject(project); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + } + + @Test + @DisplayName("validateProject debe agregar warning si no tiene URL de GitHub") + void testValidateProject_NoGitHubURL() { + // Arrange + ProjectDTO project = new ProjectDTO(); + project.setName("Test Project"); + + Map identities = new HashMap<>(); + ProjectIdentityDTO taigaIdentity = new ProjectIdentityDTO(); + taigaIdentity.setUrl("https://tree.taiga.io/project/test/"); + identities.put(DataSource.TAIGA, taigaIdentity); + + project.setIdentities(identities); + + // Act + ValidationResult result = projectValidationService.validateProject(project); + + // Assert + assertTrue(result.hasWarnings()); + assertTrue(result.getWarnings().stream() + .anyMatch(w -> w.contains("does not have a defined GitHub URL"))); + } + + @Test + @DisplayName("validateProject debe agregar warning si no tiene URL de Taiga") + void testValidateProject_NoTaigaURL() { + // Arrange + ProjectDTO project = new ProjectDTO(); + project.setName("Test Project"); + + Map identities = new HashMap<>(); + ProjectIdentityDTO githubIdentity = new ProjectIdentityDTO(); + githubIdentity.setUrl("https://github.com/testorg"); + identities.put(DataSource.GITHUB, githubIdentity); + + project.setIdentities(identities); + + // Mock explícito para este test + when(githubValidationService.validateOrganization("testorg", null)) + .thenReturn(new ValidationResult(true)); + + // Act + ValidationResult result = projectValidationService.validateProject(project); + + // Assert + assertTrue(result.hasWarnings()); + assertTrue(result.getWarnings().stream() + .anyMatch(w -> w.contains("does not have a defined Taiga URL"))); + } + + @Test + @DisplayName("validateProject debe validar estudiantes en GitHub") + void testValidateProject_WithGitHubStudents() { + // Arrange + ProjectDTO project = createValidProject(); + List students = Arrays.asList( + createStudent("Student 1", "github1", "taiga1"), + createStudent("Student 2", "github2", "taiga2") + ); + project.setStudents(students); + + // Act + ValidationResult result = projectValidationService.validateProject(project); + + // Assert + assertTrue(result.isValid()); + verify(githubValidationService).validateUsersInOrganization( + eq("testorg"), + argThat(list -> list.size() == 2 && list.contains("github1") && list.contains("github2")), + eq("github-token") + ); + } + + @Test + @DisplayName("validateProject debe validar estudiantes en Taiga") + void testValidateProject_WithTaigaStudents() { + // Arrange + ProjectDTO project = createValidProject(); + List students = Arrays.asList( + createStudent("Student 1", "github1", "taiga1"), + createStudent("Student 2", "github2", "taiga2") + ); + project.setStudents(students); + + // Act + ValidationResult result = projectValidationService.validateProject(project); + + // Assert + assertTrue(result.isValid()); + verify(taigaValidationService).validateUsersInProject( + eq("testuser-testproject"), + argThat(list -> list.size() == 2 && list.contains("taiga1") && list.contains("taiga2")) + ); + } + + @Test + @DisplayName("validateProject debe manejar URL de GitHub inválida") + void testValidateProject_InvalidGitHubURL() { + // Arrange + ProjectDTO project = new ProjectDTO(); + project.setName("Test Project"); + + Map identities = new HashMap<>(); + ProjectIdentityDTO githubIdentity = new ProjectIdentityDTO(); + githubIdentity.setUrl("invalid-url"); + identities.put(DataSource.GITHUB, githubIdentity); + + project.setIdentities(identities); + + // Act + ValidationResult result = projectValidationService.validateProject(project); + + // Assert + assertTrue(result.hasErrors()); + assertTrue(result.getErrors().stream() + .anyMatch(e -> e.contains("Invalid GitHub URL format"))); + } + + @Test + @DisplayName("validateProject debe manejar URL de Taiga inválida") + void testValidateProject_InvalidTaigaURL() { + // Arrange + ProjectDTO project = new ProjectDTO(); + project.setName("Test Project"); + + Map identities = new HashMap<>(); + ProjectIdentityDTO taigaIdentity = new ProjectIdentityDTO(); + taigaIdentity.setUrl("invalid-taiga-url"); + identities.put(DataSource.TAIGA, taigaIdentity); + + project.setIdentities(identities); + + // Act + ValidationResult result = projectValidationService.validateProject(project); + + // Assert + assertTrue(result.hasErrors()); + assertTrue(result.getErrors().stream() + .anyMatch(e -> e.contains("Invalid Taiga URL format"))); + } + + @Test + @DisplayName("validateProject debe propagar errores de GitHub validation") + void testValidateProject_GitHubValidationErrors() { + // Arrange + ProjectDTO project = createValidProject(); + ValidationResult githubError = new ValidationResult(false); + githubError.addError("GitHub organization not found"); + + when(githubValidationService.validateOrganization(anyString(), anyString())) + .thenReturn(githubError); + + // Act + ValidationResult result = projectValidationService.validateProject(project); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.getErrors().contains("GitHub organization not found")); + } + + @Test + @DisplayName("validateProject debe propagar errores de Taiga validation") + void testValidateProject_TaigaValidationErrors() { + // Arrange + ProjectDTO project = createValidProject(); + ValidationResult taigaError = new ValidationResult(false); + taigaError.addError("Taiga project not found"); + + when(taigaValidationService.validateProjectBySlug(anyString())) + .thenReturn(taigaError); + + // Act + ValidationResult result = projectValidationService.validateProject(project); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.getErrors().contains("Taiga project not found")); + } + + @Test + @DisplayName("validateProjectsWithDetails debe validar múltiples proyectos") + void testValidateProjectsWithDetails_MultipleProjects() { + // Arrange + when(ldService.getAllProjects()).thenReturn(new ArrayList<>()); + + List projects = Arrays.asList( + createValidProject(), + createValidProject() + ); + projects.get(1).setName("Project 2"); + projects.get(1).setExternalId("project-2"); + + // Act + Map result = projectValidationService.validateProjectsWithDetails(projects); + + // Assert + assertEquals(2, result.get("totalProjects")); + assertEquals(2, result.get("validCount")); + assertEquals(0, result.get("invalidCount")); + } + + @Test + @DisplayName("validateProjectsWithDetails debe detectar proyectos duplicados en archivo") + void testValidateProjectsWithDetails_DuplicatedInFile() { + // Arrange + when(ldService.getAllProjects()).thenReturn(new ArrayList<>()); + + ProjectDTO project1 = createValidProject(); + ProjectDTO project2 = createValidProject(); // Same externalId + + List projects = Arrays.asList(project1, project2); + + // Act + Map result = projectValidationService.validateProjectsWithDetails(projects); + + // Assert + assertEquals(2, result.get("totalProjects")); + assertEquals(1, result.get("validCount")); + assertEquals(1, result.get("invalidCount")); + + @SuppressWarnings("unchecked") + List> invalidProjects = (List>) result.get("invalidProjects"); + assertTrue(invalidProjects.get(0).get("errors").toString().contains("duplicated")); + } + + @Test + @DisplayName("validateProjectsWithDetails debe detectar proyectos que ya existen en BD") + void testValidateProjectsWithDetails_AlreadyExists() { + // Arrange + ProjectDTO existingProject = createValidProject(); + when(ldService.getAllProjects()).thenReturn(Arrays.asList(existingProject)); + + ProjectDTO newProject = createValidProject(); // Same externalId + List projects = Arrays.asList(newProject); + + // Act + Map result = projectValidationService.validateProjectsWithDetails(projects); + + // Assert + assertEquals(1, result.get("totalProjects")); + assertEquals(0, result.get("validCount")); + assertEquals(1, result.get("invalidCount")); + + @SuppressWarnings("unchecked") + List> invalidProjects = (List>) result.get("invalidProjects"); + assertTrue(invalidProjects.get(0).get("errors").toString().contains("already exists")); + } + + @Test + @DisplayName("validateStudent debe validar un estudiante correctamente") + void testValidateStudent_ValidStudent() { + // Arrange + StudentDTO student = createStudent("Test Student", "github1", "taiga1"); + String githubUrl = "https://github.com/testorg"; + String taigaUrl = "https://tree.taiga.io/project/testuser-testproject/"; + + // Act + ValidationResult result = projectValidationService.validateStudent( + githubUrl, taigaUrl, "token", student); + + // Assert + assertTrue(result.isValid()); + verify(githubValidationService).validateUsersInOrganization( + eq("testorg"), + argThat(list -> list.contains("github1")), + eq("token") + ); + verify(taigaValidationService).validateUsersInProject( + eq("testuser-testproject"), + argThat(list -> list.contains("taiga1")) + ); + } + + @Test + @DisplayName("validateStudent debe retornar error si estudiante es null") + void testValidateStudent_NullStudent() { + // Act + ValidationResult result = projectValidationService.validateStudent( + "url", "url", "token", null); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + } + + @Test + @DisplayName("validateStudent debe retornar error si no tiene identidades") + void testValidateStudent_NoIdentities() { + // Arrange + StudentDTO student = new StudentDTO(); + student.setName("Test"); + student.setIdentities(null); + + // Act + ValidationResult result = projectValidationService.validateStudent( + "url", "url", "token", student); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.getErrors().get(0).contains("has no defined identities")); + } + + @Test + @DisplayName("validateStudent debe manejar URLs nulas") + void testValidateStudent_NullURLs() { + // Arrange + StudentDTO student = createStudent("Test", "github1", "taiga1"); + + // Act + ValidationResult result = projectValidationService.validateStudent( + null, null, "token", student); + + // Assert + assertTrue(result.isValid()); + verify(githubValidationService, never()).validateUsersInOrganization(anyString(), anyList(), anyString()); + verify(taigaValidationService, never()).validateUsersInProject(anyString(), anyList()); + } + + @Test + @DisplayName("validateProjectsWithDetails debe manejar excepción al cargar proyectos existentes") + void testValidateProjectsWithDetails_ExceptionLoadingExisting() { + // Arrange + when(ldService.getAllProjects()).thenThrow(new RuntimeException("Database error")); + + List projects = Arrays.asList(createValidProject()); + + // Act + Map result = projectValidationService.validateProjectsWithDetails(projects); + + // Assert + assertEquals(1, result.get("totalProjects")); + assertEquals(1, result.get("validCount")); // Should continue with validation + } + + @Test + @DisplayName("validateProject debe manejar proyecto sin token GitHub") + void testValidateProject_NoGitHubToken() { + // Arrange + ProjectDTO project = createValidProject(); + project.setGithubToken(null); + + // Mock explícito para este test + when(githubValidationService.validateOrganization("testorg", null)) + .thenReturn(new ValidationResult(true)); + when(taigaValidationService.validateProjectBySlug("testuser-testproject")) + .thenReturn(new ValidationResult(true)); + + // Act + ValidationResult result = projectValidationService.validateProject(project); + + // Assert + assertTrue(result.isValid()); + verify(githubValidationService).validateOrganization("testorg", null); + } + + @Test + @DisplayName("validateProject debe manejar estudiantes sin identidades GitHub") + void testValidateProject_StudentsWithoutGitHub() { + // Arrange + ProjectDTO project = createValidProject(); + List students = Arrays.asList( + createStudent("Student 1", null, "taiga1") + ); + project.setStudents(students); + + // Act + ValidationResult result = projectValidationService.validateProject(project); + + // Assert + assertTrue(result.isValid()); + verify(githubValidationService, never()).validateUsersInOrganization( + anyString(), + argThat(list -> list.isEmpty()), + anyString() + ); + } +} diff --git a/src/test/java/com/upc/ld_admintool/domain/services/validation/TaigaValidationServiceTest.java b/src/test/java/com/upc/ld_admintool/domain/services/validation/TaigaValidationServiceTest.java index dd2501a..9602368 100644 --- a/src/test/java/com/upc/ld_admintool/domain/services/validation/TaigaValidationServiceTest.java +++ b/src/test/java/com/upc/ld_admintool/domain/services/validation/TaigaValidationServiceTest.java @@ -1,266 +1,266 @@ -package com.upc.ld_admintool.domain.services.validation; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.*; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.RestTemplate; - -import java.util.Arrays; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -/** - * Tests unitarios para TaigaValidationService - * Valida la lógica de validación de proyectos y usuarios en Taiga - */ -@ExtendWith(MockitoExtension.class) -@DisplayName("TaigaValidationService - Tests Unitarios") -class TaigaValidationServiceTest { - - @Mock - private RestTemplate restTemplate; - - @InjectMocks - private TaigaValidationService taigaValidationService; - - private static final String TAIGA_API_URL = "https://api.taiga.io/api/v1"; - private static final String TAIGA_TOKEN = "test-token"; - - @BeforeEach - void setUp() { - ReflectionTestUtils.setField(taigaValidationService, "taigaApiUrl", TAIGA_API_URL); - ReflectionTestUtils.setField(taigaValidationService, "taigaToken", TAIGA_TOKEN); - ReflectionTestUtils.setField(taigaValidationService, "restTemplate", restTemplate); - ReflectionTestUtils.setField(taigaValidationService, "objectMapper", new ObjectMapper()); - } - - @Test - @DisplayName("validateProjectBySlug debe validar proyecto existente correctamente") - void testValidateProjectBySlug_Success() { - // Arrange - String projectSlug = "test-project"; - ResponseEntity responseEntity = new ResponseEntity<>("{\"id\":1,\"name\":\"Test\"}", HttpStatus.OK); - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) - .thenReturn(responseEntity); - - // Act - ValidationResult result = taigaValidationService.validateProjectBySlug(projectSlug); - - // Assert - assertTrue(result.isValid()); - assertFalse(result.hasErrors()); - assertFalse(result.hasWarnings()); - } - - @Test - @DisplayName("validateProjectBySlug debe retornar error si proyecto no existe") - void testValidateProjectBySlug_NotFound() { - // Arrange - String projectSlug = "non-existent-project"; - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) - .thenThrow(HttpClientErrorException.NotFound.create(HttpStatus.NOT_FOUND, "Not Found", null, null, null)); - - // Act - ValidationResult result = taigaValidationService.validateProjectBySlug(projectSlug); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - } - - @Test - @DisplayName("validateProjectBySlug debe retornar error si slug está vacío") - void testValidateProjectBySlug_EmptySlug() { - // Act - ValidationResult result = taigaValidationService.validateProjectBySlug(""); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - assertTrue(result.getErrors().get(0).contains("buit")); - } - - @Test - @DisplayName("validateProjectBySlug debe retornar error si slug es null") - void testValidateProjectBySlug_NullSlug() { - // Act - ValidationResult result = taigaValidationService.validateProjectBySlug(null); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - } - - @Test - @DisplayName("validateProjectBySlug debe retornar error de autorización") - void testValidateProjectBySlug_Unauthorized() { - // Arrange - String projectSlug = "private-project"; - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) - .thenThrow(HttpClientErrorException.Unauthorized.create(HttpStatus.UNAUTHORIZED, "Unauthorized", null, null, null)); - - // Act - ValidationResult result = taigaValidationService.validateProjectBySlug(projectSlug); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - } - - @Test - @DisplayName("validateProjectBySlug debe retornar warning si proyecto es privado") - void testValidateProjectBySlug_Forbidden() { - // Arrange - String projectSlug = "private-project"; - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) - .thenThrow(HttpClientErrorException.Forbidden.create(HttpStatus.FORBIDDEN, "Forbidden", null, null, null)); - - // Act - ValidationResult result = taigaValidationService.validateProjectBySlug(projectSlug); - - // Assert - assertTrue(result.isValid()); - assertTrue(result.hasWarnings()); - assertEquals(1, result.getWarnings().size()); - } - - @Test - @DisplayName("validateUsersInProject debe validar usuarios correctamente") - void testValidateUsersInProject_Success() { - // Arrange - String projectSlug = "test-project"; - List usernames = Arrays.asList("user1", "user2"); - - String jsonResponse = "{\"members\":[" + - "{\"username\":\"user1\"}," + - "{\"username\":\"user2\"}," + - "{\"username\":\"user3\"}" + - "]}"; - ResponseEntity responseEntity = new ResponseEntity<>(jsonResponse, HttpStatus.OK); - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) - .thenReturn(responseEntity); - - // Act - ValidationResult result = taigaValidationService.validateUsersInProject(projectSlug, usernames); - - // Assert - assertTrue(result.isValid()); - assertFalse(result.hasErrors()); - } - - @Test - @DisplayName("validateUsersInProject debe retornar error si usuario no es miembro") - void testValidateUsersInProject_UserNotMember() { - // Arrange - String projectSlug = "test-project"; - List usernames = Arrays.asList("user1", "nonmember"); - - String jsonResponse = "{\"members\":[{\"username\":\"user1\"}]}"; - ResponseEntity responseEntity = new ResponseEntity<>(jsonResponse, HttpStatus.OK); - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) - .thenReturn(responseEntity); - - // Act - ValidationResult result = taigaValidationService.validateUsersInProject(projectSlug, usernames); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - } - - @Test - @DisplayName("validateUsersInProject debe retornar válido si lista de usuarios está vacía") - void testValidateUsersInProject_EmptyList() { - // Act - ValidationResult result = taigaValidationService.validateUsersInProject("test-project", Arrays.asList()); - - // Assert - assertTrue(result.isValid()); - assertFalse(result.hasErrors()); - } - - @Test - @DisplayName("validateUsersInProject debe retornar válido si lista es null") - void testValidateUsersInProject_NullList() { - // Act - ValidationResult result = taigaValidationService.validateUsersInProject("test-project", null); - - // Assert - assertTrue(result.isValid()); - assertFalse(result.hasErrors()); - } - - @Test - @DisplayName("validateUsersInProject debe retornar warning si no hay members disponibles") - void testValidateUsersInProject_NoMembers() { - // Arrange - String projectSlug = "test-project"; - List usernames = Arrays.asList("user1"); - - String jsonResponse = "{\"id\":1,\"name\":\"Test\"}"; - ResponseEntity responseEntity = new ResponseEntity<>(jsonResponse, HttpStatus.OK); - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) - .thenReturn(responseEntity); - - // Act - ValidationResult result = taigaValidationService.validateUsersInProject(projectSlug, usernames); - - // Assert - El código añade warning, no error - assertTrue(result.isValid()); - assertTrue(result.hasWarnings()); - assertEquals(1, result.getWarnings().size()); - } - - @Test - @DisplayName("validateUsersInProject debe manejar error HTTP") - void testValidateUsersInProject_HttpError() { - // Arrange - String projectSlug = "test-project"; - List usernames = Arrays.asList("user1"); - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) - .thenThrow(new HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR)); - - // Act - ValidationResult result = taigaValidationService.validateUsersInProject(projectSlug, usernames); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - } - - @Test - @DisplayName("validateUsersInProject debe manejar JSON inválido") - void testValidateUsersInProject_InvalidJson() { - // Arrange - String projectSlug = "test-project"; - List usernames = Arrays.asList("user1"); - - ResponseEntity responseEntity = new ResponseEntity<>("invalid json", HttpStatus.OK); - - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) - .thenReturn(responseEntity); - - // Act - ValidationResult result = taigaValidationService.validateUsersInProject(projectSlug, usernames); - - // Assert - assertFalse(result.isValid()); - assertTrue(result.hasErrors()); - } -} +package com.upc.ld_admintool.domain.services.validation; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.*; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * Tests unitarios para TaigaValidationService + * Valida la lógica de validación de proyectos y usuarios en Taiga + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("TaigaValidationService - Tests Unitarios") +class TaigaValidationServiceTest { + + @Mock + private RestTemplate restTemplate; + + @InjectMocks + private TaigaValidationService taigaValidationService; + + private static final String TAIGA_API_URL = "https://api.taiga.io/api/v1"; + private static final String TAIGA_TOKEN = "test-token"; + + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(taigaValidationService, "taigaApiUrl", TAIGA_API_URL); + ReflectionTestUtils.setField(taigaValidationService, "taigaToken", TAIGA_TOKEN); + ReflectionTestUtils.setField(taigaValidationService, "restTemplate", restTemplate); + ReflectionTestUtils.setField(taigaValidationService, "objectMapper", new ObjectMapper()); + } + + @Test + @DisplayName("validateProjectBySlug debe validar proyecto existente correctamente") + void testValidateProjectBySlug_Success() { + // Arrange + String projectSlug = "test-project"; + ResponseEntity responseEntity = new ResponseEntity<>("{\"id\":1,\"name\":\"Test\"}", HttpStatus.OK); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) + .thenReturn(responseEntity); + + // Act + ValidationResult result = taigaValidationService.validateProjectBySlug(projectSlug); + + // Assert + assertTrue(result.isValid()); + assertFalse(result.hasErrors()); + assertFalse(result.hasWarnings()); + } + + @Test + @DisplayName("validateProjectBySlug debe retornar error si proyecto no existe") + void testValidateProjectBySlug_NotFound() { + // Arrange + String projectSlug = "non-existent-project"; + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) + .thenThrow(HttpClientErrorException.NotFound.create(HttpStatus.NOT_FOUND, "Not Found", null, null, null)); + + // Act + ValidationResult result = taigaValidationService.validateProjectBySlug(projectSlug); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + } + + @Test + @DisplayName("validateProjectBySlug debe retornar error si slug está vacío") + void testValidateProjectBySlug_EmptySlug() { + // Act + ValidationResult result = taigaValidationService.validateProjectBySlug(""); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + assertTrue(result.getErrors().get(0).contains("buit")); + } + + @Test + @DisplayName("validateProjectBySlug debe retornar error si slug es null") + void testValidateProjectBySlug_NullSlug() { + // Act + ValidationResult result = taigaValidationService.validateProjectBySlug(null); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + } + + @Test + @DisplayName("validateProjectBySlug debe retornar error de autorización") + void testValidateProjectBySlug_Unauthorized() { + // Arrange + String projectSlug = "private-project"; + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) + .thenThrow(HttpClientErrorException.Unauthorized.create(HttpStatus.UNAUTHORIZED, "Unauthorized", null, null, null)); + + // Act + ValidationResult result = taigaValidationService.validateProjectBySlug(projectSlug); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + } + + @Test + @DisplayName("validateProjectBySlug debe retornar warning si proyecto es privado") + void testValidateProjectBySlug_Forbidden() { + // Arrange + String projectSlug = "private-project"; + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) + .thenThrow(HttpClientErrorException.Forbidden.create(HttpStatus.FORBIDDEN, "Forbidden", null, null, null)); + + // Act + ValidationResult result = taigaValidationService.validateProjectBySlug(projectSlug); + + // Assert + assertTrue(result.isValid()); + assertTrue(result.hasWarnings()); + assertEquals(1, result.getWarnings().size()); + } + + @Test + @DisplayName("validateUsersInProject debe validar usuarios correctamente") + void testValidateUsersInProject_Success() { + // Arrange + String projectSlug = "test-project"; + List usernames = Arrays.asList("user1", "user2"); + + String jsonResponse = "{\"members\":[" + + "{\"username\":\"user1\"}," + + "{\"username\":\"user2\"}," + + "{\"username\":\"user3\"}" + + "]}"; + ResponseEntity responseEntity = new ResponseEntity<>(jsonResponse, HttpStatus.OK); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) + .thenReturn(responseEntity); + + // Act + ValidationResult result = taigaValidationService.validateUsersInProject(projectSlug, usernames); + + // Assert + assertTrue(result.isValid()); + assertFalse(result.hasErrors()); + } + + @Test + @DisplayName("validateUsersInProject debe retornar error si usuario no es miembro") + void testValidateUsersInProject_UserNotMember() { + // Arrange + String projectSlug = "test-project"; + List usernames = Arrays.asList("user1", "nonmember"); + + String jsonResponse = "{\"members\":[{\"username\":\"user1\"}]}"; + ResponseEntity responseEntity = new ResponseEntity<>(jsonResponse, HttpStatus.OK); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) + .thenReturn(responseEntity); + + // Act + ValidationResult result = taigaValidationService.validateUsersInProject(projectSlug, usernames); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + } + + @Test + @DisplayName("validateUsersInProject debe retornar válido si lista de usuarios está vacía") + void testValidateUsersInProject_EmptyList() { + // Act + ValidationResult result = taigaValidationService.validateUsersInProject("test-project", Arrays.asList()); + + // Assert + assertTrue(result.isValid()); + assertFalse(result.hasErrors()); + } + + @Test + @DisplayName("validateUsersInProject debe retornar válido si lista es null") + void testValidateUsersInProject_NullList() { + // Act + ValidationResult result = taigaValidationService.validateUsersInProject("test-project", null); + + // Assert + assertTrue(result.isValid()); + assertFalse(result.hasErrors()); + } + + @Test + @DisplayName("validateUsersInProject debe retornar warning si no hay members disponibles") + void testValidateUsersInProject_NoMembers() { + // Arrange + String projectSlug = "test-project"; + List usernames = Arrays.asList("user1"); + + String jsonResponse = "{\"id\":1,\"name\":\"Test\"}"; + ResponseEntity responseEntity = new ResponseEntity<>(jsonResponse, HttpStatus.OK); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) + .thenReturn(responseEntity); + + // Act + ValidationResult result = taigaValidationService.validateUsersInProject(projectSlug, usernames); + + // Assert - El código añade warning, no error + assertTrue(result.isValid()); + assertTrue(result.hasWarnings()); + assertEquals(1, result.getWarnings().size()); + } + + @Test + @DisplayName("validateUsersInProject debe manejar error HTTP") + void testValidateUsersInProject_HttpError() { + // Arrange + String projectSlug = "test-project"; + List usernames = Arrays.asList("user1"); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) + .thenThrow(new HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR)); + + // Act + ValidationResult result = taigaValidationService.validateUsersInProject(projectSlug, usernames); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + } + + @Test + @DisplayName("validateUsersInProject debe manejar JSON inválido") + void testValidateUsersInProject_InvalidJson() { + // Arrange + String projectSlug = "test-project"; + List usernames = Arrays.asList("user1"); + + ResponseEntity responseEntity = new ResponseEntity<>("invalid json", HttpStatus.OK); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) + .thenReturn(responseEntity); + + // Act + ValidationResult result = taigaValidationService.validateUsersInProject(projectSlug, usernames); + + // Assert + assertFalse(result.isValid()); + assertTrue(result.hasErrors()); + } +} diff --git a/src/test/java/com/upc/ld_admintool/domain/services/validation/ValidationResultTest.java b/src/test/java/com/upc/ld_admintool/domain/services/validation/ValidationResultTest.java index 0e91180..514ff4d 100644 --- a/src/test/java/com/upc/ld_admintool/domain/services/validation/ValidationResultTest.java +++ b/src/test/java/com/upc/ld_admintool/domain/services/validation/ValidationResultTest.java @@ -1,308 +1,308 @@ -package com.upc.ld_admintool.domain.services.validation; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; - -import java.util.Arrays; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Tests unitarios para ValidationResult - * Valida la lógica de resultados de validación - */ -@DisplayName("ValidationResult - Tests Unitarios") -class ValidationResultTest { - - private ValidationResult validationResult; - - @BeforeEach - void setUp() { - validationResult = new ValidationResult(true); - } - - @Test - @DisplayName("Constructor con parámetro debe inicializar correctamente") - void testConstructorWithParameter() { - ValidationResult result = new ValidationResult(true); - - assertTrue(result.isValid()); - assertNotNull(result.getErrors()); - assertNotNull(result.getWarnings()); - assertTrue(result.getErrors().isEmpty()); - assertTrue(result.getWarnings().isEmpty()); - } - - @Test - @DisplayName("Constructor completo debe establecer todos los valores") - void testFullConstructor() { - List errors = Arrays.asList("Error 1", "Error 2"); - List warnings = Arrays.asList("Warning 1"); - - ValidationResult result = new ValidationResult(false, errors, warnings); - - assertFalse(result.isValid()); - assertEquals(2, result.getErrors().size()); - assertEquals(1, result.getWarnings().size()); - } - - @Test - @DisplayName("addError debe agregar error y marcar como inválido") - void testAddError() { - assertTrue(validationResult.isValid()); - - validationResult.addError("Test error"); - - assertFalse(validationResult.isValid()); - assertEquals(1, validationResult.getErrors().size()); - assertEquals("Test error", validationResult.getErrors().get(0)); - } - - @Test - @DisplayName("addWarning debe agregar advertencia sin afectar validez") - void testAddWarning() { - assertTrue(validationResult.isValid()); - - validationResult.addWarning("Test warning"); - - assertTrue(validationResult.isValid()); - assertEquals(1, validationResult.getWarnings().size()); - assertEquals("Test warning", validationResult.getWarnings().get(0)); - } - - @Test - @DisplayName("addErrors debe agregar múltiples errores") - void testAddErrors() { - List errors = Arrays.asList("Error 1", "Error 2", "Error 3"); - - validationResult.addErrors(errors); - - assertFalse(validationResult.isValid()); - assertEquals(3, validationResult.getErrors().size()); - } - - @Test - @DisplayName("addErrors con lista null no debe causar error") - void testAddErrorsWithNull() { - validationResult.addErrors(null); - - assertTrue(validationResult.isValid()); - assertEquals(0, validationResult.getErrors().size()); - } - - @Test - @DisplayName("addErrors con lista vacía no debe afectar validez") - void testAddErrorsWithEmptyList() { - validationResult.addErrors(Arrays.asList()); - - assertTrue(validationResult.isValid()); - assertEquals(0, validationResult.getErrors().size()); - } - - @Test - @DisplayName("addWarnings debe agregar múltiples advertencias") - void testAddWarnings() { - List warnings = Arrays.asList("Warning 1", "Warning 2"); - - validationResult.addWarnings(warnings); - - assertTrue(validationResult.isValid()); - assertEquals(2, validationResult.getWarnings().size()); - } - - @Test - @DisplayName("addWarnings con lista null no debe causar error") - void testAddWarningsWithNull() { - validationResult.addWarnings(null); - - assertTrue(validationResult.isValid()); - assertEquals(0, validationResult.getWarnings().size()); - } - - @Test - @DisplayName("hasErrors debe retornar true cuando hay errores") - void testHasErrors_WithErrors() { - validationResult.addError("Error"); - - assertTrue(validationResult.hasErrors()); - } - - @Test - @DisplayName("hasErrors debe retornar false cuando no hay errores") - void testHasErrors_NoErrors() { - assertFalse(validationResult.hasErrors()); - } - - @Test - @DisplayName("hasWarnings debe retornar true cuando hay advertencias") - void testHasWarnings_WithWarnings() { - validationResult.addWarning("Warning"); - - assertTrue(validationResult.hasWarnings()); - } - - @Test - @DisplayName("hasWarnings debe retornar false cuando no hay advertencias") - void testHasWarnings_NoWarnings() { - assertFalse(validationResult.hasWarnings()); - } - - @Test - @DisplayName("Múltiples errores deben mantener el estado inválido") - void testMultipleErrors() { - validationResult.addError("Error 1"); - validationResult.addError("Error 2"); - validationResult.addError("Error 3"); - - assertFalse(validationResult.isValid()); - assertEquals(3, validationResult.getErrors().size()); - assertTrue(validationResult.hasErrors()); - } - - @Test - @DisplayName("Errores y advertencias pueden coexistir") - void testErrorsAndWarningsTogether() { - validationResult.addError("Error"); - validationResult.addWarning("Warning"); - - assertFalse(validationResult.isValid()); - assertTrue(validationResult.hasErrors()); - assertTrue(validationResult.hasWarnings()); - assertEquals(1, validationResult.getErrors().size()); - assertEquals(1, validationResult.getWarnings().size()); - } - - @Test - @DisplayName("ValidationResult debe ser mutable con setters") - void testSetters() { - validationResult.setValid(false); - validationResult.setErrors(Arrays.asList("New error")); - validationResult.setWarnings(Arrays.asList("New warning")); - - assertFalse(validationResult.isValid()); - assertEquals(1, validationResult.getErrors().size()); - assertEquals(1, validationResult.getWarnings().size()); - } - - @Test - @DisplayName("equals debe retornar true para objetos iguales") - void testEquals_SameContent() { - ValidationResult result1 = new ValidationResult(true); - result1.addError("Error 1"); - result1.addWarning("Warning 1"); - - ValidationResult result2 = new ValidationResult(true); - result2.addError("Error 1"); - result2.addWarning("Warning 1"); - - assertEquals(result1, result2); - } - - @Test - @DisplayName("equals debe retornar false para objetos diferentes") - void testEquals_DifferentContent() { - ValidationResult result1 = new ValidationResult(true); - result1.addError("Error 1"); - - ValidationResult result2 = new ValidationResult(false); - result2.addError("Error 2"); - - assertNotEquals(result1, result2); - } - - @Test - @DisplayName("equals debe retornar true para el mismo objeto") - void testEquals_SameObject() { - assertEquals(validationResult, validationResult); - } - - @Test - @DisplayName("equals debe retornar false para null") - void testEquals_Null() { - assertNotEquals(null, validationResult); - } - - @Test - @DisplayName("equals debe retornar false para clase diferente") - void testEquals_DifferentClass() { - assertNotEquals(validationResult, "Not a ValidationResult"); - } - - @Test - @DisplayName("hashCode debe ser consistente para objetos iguales") - void testHashCode_Consistency() { - ValidationResult result1 = new ValidationResult(true); - result1.addError("Error 1"); - - ValidationResult result2 = new ValidationResult(true); - result2.addError("Error 1"); - - assertEquals(result1.hashCode(), result2.hashCode()); - } - - @Test - @DisplayName("hashCode debe retornar el mismo valor en múltiples llamadas") - void testHashCode_Stability() { - int hash1 = validationResult.hashCode(); - int hash2 = validationResult.hashCode(); - - assertEquals(hash1, hash2); - } - - @Test - @DisplayName("toString debe retornar representación no nula") - void testToString_NotNull() { - String result = validationResult.toString(); - - assertNotNull(result); - assertTrue(result.contains("ValidationResult")); - } - - @Test - @DisplayName("toString debe incluir información del estado") - void testToString_ContainsStateInfo() { - validationResult.addError("Test Error"); - String result = validationResult.toString(); - - assertNotNull(result); - assertTrue(result.length() > 0); - } - - @Test - @DisplayName("canEqual debe retornar true para instancias de ValidationResult") - void testCanEqual_SameClass() { - ValidationResult other = new ValidationResult(true); - - assertTrue(validationResult.canEqual(other)); - } - - @Test - @DisplayName("canEqual debe retornar false para objetos de otra clase") - void testCanEqual_DifferentClass() { - assertFalse(validationResult.canEqual("Not a ValidationResult")); - } - - @Test - @DisplayName("equals debe ser simétrico") - void testEquals_Symmetric() { - ValidationResult result1 = new ValidationResult(true); - ValidationResult result2 = new ValidationResult(true); - - assertEquals(result1, result2); - assertEquals(result2, result1); - } - - @Test - @DisplayName("equals debe ser transitivo") - void testEquals_Transitive() { - ValidationResult result1 = new ValidationResult(true); - ValidationResult result2 = new ValidationResult(true); - ValidationResult result3 = new ValidationResult(true); - - assertEquals(result1, result2); - assertEquals(result2, result3); - assertEquals(result1, result3); - } -} +package com.upc.ld_admintool.domain.services.validation; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests unitarios para ValidationResult + * Valida la lógica de resultados de validación + */ +@DisplayName("ValidationResult - Tests Unitarios") +class ValidationResultTest { + + private ValidationResult validationResult; + + @BeforeEach + void setUp() { + validationResult = new ValidationResult(true); + } + + @Test + @DisplayName("Constructor con parámetro debe inicializar correctamente") + void testConstructorWithParameter() { + ValidationResult result = new ValidationResult(true); + + assertTrue(result.isValid()); + assertNotNull(result.getErrors()); + assertNotNull(result.getWarnings()); + assertTrue(result.getErrors().isEmpty()); + assertTrue(result.getWarnings().isEmpty()); + } + + @Test + @DisplayName("Constructor completo debe establecer todos los valores") + void testFullConstructor() { + List errors = Arrays.asList("Error 1", "Error 2"); + List warnings = Arrays.asList("Warning 1"); + + ValidationResult result = new ValidationResult(false, errors, warnings); + + assertFalse(result.isValid()); + assertEquals(2, result.getErrors().size()); + assertEquals(1, result.getWarnings().size()); + } + + @Test + @DisplayName("addError debe agregar error y marcar como inválido") + void testAddError() { + assertTrue(validationResult.isValid()); + + validationResult.addError("Test error"); + + assertFalse(validationResult.isValid()); + assertEquals(1, validationResult.getErrors().size()); + assertEquals("Test error", validationResult.getErrors().get(0)); + } + + @Test + @DisplayName("addWarning debe agregar advertencia sin afectar validez") + void testAddWarning() { + assertTrue(validationResult.isValid()); + + validationResult.addWarning("Test warning"); + + assertTrue(validationResult.isValid()); + assertEquals(1, validationResult.getWarnings().size()); + assertEquals("Test warning", validationResult.getWarnings().get(0)); + } + + @Test + @DisplayName("addErrors debe agregar múltiples errores") + void testAddErrors() { + List errors = Arrays.asList("Error 1", "Error 2", "Error 3"); + + validationResult.addErrors(errors); + + assertFalse(validationResult.isValid()); + assertEquals(3, validationResult.getErrors().size()); + } + + @Test + @DisplayName("addErrors con lista null no debe causar error") + void testAddErrorsWithNull() { + validationResult.addErrors(null); + + assertTrue(validationResult.isValid()); + assertEquals(0, validationResult.getErrors().size()); + } + + @Test + @DisplayName("addErrors con lista vacía no debe afectar validez") + void testAddErrorsWithEmptyList() { + validationResult.addErrors(Arrays.asList()); + + assertTrue(validationResult.isValid()); + assertEquals(0, validationResult.getErrors().size()); + } + + @Test + @DisplayName("addWarnings debe agregar múltiples advertencias") + void testAddWarnings() { + List warnings = Arrays.asList("Warning 1", "Warning 2"); + + validationResult.addWarnings(warnings); + + assertTrue(validationResult.isValid()); + assertEquals(2, validationResult.getWarnings().size()); + } + + @Test + @DisplayName("addWarnings con lista null no debe causar error") + void testAddWarningsWithNull() { + validationResult.addWarnings(null); + + assertTrue(validationResult.isValid()); + assertEquals(0, validationResult.getWarnings().size()); + } + + @Test + @DisplayName("hasErrors debe retornar true cuando hay errores") + void testHasErrors_WithErrors() { + validationResult.addError("Error"); + + assertTrue(validationResult.hasErrors()); + } + + @Test + @DisplayName("hasErrors debe retornar false cuando no hay errores") + void testHasErrors_NoErrors() { + assertFalse(validationResult.hasErrors()); + } + + @Test + @DisplayName("hasWarnings debe retornar true cuando hay advertencias") + void testHasWarnings_WithWarnings() { + validationResult.addWarning("Warning"); + + assertTrue(validationResult.hasWarnings()); + } + + @Test + @DisplayName("hasWarnings debe retornar false cuando no hay advertencias") + void testHasWarnings_NoWarnings() { + assertFalse(validationResult.hasWarnings()); + } + + @Test + @DisplayName("Múltiples errores deben mantener el estado inválido") + void testMultipleErrors() { + validationResult.addError("Error 1"); + validationResult.addError("Error 2"); + validationResult.addError("Error 3"); + + assertFalse(validationResult.isValid()); + assertEquals(3, validationResult.getErrors().size()); + assertTrue(validationResult.hasErrors()); + } + + @Test + @DisplayName("Errores y advertencias pueden coexistir") + void testErrorsAndWarningsTogether() { + validationResult.addError("Error"); + validationResult.addWarning("Warning"); + + assertFalse(validationResult.isValid()); + assertTrue(validationResult.hasErrors()); + assertTrue(validationResult.hasWarnings()); + assertEquals(1, validationResult.getErrors().size()); + assertEquals(1, validationResult.getWarnings().size()); + } + + @Test + @DisplayName("ValidationResult debe ser mutable con setters") + void testSetters() { + validationResult.setValid(false); + validationResult.setErrors(Arrays.asList("New error")); + validationResult.setWarnings(Arrays.asList("New warning")); + + assertFalse(validationResult.isValid()); + assertEquals(1, validationResult.getErrors().size()); + assertEquals(1, validationResult.getWarnings().size()); + } + + @Test + @DisplayName("equals debe retornar true para objetos iguales") + void testEquals_SameContent() { + ValidationResult result1 = new ValidationResult(true); + result1.addError("Error 1"); + result1.addWarning("Warning 1"); + + ValidationResult result2 = new ValidationResult(true); + result2.addError("Error 1"); + result2.addWarning("Warning 1"); + + assertEquals(result1, result2); + } + + @Test + @DisplayName("equals debe retornar false para objetos diferentes") + void testEquals_DifferentContent() { + ValidationResult result1 = new ValidationResult(true); + result1.addError("Error 1"); + + ValidationResult result2 = new ValidationResult(false); + result2.addError("Error 2"); + + assertNotEquals(result1, result2); + } + + @Test + @DisplayName("equals debe retornar true para el mismo objeto") + void testEquals_SameObject() { + assertEquals(validationResult, validationResult); + } + + @Test + @DisplayName("equals debe retornar false para null") + void testEquals_Null() { + assertNotEquals(null, validationResult); + } + + @Test + @DisplayName("equals debe retornar false para clase diferente") + void testEquals_DifferentClass() { + assertNotEquals(validationResult, "Not a ValidationResult"); + } + + @Test + @DisplayName("hashCode debe ser consistente para objetos iguales") + void testHashCode_Consistency() { + ValidationResult result1 = new ValidationResult(true); + result1.addError("Error 1"); + + ValidationResult result2 = new ValidationResult(true); + result2.addError("Error 1"); + + assertEquals(result1.hashCode(), result2.hashCode()); + } + + @Test + @DisplayName("hashCode debe retornar el mismo valor en múltiples llamadas") + void testHashCode_Stability() { + int hash1 = validationResult.hashCode(); + int hash2 = validationResult.hashCode(); + + assertEquals(hash1, hash2); + } + + @Test + @DisplayName("toString debe retornar representación no nula") + void testToString_NotNull() { + String result = validationResult.toString(); + + assertNotNull(result); + assertTrue(result.contains("ValidationResult")); + } + + @Test + @DisplayName("toString debe incluir información del estado") + void testToString_ContainsStateInfo() { + validationResult.addError("Test Error"); + String result = validationResult.toString(); + + assertNotNull(result); + assertTrue(result.length() > 0); + } + + @Test + @DisplayName("canEqual debe retornar true para instancias de ValidationResult") + void testCanEqual_SameClass() { + ValidationResult other = new ValidationResult(true); + + assertTrue(validationResult.canEqual(other)); + } + + @Test + @DisplayName("canEqual debe retornar false para objetos de otra clase") + void testCanEqual_DifferentClass() { + assertFalse(validationResult.canEqual("Not a ValidationResult")); + } + + @Test + @DisplayName("equals debe ser simétrico") + void testEquals_Symmetric() { + ValidationResult result1 = new ValidationResult(true); + ValidationResult result2 = new ValidationResult(true); + + assertEquals(result1, result2); + assertEquals(result2, result1); + } + + @Test + @DisplayName("equals debe ser transitivo") + void testEquals_Transitive() { + ValidationResult result1 = new ValidationResult(true); + ValidationResult result2 = new ValidationResult(true); + ValidationResult result3 = new ValidationResult(true); + + assertEquals(result1, result2); + assertEquals(result2, result3); + assertEquals(result1, result3); + } +} diff --git a/src/test/java/com/upc/ld_admintool/rest/DTO/DTOTest.java b/src/test/java/com/upc/ld_admintool/rest/DTO/DTOTest.java index 7405cd2..ca7124e 100644 --- a/src/test/java/com/upc/ld_admintool/rest/DTO/DTOTest.java +++ b/src/test/java/com/upc/ld_admintool/rest/DTO/DTOTest.java @@ -1,639 +1,639 @@ -package com.upc.ld_admintool.rest.DTO; - -import com.upc.ld_admintool.domain.utils.DataSource; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Tests unitarios para DTOs - * Valida la correcta funcionalidad de los objetos de transferencia de datos - */ -@DisplayName("DTOs - Tests Unitarios") -class DTOTest { - - @Test - @DisplayName("ProjectDTO debe inicializarse correctamente") - void testProjectDTO_Initialization() { - ProjectDTO project = new ProjectDTO(); - project.setId(1L); - project.setName("Test Project"); - project.setDescription("Test Description"); - project.setExternalId("ext-123"); - project.setGithubToken("github-token"); - - assertEquals(1L, project.getId()); - assertEquals("Test Project", project.getName()); - assertEquals("Test Description", project.getDescription()); - assertEquals("ext-123", project.getExternalId()); - assertEquals("github-token", project.getGithubToken()); - } - - @Test - @DisplayName("ProjectDTO debe manejar identidades") - void testProjectDTO_Identities() { - ProjectDTO project = new ProjectDTO(); - Map identities = new HashMap<>(); - - ProjectIdentityDTO githubIdentity = new ProjectIdentityDTO(); - githubIdentity.setUrl("https://github.com/org"); - identities.put(DataSource.GITHUB, githubIdentity); - - project.setIdentities(identities); - - assertNotNull(project.getIdentities()); - assertEquals(1, project.getIdentities().size()); - assertTrue(project.getIdentities().containsKey(DataSource.GITHUB)); - } - - @Test - @DisplayName("ProjectDTO debe manejar lista de estudiantes") - void testProjectDTO_Students() { - ProjectDTO project = new ProjectDTO(); - StudentDTO student1 = new StudentDTO(); - student1.setId(1L); - student1.setName("Student 1"); - - StudentDTO student2 = new StudentDTO(); - student2.setId(2L); - student2.setName("Student 2"); - - project.setStudents(Arrays.asList(student1, student2)); - - assertNotNull(project.getStudents()); - assertEquals(2, project.getStudents().size()); - assertEquals("Student 1", project.getStudents().get(0).getName()); - } - - @Test - @DisplayName("StudentDTO debe inicializarse correctamente") - void testStudentDTO_Initialization() { - StudentDTO student = new StudentDTO(); - student.setId(1L); - student.setName("John Doe"); - - assertEquals(1L, student.getId()); - assertEquals("John Doe", student.getName()); - } - - @Test - @DisplayName("StudentDTO debe manejar identidades") - void testStudentDTO_Identities() { - StudentDTO student = new StudentDTO(); - Map identities = new HashMap<>(); - - StudentIdentityDTO githubIdentity = new StudentIdentityDTO(); - githubIdentity.setUsername("johndoe"); - identities.put(DataSource.GITHUB, githubIdentity); - - student.setIdentities(identities); - - assertNotNull(student.getIdentities()); - assertEquals(1, student.getIdentities().size()); - assertEquals("johndoe", student.getIdentities().get(DataSource.GITHUB).getUsername()); - } - - @Test - @DisplayName("CategoryDTO debe inicializarse correctamente") - void testCategoryDTO_Initialization() { - CategoryDTO category = new CategoryDTO(); - category.setCategory("Testing"); - category.setPatternGroup("test-group"); - - assertEquals("Testing", category.getCategory()); - assertEquals("test-group", category.getPatternGroup()); - } - - @Test - @DisplayName("MetricDTO debe inicializarse correctamente") - void testMetricDTO_Initialization() { - MetricDTO metric = new MetricDTO(); - metric.setId("1"); - metric.setName("Code Coverage"); - metric.setDescription("Percentage of code covered"); - - assertEquals("1", metric.getId()); - assertEquals("Code Coverage", metric.getName()); - assertEquals("Percentage of code covered", metric.getDescription()); - } - - @Test - @DisplayName("FactorDTO debe inicializarse correctamente") - void testFactorDTO_Initialization() { - FactorDTO factor = new FactorDTO(); - factor.setId("1"); - factor.setName("Quality Factor"); - factor.setDescription("Quality description"); - - assertEquals("1", factor.getId()); - assertEquals("Quality Factor", factor.getName()); - assertEquals("Quality description", factor.getDescription()); - } - - @Test - @DisplayName("ProjectIdentityDTO debe manejar URL") - void testProjectIdentityDTO() { - ProjectIdentityDTO identity = new ProjectIdentityDTO(); - identity.setUrl("https://github.com/test/repo"); - - assertEquals("https://github.com/test/repo", identity.getUrl()); - } - - @Test - @DisplayName("StudentIdentityDTO debe manejar username") - void testStudentIdentityDTO() { - StudentIdentityDTO identity = new StudentIdentityDTO(); - identity.setUsername("testuser"); - - assertEquals("testuser", identity.getUsername()); - } - - @Test - @DisplayName("WizardStatusDTO debe inicializarse con todos los flags") - void testWizardStatusDTO() { - WizardStatusDTO status = new WizardStatusDTO(true, true, true, true, true); - - assertTrue(status.isHasProjects()); - assertTrue(status.isHasData()); - assertTrue(status.isHasMetricsCategories()); - assertTrue(status.isHasFactorsCategories()); - assertTrue(status.isHasStrategicIndicatorCategories()); - } - - @Test - @DisplayName("WizardStatusDTO debe manejar estado parcial") - void testWizardStatusDTO_PartialState() { - WizardStatusDTO status = new WizardStatusDTO(true, false, true, false, true); - - assertTrue(status.isHasProjects()); - assertFalse(status.isHasData()); - assertTrue(status.isHasMetricsCategories()); - assertFalse(status.isHasFactorsCategories()); - assertTrue(status.isHasStrategicIndicatorCategories()); - } - - @Test - @DisplayName("ProjectDTO debe permitir null en campos opcionales") - void testProjectDTO_NullableFields() { - ProjectDTO project = new ProjectDTO(); - project.setGithubToken(null); - project.setDescription(null); - project.setStudents(null); - project.setIdentities(null); - - assertNull(project.getGithubToken()); - assertNull(project.getDescription()); - assertNull(project.getStudents()); - assertNull(project.getIdentities()); - } - - @Test - @DisplayName("StudentDTO debe manejar lista vacía de identidades") - void testStudentDTO_EmptyIdentities() { - StudentDTO student = new StudentDTO(); - student.setIdentities(new HashMap<>()); - - assertNotNull(student.getIdentities()); - assertTrue(student.getIdentities().isEmpty()); - } - - @Test - @DisplayName("MetricDTO y FactorDTO deben soportar ID tipo String") - void testMetricAndFactorDTO_StringId() { - MetricDTO metric = new MetricDTO(); - metric.setId("metric-abc-123"); - - FactorDTO factor = new FactorDTO(); - factor.setId("factor-xyz-789"); - - assertEquals("metric-abc-123", metric.getId()); - assertEquals("factor-xyz-789", factor.getId()); - } - - @Test - @DisplayName("IntervalDTO debe inicializarse correctamente") - void testIntervalDTO() { - IntervalDTO interval = new IntervalDTO(); - interval.setName("Test Interval"); - interval.setColor("#FF0000"); - - assertEquals("Test Interval", interval.getName()); - assertEquals("#FF0000", interval.getColor()); - } - - @Test - @DisplayName("SaveSyncResponseDTO y SaveSyncStepDTO deben tener equals y hashCode") - void testSaveSyncDTOs_EqualsHashCode() { - SaveSyncStepDTO step1 = new SaveSyncStepDTO(1, "Step", "Detail", "SUCCESS", null); - SaveSyncStepDTO step2 = new SaveSyncStepDTO(1, "Step", "Detail", "SUCCESS", null); - SaveSyncStepDTO step3 = new SaveSyncStepDTO(2, "Other", "Detail", "FAILED", "Error"); - - assertEquals(step1, step2); - assertEquals(step1.hashCode(), step2.hashCode()); - assertNotEquals(step1, step3); - - SaveSyncResponseDTO response1 = new SaveSyncResponseDTO(); - response1.setSuccess(true); - - SaveSyncResponseDTO response2 = new SaveSyncResponseDTO(); - response2.setSuccess(true); - - assertEquals(response1, response2); - assertEquals(response1.hashCode(), response2.hashCode()); - } - - @Test - @DisplayName("ProjectDTO debe implementar equals correctamente") - void testProjectDTO_Equals() { - ProjectDTO project1 = new ProjectDTO(); - project1.setId(1L); - project1.setName("Test"); - - ProjectDTO project2 = new ProjectDTO(); - project2.setId(1L); - project2.setName("Test"); - - ProjectDTO project3 = new ProjectDTO(); - project3.setId(2L); - project3.setName("Different"); - - assertEquals(project1, project2); - assertNotEquals(project1, project3); - assertNotEquals(project1, null); - assertEquals(project1, project1); - } - - @Test - @DisplayName("ProjectDTO debe implementar hashCode correctamente") - void testProjectDTO_HashCode() { - ProjectDTO project1 = new ProjectDTO(); - project1.setId(1L); - project1.setName("Test"); - - ProjectDTO project2 = new ProjectDTO(); - project2.setId(1L); - project2.setName("Test"); - - assertEquals(project1.hashCode(), project2.hashCode()); - } - - @Test - @DisplayName("ProjectDTO debe implementar toString correctamente") - void testProjectDTO_ToString() { - ProjectDTO project = new ProjectDTO(); - project.setId(1L); - project.setName("Test"); - - String toString = project.toString(); - assertNotNull(toString); - assertTrue(toString.contains("ProjectDTO") || toString.contains("Test")); - } - - @Test - @DisplayName("StudentDTO debe implementar equals correctamente") - void testStudentDTO_Equals() { - StudentDTO student1 = new StudentDTO(); - student1.setId(1L); - student1.setName("John"); - - StudentDTO student2 = new StudentDTO(); - student2.setId(1L); - student2.setName("John"); - - assertEquals(student1, student2); - assertEquals(student1, student1); - assertNotEquals(student1, null); - } - - @Test - @DisplayName("StudentDTO debe implementar hashCode correctamente") - void testStudentDTO_HashCode() { - StudentDTO student1 = new StudentDTO(); - student1.setId(1L); - student1.setName("John"); - - StudentDTO student2 = new StudentDTO(); - student2.setId(1L); - student2.setName("John"); - - assertEquals(student1.hashCode(), student2.hashCode()); - } - - @Test - @DisplayName("StudentDTO debe implementar toString correctamente") - void testStudentDTO_ToString() { - StudentDTO student = new StudentDTO(); - student.setId(1L); - student.setName("John"); - - assertNotNull(student.toString()); - } - - @Test - @DisplayName("CategoryDTO debe implementar equals correctamente") - void testCategoryDTO_Equals() { - CategoryDTO category1 = new CategoryDTO(); - category1.setCategory("Testing"); - - CategoryDTO category2 = new CategoryDTO(); - category2.setCategory("Testing"); - - assertEquals(category1, category2); - assertNotEquals(category1, null); - } - - @Test - @DisplayName("CategoryDTO debe implementar hashCode correctamente") - void testCategoryDTO_HashCode() { - CategoryDTO category1 = new CategoryDTO(); - category1.setCategory("Testing"); - - CategoryDTO category2 = new CategoryDTO(); - category2.setCategory("Testing"); - - assertEquals(category1.hashCode(), category2.hashCode()); - } - - @Test - @DisplayName("CategoryDTO debe implementar toString correctamente") - void testCategoryDTO_ToString() { - CategoryDTO category = new CategoryDTO(); - category.setCategory("Testing"); - - assertNotNull(category.toString()); - } - - @Test - @DisplayName("MetricDTO debe implementar equals correctamente") - void testMetricDTO_Equals() { - MetricDTO metric1 = new MetricDTO(); - metric1.setId("1"); - metric1.setName("Coverage"); - - MetricDTO metric2 = new MetricDTO(); - metric2.setId("1"); - metric2.setName("Coverage"); - - assertEquals(metric1, metric2); - assertNotEquals(metric1, null); - } - - @Test - @DisplayName("MetricDTO debe implementar hashCode correctamente") - void testMetricDTO_HashCode() { - MetricDTO metric1 = new MetricDTO(); - metric1.setId("1"); - metric1.setName("Coverage"); - - MetricDTO metric2 = new MetricDTO(); - metric2.setId("1"); - metric2.setName("Coverage"); - - assertEquals(metric1.hashCode(), metric2.hashCode()); - } - - @Test - @DisplayName("MetricDTO debe implementar toString correctamente") - void testMetricDTO_ToString() { - MetricDTO metric = new MetricDTO(); - metric.setId("1"); - metric.setName("Coverage"); - - assertNotNull(metric.toString()); - } - - @Test - @DisplayName("FactorDTO debe implementar equals correctamente") - void testFactorDTO_Equals() { - FactorDTO factor1 = new FactorDTO(); - factor1.setId("1"); - factor1.setName("Quality"); - - FactorDTO factor2 = new FactorDTO(); - factor2.setId("1"); - factor2.setName("Quality"); - - assertEquals(factor1, factor2); - assertNotEquals(factor1, null); - } - - @Test - @DisplayName("FactorDTO debe implementar hashCode correctamente") - void testFactorDTO_HashCode() { - FactorDTO factor1 = new FactorDTO(); - factor1.setId("1"); - factor1.setName("Quality"); - - FactorDTO factor2 = new FactorDTO(); - factor2.setId("1"); - factor2.setName("Quality"); - - assertEquals(factor1.hashCode(), factor2.hashCode()); - } - - @Test - @DisplayName("FactorDTO debe implementar toString correctamente") - void testFactorDTO_ToString() { - FactorDTO factor = new FactorDTO(); - factor.setId("1"); - factor.setName("Quality"); - - assertNotNull(factor.toString()); - } - - @Test - @DisplayName("WizardStatusDTO debe implementar equals correctamente") - void testWizardStatusDTO_Equals() { - WizardStatusDTO status1 = new WizardStatusDTO(true, true, true, true, true); - WizardStatusDTO status2 = new WizardStatusDTO(true, true, true, true, true); - - assertEquals(status1, status2); - assertNotEquals(status1, null); - } - - @Test - @DisplayName("WizardStatusDTO debe implementar hashCode correctamente") - void testWizardStatusDTO_HashCode() { - WizardStatusDTO status1 = new WizardStatusDTO(true, true, true, true, true); - WizardStatusDTO status2 = new WizardStatusDTO(true, true, true, true, true); - - assertEquals(status1.hashCode(), status2.hashCode()); - } - - @Test - @DisplayName("WizardStatusDTO debe implementar toString correctamente") - void testWizardStatusDTO_ToString() { - WizardStatusDTO status = new WizardStatusDTO(true, true, true, true, true); - - assertNotNull(status.toString()); - } - - @Test - @DisplayName("FactorDTO debe inicializarse con constructor completo") - void testFactorDTO_AllArgsConstructor() { - // Arrange - List metrics = Arrays.asList("metric1", "metric2"); - List weights = Arrays.asList("0.5", "0.5"); - - // Act - FactorDTO factor = new FactorDTO("1", "ext-1", "Quality Factor", "Description", - "Category1", "0.7", "MEAN", metrics, weights); - - // Assert - assertEquals("1", factor.getId()); - assertEquals("ext-1", factor.getExternalId()); - assertEquals("Quality Factor", factor.getName()); - assertEquals("Description", factor.getDescription()); - assertEquals("Category1", factor.getCategory()); - assertEquals("0.7", factor.getThreshold()); - assertEquals("MEAN", factor.getType()); - assertEquals(2, factor.getMetrics().size()); - assertEquals(2, factor.getMetricsWeights().size()); - } - - @Test - @DisplayName("FactorDTO debe configurar threshold correctamente") - void testFactorDTO_SetThreshold() { - // Arrange - FactorDTO factor = new FactorDTO(); - - // Act - factor.setThreshold("0.8"); - - // Assert - assertEquals("0.8", factor.getThreshold()); - } - - @Test - @DisplayName("FactorDTO debe configurar type correctamente") - void testFactorDTO_SetType() { - // Arrange - FactorDTO factor = new FactorDTO(); - - // Act - factor.setType("WEIGHTED_MEAN"); - - // Assert - assertEquals("WEIGHTED_MEAN", factor.getType()); - } - - @Test - @DisplayName("FactorDTO debe configurar metrics correctamente") - void testFactorDTO_SetMetrics() { - // Arrange - FactorDTO factor = new FactorDTO(); - List metrics = Arrays.asList("coverage", "complexity", "duplication"); - - // Act - factor.setMetrics(metrics); - - // Assert - assertNotNull(factor.getMetrics()); - assertEquals(3, factor.getMetrics().size()); - assertTrue(factor.getMetrics().contains("coverage")); - assertTrue(factor.getMetrics().contains("complexity")); - } - - @Test - @DisplayName("FactorDTO debe configurar metricsWeights correctamente") - void testFactorDTO_SetMetricsWeights() { - // Arrange - FactorDTO factor = new FactorDTO(); - List weights = Arrays.asList("0.4", "0.3", "0.3"); - - // Act - factor.setMetricsWeights(weights); - - // Assert - assertNotNull(factor.getMetricsWeights()); - assertEquals(3, factor.getMetricsWeights().size()); - assertEquals("0.4", factor.getMetricsWeights().get(0)); - } - - @Test - @DisplayName("FactorDTO equals debe comparar todos los campos") - void testFactorDTO_EqualsAllFields() { - // Arrange - List metrics = Arrays.asList("m1", "m2"); - List weights = Arrays.asList("0.5", "0.5"); - - FactorDTO factor1 = new FactorDTO("1", "ext-1", "Name", "Desc", "Cat", "0.7", "MEAN", metrics, weights); - FactorDTO factor2 = new FactorDTO("1", "ext-1", "Name", "Desc", "Cat", "0.7", "MEAN", metrics, weights); - FactorDTO factor3 = new FactorDTO("2", "ext-2", "Other", "Desc", "Cat", "0.8", "SUM", metrics, weights); - - // Assert - assertEquals(factor1, factor2); - assertNotEquals(factor1, factor3); - assertEquals(factor1.hashCode(), factor2.hashCode()); - } - - @Test - @DisplayName("FactorDTO equals debe manejar campos null") - void testFactorDTO_EqualsWithNulls() { - // Arrange - FactorDTO factor1 = new FactorDTO(); - FactorDTO factor2 = new FactorDTO(); - - // Assert - assertEquals(factor1, factor2); - assertEquals(factor1.hashCode(), factor2.hashCode()); - } - - @Test - @DisplayName("FactorDTO debe manejar listas vacías") - void testFactorDTO_EmptyLists() { - // Arrange - FactorDTO factor = new FactorDTO(); - - // Act - factor.setMetrics(Arrays.asList()); - factor.setMetricsWeights(Arrays.asList()); - - // Assert - assertNotNull(factor.getMetrics()); - assertNotNull(factor.getMetricsWeights()); - assertTrue(factor.getMetrics().isEmpty()); - assertTrue(factor.getMetricsWeights().isEmpty()); - } - - @Test - @DisplayName("FactorDTO toString debe contener información relevante") - void testFactorDTO_ToStringContent() { - // Arrange - FactorDTO factor = new FactorDTO(); - factor.setId("factor-123"); - factor.setName("Quality Factor"); - factor.setThreshold("0.75"); - - // Act - String toString = factor.toString(); - - // Assert - assertNotNull(toString); - assertTrue(toString.contains("FactorDTO") || toString.contains("factor-123") || toString.contains("Quality")); - } - - @Test - @DisplayName("FactorDTO debe soportar modificación de campos después de construcción") - void testFactorDTO_ModifyAfterConstruction() { - // Arrange - FactorDTO factor = new FactorDTO("1", "ext", "Name", "Desc", "Cat", "0.5", "MEAN", null, null); - - // Act - factor.setName("Updated Name"); - factor.setThreshold("0.9"); - factor.setMetrics(Arrays.asList("new-metric")); - - // Assert - assertEquals("Updated Name", factor.getName()); - assertEquals("0.9", factor.getThreshold()); - assertEquals(1, factor.getMetrics().size()); - } -} - +package com.upc.ld_admintool.rest.DTO; + +import com.upc.ld_admintool.domain.utils.DataSource; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests unitarios para DTOs + * Valida la correcta funcionalidad de los objetos de transferencia de datos + */ +@DisplayName("DTOs - Tests Unitarios") +class DTOTest { + + @Test + @DisplayName("ProjectDTO debe inicializarse correctamente") + void testProjectDTO_Initialization() { + ProjectDTO project = new ProjectDTO(); + project.setId(1L); + project.setName("Test Project"); + project.setDescription("Test Description"); + project.setExternalId("ext-123"); + project.setGithubToken("github-token"); + + assertEquals(1L, project.getId()); + assertEquals("Test Project", project.getName()); + assertEquals("Test Description", project.getDescription()); + assertEquals("ext-123", project.getExternalId()); + assertEquals("github-token", project.getGithubToken()); + } + + @Test + @DisplayName("ProjectDTO debe manejar identidades") + void testProjectDTO_Identities() { + ProjectDTO project = new ProjectDTO(); + Map identities = new HashMap<>(); + + ProjectIdentityDTO githubIdentity = new ProjectIdentityDTO(); + githubIdentity.setUrl("https://github.com/org"); + identities.put(DataSource.GITHUB, githubIdentity); + + project.setIdentities(identities); + + assertNotNull(project.getIdentities()); + assertEquals(1, project.getIdentities().size()); + assertTrue(project.getIdentities().containsKey(DataSource.GITHUB)); + } + + @Test + @DisplayName("ProjectDTO debe manejar lista de estudiantes") + void testProjectDTO_Students() { + ProjectDTO project = new ProjectDTO(); + StudentDTO student1 = new StudentDTO(); + student1.setId(1L); + student1.setName("Student 1"); + + StudentDTO student2 = new StudentDTO(); + student2.setId(2L); + student2.setName("Student 2"); + + project.setStudents(Arrays.asList(student1, student2)); + + assertNotNull(project.getStudents()); + assertEquals(2, project.getStudents().size()); + assertEquals("Student 1", project.getStudents().get(0).getName()); + } + + @Test + @DisplayName("StudentDTO debe inicializarse correctamente") + void testStudentDTO_Initialization() { + StudentDTO student = new StudentDTO(); + student.setId(1L); + student.setName("John Doe"); + + assertEquals(1L, student.getId()); + assertEquals("John Doe", student.getName()); + } + + @Test + @DisplayName("StudentDTO debe manejar identidades") + void testStudentDTO_Identities() { + StudentDTO student = new StudentDTO(); + Map identities = new HashMap<>(); + + StudentIdentityDTO githubIdentity = new StudentIdentityDTO(); + githubIdentity.setUsername("johndoe"); + identities.put(DataSource.GITHUB, githubIdentity); + + student.setIdentities(identities); + + assertNotNull(student.getIdentities()); + assertEquals(1, student.getIdentities().size()); + assertEquals("johndoe", student.getIdentities().get(DataSource.GITHUB).getUsername()); + } + + @Test + @DisplayName("CategoryDTO debe inicializarse correctamente") + void testCategoryDTO_Initialization() { + CategoryDTO category = new CategoryDTO(); + category.setCategory("Testing"); + category.setPatternGroup("test-group"); + + assertEquals("Testing", category.getCategory()); + assertEquals("test-group", category.getPatternGroup()); + } + + @Test + @DisplayName("MetricDTO debe inicializarse correctamente") + void testMetricDTO_Initialization() { + MetricDTO metric = new MetricDTO(); + metric.setId("1"); + metric.setName("Code Coverage"); + metric.setDescription("Percentage of code covered"); + + assertEquals("1", metric.getId()); + assertEquals("Code Coverage", metric.getName()); + assertEquals("Percentage of code covered", metric.getDescription()); + } + + @Test + @DisplayName("FactorDTO debe inicializarse correctamente") + void testFactorDTO_Initialization() { + FactorDTO factor = new FactorDTO(); + factor.setId("1"); + factor.setName("Quality Factor"); + factor.setDescription("Quality description"); + + assertEquals("1", factor.getId()); + assertEquals("Quality Factor", factor.getName()); + assertEquals("Quality description", factor.getDescription()); + } + + @Test + @DisplayName("ProjectIdentityDTO debe manejar URL") + void testProjectIdentityDTO() { + ProjectIdentityDTO identity = new ProjectIdentityDTO(); + identity.setUrl("https://github.com/test/repo"); + + assertEquals("https://github.com/test/repo", identity.getUrl()); + } + + @Test + @DisplayName("StudentIdentityDTO debe manejar username") + void testStudentIdentityDTO() { + StudentIdentityDTO identity = new StudentIdentityDTO(); + identity.setUsername("testuser"); + + assertEquals("testuser", identity.getUsername()); + } + + @Test + @DisplayName("WizardStatusDTO debe inicializarse con todos los flags") + void testWizardStatusDTO() { + WizardStatusDTO status = new WizardStatusDTO(true, true, true, true, true); + + assertTrue(status.isHasProjects()); + assertTrue(status.isHasData()); + assertTrue(status.isHasMetricsCategories()); + assertTrue(status.isHasFactorsCategories()); + assertTrue(status.isHasStrategicIndicatorCategories()); + } + + @Test + @DisplayName("WizardStatusDTO debe manejar estado parcial") + void testWizardStatusDTO_PartialState() { + WizardStatusDTO status = new WizardStatusDTO(true, false, true, false, true); + + assertTrue(status.isHasProjects()); + assertFalse(status.isHasData()); + assertTrue(status.isHasMetricsCategories()); + assertFalse(status.isHasFactorsCategories()); + assertTrue(status.isHasStrategicIndicatorCategories()); + } + + @Test + @DisplayName("ProjectDTO debe permitir null en campos opcionales") + void testProjectDTO_NullableFields() { + ProjectDTO project = new ProjectDTO(); + project.setGithubToken(null); + project.setDescription(null); + project.setStudents(null); + project.setIdentities(null); + + assertNull(project.getGithubToken()); + assertNull(project.getDescription()); + assertNull(project.getStudents()); + assertNull(project.getIdentities()); + } + + @Test + @DisplayName("StudentDTO debe manejar lista vacía de identidades") + void testStudentDTO_EmptyIdentities() { + StudentDTO student = new StudentDTO(); + student.setIdentities(new HashMap<>()); + + assertNotNull(student.getIdentities()); + assertTrue(student.getIdentities().isEmpty()); + } + + @Test + @DisplayName("MetricDTO y FactorDTO deben soportar ID tipo String") + void testMetricAndFactorDTO_StringId() { + MetricDTO metric = new MetricDTO(); + metric.setId("metric-abc-123"); + + FactorDTO factor = new FactorDTO(); + factor.setId("factor-xyz-789"); + + assertEquals("metric-abc-123", metric.getId()); + assertEquals("factor-xyz-789", factor.getId()); + } + + @Test + @DisplayName("IntervalDTO debe inicializarse correctamente") + void testIntervalDTO() { + IntervalDTO interval = new IntervalDTO(); + interval.setName("Test Interval"); + interval.setColor("#FF0000"); + + assertEquals("Test Interval", interval.getName()); + assertEquals("#FF0000", interval.getColor()); + } + + @Test + @DisplayName("SaveSyncResponseDTO y SaveSyncStepDTO deben tener equals y hashCode") + void testSaveSyncDTOs_EqualsHashCode() { + SaveSyncStepDTO step1 = new SaveSyncStepDTO(1, "Step", "Detail", "SUCCESS", null); + SaveSyncStepDTO step2 = new SaveSyncStepDTO(1, "Step", "Detail", "SUCCESS", null); + SaveSyncStepDTO step3 = new SaveSyncStepDTO(2, "Other", "Detail", "FAILED", "Error"); + + assertEquals(step1, step2); + assertEquals(step1.hashCode(), step2.hashCode()); + assertNotEquals(step1, step3); + + SaveSyncResponseDTO response1 = new SaveSyncResponseDTO(); + response1.setSuccess(true); + + SaveSyncResponseDTO response2 = new SaveSyncResponseDTO(); + response2.setSuccess(true); + + assertEquals(response1, response2); + assertEquals(response1.hashCode(), response2.hashCode()); + } + + @Test + @DisplayName("ProjectDTO debe implementar equals correctamente") + void testProjectDTO_Equals() { + ProjectDTO project1 = new ProjectDTO(); + project1.setId(1L); + project1.setName("Test"); + + ProjectDTO project2 = new ProjectDTO(); + project2.setId(1L); + project2.setName("Test"); + + ProjectDTO project3 = new ProjectDTO(); + project3.setId(2L); + project3.setName("Different"); + + assertEquals(project1, project2); + assertNotEquals(project1, project3); + assertNotEquals(project1, null); + assertEquals(project1, project1); + } + + @Test + @DisplayName("ProjectDTO debe implementar hashCode correctamente") + void testProjectDTO_HashCode() { + ProjectDTO project1 = new ProjectDTO(); + project1.setId(1L); + project1.setName("Test"); + + ProjectDTO project2 = new ProjectDTO(); + project2.setId(1L); + project2.setName("Test"); + + assertEquals(project1.hashCode(), project2.hashCode()); + } + + @Test + @DisplayName("ProjectDTO debe implementar toString correctamente") + void testProjectDTO_ToString() { + ProjectDTO project = new ProjectDTO(); + project.setId(1L); + project.setName("Test"); + + String toString = project.toString(); + assertNotNull(toString); + assertTrue(toString.contains("ProjectDTO") || toString.contains("Test")); + } + + @Test + @DisplayName("StudentDTO debe implementar equals correctamente") + void testStudentDTO_Equals() { + StudentDTO student1 = new StudentDTO(); + student1.setId(1L); + student1.setName("John"); + + StudentDTO student2 = new StudentDTO(); + student2.setId(1L); + student2.setName("John"); + + assertEquals(student1, student2); + assertEquals(student1, student1); + assertNotEquals(student1, null); + } + + @Test + @DisplayName("StudentDTO debe implementar hashCode correctamente") + void testStudentDTO_HashCode() { + StudentDTO student1 = new StudentDTO(); + student1.setId(1L); + student1.setName("John"); + + StudentDTO student2 = new StudentDTO(); + student2.setId(1L); + student2.setName("John"); + + assertEquals(student1.hashCode(), student2.hashCode()); + } + + @Test + @DisplayName("StudentDTO debe implementar toString correctamente") + void testStudentDTO_ToString() { + StudentDTO student = new StudentDTO(); + student.setId(1L); + student.setName("John"); + + assertNotNull(student.toString()); + } + + @Test + @DisplayName("CategoryDTO debe implementar equals correctamente") + void testCategoryDTO_Equals() { + CategoryDTO category1 = new CategoryDTO(); + category1.setCategory("Testing"); + + CategoryDTO category2 = new CategoryDTO(); + category2.setCategory("Testing"); + + assertEquals(category1, category2); + assertNotEquals(category1, null); + } + + @Test + @DisplayName("CategoryDTO debe implementar hashCode correctamente") + void testCategoryDTO_HashCode() { + CategoryDTO category1 = new CategoryDTO(); + category1.setCategory("Testing"); + + CategoryDTO category2 = new CategoryDTO(); + category2.setCategory("Testing"); + + assertEquals(category1.hashCode(), category2.hashCode()); + } + + @Test + @DisplayName("CategoryDTO debe implementar toString correctamente") + void testCategoryDTO_ToString() { + CategoryDTO category = new CategoryDTO(); + category.setCategory("Testing"); + + assertNotNull(category.toString()); + } + + @Test + @DisplayName("MetricDTO debe implementar equals correctamente") + void testMetricDTO_Equals() { + MetricDTO metric1 = new MetricDTO(); + metric1.setId("1"); + metric1.setName("Coverage"); + + MetricDTO metric2 = new MetricDTO(); + metric2.setId("1"); + metric2.setName("Coverage"); + + assertEquals(metric1, metric2); + assertNotEquals(metric1, null); + } + + @Test + @DisplayName("MetricDTO debe implementar hashCode correctamente") + void testMetricDTO_HashCode() { + MetricDTO metric1 = new MetricDTO(); + metric1.setId("1"); + metric1.setName("Coverage"); + + MetricDTO metric2 = new MetricDTO(); + metric2.setId("1"); + metric2.setName("Coverage"); + + assertEquals(metric1.hashCode(), metric2.hashCode()); + } + + @Test + @DisplayName("MetricDTO debe implementar toString correctamente") + void testMetricDTO_ToString() { + MetricDTO metric = new MetricDTO(); + metric.setId("1"); + metric.setName("Coverage"); + + assertNotNull(metric.toString()); + } + + @Test + @DisplayName("FactorDTO debe implementar equals correctamente") + void testFactorDTO_Equals() { + FactorDTO factor1 = new FactorDTO(); + factor1.setId("1"); + factor1.setName("Quality"); + + FactorDTO factor2 = new FactorDTO(); + factor2.setId("1"); + factor2.setName("Quality"); + + assertEquals(factor1, factor2); + assertNotEquals(factor1, null); + } + + @Test + @DisplayName("FactorDTO debe implementar hashCode correctamente") + void testFactorDTO_HashCode() { + FactorDTO factor1 = new FactorDTO(); + factor1.setId("1"); + factor1.setName("Quality"); + + FactorDTO factor2 = new FactorDTO(); + factor2.setId("1"); + factor2.setName("Quality"); + + assertEquals(factor1.hashCode(), factor2.hashCode()); + } + + @Test + @DisplayName("FactorDTO debe implementar toString correctamente") + void testFactorDTO_ToString() { + FactorDTO factor = new FactorDTO(); + factor.setId("1"); + factor.setName("Quality"); + + assertNotNull(factor.toString()); + } + + @Test + @DisplayName("WizardStatusDTO debe implementar equals correctamente") + void testWizardStatusDTO_Equals() { + WizardStatusDTO status1 = new WizardStatusDTO(true, true, true, true, true); + WizardStatusDTO status2 = new WizardStatusDTO(true, true, true, true, true); + + assertEquals(status1, status2); + assertNotEquals(status1, null); + } + + @Test + @DisplayName("WizardStatusDTO debe implementar hashCode correctamente") + void testWizardStatusDTO_HashCode() { + WizardStatusDTO status1 = new WizardStatusDTO(true, true, true, true, true); + WizardStatusDTO status2 = new WizardStatusDTO(true, true, true, true, true); + + assertEquals(status1.hashCode(), status2.hashCode()); + } + + @Test + @DisplayName("WizardStatusDTO debe implementar toString correctamente") + void testWizardStatusDTO_ToString() { + WizardStatusDTO status = new WizardStatusDTO(true, true, true, true, true); + + assertNotNull(status.toString()); + } + + @Test + @DisplayName("FactorDTO debe inicializarse con constructor completo") + void testFactorDTO_AllArgsConstructor() { + // Arrange + List metrics = Arrays.asList("metric1", "metric2"); + List weights = Arrays.asList("0.5", "0.5"); + + // Act + FactorDTO factor = new FactorDTO("1", "ext-1", "Quality Factor", "Description", + "Category1", "0.7", "MEAN", metrics, weights); + + // Assert + assertEquals("1", factor.getId()); + assertEquals("ext-1", factor.getExternalId()); + assertEquals("Quality Factor", factor.getName()); + assertEquals("Description", factor.getDescription()); + assertEquals("Category1", factor.getCategory()); + assertEquals("0.7", factor.getThreshold()); + assertEquals("MEAN", factor.getType()); + assertEquals(2, factor.getMetrics().size()); + assertEquals(2, factor.getMetricsWeights().size()); + } + + @Test + @DisplayName("FactorDTO debe configurar threshold correctamente") + void testFactorDTO_SetThreshold() { + // Arrange + FactorDTO factor = new FactorDTO(); + + // Act + factor.setThreshold("0.8"); + + // Assert + assertEquals("0.8", factor.getThreshold()); + } + + @Test + @DisplayName("FactorDTO debe configurar type correctamente") + void testFactorDTO_SetType() { + // Arrange + FactorDTO factor = new FactorDTO(); + + // Act + factor.setType("WEIGHTED_MEAN"); + + // Assert + assertEquals("WEIGHTED_MEAN", factor.getType()); + } + + @Test + @DisplayName("FactorDTO debe configurar metrics correctamente") + void testFactorDTO_SetMetrics() { + // Arrange + FactorDTO factor = new FactorDTO(); + List metrics = Arrays.asList("coverage", "complexity", "duplication"); + + // Act + factor.setMetrics(metrics); + + // Assert + assertNotNull(factor.getMetrics()); + assertEquals(3, factor.getMetrics().size()); + assertTrue(factor.getMetrics().contains("coverage")); + assertTrue(factor.getMetrics().contains("complexity")); + } + + @Test + @DisplayName("FactorDTO debe configurar metricsWeights correctamente") + void testFactorDTO_SetMetricsWeights() { + // Arrange + FactorDTO factor = new FactorDTO(); + List weights = Arrays.asList("0.4", "0.3", "0.3"); + + // Act + factor.setMetricsWeights(weights); + + // Assert + assertNotNull(factor.getMetricsWeights()); + assertEquals(3, factor.getMetricsWeights().size()); + assertEquals("0.4", factor.getMetricsWeights().get(0)); + } + + @Test + @DisplayName("FactorDTO equals debe comparar todos los campos") + void testFactorDTO_EqualsAllFields() { + // Arrange + List metrics = Arrays.asList("m1", "m2"); + List weights = Arrays.asList("0.5", "0.5"); + + FactorDTO factor1 = new FactorDTO("1", "ext-1", "Name", "Desc", "Cat", "0.7", "MEAN", metrics, weights); + FactorDTO factor2 = new FactorDTO("1", "ext-1", "Name", "Desc", "Cat", "0.7", "MEAN", metrics, weights); + FactorDTO factor3 = new FactorDTO("2", "ext-2", "Other", "Desc", "Cat", "0.8", "SUM", metrics, weights); + + // Assert + assertEquals(factor1, factor2); + assertNotEquals(factor1, factor3); + assertEquals(factor1.hashCode(), factor2.hashCode()); + } + + @Test + @DisplayName("FactorDTO equals debe manejar campos null") + void testFactorDTO_EqualsWithNulls() { + // Arrange + FactorDTO factor1 = new FactorDTO(); + FactorDTO factor2 = new FactorDTO(); + + // Assert + assertEquals(factor1, factor2); + assertEquals(factor1.hashCode(), factor2.hashCode()); + } + + @Test + @DisplayName("FactorDTO debe manejar listas vacías") + void testFactorDTO_EmptyLists() { + // Arrange + FactorDTO factor = new FactorDTO(); + + // Act + factor.setMetrics(Arrays.asList()); + factor.setMetricsWeights(Arrays.asList()); + + // Assert + assertNotNull(factor.getMetrics()); + assertNotNull(factor.getMetricsWeights()); + assertTrue(factor.getMetrics().isEmpty()); + assertTrue(factor.getMetricsWeights().isEmpty()); + } + + @Test + @DisplayName("FactorDTO toString debe contener información relevante") + void testFactorDTO_ToStringContent() { + // Arrange + FactorDTO factor = new FactorDTO(); + factor.setId("factor-123"); + factor.setName("Quality Factor"); + factor.setThreshold("0.75"); + + // Act + String toString = factor.toString(); + + // Assert + assertNotNull(toString); + assertTrue(toString.contains("FactorDTO") || toString.contains("factor-123") || toString.contains("Quality")); + } + + @Test + @DisplayName("FactorDTO debe soportar modificación de campos después de construcción") + void testFactorDTO_ModifyAfterConstruction() { + // Arrange + FactorDTO factor = new FactorDTO("1", "ext", "Name", "Desc", "Cat", "0.5", "MEAN", null, null); + + // Act + factor.setName("Updated Name"); + factor.setThreshold("0.9"); + factor.setMetrics(Arrays.asList("new-metric")); + + // Assert + assertEquals("Updated Name", factor.getName()); + assertEquals("0.9", factor.getThreshold()); + assertEquals(1, factor.getMetrics().size()); + } +} + diff --git a/src/test/java/com/upc/ld_admintool/rest/DTO/IntervalDTOTest.java b/src/test/java/com/upc/ld_admintool/rest/DTO/IntervalDTOTest.java index 1637d95..0a87728 100644 --- a/src/test/java/com/upc/ld_admintool/rest/DTO/IntervalDTOTest.java +++ b/src/test/java/com/upc/ld_admintool/rest/DTO/IntervalDTOTest.java @@ -1,310 +1,310 @@ -package com.upc.ld_admintool.rest.DTO; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Tests unitarios para IntervalDTO - * Valida la funcionalidad del DTO para intervalos de indicadores estratégicos - */ -@DisplayName("IntervalDTO - Tests Unitarios") -class IntervalDTOTest { - - private IntervalDTO interval; - - @BeforeEach - void setUp() { - interval = new IntervalDTO(); - } - - @Test - @DisplayName("Debe crear instancia con valores por defecto") - void testDefaultConstructor() { - // Arrange & Act - IntervalDTO newInterval = new IntervalDTO(); - - // Assert - assertNull(newInterval.getName()); - assertNull(newInterval.getColor()); - } - - @Test - @DisplayName("Debe configurar name correctamente") - void testSetName() { - // Act - interval.setName("Good"); - - // Assert - assertEquals("Good", interval.getName()); - } - - @Test - @DisplayName("Debe configurar color correctamente") - void testSetColor() { - // Act - interval.setColor("#00FF00"); - - // Assert - assertEquals("#00FF00", interval.getColor()); - } - - @Test - @DisplayName("Debe manejar diferentes nombres de intervalo") - void testDifferentNames() { - // Test "Excellent" - interval.setName("Excellent"); - assertEquals("Excellent", interval.getName()); - - // Test "Good" - interval.setName("Good"); - assertEquals("Good", interval.getName()); - - // Test "Average" - interval.setName("Average"); - assertEquals("Average", interval.getName()); - - // Test "Poor" - interval.setName("Poor"); - assertEquals("Poor", interval.getName()); - } - - @Test - @DisplayName("Debe manejar diferentes colores hexadecimales") - void testDifferentColors() { - // Test Green - interval.setColor("#00FF00"); - assertEquals("#00FF00", interval.getColor()); - - // Test Red - interval.setColor("#FF0000"); - assertEquals("#FF0000", interval.getColor()); - - // Test Blue - interval.setColor("#0000FF"); - assertEquals("#0000FF", interval.getColor()); - - // Test Yellow - interval.setColor("#FFFF00"); - assertEquals("#FFFF00", interval.getColor()); - } - - @Test - @DisplayName("Debe permitir valores null") - void testNullValues() { - // Act - interval.setName(null); - interval.setColor(null); - - // Assert - assertNull(interval.getName()); - assertNull(interval.getColor()); - } - - @Test - @DisplayName("Debe implementar equals correctamente") - void testEquals() { - // Arrange - IntervalDTO interval1 = new IntervalDTO(); - interval1.setName("Good"); - interval1.setColor("#00FF00"); - - IntervalDTO interval2 = new IntervalDTO(); - interval2.setName("Good"); - interval2.setColor("#00FF00"); - - IntervalDTO interval3 = new IntervalDTO(); - interval3.setName("Excellent"); - interval3.setColor("#008000"); - - // Assert - assertEquals(interval1, interval2); - assertNotEquals(interval1, interval3); - assertEquals(interval1, interval1); - assertNotEquals(interval1, null); - } - - @Test - @DisplayName("Debe implementar hashCode correctamente") - void testHashCode() { - // Arrange - IntervalDTO interval1 = new IntervalDTO(); - interval1.setName("Good"); - interval1.setColor("#00FF00"); - - IntervalDTO interval2 = new IntervalDTO(); - interval2.setName("Good"); - interval2.setColor("#00FF00"); - - // Assert - assertEquals(interval1.hashCode(), interval2.hashCode()); - } - - @Test - @DisplayName("Debe implementar toString correctamente") - void testToString() { - // Arrange - interval.setName("Good"); - interval.setColor("#00FF00"); - - // Act - String result = interval.toString(); - - // Assert - assertNotNull(result); - assertTrue(result.contains("IntervalDTO") || result.contains("Good") || result.contains("#00FF00")); - } - - @Test - @DisplayName("Debe mantener consistencia de hashCode") - void testHashCodeConsistency() { - // Arrange - interval.setName("Good"); - interval.setColor("#00FF00"); - - // Act - int hash1 = interval.hashCode(); - int hash2 = interval.hashCode(); - - // Assert - assertEquals(hash1, hash2); - } - - @Test - @DisplayName("Debe comparar correctamente con objetos de diferente tipo") - void testEqualsDifferentType() { - // Arrange - interval.setName("Good"); - - // Assert - assertNotEquals(interval, new Object()); - assertNotEquals(interval, "String"); - } - - @Test - @DisplayName("Debe manejar valores vacíos") - void testEmptyValues() { - // Act - interval.setName(""); - interval.setColor(""); - - // Assert - assertEquals("", interval.getName()); - assertEquals("", interval.getColor()); - } - - @Test - @DisplayName("Debe actualizar valores existentes") - void testUpdateValues() { - // Arrange - Initial values - interval.setName("Good"); - interval.setColor("#00FF00"); - - // Act - Update values - interval.setName("Excellent"); - interval.setColor("#008000"); - - // Assert - assertEquals("Excellent", interval.getName()); - assertEquals("#008000", interval.getColor()); - } - - @Test - @DisplayName("Debe manejar intervalo completo") - void testCompleteInterval() { - // Act - interval.setName("Very Good"); - interval.setColor("#32CD32"); - - // Assert - assertAll( - () -> assertEquals("Very Good", interval.getName()), - () -> assertEquals("#32CD32", interval.getColor()) - ); - } - - @Test - @DisplayName("Debe manejar nombres con espacios") - void testNamesWithSpaces() { - // Act - interval.setName("Very Good Performance"); - - // Assert - assertEquals("Very Good Performance", interval.getName()); - } - - @Test - @DisplayName("Debe manejar colores en minúsculas") - void testLowercaseColors() { - // Act - interval.setColor("#00ff00"); - - // Assert - assertEquals("#00ff00", interval.getColor()); - } - - @Test - @DisplayName("Debe manejar colores sin símbolo #") - void testColorsWithoutHash() { - // Act - interval.setColor("00FF00"); - - // Assert - assertEquals("00FF00", interval.getColor()); - } - - @Test - @DisplayName("Debe crear múltiples instancias independientes") - void testMultipleInstances() { - // Arrange & Act - IntervalDTO interval1 = new IntervalDTO(); - interval1.setName("Good"); - interval1.setColor("#00FF00"); - - IntervalDTO interval2 = new IntervalDTO(); - interval2.setName("Poor"); - interval2.setColor("#FF0000"); - - // Assert - assertNotEquals(interval1.getName(), interval2.getName()); - assertNotEquals(interval1.getColor(), interval2.getColor()); - } - - @Test - @DisplayName("Debe comparar igualdad solo con mismo nombre y color") - void testEqualsBothFieldsRequired() { - // Arrange - IntervalDTO interval1 = new IntervalDTO(); - interval1.setName("Good"); - interval1.setColor("#00FF00"); - - IntervalDTO interval2 = new IntervalDTO(); - interval2.setName("Good"); - interval2.setColor("#FF0000"); // Different color - - // Assert - assertNotEquals(interval1, interval2); - } - - @Test - @DisplayName("Debe manejar nombres especiales") - void testSpecialNames() { - // Act - interval.setName("Top 25%"); - - // Assert - assertEquals("Top 25%", interval.getName()); - } - - @Test - @DisplayName("Debe manejar colores RGB completos") - void testFullRGBColors() { - // Act - interval.setColor("#1A2B3C"); - - // Assert - assertEquals("#1A2B3C", interval.getColor()); - } -} +package com.upc.ld_admintool.rest.DTO; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests unitarios para IntervalDTO + * Valida la funcionalidad del DTO para intervalos de indicadores estratégicos + */ +@DisplayName("IntervalDTO - Tests Unitarios") +class IntervalDTOTest { + + private IntervalDTO interval; + + @BeforeEach + void setUp() { + interval = new IntervalDTO(); + } + + @Test + @DisplayName("Debe crear instancia con valores por defecto") + void testDefaultConstructor() { + // Arrange & Act + IntervalDTO newInterval = new IntervalDTO(); + + // Assert + assertNull(newInterval.getName()); + assertNull(newInterval.getColor()); + } + + @Test + @DisplayName("Debe configurar name correctamente") + void testSetName() { + // Act + interval.setName("Good"); + + // Assert + assertEquals("Good", interval.getName()); + } + + @Test + @DisplayName("Debe configurar color correctamente") + void testSetColor() { + // Act + interval.setColor("#00FF00"); + + // Assert + assertEquals("#00FF00", interval.getColor()); + } + + @Test + @DisplayName("Debe manejar diferentes nombres de intervalo") + void testDifferentNames() { + // Test "Excellent" + interval.setName("Excellent"); + assertEquals("Excellent", interval.getName()); + + // Test "Good" + interval.setName("Good"); + assertEquals("Good", interval.getName()); + + // Test "Average" + interval.setName("Average"); + assertEquals("Average", interval.getName()); + + // Test "Poor" + interval.setName("Poor"); + assertEquals("Poor", interval.getName()); + } + + @Test + @DisplayName("Debe manejar diferentes colores hexadecimales") + void testDifferentColors() { + // Test Green + interval.setColor("#00FF00"); + assertEquals("#00FF00", interval.getColor()); + + // Test Red + interval.setColor("#FF0000"); + assertEquals("#FF0000", interval.getColor()); + + // Test Blue + interval.setColor("#0000FF"); + assertEquals("#0000FF", interval.getColor()); + + // Test Yellow + interval.setColor("#FFFF00"); + assertEquals("#FFFF00", interval.getColor()); + } + + @Test + @DisplayName("Debe permitir valores null") + void testNullValues() { + // Act + interval.setName(null); + interval.setColor(null); + + // Assert + assertNull(interval.getName()); + assertNull(interval.getColor()); + } + + @Test + @DisplayName("Debe implementar equals correctamente") + void testEquals() { + // Arrange + IntervalDTO interval1 = new IntervalDTO(); + interval1.setName("Good"); + interval1.setColor("#00FF00"); + + IntervalDTO interval2 = new IntervalDTO(); + interval2.setName("Good"); + interval2.setColor("#00FF00"); + + IntervalDTO interval3 = new IntervalDTO(); + interval3.setName("Excellent"); + interval3.setColor("#008000"); + + // Assert + assertEquals(interval1, interval2); + assertNotEquals(interval1, interval3); + assertEquals(interval1, interval1); + assertNotEquals(interval1, null); + } + + @Test + @DisplayName("Debe implementar hashCode correctamente") + void testHashCode() { + // Arrange + IntervalDTO interval1 = new IntervalDTO(); + interval1.setName("Good"); + interval1.setColor("#00FF00"); + + IntervalDTO interval2 = new IntervalDTO(); + interval2.setName("Good"); + interval2.setColor("#00FF00"); + + // Assert + assertEquals(interval1.hashCode(), interval2.hashCode()); + } + + @Test + @DisplayName("Debe implementar toString correctamente") + void testToString() { + // Arrange + interval.setName("Good"); + interval.setColor("#00FF00"); + + // Act + String result = interval.toString(); + + // Assert + assertNotNull(result); + assertTrue(result.contains("IntervalDTO") || result.contains("Good") || result.contains("#00FF00")); + } + + @Test + @DisplayName("Debe mantener consistencia de hashCode") + void testHashCodeConsistency() { + // Arrange + interval.setName("Good"); + interval.setColor("#00FF00"); + + // Act + int hash1 = interval.hashCode(); + int hash2 = interval.hashCode(); + + // Assert + assertEquals(hash1, hash2); + } + + @Test + @DisplayName("Debe comparar correctamente con objetos de diferente tipo") + void testEqualsDifferentType() { + // Arrange + interval.setName("Good"); + + // Assert + assertNotEquals(interval, new Object()); + assertNotEquals(interval, "String"); + } + + @Test + @DisplayName("Debe manejar valores vacíos") + void testEmptyValues() { + // Act + interval.setName(""); + interval.setColor(""); + + // Assert + assertEquals("", interval.getName()); + assertEquals("", interval.getColor()); + } + + @Test + @DisplayName("Debe actualizar valores existentes") + void testUpdateValues() { + // Arrange - Initial values + interval.setName("Good"); + interval.setColor("#00FF00"); + + // Act - Update values + interval.setName("Excellent"); + interval.setColor("#008000"); + + // Assert + assertEquals("Excellent", interval.getName()); + assertEquals("#008000", interval.getColor()); + } + + @Test + @DisplayName("Debe manejar intervalo completo") + void testCompleteInterval() { + // Act + interval.setName("Very Good"); + interval.setColor("#32CD32"); + + // Assert + assertAll( + () -> assertEquals("Very Good", interval.getName()), + () -> assertEquals("#32CD32", interval.getColor()) + ); + } + + @Test + @DisplayName("Debe manejar nombres con espacios") + void testNamesWithSpaces() { + // Act + interval.setName("Very Good Performance"); + + // Assert + assertEquals("Very Good Performance", interval.getName()); + } + + @Test + @DisplayName("Debe manejar colores en minúsculas") + void testLowercaseColors() { + // Act + interval.setColor("#00ff00"); + + // Assert + assertEquals("#00ff00", interval.getColor()); + } + + @Test + @DisplayName("Debe manejar colores sin símbolo #") + void testColorsWithoutHash() { + // Act + interval.setColor("00FF00"); + + // Assert + assertEquals("00FF00", interval.getColor()); + } + + @Test + @DisplayName("Debe crear múltiples instancias independientes") + void testMultipleInstances() { + // Arrange & Act + IntervalDTO interval1 = new IntervalDTO(); + interval1.setName("Good"); + interval1.setColor("#00FF00"); + + IntervalDTO interval2 = new IntervalDTO(); + interval2.setName("Poor"); + interval2.setColor("#FF0000"); + + // Assert + assertNotEquals(interval1.getName(), interval2.getName()); + assertNotEquals(interval1.getColor(), interval2.getColor()); + } + + @Test + @DisplayName("Debe comparar igualdad solo con mismo nombre y color") + void testEqualsBothFieldsRequired() { + // Arrange + IntervalDTO interval1 = new IntervalDTO(); + interval1.setName("Good"); + interval1.setColor("#00FF00"); + + IntervalDTO interval2 = new IntervalDTO(); + interval2.setName("Good"); + interval2.setColor("#FF0000"); // Different color + + // Assert + assertNotEquals(interval1, interval2); + } + + @Test + @DisplayName("Debe manejar nombres especiales") + void testSpecialNames() { + // Act + interval.setName("Top 25%"); + + // Assert + assertEquals("Top 25%", interval.getName()); + } + + @Test + @DisplayName("Debe manejar colores RGB completos") + void testFullRGBColors() { + // Act + interval.setColor("#1A2B3C"); + + // Assert + assertEquals("#1A2B3C", interval.getColor()); + } +} diff --git a/src/test/java/com/upc/ld_admintool/rest/DTO/ProjectIdentityDTOTest.java b/src/test/java/com/upc/ld_admintool/rest/DTO/ProjectIdentityDTOTest.java index 4cb6bcb..5c001aa 100644 --- a/src/test/java/com/upc/ld_admintool/rest/DTO/ProjectIdentityDTOTest.java +++ b/src/test/java/com/upc/ld_admintool/rest/DTO/ProjectIdentityDTOTest.java @@ -1,301 +1,301 @@ -package com.upc.ld_admintool.rest.DTO; - -import com.upc.ld_admintool.domain.utils.DataSource; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Tests unitarios para ProjectIdentityDTO - * Valida la funcionalidad del DTO de identidad de proyecto - */ -@DisplayName("ProjectIdentityDTO - Tests Unitarios") -class ProjectIdentityDTOTest { - - private ProjectIdentityDTO projectIdentity; - - @BeforeEach - void setUp() { - projectIdentity = new ProjectIdentityDTO(); - } - - @Test - @DisplayName("Debe crear instancia con constructor sin argumentos") - void testNoArgsConstructor() { - // Arrange & Act - ProjectIdentityDTO identity = new ProjectIdentityDTO(); - - // Assert - assertNull(identity.getDataSource()); - assertNull(identity.getUrl()); - assertNull(identity.getProject()); - } - - @Test - @DisplayName("Debe crear instancia con constructor con todos los argumentos") - void testAllArgsConstructor() { - // Arrange - ProjectDTO project = new ProjectDTO(); - project.setId(1L); - project.setName("Test Project"); - - // Act - ProjectIdentityDTO identity = new ProjectIdentityDTO( - DataSource.GITHUB, - "https://github.com/testorg", - project - ); - - // Assert - assertEquals(DataSource.GITHUB, identity.getDataSource()); - assertEquals("https://github.com/testorg", identity.getUrl()); - assertNotNull(identity.getProject()); - assertEquals("Test Project", identity.getProject().getName()); - } - - @Test - @DisplayName("Debe configurar dataSource correctamente") - void testSetDataSource() { - // Act - projectIdentity.setDataSource(DataSource.GITHUB); - - // Assert - assertEquals(DataSource.GITHUB, projectIdentity.getDataSource()); - } - - @Test - @DisplayName("Debe configurar URL correctamente") - void testSetUrl() { - // Act - projectIdentity.setUrl("https://github.com/myorg"); - - // Assert - assertEquals("https://github.com/myorg", projectIdentity.getUrl()); - } - - @Test - @DisplayName("Debe configurar proyecto correctamente") - void testSetProject() { - // Arrange - ProjectDTO project = new ProjectDTO(); - project.setId(100L); - project.setName("My Project"); - - // Act - projectIdentity.setProject(project); - - // Assert - assertNotNull(projectIdentity.getProject()); - assertEquals(100L, projectIdentity.getProject().getId()); - assertEquals("My Project", projectIdentity.getProject().getName()); - } - - @Test - @DisplayName("Debe manejar diferentes DataSources") - void testDifferentDataSources() { - // Test GITHUB - projectIdentity.setDataSource(DataSource.GITHUB); - assertEquals(DataSource.GITHUB, projectIdentity.getDataSource()); - - // Test TAIGA - projectIdentity.setDataSource(DataSource.TAIGA); - assertEquals(DataSource.TAIGA, projectIdentity.getDataSource()); - } - - @Test - @DisplayName("Debe permitir valores null") - void testNullValues() { - // Act - projectIdentity.setDataSource(null); - projectIdentity.setUrl(null); - projectIdentity.setProject(null); - - // Assert - assertNull(projectIdentity.getDataSource()); - assertNull(projectIdentity.getUrl()); - assertNull(projectIdentity.getProject()); - } - - @Test - @DisplayName("Debe implementar equals correctamente") - void testEquals() { - // Arrange - ProjectDTO project = new ProjectDTO(); - project.setId(1L); - - ProjectIdentityDTO identity1 = new ProjectIdentityDTO( - DataSource.GITHUB, - "https://github.com/test", - project - ); - - ProjectIdentityDTO identity2 = new ProjectIdentityDTO( - DataSource.GITHUB, - "https://github.com/test", - project - ); - - ProjectIdentityDTO identity3 = new ProjectIdentityDTO( - DataSource.TAIGA, - "https://taiga.io/test", - project - ); - - // Assert - assertEquals(identity1, identity2); - assertNotEquals(identity1, identity3); - assertEquals(identity1, identity1); - assertNotEquals(identity1, null); - } - - @Test - @DisplayName("Debe implementar hashCode correctamente") - void testHashCode() { - // Arrange - ProjectDTO project = new ProjectDTO(); - project.setId(1L); - - ProjectIdentityDTO identity1 = new ProjectIdentityDTO( - DataSource.GITHUB, - "https://github.com/test", - project - ); - - ProjectIdentityDTO identity2 = new ProjectIdentityDTO( - DataSource.GITHUB, - "https://github.com/test", - project - ); - - // Assert - assertEquals(identity1.hashCode(), identity2.hashCode()); - } - - @Test - @DisplayName("Debe implementar toString correctamente") - void testToString() { - // Arrange - projectIdentity.setDataSource(DataSource.GITHUB); - projectIdentity.setUrl("https://github.com/test"); - - // Act - String result = projectIdentity.toString(); - - // Assert - assertNotNull(result); - assertTrue(result.contains("ProjectIdentityDTO") || result.contains("GITHUB")); - } - - @Test - @DisplayName("Debe manejar URL de GitHub") - void testGitHubUrl() { - // Act - projectIdentity.setDataSource(DataSource.GITHUB); - projectIdentity.setUrl("https://github.com/organization/repo"); - - // Assert - assertEquals(DataSource.GITHUB, projectIdentity.getDataSource()); - assertEquals("https://github.com/organization/repo", projectIdentity.getUrl()); - } - - @Test - @DisplayName("Debe manejar URL de Taiga") - void testTaigaUrl() { - // Act - projectIdentity.setDataSource(DataSource.TAIGA); - projectIdentity.setUrl("https://tree.taiga.io/project/user-project"); - - // Assert - assertEquals(DataSource.TAIGA, projectIdentity.getDataSource()); - assertEquals("https://tree.taiga.io/project/user-project", projectIdentity.getUrl()); - } - - @Test - @DisplayName("Debe actualizar todos los campos") - void testUpdateAllFields() { - // Arrange - ProjectDTO project1 = new ProjectDTO(); - project1.setId(1L); - project1.setName("Project 1"); - - ProjectDTO project2 = new ProjectDTO(); - project2.setId(2L); - project2.setName("Project 2"); - - // Act - Initial values - projectIdentity.setDataSource(DataSource.GITHUB); - projectIdentity.setUrl("https://github.com/old"); - projectIdentity.setProject(project1); - - // Act - Update values - projectIdentity.setDataSource(DataSource.TAIGA); - projectIdentity.setUrl("https://taiga.io/new"); - projectIdentity.setProject(project2); - - // Assert - assertEquals(DataSource.TAIGA, projectIdentity.getDataSource()); - assertEquals("https://taiga.io/new", projectIdentity.getUrl()); - assertEquals("Project 2", projectIdentity.getProject().getName()); - } - - @Test - @DisplayName("Debe mantener consistencia de hashCode") - void testHashCodeConsistency() { - // Arrange - projectIdentity.setDataSource(DataSource.GITHUB); - projectIdentity.setUrl("https://github.com/test"); - - // Act - int hash1 = projectIdentity.hashCode(); - int hash2 = projectIdentity.hashCode(); - - // Assert - assertEquals(hash1, hash2); - } - - @Test - @DisplayName("Debe comparar correctamente con objetos de diferente tipo") - void testEqualsDifferentType() { - // Arrange - projectIdentity.setDataSource(DataSource.GITHUB); - - // Assert - assertNotEquals(projectIdentity, new Object()); - assertNotEquals(projectIdentity, "String"); - } - - @Test - @DisplayName("Debe crear múltiples instancias independientes") - void testMultipleInstances() { - // Arrange & Act - ProjectIdentityDTO identity1 = new ProjectIdentityDTO(); - identity1.setDataSource(DataSource.GITHUB); - identity1.setUrl("https://github.com/org1"); - - ProjectIdentityDTO identity2 = new ProjectIdentityDTO(); - identity2.setDataSource(DataSource.TAIGA); - identity2.setUrl("https://taiga.io/org2"); - - // Assert - assertNotEquals(identity1.getDataSource(), identity2.getDataSource()); - assertNotEquals(identity1.getUrl(), identity2.getUrl()); - } - - @Test - @DisplayName("Debe manejar proyecto con identidades") - void testProjectWithIdentities() { - // Arrange - ProjectDTO project = new ProjectDTO(); - project.setId(1L); - project.setName("Complex Project"); - project.setExternalId("ext-123"); - - // Act - projectIdentity.setProject(project); - - // Assert - assertEquals("ext-123", projectIdentity.getProject().getExternalId()); - } -} +package com.upc.ld_admintool.rest.DTO; + +import com.upc.ld_admintool.domain.utils.DataSource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests unitarios para ProjectIdentityDTO + * Valida la funcionalidad del DTO de identidad de proyecto + */ +@DisplayName("ProjectIdentityDTO - Tests Unitarios") +class ProjectIdentityDTOTest { + + private ProjectIdentityDTO projectIdentity; + + @BeforeEach + void setUp() { + projectIdentity = new ProjectIdentityDTO(); + } + + @Test + @DisplayName("Debe crear instancia con constructor sin argumentos") + void testNoArgsConstructor() { + // Arrange & Act + ProjectIdentityDTO identity = new ProjectIdentityDTO(); + + // Assert + assertNull(identity.getDataSource()); + assertNull(identity.getUrl()); + assertNull(identity.getProject()); + } + + @Test + @DisplayName("Debe crear instancia con constructor con todos los argumentos") + void testAllArgsConstructor() { + // Arrange + ProjectDTO project = new ProjectDTO(); + project.setId(1L); + project.setName("Test Project"); + + // Act + ProjectIdentityDTO identity = new ProjectIdentityDTO( + DataSource.GITHUB, + "https://github.com/testorg", + project + ); + + // Assert + assertEquals(DataSource.GITHUB, identity.getDataSource()); + assertEquals("https://github.com/testorg", identity.getUrl()); + assertNotNull(identity.getProject()); + assertEquals("Test Project", identity.getProject().getName()); + } + + @Test + @DisplayName("Debe configurar dataSource correctamente") + void testSetDataSource() { + // Act + projectIdentity.setDataSource(DataSource.GITHUB); + + // Assert + assertEquals(DataSource.GITHUB, projectIdentity.getDataSource()); + } + + @Test + @DisplayName("Debe configurar URL correctamente") + void testSetUrl() { + // Act + projectIdentity.setUrl("https://github.com/myorg"); + + // Assert + assertEquals("https://github.com/myorg", projectIdentity.getUrl()); + } + + @Test + @DisplayName("Debe configurar proyecto correctamente") + void testSetProject() { + // Arrange + ProjectDTO project = new ProjectDTO(); + project.setId(100L); + project.setName("My Project"); + + // Act + projectIdentity.setProject(project); + + // Assert + assertNotNull(projectIdentity.getProject()); + assertEquals(100L, projectIdentity.getProject().getId()); + assertEquals("My Project", projectIdentity.getProject().getName()); + } + + @Test + @DisplayName("Debe manejar diferentes DataSources") + void testDifferentDataSources() { + // Test GITHUB + projectIdentity.setDataSource(DataSource.GITHUB); + assertEquals(DataSource.GITHUB, projectIdentity.getDataSource()); + + // Test TAIGA + projectIdentity.setDataSource(DataSource.TAIGA); + assertEquals(DataSource.TAIGA, projectIdentity.getDataSource()); + } + + @Test + @DisplayName("Debe permitir valores null") + void testNullValues() { + // Act + projectIdentity.setDataSource(null); + projectIdentity.setUrl(null); + projectIdentity.setProject(null); + + // Assert + assertNull(projectIdentity.getDataSource()); + assertNull(projectIdentity.getUrl()); + assertNull(projectIdentity.getProject()); + } + + @Test + @DisplayName("Debe implementar equals correctamente") + void testEquals() { + // Arrange + ProjectDTO project = new ProjectDTO(); + project.setId(1L); + + ProjectIdentityDTO identity1 = new ProjectIdentityDTO( + DataSource.GITHUB, + "https://github.com/test", + project + ); + + ProjectIdentityDTO identity2 = new ProjectIdentityDTO( + DataSource.GITHUB, + "https://github.com/test", + project + ); + + ProjectIdentityDTO identity3 = new ProjectIdentityDTO( + DataSource.TAIGA, + "https://taiga.io/test", + project + ); + + // Assert + assertEquals(identity1, identity2); + assertNotEquals(identity1, identity3); + assertEquals(identity1, identity1); + assertNotEquals(identity1, null); + } + + @Test + @DisplayName("Debe implementar hashCode correctamente") + void testHashCode() { + // Arrange + ProjectDTO project = new ProjectDTO(); + project.setId(1L); + + ProjectIdentityDTO identity1 = new ProjectIdentityDTO( + DataSource.GITHUB, + "https://github.com/test", + project + ); + + ProjectIdentityDTO identity2 = new ProjectIdentityDTO( + DataSource.GITHUB, + "https://github.com/test", + project + ); + + // Assert + assertEquals(identity1.hashCode(), identity2.hashCode()); + } + + @Test + @DisplayName("Debe implementar toString correctamente") + void testToString() { + // Arrange + projectIdentity.setDataSource(DataSource.GITHUB); + projectIdentity.setUrl("https://github.com/test"); + + // Act + String result = projectIdentity.toString(); + + // Assert + assertNotNull(result); + assertTrue(result.contains("ProjectIdentityDTO") || result.contains("GITHUB")); + } + + @Test + @DisplayName("Debe manejar URL de GitHub") + void testGitHubUrl() { + // Act + projectIdentity.setDataSource(DataSource.GITHUB); + projectIdentity.setUrl("https://github.com/organization/repo"); + + // Assert + assertEquals(DataSource.GITHUB, projectIdentity.getDataSource()); + assertEquals("https://github.com/organization/repo", projectIdentity.getUrl()); + } + + @Test + @DisplayName("Debe manejar URL de Taiga") + void testTaigaUrl() { + // Act + projectIdentity.setDataSource(DataSource.TAIGA); + projectIdentity.setUrl("https://tree.taiga.io/project/user-project"); + + // Assert + assertEquals(DataSource.TAIGA, projectIdentity.getDataSource()); + assertEquals("https://tree.taiga.io/project/user-project", projectIdentity.getUrl()); + } + + @Test + @DisplayName("Debe actualizar todos los campos") + void testUpdateAllFields() { + // Arrange + ProjectDTO project1 = new ProjectDTO(); + project1.setId(1L); + project1.setName("Project 1"); + + ProjectDTO project2 = new ProjectDTO(); + project2.setId(2L); + project2.setName("Project 2"); + + // Act - Initial values + projectIdentity.setDataSource(DataSource.GITHUB); + projectIdentity.setUrl("https://github.com/old"); + projectIdentity.setProject(project1); + + // Act - Update values + projectIdentity.setDataSource(DataSource.TAIGA); + projectIdentity.setUrl("https://taiga.io/new"); + projectIdentity.setProject(project2); + + // Assert + assertEquals(DataSource.TAIGA, projectIdentity.getDataSource()); + assertEquals("https://taiga.io/new", projectIdentity.getUrl()); + assertEquals("Project 2", projectIdentity.getProject().getName()); + } + + @Test + @DisplayName("Debe mantener consistencia de hashCode") + void testHashCodeConsistency() { + // Arrange + projectIdentity.setDataSource(DataSource.GITHUB); + projectIdentity.setUrl("https://github.com/test"); + + // Act + int hash1 = projectIdentity.hashCode(); + int hash2 = projectIdentity.hashCode(); + + // Assert + assertEquals(hash1, hash2); + } + + @Test + @DisplayName("Debe comparar correctamente con objetos de diferente tipo") + void testEqualsDifferentType() { + // Arrange + projectIdentity.setDataSource(DataSource.GITHUB); + + // Assert + assertNotEquals(projectIdentity, new Object()); + assertNotEquals(projectIdentity, "String"); + } + + @Test + @DisplayName("Debe crear múltiples instancias independientes") + void testMultipleInstances() { + // Arrange & Act + ProjectIdentityDTO identity1 = new ProjectIdentityDTO(); + identity1.setDataSource(DataSource.GITHUB); + identity1.setUrl("https://github.com/org1"); + + ProjectIdentityDTO identity2 = new ProjectIdentityDTO(); + identity2.setDataSource(DataSource.TAIGA); + identity2.setUrl("https://taiga.io/org2"); + + // Assert + assertNotEquals(identity1.getDataSource(), identity2.getDataSource()); + assertNotEquals(identity1.getUrl(), identity2.getUrl()); + } + + @Test + @DisplayName("Debe manejar proyecto con identidades") + void testProjectWithIdentities() { + // Arrange + ProjectDTO project = new ProjectDTO(); + project.setId(1L); + project.setName("Complex Project"); + project.setExternalId("ext-123"); + + // Act + projectIdentity.setProject(project); + + // Assert + assertEquals("ext-123", projectIdentity.getProject().getExternalId()); + } +} diff --git a/src/test/java/com/upc/ld_admintool/rest/DTO/SaveSyncDTOTest.java b/src/test/java/com/upc/ld_admintool/rest/DTO/SaveSyncDTOTest.java index a6198d9..5d56869 100644 --- a/src/test/java/com/upc/ld_admintool/rest/DTO/SaveSyncDTOTest.java +++ b/src/test/java/com/upc/ld_admintool/rest/DTO/SaveSyncDTOTest.java @@ -1,298 +1,298 @@ -package com.upc.ld_admintool.rest.DTO; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; - -import java.util.Arrays; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Tests unitarios completos para SaveSyncResponseDTO y SaveSyncStepDTO - * Valida la lógica de respuesta de sincronización - */ -@DisplayName("SaveSync DTOs - Tests Unitarios") -class SaveSyncDTOTest { - - private SaveSyncResponseDTO response; - - @BeforeEach - void setUp() { - response = new SaveSyncResponseDTO(); - } - - @Test - @DisplayName("SaveSyncResponseDTO debe inicializarse con valores por defecto") - void testSaveSyncResponseDTO_DefaultValues() { - assertTrue(response.isSuccess()); - assertEquals(0, response.getFinalTeamSize()); - assertNotNull(response.getSteps()); - assertTrue(response.getSteps().isEmpty()); - } - - @Test - @DisplayName("SaveSyncResponseDTO debe permitir establecer valores") - void testSaveSyncResponseDTO_SetValues() { - response.setSuccess(false); - response.setFinalTeamSize(5); - - assertFalse(response.isSuccess()); - assertEquals(5, response.getFinalTeamSize()); - } - - @Test - @DisplayName("addSuccessStep debe agregar paso exitoso") - void testAddSuccessStep() { - response.addSuccessStep(1, "Create Project", "Project created successfully"); - - assertEquals(1, response.getSteps().size()); - assertTrue(response.isSuccess()); - - SaveSyncStepDTO step = response.getSteps().get(0); - assertEquals(1, step.getOrder()); - assertEquals("Create Project", step.getName()); - assertEquals("Project created successfully", step.getDetail()); - assertEquals("SUCCESS", step.getStatus()); - assertNull(step.getError()); - } - - @Test - @DisplayName("addFailureStep debe agregar paso fallido y marcar como no exitoso") - void testAddFailureStep() { - response.addFailureStep(1, "Create Student", "Student creation", "Validation error"); - - assertEquals(1, response.getSteps().size()); - assertFalse(response.isSuccess()); - - SaveSyncStepDTO step = response.getSteps().get(0); - assertEquals(1, step.getOrder()); - assertEquals("Create Student", step.getName()); - assertEquals("FAILED", step.getStatus()); - assertEquals("Validation error", step.getError()); - } - - @Test - @DisplayName("addSkippedStep debe agregar paso omitido sin afectar success") - void testAddSkippedStep() { - response.addSkippedStep(1, "Optional Step", "Step was skipped"); - - assertEquals(1, response.getSteps().size()); - assertTrue(response.isSuccess()); - - SaveSyncStepDTO step = response.getSteps().get(0); - assertEquals("SKIPPED", step.getStatus()); - assertNull(step.getError()); - } - - @Test - @DisplayName("Múltiples pasos exitosos deben mantener success en true") - void testMultipleSuccessSteps() { - response.addSuccessStep(1, "Step 1", "Detail 1"); - response.addSuccessStep(2, "Step 2", "Detail 2"); - response.addSuccessStep(3, "Step 3", "Detail 3"); - - assertEquals(3, response.getSteps().size()); - assertTrue(response.isSuccess()); - } - - @Test - @DisplayName("Un paso fallido debe marcar toda la respuesta como fallida") - void testOneFailureMarksTotalAsFailed() { - response.addSuccessStep(1, "Step 1", "Detail 1"); - response.addSuccessStep(2, "Step 2", "Detail 2"); - response.addFailureStep(3, "Step 3", "Detail 3", "Error occurred"); - - assertEquals(3, response.getSteps().size()); - assertFalse(response.isSuccess()); - } - - @Test - @DisplayName("SaveSyncStepDTO constructor por defecto debe inicializar correctamente") - void testSaveSyncStepDTO_DefaultConstructor() { - SaveSyncStepDTO step = new SaveSyncStepDTO(); - - assertNotNull(step); - assertEquals(0, step.getOrder()); - assertNull(step.getName()); - assertNull(step.getDetail()); - assertNull(step.getStatus()); - assertNull(step.getError()); - } - - @Test - @DisplayName("SaveSyncStepDTO constructor con parámetros debe inicializar correctamente") - void testSaveSyncStepDTO_ParameterizedConstructor() { - SaveSyncStepDTO step = new SaveSyncStepDTO(1, "Test Step", "Test Detail", "SUCCESS", null); - - assertEquals(1, step.getOrder()); - assertEquals("Test Step", step.getName()); - assertEquals("Test Detail", step.getDetail()); - assertEquals("SUCCESS", step.getStatus()); - assertNull(step.getError()); - } - - @Test - @DisplayName("SaveSyncStepDTO debe permitir establecer todos los valores") - void testSaveSyncStepDTO_SetValues() { - SaveSyncStepDTO step = new SaveSyncStepDTO(); - step.setOrder(2); - step.setName("Update Project"); - step.setDetail("Updating project data"); - step.setStatus("FAILED"); - step.setError("Network timeout"); - - assertEquals(2, step.getOrder()); - assertEquals("Update Project", step.getName()); - assertEquals("Updating project data", step.getDetail()); - assertEquals("FAILED", step.getStatus()); - assertEquals("Network timeout", step.getError()); - } - - @Test - @DisplayName("SaveSyncStepDTO debe permitir verificar status") - void testSaveSyncStepDTO_CheckStatus() { - SaveSyncStepDTO successStep = new SaveSyncStepDTO(1, "Test", "Detail", "SUCCESS", null); - SaveSyncStepDTO failedStep = new SaveSyncStepDTO(2, "Test", "Detail", "FAILED", "Error"); - - assertEquals("SUCCESS", successStep.getStatus()); - assertEquals("FAILED", failedStep.getStatus()); - } - - @Test - @DisplayName("SaveSyncResponseDTO debe permitir establecer lista de pasos") - void testSetSteps() { - SaveSyncStepDTO step1 = new SaveSyncStepDTO(1, "Step 1", "Detail 1", "SUCCESS", null); - SaveSyncStepDTO step2 = new SaveSyncStepDTO(2, "Step 2", "Detail 2", "SUCCESS", null); - - response.setSteps(Arrays.asList(step1, step2)); - - assertEquals(2, response.getSteps().size()); - } - - @Test - @DisplayName("Pasos omitidos no deben afectar el resultado final") - void testSkippedStepsDoNotAffectSuccess() { - response.addSuccessStep(1, "Step 1", "Detail 1"); - response.addSkippedStep(2, "Step 2", "Skipped"); - response.addSkippedStep(3, "Step 3", "Skipped"); - response.addSuccessStep(4, "Step 4", "Detail 4"); - - assertEquals(4, response.getSteps().size()); - assertTrue(response.isSuccess()); - } - - @Test - @DisplayName("Orden de pasos debe mantenerse correctamente") - void testStepOrderPreserved() { - response.addSuccessStep(3, "Step 3", "Detail"); - response.addSuccessStep(1, "Step 1", "Detail"); - response.addSuccessStep(2, "Step 2", "Detail"); - - assertEquals(3, response.getSteps().get(0).getOrder()); - assertEquals(1, response.getSteps().get(1).getOrder()); - assertEquals(2, response.getSteps().get(2).getOrder()); - } - - @Test - @DisplayName("SaveSyncResponseDTO debe manejar team size correctamente") - void testFinalTeamSize() { - response.setFinalTeamSize(10); - assertEquals(10, response.getFinalTeamSize()); - - response.setFinalTeamSize(0); - assertEquals(0, response.getFinalTeamSize()); - - response.setFinalTeamSize(-1); - assertEquals(-1, response.getFinalTeamSize()); - } - - @Test - @DisplayName("SaveSyncResponseDTO debe implementar equals correctamente") - void testSaveSyncResponseDTO_Equals() { - SaveSyncResponseDTO response1 = new SaveSyncResponseDTO(); - response1.setSuccess(true); - response1.setFinalTeamSize(5); - - SaveSyncResponseDTO response2 = new SaveSyncResponseDTO(); - response2.setSuccess(true); - response2.setFinalTeamSize(5); - - SaveSyncResponseDTO response3 = new SaveSyncResponseDTO(); - response3.setSuccess(false); - response3.setFinalTeamSize(3); - - assertEquals(response1, response2); - assertNotEquals(response1, response3); - assertNotEquals(response1, null); - assertEquals(response1, response1); - } - - @Test - @DisplayName("SaveSyncResponseDTO debe implementar hashCode correctamente") - void testSaveSyncResponseDTO_HashCode() { - SaveSyncResponseDTO response1 = new SaveSyncResponseDTO(); - response1.setSuccess(true); - response1.setFinalTeamSize(5); - - SaveSyncResponseDTO response2 = new SaveSyncResponseDTO(); - response2.setSuccess(true); - response2.setFinalTeamSize(5); - - assertEquals(response1.hashCode(), response2.hashCode()); - - SaveSyncResponseDTO response3 = new SaveSyncResponseDTO(); - response3.setSuccess(false); - - assertNotEquals(response1.hashCode(), response3.hashCode()); - } - - @Test - @DisplayName("SaveSyncResponseDTO debe implementar toString correctamente") - void testSaveSyncResponseDTO_ToString() { - SaveSyncResponseDTO response = new SaveSyncResponseDTO(); - response.setSuccess(true); - response.setFinalTeamSize(5); - - String toString = response.toString(); - - assertNotNull(toString); - assertTrue(toString.contains("SaveSyncResponseDTO")); - } - - @Test - @DisplayName("SaveSyncStepDTO debe implementar equals correctamente") - void testSaveSyncStepDTO_Equals() { - SaveSyncStepDTO step1 = new SaveSyncStepDTO(1, "Step 1", "Detail", "SUCCESS", null); - SaveSyncStepDTO step2 = new SaveSyncStepDTO(1, "Step 1", "Detail", "SUCCESS", null); - SaveSyncStepDTO step3 = new SaveSyncStepDTO(2, "Step 2", "Detail", "FAILED", "Error"); - - assertEquals(step1, step2); - assertNotEquals(step1, step3); - assertNotEquals(step1, null); - assertEquals(step1, step1); - } - - @Test - @DisplayName("SaveSyncStepDTO debe implementar hashCode correctamente") - void testSaveSyncStepDTO_HashCode() { - SaveSyncStepDTO step1 = new SaveSyncStepDTO(1, "Step 1", "Detail", "SUCCESS", null); - SaveSyncStepDTO step2 = new SaveSyncStepDTO(1, "Step 1", "Detail", "SUCCESS", null); - - assertEquals(step1.hashCode(), step2.hashCode()); - - SaveSyncStepDTO step3 = new SaveSyncStepDTO(2, "Step 2", "Detail", "FAILED", "Error"); - assertNotEquals(step1.hashCode(), step3.hashCode()); - } - - @Test - @DisplayName("SaveSyncStepDTO debe implementar toString correctamente") - void testSaveSyncStepDTO_ToString() { - SaveSyncStepDTO step = new SaveSyncStepDTO(1, "Test Step", "Detail", "SUCCESS", null); - - String toString = step.toString(); - - assertNotNull(toString); - assertTrue(toString.contains("SaveSyncStepDTO")); - } -} +package com.upc.ld_admintool.rest.DTO; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests unitarios completos para SaveSyncResponseDTO y SaveSyncStepDTO + * Valida la lógica de respuesta de sincronización + */ +@DisplayName("SaveSync DTOs - Tests Unitarios") +class SaveSyncDTOTest { + + private SaveSyncResponseDTO response; + + @BeforeEach + void setUp() { + response = new SaveSyncResponseDTO(); + } + + @Test + @DisplayName("SaveSyncResponseDTO debe inicializarse con valores por defecto") + void testSaveSyncResponseDTO_DefaultValues() { + assertTrue(response.isSuccess()); + assertEquals(0, response.getFinalTeamSize()); + assertNotNull(response.getSteps()); + assertTrue(response.getSteps().isEmpty()); + } + + @Test + @DisplayName("SaveSyncResponseDTO debe permitir establecer valores") + void testSaveSyncResponseDTO_SetValues() { + response.setSuccess(false); + response.setFinalTeamSize(5); + + assertFalse(response.isSuccess()); + assertEquals(5, response.getFinalTeamSize()); + } + + @Test + @DisplayName("addSuccessStep debe agregar paso exitoso") + void testAddSuccessStep() { + response.addSuccessStep(1, "Create Project", "Project created successfully"); + + assertEquals(1, response.getSteps().size()); + assertTrue(response.isSuccess()); + + SaveSyncStepDTO step = response.getSteps().get(0); + assertEquals(1, step.getOrder()); + assertEquals("Create Project", step.getName()); + assertEquals("Project created successfully", step.getDetail()); + assertEquals("SUCCESS", step.getStatus()); + assertNull(step.getError()); + } + + @Test + @DisplayName("addFailureStep debe agregar paso fallido y marcar como no exitoso") + void testAddFailureStep() { + response.addFailureStep(1, "Create Student", "Student creation", "Validation error"); + + assertEquals(1, response.getSteps().size()); + assertFalse(response.isSuccess()); + + SaveSyncStepDTO step = response.getSteps().get(0); + assertEquals(1, step.getOrder()); + assertEquals("Create Student", step.getName()); + assertEquals("FAILED", step.getStatus()); + assertEquals("Validation error", step.getError()); + } + + @Test + @DisplayName("addSkippedStep debe agregar paso omitido sin afectar success") + void testAddSkippedStep() { + response.addSkippedStep(1, "Optional Step", "Step was skipped"); + + assertEquals(1, response.getSteps().size()); + assertTrue(response.isSuccess()); + + SaveSyncStepDTO step = response.getSteps().get(0); + assertEquals("SKIPPED", step.getStatus()); + assertNull(step.getError()); + } + + @Test + @DisplayName("Múltiples pasos exitosos deben mantener success en true") + void testMultipleSuccessSteps() { + response.addSuccessStep(1, "Step 1", "Detail 1"); + response.addSuccessStep(2, "Step 2", "Detail 2"); + response.addSuccessStep(3, "Step 3", "Detail 3"); + + assertEquals(3, response.getSteps().size()); + assertTrue(response.isSuccess()); + } + + @Test + @DisplayName("Un paso fallido debe marcar toda la respuesta como fallida") + void testOneFailureMarksTotalAsFailed() { + response.addSuccessStep(1, "Step 1", "Detail 1"); + response.addSuccessStep(2, "Step 2", "Detail 2"); + response.addFailureStep(3, "Step 3", "Detail 3", "Error occurred"); + + assertEquals(3, response.getSteps().size()); + assertFalse(response.isSuccess()); + } + + @Test + @DisplayName("SaveSyncStepDTO constructor por defecto debe inicializar correctamente") + void testSaveSyncStepDTO_DefaultConstructor() { + SaveSyncStepDTO step = new SaveSyncStepDTO(); + + assertNotNull(step); + assertEquals(0, step.getOrder()); + assertNull(step.getName()); + assertNull(step.getDetail()); + assertNull(step.getStatus()); + assertNull(step.getError()); + } + + @Test + @DisplayName("SaveSyncStepDTO constructor con parámetros debe inicializar correctamente") + void testSaveSyncStepDTO_ParameterizedConstructor() { + SaveSyncStepDTO step = new SaveSyncStepDTO(1, "Test Step", "Test Detail", "SUCCESS", null); + + assertEquals(1, step.getOrder()); + assertEquals("Test Step", step.getName()); + assertEquals("Test Detail", step.getDetail()); + assertEquals("SUCCESS", step.getStatus()); + assertNull(step.getError()); + } + + @Test + @DisplayName("SaveSyncStepDTO debe permitir establecer todos los valores") + void testSaveSyncStepDTO_SetValues() { + SaveSyncStepDTO step = new SaveSyncStepDTO(); + step.setOrder(2); + step.setName("Update Project"); + step.setDetail("Updating project data"); + step.setStatus("FAILED"); + step.setError("Network timeout"); + + assertEquals(2, step.getOrder()); + assertEquals("Update Project", step.getName()); + assertEquals("Updating project data", step.getDetail()); + assertEquals("FAILED", step.getStatus()); + assertEquals("Network timeout", step.getError()); + } + + @Test + @DisplayName("SaveSyncStepDTO debe permitir verificar status") + void testSaveSyncStepDTO_CheckStatus() { + SaveSyncStepDTO successStep = new SaveSyncStepDTO(1, "Test", "Detail", "SUCCESS", null); + SaveSyncStepDTO failedStep = new SaveSyncStepDTO(2, "Test", "Detail", "FAILED", "Error"); + + assertEquals("SUCCESS", successStep.getStatus()); + assertEquals("FAILED", failedStep.getStatus()); + } + + @Test + @DisplayName("SaveSyncResponseDTO debe permitir establecer lista de pasos") + void testSetSteps() { + SaveSyncStepDTO step1 = new SaveSyncStepDTO(1, "Step 1", "Detail 1", "SUCCESS", null); + SaveSyncStepDTO step2 = new SaveSyncStepDTO(2, "Step 2", "Detail 2", "SUCCESS", null); + + response.setSteps(Arrays.asList(step1, step2)); + + assertEquals(2, response.getSteps().size()); + } + + @Test + @DisplayName("Pasos omitidos no deben afectar el resultado final") + void testSkippedStepsDoNotAffectSuccess() { + response.addSuccessStep(1, "Step 1", "Detail 1"); + response.addSkippedStep(2, "Step 2", "Skipped"); + response.addSkippedStep(3, "Step 3", "Skipped"); + response.addSuccessStep(4, "Step 4", "Detail 4"); + + assertEquals(4, response.getSteps().size()); + assertTrue(response.isSuccess()); + } + + @Test + @DisplayName("Orden de pasos debe mantenerse correctamente") + void testStepOrderPreserved() { + response.addSuccessStep(3, "Step 3", "Detail"); + response.addSuccessStep(1, "Step 1", "Detail"); + response.addSuccessStep(2, "Step 2", "Detail"); + + assertEquals(3, response.getSteps().get(0).getOrder()); + assertEquals(1, response.getSteps().get(1).getOrder()); + assertEquals(2, response.getSteps().get(2).getOrder()); + } + + @Test + @DisplayName("SaveSyncResponseDTO debe manejar team size correctamente") + void testFinalTeamSize() { + response.setFinalTeamSize(10); + assertEquals(10, response.getFinalTeamSize()); + + response.setFinalTeamSize(0); + assertEquals(0, response.getFinalTeamSize()); + + response.setFinalTeamSize(-1); + assertEquals(-1, response.getFinalTeamSize()); + } + + @Test + @DisplayName("SaveSyncResponseDTO debe implementar equals correctamente") + void testSaveSyncResponseDTO_Equals() { + SaveSyncResponseDTO response1 = new SaveSyncResponseDTO(); + response1.setSuccess(true); + response1.setFinalTeamSize(5); + + SaveSyncResponseDTO response2 = new SaveSyncResponseDTO(); + response2.setSuccess(true); + response2.setFinalTeamSize(5); + + SaveSyncResponseDTO response3 = new SaveSyncResponseDTO(); + response3.setSuccess(false); + response3.setFinalTeamSize(3); + + assertEquals(response1, response2); + assertNotEquals(response1, response3); + assertNotEquals(response1, null); + assertEquals(response1, response1); + } + + @Test + @DisplayName("SaveSyncResponseDTO debe implementar hashCode correctamente") + void testSaveSyncResponseDTO_HashCode() { + SaveSyncResponseDTO response1 = new SaveSyncResponseDTO(); + response1.setSuccess(true); + response1.setFinalTeamSize(5); + + SaveSyncResponseDTO response2 = new SaveSyncResponseDTO(); + response2.setSuccess(true); + response2.setFinalTeamSize(5); + + assertEquals(response1.hashCode(), response2.hashCode()); + + SaveSyncResponseDTO response3 = new SaveSyncResponseDTO(); + response3.setSuccess(false); + + assertNotEquals(response1.hashCode(), response3.hashCode()); + } + + @Test + @DisplayName("SaveSyncResponseDTO debe implementar toString correctamente") + void testSaveSyncResponseDTO_ToString() { + SaveSyncResponseDTO response = new SaveSyncResponseDTO(); + response.setSuccess(true); + response.setFinalTeamSize(5); + + String toString = response.toString(); + + assertNotNull(toString); + assertTrue(toString.contains("SaveSyncResponseDTO")); + } + + @Test + @DisplayName("SaveSyncStepDTO debe implementar equals correctamente") + void testSaveSyncStepDTO_Equals() { + SaveSyncStepDTO step1 = new SaveSyncStepDTO(1, "Step 1", "Detail", "SUCCESS", null); + SaveSyncStepDTO step2 = new SaveSyncStepDTO(1, "Step 1", "Detail", "SUCCESS", null); + SaveSyncStepDTO step3 = new SaveSyncStepDTO(2, "Step 2", "Detail", "FAILED", "Error"); + + assertEquals(step1, step2); + assertNotEquals(step1, step3); + assertNotEquals(step1, null); + assertEquals(step1, step1); + } + + @Test + @DisplayName("SaveSyncStepDTO debe implementar hashCode correctamente") + void testSaveSyncStepDTO_HashCode() { + SaveSyncStepDTO step1 = new SaveSyncStepDTO(1, "Step 1", "Detail", "SUCCESS", null); + SaveSyncStepDTO step2 = new SaveSyncStepDTO(1, "Step 1", "Detail", "SUCCESS", null); + + assertEquals(step1.hashCode(), step2.hashCode()); + + SaveSyncStepDTO step3 = new SaveSyncStepDTO(2, "Step 2", "Detail", "FAILED", "Error"); + assertNotEquals(step1.hashCode(), step3.hashCode()); + } + + @Test + @DisplayName("SaveSyncStepDTO debe implementar toString correctamente") + void testSaveSyncStepDTO_ToString() { + SaveSyncStepDTO step = new SaveSyncStepDTO(1, "Test Step", "Detail", "SUCCESS", null); + + String toString = step.toString(); + + assertNotNull(toString); + assertTrue(toString.contains("SaveSyncStepDTO")); + } +} diff --git a/src/test/java/com/upc/ld_admintool/rest/DTO/StudentIdentityDTOTest.java b/src/test/java/com/upc/ld_admintool/rest/DTO/StudentIdentityDTOTest.java index a0cfbeb..54fa93a 100644 --- a/src/test/java/com/upc/ld_admintool/rest/DTO/StudentIdentityDTOTest.java +++ b/src/test/java/com/upc/ld_admintool/rest/DTO/StudentIdentityDTOTest.java @@ -1,296 +1,296 @@ -package com.upc.ld_admintool.rest.DTO; - -import com.upc.ld_admintool.domain.utils.DataSource; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Tests unitarios para StudentIdentityDTO - * Valida la funcionalidad del DTO de identidad de estudiante - */ -@DisplayName("StudentIdentityDTO - Tests Unitarios") -class StudentIdentityDTOTest { - - private StudentIdentityDTO studentIdentity; - - @BeforeEach - void setUp() { - studentIdentity = new StudentIdentityDTO(); - } - - @Test - @DisplayName("Debe crear instancia con constructor sin argumentos") - void testNoArgsConstructor() { - // Arrange & Act - StudentIdentityDTO identity = new StudentIdentityDTO(); - - // Assert - assertNull(identity.getDataSource()); - assertNull(identity.getUsername()); - } - - @Test - @DisplayName("Debe crear instancia con constructor con todos los argumentos") - void testAllArgsConstructor() { - // Act - StudentIdentityDTO identity = new StudentIdentityDTO(DataSource.GITHUB, "johndoe"); - - // Assert - assertEquals(DataSource.GITHUB, identity.getDataSource()); - assertEquals("johndoe", identity.getUsername()); - } - - @Test - @DisplayName("Debe configurar dataSource correctamente") - void testSetDataSource() { - // Act - studentIdentity.setDataSource(DataSource.GITHUB); - - // Assert - assertEquals(DataSource.GITHUB, studentIdentity.getDataSource()); - } - - @Test - @DisplayName("Debe configurar username correctamente") - void testSetUsername() { - // Act - studentIdentity.setUsername("testuser"); - - // Assert - assertEquals("testuser", studentIdentity.getUsername()); - } - - @Test - @DisplayName("Debe manejar diferentes DataSources") - void testDifferentDataSources() { - // Test GITHUB - studentIdentity.setDataSource(DataSource.GITHUB); - studentIdentity.setUsername("github_user"); - assertEquals(DataSource.GITHUB, studentIdentity.getDataSource()); - assertEquals("github_user", studentIdentity.getUsername()); - - // Test TAIGA - studentIdentity.setDataSource(DataSource.TAIGA); - studentIdentity.setUsername("taiga_user"); - assertEquals(DataSource.TAIGA, studentIdentity.getDataSource()); - assertEquals("taiga_user", studentIdentity.getUsername()); - } - - @Test - @DisplayName("Debe permitir valores null") - void testNullValues() { - // Act - studentIdentity.setDataSource(null); - studentIdentity.setUsername(null); - - // Assert - assertNull(studentIdentity.getDataSource()); - assertNull(studentIdentity.getUsername()); - } - - @Test - @DisplayName("Debe implementar equals correctamente") - void testEquals() { - // Arrange - StudentIdentityDTO identity1 = new StudentIdentityDTO(DataSource.GITHUB, "user1"); - StudentIdentityDTO identity2 = new StudentIdentityDTO(DataSource.GITHUB, "user1"); - StudentIdentityDTO identity3 = new StudentIdentityDTO(DataSource.TAIGA, "user2"); - - // Assert - assertEquals(identity1, identity2); - assertNotEquals(identity1, identity3); - assertEquals(identity1, identity1); - assertNotEquals(identity1, null); - } - - @Test - @DisplayName("Debe implementar hashCode correctamente") - void testHashCode() { - // Arrange - StudentIdentityDTO identity1 = new StudentIdentityDTO(DataSource.GITHUB, "user1"); - StudentIdentityDTO identity2 = new StudentIdentityDTO(DataSource.GITHUB, "user1"); - - // Assert - assertEquals(identity1.hashCode(), identity2.hashCode()); - } - - @Test - @DisplayName("Debe implementar toString correctamente") - void testToString() { - // Arrange - studentIdentity.setDataSource(DataSource.GITHUB); - studentIdentity.setUsername("testuser"); - - // Act - String result = studentIdentity.toString(); - - // Assert - assertNotNull(result); - assertTrue(result.contains("StudentIdentityDTO") || result.contains("testuser") || result.contains("GITHUB")); - } - - @Test - @DisplayName("Debe manejar username de GitHub") - void testGitHubUsername() { - // Act - studentIdentity.setDataSource(DataSource.GITHUB); - studentIdentity.setUsername("github_developer"); - - // Assert - assertEquals(DataSource.GITHUB, studentIdentity.getDataSource()); - assertEquals("github_developer", studentIdentity.getUsername()); - } - - @Test - @DisplayName("Debe manejar username de Taiga") - void testTaigaUsername() { - // Act - studentIdentity.setDataSource(DataSource.TAIGA); - studentIdentity.setUsername("taiga_developer"); - - // Assert - assertEquals(DataSource.TAIGA, studentIdentity.getDataSource()); - assertEquals("taiga_developer", studentIdentity.getUsername()); - } - - @Test - @DisplayName("Debe actualizar campos existentes") - void testUpdateFields() { - // Arrange - Initial values - studentIdentity.setDataSource(DataSource.GITHUB); - studentIdentity.setUsername("olduser"); - - // Act - Update values - studentIdentity.setDataSource(DataSource.TAIGA); - studentIdentity.setUsername("newuser"); - - // Assert - assertEquals(DataSource.TAIGA, studentIdentity.getDataSource()); - assertEquals("newuser", studentIdentity.getUsername()); - } - - @Test - @DisplayName("Debe mantener consistencia de hashCode") - void testHashCodeConsistency() { - // Arrange - studentIdentity.setDataSource(DataSource.GITHUB); - studentIdentity.setUsername("user"); - - // Act - int hash1 = studentIdentity.hashCode(); - int hash2 = studentIdentity.hashCode(); - - // Assert - assertEquals(hash1, hash2); - } - - @Test - @DisplayName("Debe comparar correctamente con objetos de diferente tipo") - void testEqualsDifferentType() { - // Arrange - studentIdentity.setDataSource(DataSource.GITHUB); - studentIdentity.setUsername("user"); - - // Assert - assertNotEquals(studentIdentity, new Object()); - assertNotEquals(studentIdentity, "String"); - } - - @Test - @DisplayName("Debe manejar username vacío") - void testEmptyUsername() { - // Act - studentIdentity.setUsername(""); - - // Assert - assertEquals("", studentIdentity.getUsername()); - } - - @Test - @DisplayName("Debe manejar username con caracteres especiales") - void testUsernameWithSpecialCharacters() { - // Act - studentIdentity.setDataSource(DataSource.GITHUB); - studentIdentity.setUsername("user-name_123"); - - // Assert - assertEquals("user-name_123", studentIdentity.getUsername()); - } - - @Test - @DisplayName("Debe crear múltiples instancias independientes") - void testMultipleInstances() { - // Arrange & Act - StudentIdentityDTO identity1 = new StudentIdentityDTO(DataSource.GITHUB, "user1"); - StudentIdentityDTO identity2 = new StudentIdentityDTO(DataSource.TAIGA, "user2"); - - // Assert - assertNotEquals(identity1.getDataSource(), identity2.getDataSource()); - assertNotEquals(identity1.getUsername(), identity2.getUsername()); - } - - @Test - @DisplayName("Debe permitir reasignar mismo DataSource") - void testReassignSameDataSource() { - // Act - studentIdentity.setDataSource(DataSource.GITHUB); - studentIdentity.setDataSource(DataSource.GITHUB); - - // Assert - assertEquals(DataSource.GITHUB, studentIdentity.getDataSource()); - } - - @Test - @DisplayName("Debe manejar cambio de username manteniendo DataSource") - void testChangeUsernameKeepDataSource() { - // Arrange - studentIdentity.setDataSource(DataSource.GITHUB); - studentIdentity.setUsername("olduser"); - - // Act - studentIdentity.setUsername("newuser"); - - // Assert - assertEquals(DataSource.GITHUB, studentIdentity.getDataSource()); - assertEquals("newuser", studentIdentity.getUsername()); - } - - @Test - @DisplayName("Debe comparar igualdad con mismo username y diferente DataSource") - void testEqualsDifferentDataSourceSameUsername() { - // Arrange - StudentIdentityDTO identity1 = new StudentIdentityDTO(DataSource.GITHUB, "sameuser"); - StudentIdentityDTO identity2 = new StudentIdentityDTO(DataSource.TAIGA, "sameuser"); - - // Assert - assertNotEquals(identity1, identity2); - } - - @Test - @DisplayName("Debe manejar username con mayúsculas y minúsculas") - void testUsernameCaseSensitive() { - // Act - studentIdentity.setUsername("UserName"); - - // Assert - assertEquals("UserName", studentIdentity.getUsername()); - assertNotEquals("username", studentIdentity.getUsername()); - } - - @Test - @DisplayName("Debe crear identidad completa con constructor") - void testFullConstructor() { - // Act - StudentIdentityDTO identity = new StudentIdentityDTO(DataSource.GITHUB, "complete_user"); - - // Assert - assertAll( - () -> assertNotNull(identity), - () -> assertEquals(DataSource.GITHUB, identity.getDataSource()), - () -> assertEquals("complete_user", identity.getUsername()) - ); - } -} +package com.upc.ld_admintool.rest.DTO; + +import com.upc.ld_admintool.domain.utils.DataSource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests unitarios para StudentIdentityDTO + * Valida la funcionalidad del DTO de identidad de estudiante + */ +@DisplayName("StudentIdentityDTO - Tests Unitarios") +class StudentIdentityDTOTest { + + private StudentIdentityDTO studentIdentity; + + @BeforeEach + void setUp() { + studentIdentity = new StudentIdentityDTO(); + } + + @Test + @DisplayName("Debe crear instancia con constructor sin argumentos") + void testNoArgsConstructor() { + // Arrange & Act + StudentIdentityDTO identity = new StudentIdentityDTO(); + + // Assert + assertNull(identity.getDataSource()); + assertNull(identity.getUsername()); + } + + @Test + @DisplayName("Debe crear instancia con constructor con todos los argumentos") + void testAllArgsConstructor() { + // Act + StudentIdentityDTO identity = new StudentIdentityDTO(DataSource.GITHUB, "johndoe"); + + // Assert + assertEquals(DataSource.GITHUB, identity.getDataSource()); + assertEquals("johndoe", identity.getUsername()); + } + + @Test + @DisplayName("Debe configurar dataSource correctamente") + void testSetDataSource() { + // Act + studentIdentity.setDataSource(DataSource.GITHUB); + + // Assert + assertEquals(DataSource.GITHUB, studentIdentity.getDataSource()); + } + + @Test + @DisplayName("Debe configurar username correctamente") + void testSetUsername() { + // Act + studentIdentity.setUsername("testuser"); + + // Assert + assertEquals("testuser", studentIdentity.getUsername()); + } + + @Test + @DisplayName("Debe manejar diferentes DataSources") + void testDifferentDataSources() { + // Test GITHUB + studentIdentity.setDataSource(DataSource.GITHUB); + studentIdentity.setUsername("github_user"); + assertEquals(DataSource.GITHUB, studentIdentity.getDataSource()); + assertEquals("github_user", studentIdentity.getUsername()); + + // Test TAIGA + studentIdentity.setDataSource(DataSource.TAIGA); + studentIdentity.setUsername("taiga_user"); + assertEquals(DataSource.TAIGA, studentIdentity.getDataSource()); + assertEquals("taiga_user", studentIdentity.getUsername()); + } + + @Test + @DisplayName("Debe permitir valores null") + void testNullValues() { + // Act + studentIdentity.setDataSource(null); + studentIdentity.setUsername(null); + + // Assert + assertNull(studentIdentity.getDataSource()); + assertNull(studentIdentity.getUsername()); + } + + @Test + @DisplayName("Debe implementar equals correctamente") + void testEquals() { + // Arrange + StudentIdentityDTO identity1 = new StudentIdentityDTO(DataSource.GITHUB, "user1"); + StudentIdentityDTO identity2 = new StudentIdentityDTO(DataSource.GITHUB, "user1"); + StudentIdentityDTO identity3 = new StudentIdentityDTO(DataSource.TAIGA, "user2"); + + // Assert + assertEquals(identity1, identity2); + assertNotEquals(identity1, identity3); + assertEquals(identity1, identity1); + assertNotEquals(identity1, null); + } + + @Test + @DisplayName("Debe implementar hashCode correctamente") + void testHashCode() { + // Arrange + StudentIdentityDTO identity1 = new StudentIdentityDTO(DataSource.GITHUB, "user1"); + StudentIdentityDTO identity2 = new StudentIdentityDTO(DataSource.GITHUB, "user1"); + + // Assert + assertEquals(identity1.hashCode(), identity2.hashCode()); + } + + @Test + @DisplayName("Debe implementar toString correctamente") + void testToString() { + // Arrange + studentIdentity.setDataSource(DataSource.GITHUB); + studentIdentity.setUsername("testuser"); + + // Act + String result = studentIdentity.toString(); + + // Assert + assertNotNull(result); + assertTrue(result.contains("StudentIdentityDTO") || result.contains("testuser") || result.contains("GITHUB")); + } + + @Test + @DisplayName("Debe manejar username de GitHub") + void testGitHubUsername() { + // Act + studentIdentity.setDataSource(DataSource.GITHUB); + studentIdentity.setUsername("github_developer"); + + // Assert + assertEquals(DataSource.GITHUB, studentIdentity.getDataSource()); + assertEquals("github_developer", studentIdentity.getUsername()); + } + + @Test + @DisplayName("Debe manejar username de Taiga") + void testTaigaUsername() { + // Act + studentIdentity.setDataSource(DataSource.TAIGA); + studentIdentity.setUsername("taiga_developer"); + + // Assert + assertEquals(DataSource.TAIGA, studentIdentity.getDataSource()); + assertEquals("taiga_developer", studentIdentity.getUsername()); + } + + @Test + @DisplayName("Debe actualizar campos existentes") + void testUpdateFields() { + // Arrange - Initial values + studentIdentity.setDataSource(DataSource.GITHUB); + studentIdentity.setUsername("olduser"); + + // Act - Update values + studentIdentity.setDataSource(DataSource.TAIGA); + studentIdentity.setUsername("newuser"); + + // Assert + assertEquals(DataSource.TAIGA, studentIdentity.getDataSource()); + assertEquals("newuser", studentIdentity.getUsername()); + } + + @Test + @DisplayName("Debe mantener consistencia de hashCode") + void testHashCodeConsistency() { + // Arrange + studentIdentity.setDataSource(DataSource.GITHUB); + studentIdentity.setUsername("user"); + + // Act + int hash1 = studentIdentity.hashCode(); + int hash2 = studentIdentity.hashCode(); + + // Assert + assertEquals(hash1, hash2); + } + + @Test + @DisplayName("Debe comparar correctamente con objetos de diferente tipo") + void testEqualsDifferentType() { + // Arrange + studentIdentity.setDataSource(DataSource.GITHUB); + studentIdentity.setUsername("user"); + + // Assert + assertNotEquals(studentIdentity, new Object()); + assertNotEquals(studentIdentity, "String"); + } + + @Test + @DisplayName("Debe manejar username vacío") + void testEmptyUsername() { + // Act + studentIdentity.setUsername(""); + + // Assert + assertEquals("", studentIdentity.getUsername()); + } + + @Test + @DisplayName("Debe manejar username con caracteres especiales") + void testUsernameWithSpecialCharacters() { + // Act + studentIdentity.setDataSource(DataSource.GITHUB); + studentIdentity.setUsername("user-name_123"); + + // Assert + assertEquals("user-name_123", studentIdentity.getUsername()); + } + + @Test + @DisplayName("Debe crear múltiples instancias independientes") + void testMultipleInstances() { + // Arrange & Act + StudentIdentityDTO identity1 = new StudentIdentityDTO(DataSource.GITHUB, "user1"); + StudentIdentityDTO identity2 = new StudentIdentityDTO(DataSource.TAIGA, "user2"); + + // Assert + assertNotEquals(identity1.getDataSource(), identity2.getDataSource()); + assertNotEquals(identity1.getUsername(), identity2.getUsername()); + } + + @Test + @DisplayName("Debe permitir reasignar mismo DataSource") + void testReassignSameDataSource() { + // Act + studentIdentity.setDataSource(DataSource.GITHUB); + studentIdentity.setDataSource(DataSource.GITHUB); + + // Assert + assertEquals(DataSource.GITHUB, studentIdentity.getDataSource()); + } + + @Test + @DisplayName("Debe manejar cambio de username manteniendo DataSource") + void testChangeUsernameKeepDataSource() { + // Arrange + studentIdentity.setDataSource(DataSource.GITHUB); + studentIdentity.setUsername("olduser"); + + // Act + studentIdentity.setUsername("newuser"); + + // Assert + assertEquals(DataSource.GITHUB, studentIdentity.getDataSource()); + assertEquals("newuser", studentIdentity.getUsername()); + } + + @Test + @DisplayName("Debe comparar igualdad con mismo username y diferente DataSource") + void testEqualsDifferentDataSourceSameUsername() { + // Arrange + StudentIdentityDTO identity1 = new StudentIdentityDTO(DataSource.GITHUB, "sameuser"); + StudentIdentityDTO identity2 = new StudentIdentityDTO(DataSource.TAIGA, "sameuser"); + + // Assert + assertNotEquals(identity1, identity2); + } + + @Test + @DisplayName("Debe manejar username con mayúsculas y minúsculas") + void testUsernameCaseSensitive() { + // Act + studentIdentity.setUsername("UserName"); + + // Assert + assertEquals("UserName", studentIdentity.getUsername()); + assertNotEquals("username", studentIdentity.getUsername()); + } + + @Test + @DisplayName("Debe crear identidad completa con constructor") + void testFullConstructor() { + // Act + StudentIdentityDTO identity = new StudentIdentityDTO(DataSource.GITHUB, "complete_user"); + + // Assert + assertAll( + () -> assertNotNull(identity), + () -> assertEquals(DataSource.GITHUB, identity.getDataSource()), + () -> assertEquals("complete_user", identity.getUsername()) + ); + } +} diff --git a/src/test/java/com/upc/ld_admintool/rest/DTO/StudentValidationDTOTest.java b/src/test/java/com/upc/ld_admintool/rest/DTO/StudentValidationDTOTest.java index 8d7acc9..d4a192c 100644 --- a/src/test/java/com/upc/ld_admintool/rest/DTO/StudentValidationDTOTest.java +++ b/src/test/java/com/upc/ld_admintool/rest/DTO/StudentValidationDTOTest.java @@ -1,269 +1,269 @@ -package com.upc.ld_admintool.rest.DTO; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Tests unitarios para StudentValidationDTO - * Valida la funcionalidad del DTO usado para validación de estudiantes - */ -@DisplayName("StudentValidationDTO - Tests Unitarios") -class StudentValidationDTOTest { - - private StudentValidationDTO studentValidation; - - @BeforeEach - void setUp() { - studentValidation = new StudentValidationDTO(); - } - - @Test - @DisplayName("Debe inicializar todos los campos correctamente") - void testInitialization() { - // Arrange - StudentDTO student = new StudentDTO(); - student.setId(1L); - student.setName("Test Student"); - - // Act - studentValidation.setProjectId(100L); - studentValidation.setGithubUrl("https://github.com/testorg"); - studentValidation.setTaigaUrl("https://tree.taiga.io/project/test"); - studentValidation.setGithubToken("test-token"); - studentValidation.setStudent(student); - - // Assert - assertEquals(100L, studentValidation.getProjectId()); - assertEquals("https://github.com/testorg", studentValidation.getGithubUrl()); - assertEquals("https://tree.taiga.io/project/test", studentValidation.getTaigaUrl()); - assertEquals("test-token", studentValidation.getGithubToken()); - assertNotNull(studentValidation.getStudent()); - assertEquals("Test Student", studentValidation.getStudent().getName()); - } - - @Test - @DisplayName("Debe permitir valores null en campos opcionales") - void testNullValues() { - // Act - studentValidation.setProjectId(null); - studentValidation.setGithubUrl(null); - studentValidation.setTaigaUrl(null); - studentValidation.setGithubToken(null); - studentValidation.setStudent(null); - - // Assert - assertNull(studentValidation.getProjectId()); - assertNull(studentValidation.getGithubUrl()); - assertNull(studentValidation.getTaigaUrl()); - assertNull(studentValidation.getGithubToken()); - assertNull(studentValidation.getStudent()); - } - - @Test - @DisplayName("Debe implementar equals correctamente con Lombok") - void testEquals() { - // Arrange - StudentDTO student = new StudentDTO(); - student.setId(1L); - student.setName("Test"); - - StudentValidationDTO validation1 = new StudentValidationDTO(); - validation1.setProjectId(1L); - validation1.setGithubUrl("https://github.com/test"); - validation1.setStudent(student); - - StudentValidationDTO validation2 = new StudentValidationDTO(); - validation2.setProjectId(1L); - validation2.setGithubUrl("https://github.com/test"); - validation2.setStudent(student); - - StudentValidationDTO validation3 = new StudentValidationDTO(); - validation3.setProjectId(2L); - - // Assert - assertEquals(validation1, validation2); - assertNotEquals(validation1, validation3); - assertEquals(validation1, validation1); - assertNotEquals(validation1, null); - } - - @Test - @DisplayName("Debe implementar hashCode correctamente con Lombok") - void testHashCode() { - // Arrange - StudentDTO student = new StudentDTO(); - student.setId(1L); - - StudentValidationDTO validation1 = new StudentValidationDTO(); - validation1.setProjectId(1L); - validation1.setStudent(student); - - StudentValidationDTO validation2 = new StudentValidationDTO(); - validation2.setProjectId(1L); - validation2.setStudent(student); - - // Assert - assertEquals(validation1.hashCode(), validation2.hashCode()); - } - - @Test - @DisplayName("Debe implementar toString correctamente con Lombok") - void testToString() { - // Arrange - studentValidation.setProjectId(1L); - studentValidation.setGithubUrl("https://github.com/test"); - - // Act - String result = studentValidation.toString(); - - // Assert - assertNotNull(result); - assertTrue(result.contains("StudentValidationDTO") || result.contains("projectId")); - } - - @Test - @DisplayName("Debe manejar múltiples campos con valores") - void testMultipleFields() { - // Arrange - StudentDTO student = new StudentDTO(); - student.setId(5L); - student.setName("John Doe"); - - // Act - studentValidation.setProjectId(100L); - studentValidation.setGithubUrl("https://github.com/myorg"); - studentValidation.setTaigaUrl("https://tree.taiga.io/project/myproject"); - studentValidation.setGithubToken("ghp_123456"); - studentValidation.setStudent(student); - - // Assert - assertAll( - () -> assertEquals(100L, studentValidation.getProjectId()), - () -> assertEquals("https://github.com/myorg", studentValidation.getGithubUrl()), - () -> assertEquals("https://tree.taiga.io/project/myproject", studentValidation.getTaigaUrl()), - () -> assertEquals("ghp_123456", studentValidation.getGithubToken()), - () -> assertEquals(5L, studentValidation.getStudent().getId()), - () -> assertEquals("John Doe", studentValidation.getStudent().getName()) - ); - } - - @Test - @DisplayName("Debe actualizar campos individualmente") - void testFieldUpdate() { - // Arrange - studentValidation.setProjectId(1L); - studentValidation.setGithubUrl("https://github.com/old"); - - // Act - studentValidation.setProjectId(2L); - studentValidation.setGithubUrl("https://github.com/new"); - - // Assert - assertEquals(2L, studentValidation.getProjectId()); - assertEquals("https://github.com/new", studentValidation.getGithubUrl()); - } - - @Test - @DisplayName("Debe manejar estudiante con identidades") - void testStudentWithIdentities() { - // Arrange - StudentDTO student = new StudentDTO(); - student.setId(1L); - student.setName("Test Student"); - - // Act - studentValidation.setStudent(student); - - // Assert - assertNotNull(studentValidation.getStudent()); - assertEquals(1L, studentValidation.getStudent().getId()); - } - - @Test - @DisplayName("Debe permitir cambiar estudiante") - void testChangeStudent() { - // Arrange - StudentDTO student1 = new StudentDTO(); - student1.setId(1L); - student1.setName("Student 1"); - - StudentDTO student2 = new StudentDTO(); - student2.setId(2L); - student2.setName("Student 2"); - - // Act - studentValidation.setStudent(student1); - assertEquals("Student 1", studentValidation.getStudent().getName()); - - studentValidation.setStudent(student2); - - // Assert - assertEquals("Student 2", studentValidation.getStudent().getName()); - assertEquals(2L, studentValidation.getStudent().getId()); - } - - @Test - @DisplayName("Debe manejar tokens vacíos") - void testEmptyToken() { - // Act - studentValidation.setGithubToken(""); - - // Assert - assertEquals("", studentValidation.getGithubToken()); - } - - @Test - @DisplayName("Debe manejar URLs vacías") - void testEmptyUrls() { - // Act - studentValidation.setGithubUrl(""); - studentValidation.setTaigaUrl(""); - - // Assert - assertEquals("", studentValidation.getGithubUrl()); - assertEquals("", studentValidation.getTaigaUrl()); - } - - @Test - @DisplayName("Debe crear instancia sin valores iniciales") - void testDefaultConstructor() { - // Arrange & Act - StudentValidationDTO newValidation = new StudentValidationDTO(); - - // Assert - assertNull(newValidation.getProjectId()); - assertNull(newValidation.getGithubUrl()); - assertNull(newValidation.getTaigaUrl()); - assertNull(newValidation.getGithubToken()); - assertNull(newValidation.getStudent()); - } - - @Test - @DisplayName("Debe comparar correctamente con objetos de diferente tipo") - void testEqualsDifferentType() { - // Arrange - studentValidation.setProjectId(1L); - - // Assert - assertNotEquals(studentValidation, new Object()); - assertNotEquals(studentValidation, "String"); - } - - @Test - @DisplayName("Debe mantener consistencia de hashCode") - void testHashCodeConsistency() { - // Arrange - studentValidation.setProjectId(1L); - studentValidation.setGithubUrl("https://github.com/test"); - - // Act - int hashCode1 = studentValidation.hashCode(); - int hashCode2 = studentValidation.hashCode(); - - // Assert - assertEquals(hashCode1, hashCode2); - } -} +package com.upc.ld_admintool.rest.DTO; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests unitarios para StudentValidationDTO + * Valida la funcionalidad del DTO usado para validación de estudiantes + */ +@DisplayName("StudentValidationDTO - Tests Unitarios") +class StudentValidationDTOTest { + + private StudentValidationDTO studentValidation; + + @BeforeEach + void setUp() { + studentValidation = new StudentValidationDTO(); + } + + @Test + @DisplayName("Debe inicializar todos los campos correctamente") + void testInitialization() { + // Arrange + StudentDTO student = new StudentDTO(); + student.setId(1L); + student.setName("Test Student"); + + // Act + studentValidation.setProjectId(100L); + studentValidation.setGithubUrl("https://github.com/testorg"); + studentValidation.setTaigaUrl("https://tree.taiga.io/project/test"); + studentValidation.setGithubToken("test-token"); + studentValidation.setStudent(student); + + // Assert + assertEquals(100L, studentValidation.getProjectId()); + assertEquals("https://github.com/testorg", studentValidation.getGithubUrl()); + assertEquals("https://tree.taiga.io/project/test", studentValidation.getTaigaUrl()); + assertEquals("test-token", studentValidation.getGithubToken()); + assertNotNull(studentValidation.getStudent()); + assertEquals("Test Student", studentValidation.getStudent().getName()); + } + + @Test + @DisplayName("Debe permitir valores null en campos opcionales") + void testNullValues() { + // Act + studentValidation.setProjectId(null); + studentValidation.setGithubUrl(null); + studentValidation.setTaigaUrl(null); + studentValidation.setGithubToken(null); + studentValidation.setStudent(null); + + // Assert + assertNull(studentValidation.getProjectId()); + assertNull(studentValidation.getGithubUrl()); + assertNull(studentValidation.getTaigaUrl()); + assertNull(studentValidation.getGithubToken()); + assertNull(studentValidation.getStudent()); + } + + @Test + @DisplayName("Debe implementar equals correctamente con Lombok") + void testEquals() { + // Arrange + StudentDTO student = new StudentDTO(); + student.setId(1L); + student.setName("Test"); + + StudentValidationDTO validation1 = new StudentValidationDTO(); + validation1.setProjectId(1L); + validation1.setGithubUrl("https://github.com/test"); + validation1.setStudent(student); + + StudentValidationDTO validation2 = new StudentValidationDTO(); + validation2.setProjectId(1L); + validation2.setGithubUrl("https://github.com/test"); + validation2.setStudent(student); + + StudentValidationDTO validation3 = new StudentValidationDTO(); + validation3.setProjectId(2L); + + // Assert + assertEquals(validation1, validation2); + assertNotEquals(validation1, validation3); + assertEquals(validation1, validation1); + assertNotEquals(validation1, null); + } + + @Test + @DisplayName("Debe implementar hashCode correctamente con Lombok") + void testHashCode() { + // Arrange + StudentDTO student = new StudentDTO(); + student.setId(1L); + + StudentValidationDTO validation1 = new StudentValidationDTO(); + validation1.setProjectId(1L); + validation1.setStudent(student); + + StudentValidationDTO validation2 = new StudentValidationDTO(); + validation2.setProjectId(1L); + validation2.setStudent(student); + + // Assert + assertEquals(validation1.hashCode(), validation2.hashCode()); + } + + @Test + @DisplayName("Debe implementar toString correctamente con Lombok") + void testToString() { + // Arrange + studentValidation.setProjectId(1L); + studentValidation.setGithubUrl("https://github.com/test"); + + // Act + String result = studentValidation.toString(); + + // Assert + assertNotNull(result); + assertTrue(result.contains("StudentValidationDTO") || result.contains("projectId")); + } + + @Test + @DisplayName("Debe manejar múltiples campos con valores") + void testMultipleFields() { + // Arrange + StudentDTO student = new StudentDTO(); + student.setId(5L); + student.setName("John Doe"); + + // Act + studentValidation.setProjectId(100L); + studentValidation.setGithubUrl("https://github.com/myorg"); + studentValidation.setTaigaUrl("https://tree.taiga.io/project/myproject"); + studentValidation.setGithubToken("ghp_123456"); + studentValidation.setStudent(student); + + // Assert + assertAll( + () -> assertEquals(100L, studentValidation.getProjectId()), + () -> assertEquals("https://github.com/myorg", studentValidation.getGithubUrl()), + () -> assertEquals("https://tree.taiga.io/project/myproject", studentValidation.getTaigaUrl()), + () -> assertEquals("ghp_123456", studentValidation.getGithubToken()), + () -> assertEquals(5L, studentValidation.getStudent().getId()), + () -> assertEquals("John Doe", studentValidation.getStudent().getName()) + ); + } + + @Test + @DisplayName("Debe actualizar campos individualmente") + void testFieldUpdate() { + // Arrange + studentValidation.setProjectId(1L); + studentValidation.setGithubUrl("https://github.com/old"); + + // Act + studentValidation.setProjectId(2L); + studentValidation.setGithubUrl("https://github.com/new"); + + // Assert + assertEquals(2L, studentValidation.getProjectId()); + assertEquals("https://github.com/new", studentValidation.getGithubUrl()); + } + + @Test + @DisplayName("Debe manejar estudiante con identidades") + void testStudentWithIdentities() { + // Arrange + StudentDTO student = new StudentDTO(); + student.setId(1L); + student.setName("Test Student"); + + // Act + studentValidation.setStudent(student); + + // Assert + assertNotNull(studentValidation.getStudent()); + assertEquals(1L, studentValidation.getStudent().getId()); + } + + @Test + @DisplayName("Debe permitir cambiar estudiante") + void testChangeStudent() { + // Arrange + StudentDTO student1 = new StudentDTO(); + student1.setId(1L); + student1.setName("Student 1"); + + StudentDTO student2 = new StudentDTO(); + student2.setId(2L); + student2.setName("Student 2"); + + // Act + studentValidation.setStudent(student1); + assertEquals("Student 1", studentValidation.getStudent().getName()); + + studentValidation.setStudent(student2); + + // Assert + assertEquals("Student 2", studentValidation.getStudent().getName()); + assertEquals(2L, studentValidation.getStudent().getId()); + } + + @Test + @DisplayName("Debe manejar tokens vacíos") + void testEmptyToken() { + // Act + studentValidation.setGithubToken(""); + + // Assert + assertEquals("", studentValidation.getGithubToken()); + } + + @Test + @DisplayName("Debe manejar URLs vacías") + void testEmptyUrls() { + // Act + studentValidation.setGithubUrl(""); + studentValidation.setTaigaUrl(""); + + // Assert + assertEquals("", studentValidation.getGithubUrl()); + assertEquals("", studentValidation.getTaigaUrl()); + } + + @Test + @DisplayName("Debe crear instancia sin valores iniciales") + void testDefaultConstructor() { + // Arrange & Act + StudentValidationDTO newValidation = new StudentValidationDTO(); + + // Assert + assertNull(newValidation.getProjectId()); + assertNull(newValidation.getGithubUrl()); + assertNull(newValidation.getTaigaUrl()); + assertNull(newValidation.getGithubToken()); + assertNull(newValidation.getStudent()); + } + + @Test + @DisplayName("Debe comparar correctamente con objetos de diferente tipo") + void testEqualsDifferentType() { + // Arrange + studentValidation.setProjectId(1L); + + // Assert + assertNotEquals(studentValidation, new Object()); + assertNotEquals(studentValidation, "String"); + } + + @Test + @DisplayName("Debe mantener consistencia de hashCode") + void testHashCodeConsistency() { + // Arrange + studentValidation.setProjectId(1L); + studentValidation.setGithubUrl("https://github.com/test"); + + // Act + int hashCode1 = studentValidation.hashCode(); + int hashCode2 = studentValidation.hashCode(); + + // Assert + assertEquals(hashCode1, hashCode2); + } +} diff --git a/src/test/java/com/upc/ld_admintool/rest/controllers/CategoriesControllerTest.java b/src/test/java/com/upc/ld_admintool/rest/controllers/CategoriesControllerTest.java index 168db1e..ce60bef 100644 --- a/src/test/java/com/upc/ld_admintool/rest/controllers/CategoriesControllerTest.java +++ b/src/test/java/com/upc/ld_admintool/rest/controllers/CategoriesControllerTest.java @@ -1,116 +1,116 @@ -package com.upc.ld_admintool.rest.controllers; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.upc.ld_admintool.domain.services.CategoriesService; -import com.upc.ld_admintool.rest.DTO.CategoryDTO; -import com.upc.ld_admintool.rest.DTO.IntervalDTO; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; - -import java.util.Arrays; -import java.util.List; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -/** - * Tests de integración para CategoriesController - * Valida los endpoints REST de categorías - */ -@WebMvcTest(CategoriesController.class) -@DisplayName("CategoriesController - Tests de Integración") -class CategoriesControllerTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper objectMapper; - - @MockBean - private CategoriesService categoriesService; - - private List testCategories; - private List testIntervals; - - @BeforeEach - void setUp() { - CategoryDTO category1 = new CategoryDTO(); - category1.setCategory("Category 1"); - - CategoryDTO category2 = new CategoryDTO(); - category2.setCategory("Category 2"); - - testCategories = Arrays.asList(category1, category2); - - IntervalDTO interval1 = new IntervalDTO(); - interval1.setName("Interval 1"); - - testIntervals = Arrays.asList(interval1); - } - - @Test - @DisplayName("POST /api/categories/metrics debe importar categorías de métricas") - void testImportMetriquesCategories_Success() throws Exception { - // Arrange - doNothing().when(categoriesService).importarCategoriesMetriques(any()); - - // Act & Assert - mockMvc.perform(post("/api/categories/metrics") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(testCategories))) - .andExpect(status().isOk()); - - verify(categoriesService, times(1)).importarCategoriesMetriques(any()); - } - - @Test - @DisplayName("POST /api/categories/factors debe importar categorías de factores") - void testImportFactorsCategories_Success() throws Exception { - // Arrange - doNothing().when(categoriesService).importarCategoriesFactors(any()); - - // Act & Assert - mockMvc.perform(post("/api/categories/factors") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(testCategories))) - .andExpect(status().isOk()); - - verify(categoriesService, times(1)).importarCategoriesFactors(any()); - } - - @Test - @DisplayName("POST /api/categories/strategicIndicators debe importar categorías de indicadores estratégicos") - void testImportStrategicIndicatorsCategories_Success() throws Exception { - // Arrange - doNothing().when(categoriesService).importarCategoriesStrategicIndicators(any()); - - // Act & Assert - mockMvc.perform(post("/api/categories/strategicIndicators") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(testIntervals))) - .andExpect(status().isOk()); - - verify(categoriesService, times(1)).importarCategoriesStrategicIndicators(any()); - } - - @Test - @DisplayName("POST /api/categories/metrics debe retornar 400 con body inválido") - void testImportMetriquesCategories_InvalidBody() throws Exception { - // Act & Assert - mockMvc.perform(post("/api/categories/metrics") - .contentType(MediaType.APPLICATION_JSON) - .content("invalid json")) - .andExpect(status().isBadRequest()); - - verify(categoriesService, never()).importarCategoriesMetriques(any()); - } -} +package com.upc.ld_admintool.rest.controllers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.upc.ld_admintool.domain.services.CategoriesService; +import com.upc.ld_admintool.rest.DTO.CategoryDTO; +import com.upc.ld_admintool.rest.DTO.IntervalDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Arrays; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests de integración para CategoriesController + * Valida los endpoints REST de categorías + */ +@WebMvcTest(CategoriesController.class) +@DisplayName("CategoriesController - Tests de Integración") +class CategoriesControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private CategoriesService categoriesService; + + private List testCategories; + private List testIntervals; + + @BeforeEach + void setUp() { + CategoryDTO category1 = new CategoryDTO(); + category1.setCategory("Category 1"); + + CategoryDTO category2 = new CategoryDTO(); + category2.setCategory("Category 2"); + + testCategories = Arrays.asList(category1, category2); + + IntervalDTO interval1 = new IntervalDTO(); + interval1.setName("Interval 1"); + + testIntervals = Arrays.asList(interval1); + } + + @Test + @DisplayName("POST /api/categories/metrics debe importar categorías de métricas") + void testImportMetriquesCategories_Success() throws Exception { + // Arrange + doNothing().when(categoriesService).importarCategoriesMetriques(any()); + + // Act & Assert + mockMvc.perform(post("/api/categories/metrics") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testCategories))) + .andExpect(status().isOk()); + + verify(categoriesService, times(1)).importarCategoriesMetriques(any()); + } + + @Test + @DisplayName("POST /api/categories/factors debe importar categorías de factores") + void testImportFactorsCategories_Success() throws Exception { + // Arrange + doNothing().when(categoriesService).importarCategoriesFactors(any()); + + // Act & Assert + mockMvc.perform(post("/api/categories/factors") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testCategories))) + .andExpect(status().isOk()); + + verify(categoriesService, times(1)).importarCategoriesFactors(any()); + } + + @Test + @DisplayName("POST /api/categories/strategicIndicators debe importar categorías de indicadores estratégicos") + void testImportStrategicIndicatorsCategories_Success() throws Exception { + // Arrange + doNothing().when(categoriesService).importarCategoriesStrategicIndicators(any()); + + // Act & Assert + mockMvc.perform(post("/api/categories/strategicIndicators") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testIntervals))) + .andExpect(status().isOk()); + + verify(categoriesService, times(1)).importarCategoriesStrategicIndicators(any()); + } + + @Test + @DisplayName("POST /api/categories/metrics debe retornar 400 con body inválido") + void testImportMetriquesCategories_InvalidBody() throws Exception { + // Act & Assert + mockMvc.perform(post("/api/categories/metrics") + .contentType(MediaType.APPLICATION_JSON) + .content("invalid json")) + .andExpect(status().isBadRequest()); + + verify(categoriesService, never()).importarCategoriesMetriques(any()); + } +} diff --git a/src/test/java/com/upc/ld_admintool/rest/controllers/FactorsControllerTest.java b/src/test/java/com/upc/ld_admintool/rest/controllers/FactorsControllerTest.java index 858b827..2f88ef4 100644 --- a/src/test/java/com/upc/ld_admintool/rest/controllers/FactorsControllerTest.java +++ b/src/test/java/com/upc/ld_admintool/rest/controllers/FactorsControllerTest.java @@ -1,163 +1,163 @@ -package com.upc.ld_admintool.rest.controllers; - -import com.upc.ld_admintool.domain.services.FactorsService; -import com.upc.ld_admintool.rest.DTO.FactorDTO; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.test.web.servlet.MockMvc; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -/** - * Tests de integración para FactorsController - * Valida los endpoints REST de factores - */ -@WebMvcTest(FactorsController.class) -@DisplayName("FactorsController - Tests de Integración") -class FactorsControllerTest { - - @Autowired - private MockMvc mockMvc; - - @MockBean - private FactorsService factorsService; - - private List testFactors; - private List testCategoriesList; - private List> testCategoriesMap; - - @BeforeEach - void setUp() { - FactorDTO factor1 = new FactorDTO(); - factor1.setId("1"); - factor1.setName("Test Factor 1"); - - FactorDTO factor2 = new FactorDTO(); - factor2.setId("2"); - factor2.setName("Test Factor 2"); - - testFactors = Arrays.asList(factor1, factor2); - testCategoriesList = Arrays.asList("Category A", "Category B"); - - Map categoryMap = new HashMap<>(); - categoryMap.put("name", "Category A"); - categoryMap.put("count", 5); - - testCategoriesMap = Arrays.asList(categoryMap); - } - - @Test - @DisplayName("GET /api/factors debe retornar factores por proyecto") - void testGetFactorsByProject_Success() throws Exception { - // Arrange - when(factorsService.getFactorsByProject("project-123")).thenReturn(testFactors); - - // Act & Assert - mockMvc.perform(get("/api/factors") - .param("prj", "project-123")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$").isArray()) - .andExpect(jsonPath("$.length()").value(2)) - .andExpect(jsonPath("$[0].name").value("Test Factor 1")); - - verify(factorsService, times(1)).getFactorsByProject("project-123"); - } - - @Test - @DisplayName("GET /api/factors/list debe retornar lista de categorías") - void testGetFactorsCategoriesList_Success() throws Exception { - // Arrange - when(factorsService.getFactorsCategoriesList()).thenReturn(testCategoriesList); - - // Act & Assert - mockMvc.perform(get("/api/factors/list")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$").isArray()) - .andExpect(jsonPath("$.length()").value(2)) - .andExpect(jsonPath("$[0]").value("Category A")); - - verify(factorsService, times(1)).getFactorsCategoriesList(); - } - - @Test - @DisplayName("GET /api/factors/categories debe retornar todas las categorías") - void testGetAllFactorsCategories_Success() throws Exception { - // Arrange - when(factorsService.getAllFactorsCategories()).thenReturn(testCategoriesMap); - - // Act & Assert - mockMvc.perform(get("/api/factors/categories")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$").isArray()) - .andExpect(jsonPath("$[0].name").value("Category A")) - .andExpect(jsonPath("$[0].count").value(5)); - - verify(factorsService, times(1)).getAllFactorsCategories(); - } - - @Test - @DisplayName("PUT /api/factors/{id}/category debe actualizar categoría") - void testUpdateFactorCategory_Success() throws Exception { - // Arrange - doNothing().when(factorsService).updateFactorCategory(1L, "New Category", "project-123"); - - // Act & Assert - mockMvc.perform(put("/api/factors/1/category") - .param("category", "New Category") - .param("prj", "project-123")) - .andExpect(status().isOk()); - - verify(factorsService, times(1)).updateFactorCategory(1L, "New Category", "project-123"); - } - - @Test - @DisplayName("GET /api/factors/import debe importar factores de calidad") - void testImportQualityFactors_Success() throws Exception { - // Arrange - doNothing().when(factorsService).importQualityFactors(); - - // Act & Assert - mockMvc.perform(get("/api/factors/import")) - .andExpect(status().isOk()); - - verify(factorsService, times(1)).importQualityFactors(); - } - - @Test - @DisplayName("GET /api/factors sin parámetro prj debe retornar 400") - void testGetFactorsByProject_MissingParameter() throws Exception { - // Act & Assert - mockMvc.perform(get("/api/factors")) - .andExpect(status().isBadRequest()); - - verify(factorsService, never()).getFactorsByProject(anyString()); - } - - @Test - @DisplayName("GET /api/factors debe retornar lista vacía cuando no hay factores") - void testGetFactorsByProject_EmptyList() throws Exception { - // Arrange - when(factorsService.getFactorsByProject("empty-project")).thenReturn(Arrays.asList()); - - // Act & Assert - mockMvc.perform(get("/api/factors") - .param("prj", "empty-project")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$").isArray()) - .andExpect(jsonPath("$.length()").value(0)); - - verify(factorsService, times(1)).getFactorsByProject("empty-project"); - } -} +package com.upc.ld_admintool.rest.controllers; + +import com.upc.ld_admintool.domain.services.FactorsService; +import com.upc.ld_admintool.rest.DTO.FactorDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests de integración para FactorsController + * Valida los endpoints REST de factores + */ +@WebMvcTest(FactorsController.class) +@DisplayName("FactorsController - Tests de Integración") +class FactorsControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private FactorsService factorsService; + + private List testFactors; + private List testCategoriesList; + private List> testCategoriesMap; + + @BeforeEach + void setUp() { + FactorDTO factor1 = new FactorDTO(); + factor1.setId("1"); + factor1.setName("Test Factor 1"); + + FactorDTO factor2 = new FactorDTO(); + factor2.setId("2"); + factor2.setName("Test Factor 2"); + + testFactors = Arrays.asList(factor1, factor2); + testCategoriesList = Arrays.asList("Category A", "Category B"); + + Map categoryMap = new HashMap<>(); + categoryMap.put("name", "Category A"); + categoryMap.put("count", 5); + + testCategoriesMap = Arrays.asList(categoryMap); + } + + @Test + @DisplayName("GET /api/factors debe retornar factores por proyecto") + void testGetFactorsByProject_Success() throws Exception { + // Arrange + when(factorsService.getFactorsByProject("project-123")).thenReturn(testFactors); + + // Act & Assert + mockMvc.perform(get("/api/factors") + .param("prj", "project-123")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(2)) + .andExpect(jsonPath("$[0].name").value("Test Factor 1")); + + verify(factorsService, times(1)).getFactorsByProject("project-123"); + } + + @Test + @DisplayName("GET /api/factors/list debe retornar lista de categorías") + void testGetFactorsCategoriesList_Success() throws Exception { + // Arrange + when(factorsService.getFactorsCategoriesList()).thenReturn(testCategoriesList); + + // Act & Assert + mockMvc.perform(get("/api/factors/list")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(2)) + .andExpect(jsonPath("$[0]").value("Category A")); + + verify(factorsService, times(1)).getFactorsCategoriesList(); + } + + @Test + @DisplayName("GET /api/factors/categories debe retornar todas las categorías") + void testGetAllFactorsCategories_Success() throws Exception { + // Arrange + when(factorsService.getAllFactorsCategories()).thenReturn(testCategoriesMap); + + // Act & Assert + mockMvc.perform(get("/api/factors/categories")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$[0].name").value("Category A")) + .andExpect(jsonPath("$[0].count").value(5)); + + verify(factorsService, times(1)).getAllFactorsCategories(); + } + + @Test + @DisplayName("PUT /api/factors/{id}/category debe actualizar categoría") + void testUpdateFactorCategory_Success() throws Exception { + // Arrange + doNothing().when(factorsService).updateFactorCategory(1L, "New Category", "project-123"); + + // Act & Assert + mockMvc.perform(put("/api/factors/1/category") + .param("category", "New Category") + .param("prj", "project-123")) + .andExpect(status().isOk()); + + verify(factorsService, times(1)).updateFactorCategory(1L, "New Category", "project-123"); + } + + @Test + @DisplayName("GET /api/factors/import debe importar factores de calidad") + void testImportQualityFactors_Success() throws Exception { + // Arrange + doNothing().when(factorsService).importQualityFactors(); + + // Act & Assert + mockMvc.perform(get("/api/factors/import")) + .andExpect(status().isOk()); + + verify(factorsService, times(1)).importQualityFactors(); + } + + @Test + @DisplayName("GET /api/factors sin parámetro prj debe retornar 400") + void testGetFactorsByProject_MissingParameter() throws Exception { + // Act & Assert + mockMvc.perform(get("/api/factors")) + .andExpect(status().isBadRequest()); + + verify(factorsService, never()).getFactorsByProject(anyString()); + } + + @Test + @DisplayName("GET /api/factors debe retornar lista vacía cuando no hay factores") + void testGetFactorsByProject_EmptyList() throws Exception { + // Arrange + when(factorsService.getFactorsByProject("empty-project")).thenReturn(Arrays.asList()); + + // Act & Assert + mockMvc.perform(get("/api/factors") + .param("prj", "empty-project")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(0)); + + verify(factorsService, times(1)).getFactorsByProject("empty-project"); + } +} diff --git a/src/test/java/com/upc/ld_admintool/rest/controllers/MetricsControllerTest.java b/src/test/java/com/upc/ld_admintool/rest/controllers/MetricsControllerTest.java index 547884c..c331365 100644 --- a/src/test/java/com/upc/ld_admintool/rest/controllers/MetricsControllerTest.java +++ b/src/test/java/com/upc/ld_admintool/rest/controllers/MetricsControllerTest.java @@ -1,193 +1,193 @@ -package com.upc.ld_admintool.rest.controllers; - -import com.upc.ld_admintool.domain.services.MetricsService; -import com.upc.ld_admintool.rest.DTO.MetricDTO; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.test.web.servlet.MockMvc; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -/** - * Tests de integración para MetricsController - * Valida los endpoints REST de métricas - */ -@WebMvcTest(MetricsController.class) -@DisplayName("MetricsController - Tests de Integración") -class MetricsControllerTest { - - @Autowired - private MockMvc mockMvc; - - @MockBean - private MetricsService metricsService; - - private List testMetrics; - private List testCategoriesList; - private List> testCategoriesMap; - - @BeforeEach - void setUp() { - MetricDTO metric1 = new MetricDTO(); - metric1.setId("1"); - metric1.setName("Test Metric 1"); - - MetricDTO metric2 = new MetricDTO(); - metric2.setId("2"); - metric2.setName("Test Metric 2"); - - testMetrics = Arrays.asList(metric1, metric2); - testCategoriesList = Arrays.asList("Performance", "Security"); - - Map categoryMap = new HashMap<>(); - categoryMap.put("name", "Performance"); - categoryMap.put("metrics", 10); - - testCategoriesMap = Arrays.asList(categoryMap); - } - - @Test - @DisplayName("GET /api/metrics debe retornar métricas por proyecto") - void testGetMetricsByProject_Success() throws Exception { - // Arrange - when(metricsService.getMetricsByProject("project-123")).thenReturn(testMetrics); - - // Act & Assert - mockMvc.perform(get("/api/metrics") - .param("prj", "project-123")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$").isArray()) - .andExpect(jsonPath("$.length()").value(2)) - .andExpect(jsonPath("$[0].name").value("Test Metric 1")); - - verify(metricsService, times(1)).getMetricsByProject("project-123"); - } - - @Test - @DisplayName("GET /api/metrics/list debe retornar lista de categorías") - void testGetMetricsCategoriesList_Success() throws Exception { - // Arrange - when(metricsService.getMetricsCategoriesList()).thenReturn(testCategoriesList); - - // Act & Assert - mockMvc.perform(get("/api/metrics/list")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$").isArray()) - .andExpect(jsonPath("$.length()").value(2)) - .andExpect(jsonPath("$[0]").value("Performance")); - - verify(metricsService, times(1)).getMetricsCategoriesList(); - } - - @Test - @DisplayName("GET /api/metrics/categories debe retornar todas las categorías") - void testGetAllMetricsCategories_Success() throws Exception { - // Arrange - when(metricsService.getAllMetricsCategories()).thenReturn(testCategoriesMap); - - // Act & Assert - mockMvc.perform(get("/api/metrics/categories")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$").isArray()) - .andExpect(jsonPath("$[0].name").value("Performance")) - .andExpect(jsonPath("$[0].metrics").value(10)); - - verify(metricsService, times(1)).getAllMetricsCategories(); - } - - @Test - @DisplayName("PUT /api/metrics/{id} debe editar métrica con todos los parámetros") - void testEditMetric_WithAllParameters() throws Exception { - // Arrange - doNothing().when(metricsService).editMetric( - 1L, "0.7", "http://example.com", "Performance", "global", "project-123"); - - // Act & Assert - mockMvc.perform(put("/api/metrics/1") - .param("threshold", "0.7") - .param("url", "http://example.com") - .param("categoryName", "Performance") - .param("scope", "global") - .param("prj", "project-123")) - .andExpect(status().isOk()); - - verify(metricsService, times(1)).editMetric( - 1L, "0.7", "http://example.com", "Performance", "global", "project-123"); - } - - @Test - @DisplayName("PUT /api/metrics/{id} debe editar métrica con parámetros opcionales null") - void testEditMetric_WithOptionalParameters() throws Exception { - // Arrange - doNothing().when(metricsService).editMetric(1L, null, null, null, null, "project-123"); - - // Act & Assert - mockMvc.perform(put("/api/metrics/1") - .param("prj", "project-123")) - .andExpect(status().isOk()); - - verify(metricsService, times(1)).editMetric(1L, null, null, null, null, "project-123"); - } - - @Test - @DisplayName("GET /api/metrics/import debe importar métricas") - void testImportMetrics_Success() throws Exception { - // Arrange - doNothing().when(metricsService).importMetrics(); - - // Act & Assert - mockMvc.perform(get("/api/metrics/import")) - .andExpect(status().isOk()); - - verify(metricsService, times(1)).importMetrics(); - } - - @Test - @DisplayName("GET /api/metrics sin parámetro prj debe retornar 400") - void testGetMetricsByProject_MissingParameter() throws Exception { - // Act & Assert - mockMvc.perform(get("/api/metrics")) - .andExpect(status().isBadRequest()); - - verify(metricsService, never()).getMetricsByProject(anyString()); - } - - @Test - @DisplayName("GET /api/metrics debe retornar lista vacía cuando no hay métricas") - void testGetMetricsByProject_EmptyList() throws Exception { - // Arrange - when(metricsService.getMetricsByProject("empty-project")).thenReturn(Arrays.asList()); - - // Act & Assert - mockMvc.perform(get("/api/metrics") - .param("prj", "empty-project")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$").isArray()) - .andExpect(jsonPath("$.length()").value(0)); - - verify(metricsService, times(1)).getMetricsByProject("empty-project"); - } - - @Test - @DisplayName("PUT /api/metrics/{id} sin parámetro prj debe retornar 400") - void testEditMetric_MissingRequiredParameter() throws Exception { - // Act & Assert - mockMvc.perform(put("/api/metrics/1")) - .andExpect(status().isBadRequest()); - - verify(metricsService, never()).editMetric(anyLong(), anyString(), anyString(), - anyString(), anyString(), anyString()); - } -} +package com.upc.ld_admintool.rest.controllers; + +import com.upc.ld_admintool.domain.services.MetricsService; +import com.upc.ld_admintool.rest.DTO.MetricDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests de integración para MetricsController + * Valida los endpoints REST de métricas + */ +@WebMvcTest(MetricsController.class) +@DisplayName("MetricsController - Tests de Integración") +class MetricsControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private MetricsService metricsService; + + private List testMetrics; + private List testCategoriesList; + private List> testCategoriesMap; + + @BeforeEach + void setUp() { + MetricDTO metric1 = new MetricDTO(); + metric1.setId("1"); + metric1.setName("Test Metric 1"); + + MetricDTO metric2 = new MetricDTO(); + metric2.setId("2"); + metric2.setName("Test Metric 2"); + + testMetrics = Arrays.asList(metric1, metric2); + testCategoriesList = Arrays.asList("Performance", "Security"); + + Map categoryMap = new HashMap<>(); + categoryMap.put("name", "Performance"); + categoryMap.put("metrics", 10); + + testCategoriesMap = Arrays.asList(categoryMap); + } + + @Test + @DisplayName("GET /api/metrics debe retornar métricas por proyecto") + void testGetMetricsByProject_Success() throws Exception { + // Arrange + when(metricsService.getMetricsByProject("project-123")).thenReturn(testMetrics); + + // Act & Assert + mockMvc.perform(get("/api/metrics") + .param("prj", "project-123")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(2)) + .andExpect(jsonPath("$[0].name").value("Test Metric 1")); + + verify(metricsService, times(1)).getMetricsByProject("project-123"); + } + + @Test + @DisplayName("GET /api/metrics/list debe retornar lista de categorías") + void testGetMetricsCategoriesList_Success() throws Exception { + // Arrange + when(metricsService.getMetricsCategoriesList()).thenReturn(testCategoriesList); + + // Act & Assert + mockMvc.perform(get("/api/metrics/list")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(2)) + .andExpect(jsonPath("$[0]").value("Performance")); + + verify(metricsService, times(1)).getMetricsCategoriesList(); + } + + @Test + @DisplayName("GET /api/metrics/categories debe retornar todas las categorías") + void testGetAllMetricsCategories_Success() throws Exception { + // Arrange + when(metricsService.getAllMetricsCategories()).thenReturn(testCategoriesMap); + + // Act & Assert + mockMvc.perform(get("/api/metrics/categories")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$[0].name").value("Performance")) + .andExpect(jsonPath("$[0].metrics").value(10)); + + verify(metricsService, times(1)).getAllMetricsCategories(); + } + + @Test + @DisplayName("PUT /api/metrics/{id} debe editar métrica con todos los parámetros") + void testEditMetric_WithAllParameters() throws Exception { + // Arrange + doNothing().when(metricsService).editMetric( + 1L, "0.7", "http://example.com", "Performance", "global", "project-123"); + + // Act & Assert + mockMvc.perform(put("/api/metrics/1") + .param("threshold", "0.7") + .param("url", "http://example.com") + .param("categoryName", "Performance") + .param("scope", "global") + .param("prj", "project-123")) + .andExpect(status().isOk()); + + verify(metricsService, times(1)).editMetric( + 1L, "0.7", "http://example.com", "Performance", "global", "project-123"); + } + + @Test + @DisplayName("PUT /api/metrics/{id} debe editar métrica con parámetros opcionales null") + void testEditMetric_WithOptionalParameters() throws Exception { + // Arrange + doNothing().when(metricsService).editMetric(1L, null, null, null, null, "project-123"); + + // Act & Assert + mockMvc.perform(put("/api/metrics/1") + .param("prj", "project-123")) + .andExpect(status().isOk()); + + verify(metricsService, times(1)).editMetric(1L, null, null, null, null, "project-123"); + } + + @Test + @DisplayName("GET /api/metrics/import debe importar métricas") + void testImportMetrics_Success() throws Exception { + // Arrange + doNothing().when(metricsService).importMetrics(); + + // Act & Assert + mockMvc.perform(get("/api/metrics/import")) + .andExpect(status().isOk()); + + verify(metricsService, times(1)).importMetrics(); + } + + @Test + @DisplayName("GET /api/metrics sin parámetro prj debe retornar 400") + void testGetMetricsByProject_MissingParameter() throws Exception { + // Act & Assert + mockMvc.perform(get("/api/metrics")) + .andExpect(status().isBadRequest()); + + verify(metricsService, never()).getMetricsByProject(anyString()); + } + + @Test + @DisplayName("GET /api/metrics debe retornar lista vacía cuando no hay métricas") + void testGetMetricsByProject_EmptyList() throws Exception { + // Arrange + when(metricsService.getMetricsByProject("empty-project")).thenReturn(Arrays.asList()); + + // Act & Assert + mockMvc.perform(get("/api/metrics") + .param("prj", "empty-project")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(0)); + + verify(metricsService, times(1)).getMetricsByProject("empty-project"); + } + + @Test + @DisplayName("PUT /api/metrics/{id} sin parámetro prj debe retornar 400") + void testEditMetric_MissingRequiredParameter() throws Exception { + // Act & Assert + mockMvc.perform(put("/api/metrics/1")) + .andExpect(status().isBadRequest()); + + verify(metricsService, never()).editMetric(anyLong(), anyString(), anyString(), + anyString(), anyString(), anyString()); + } +} diff --git a/src/test/java/com/upc/ld_admintool/rest/controllers/ProjectControllerTest.java b/src/test/java/com/upc/ld_admintool/rest/controllers/ProjectControllerTest.java index a3af5ed..f00e392 100644 --- a/src/test/java/com/upc/ld_admintool/rest/controllers/ProjectControllerTest.java +++ b/src/test/java/com/upc/ld_admintool/rest/controllers/ProjectControllerTest.java @@ -1,198 +1,198 @@ -package com.upc.ld_admintool.rest.controllers; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.upc.ld_admintool.domain.services.ProjectService; -import com.upc.ld_admintool.domain.services.validation.ProjectValidationService; -import com.upc.ld_admintool.rest.DTO.ProjectDTO; -import com.upc.ld_admintool.rest.DTO.StudentDTO; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -/** - * Tests de integración para ProjectController - * Valida los endpoints REST de proyectos - */ -@WebMvcTest(ProjectController.class) -@DisplayName("ProjectController - Tests de Integración") -class ProjectControllerTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper objectMapper; - - @MockBean - private ProjectService projectService; - - @MockBean - private ProjectValidationService validationService; - - private ProjectDTO testProject; - private List testProjects; - - @BeforeEach - void setUp() { - testProject = new ProjectDTO(); - testProject.setId(1L); - testProject.setName("Test Project"); - testProject.setDescription("Test Description"); - - StudentDTO student = new StudentDTO(); - student.setId(1L); - student.setName("Test Student"); - testProject.setStudents(Arrays.asList(student)); - - testProjects = Arrays.asList(testProject); - } - - @Test - @DisplayName("GET /api/projects debe retornar todos los proyectos") - void testGetAllProjects_Success() throws Exception { - // Arrange - when(projectService.llistarProjectesAmbStudents()).thenReturn(testProjects); - - // Act & Assert - mockMvc.perform(get("/api/projects")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$").isArray()) - .andExpect(jsonPath("$.length()").value(1)) - .andExpect(jsonPath("$[0].name").value("Test Project")); - - verify(projectService, times(1)).llistarProjectesAmbStudents(); - } - - @Test - @DisplayName("GET /api/projects/{id} debe retornar proyecto por ID") - void testGetProjectById_Success() throws Exception { - // Arrange - when(projectService.getProjectById(1L)).thenReturn(testProject); - - // Act & Assert - mockMvc.perform(get("/api/projects/1")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").value(1)) - .andExpect(jsonPath("$.name").value("Test Project")); - - verify(projectService, times(1)).getProjectById(1L); - } - - @Test - @DisplayName("POST /api/projects debe importar proyectos válidos") - void testImportProjectsExcel_Success() throws Exception { - // Arrange - Map validationResult = new HashMap<>(); - validationResult.put("validProjects", testProjects); - validationResult.put("invalidProjects", Arrays.asList()); - validationResult.put("totalValid", 1); - validationResult.put("totalInvalid", 0); - - when(validationService.validateProjectsWithDetails(anyList())).thenReturn(validationResult); - doNothing().when(projectService).importProjects(anyList()); - - // Act & Assert - mockMvc.perform(post("/api/projects") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(testProjects))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.totalValid").value(1)) - .andExpect(jsonPath("$.totalInvalid").value(0)); - - verify(validationService, times(1)).validateProjectsWithDetails(anyList()); - verify(projectService, times(1)).importProjects(anyList()); - } - - @Test - @DisplayName("POST /api/projects no debe importar si no hay proyectos válidos") - void testImportProjectsExcel_NoValidProjects() throws Exception { - // Arrange - Map validationResult = new HashMap<>(); - validationResult.put("validProjects", Arrays.asList()); - validationResult.put("invalidProjects", testProjects); - validationResult.put("totalValid", 0); - validationResult.put("totalInvalid", 1); - - when(validationService.validateProjectsWithDetails(anyList())).thenReturn(validationResult); - - // Act & Assert - mockMvc.perform(post("/api/projects") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(testProjects))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.totalValid").value(0)) - .andExpect(jsonPath("$.totalInvalid").value(1)); - - verify(validationService, times(1)).validateProjectsWithDetails(anyList()); - verify(projectService, never()).importProjects(anyList()); - } - - @Test - @DisplayName("POST /api/projects/sync-categories debe sincronizar categorías") - void testSyncCategoriesAfterImport_Success() throws Exception { - // Arrange - doNothing().when(projectService).synchronizeCategoriesAfterDataImport(); - - // Act & Assert - mockMvc.perform(post("/api/projects/sync-categories")) - .andExpect(status().isOk()); - - verify(projectService, times(1)).synchronizeCategoriesAfterDataImport(); - } - - @Test - @DisplayName("DELETE /api/projects/{id} debe eliminar proyecto") - void testEsborrarProjecte_Success() throws Exception { - // Arrange - doNothing().when(projectService).esborrarProjecte(1L); - - // Act & Assert - mockMvc.perform(delete("/api/projects/1")) - .andExpect(status().isOk()); - - verify(projectService, times(1)).esborrarProjecte(1L); - } - - @Test - @DisplayName("GET /api/projects debe retornar lista vacía cuando no hay proyectos") - void testGetAllProjects_EmptyList() throws Exception { - // Arrange - when(projectService.llistarProjectesAmbStudents()).thenReturn(Arrays.asList()); - - // Act & Assert - mockMvc.perform(get("/api/projects")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$").isArray()) - .andExpect(jsonPath("$.length()").value(0)); - - verify(projectService, times(1)).llistarProjectesAmbStudents(); - } - - @Test - @DisplayName("POST /api/projects con body inválido debe retornar 400") - void testImportProjectsExcel_InvalidBody() throws Exception { - // Act & Assert - mockMvc.perform(post("/api/projects") - .contentType(MediaType.APPLICATION_JSON) - .content("invalid json")) - .andExpect(status().isBadRequest()); - - verify(validationService, never()).validateProjectsWithDetails(anyList()); - verify(projectService, never()).importProjects(anyList()); - } -} +package com.upc.ld_admintool.rest.controllers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.upc.ld_admintool.domain.services.ProjectService; +import com.upc.ld_admintool.domain.services.validation.ProjectValidationService; +import com.upc.ld_admintool.rest.DTO.ProjectDTO; +import com.upc.ld_admintool.rest.DTO.StudentDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests de integración para ProjectController + * Valida los endpoints REST de proyectos + */ +@WebMvcTest(ProjectController.class) +@DisplayName("ProjectController - Tests de Integración") +class ProjectControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private ProjectService projectService; + + @MockBean + private ProjectValidationService validationService; + + private ProjectDTO testProject; + private List testProjects; + + @BeforeEach + void setUp() { + testProject = new ProjectDTO(); + testProject.setId(1L); + testProject.setName("Test Project"); + testProject.setDescription("Test Description"); + + StudentDTO student = new StudentDTO(); + student.setId(1L); + student.setName("Test Student"); + testProject.setStudents(Arrays.asList(student)); + + testProjects = Arrays.asList(testProject); + } + + @Test + @DisplayName("GET /api/projects debe retornar todos los proyectos") + void testGetAllProjects_Success() throws Exception { + // Arrange + when(projectService.llistarProjectesAmbStudents()).thenReturn(testProjects); + + // Act & Assert + mockMvc.perform(get("/api/projects")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(1)) + .andExpect(jsonPath("$[0].name").value("Test Project")); + + verify(projectService, times(1)).llistarProjectesAmbStudents(); + } + + @Test + @DisplayName("GET /api/projects/{id} debe retornar proyecto por ID") + void testGetProjectById_Success() throws Exception { + // Arrange + when(projectService.getProjectById(1L)).thenReturn(testProject); + + // Act & Assert + mockMvc.perform(get("/api/projects/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("Test Project")); + + verify(projectService, times(1)).getProjectById(1L); + } + + @Test + @DisplayName("POST /api/projects debe importar proyectos válidos") + void testImportProjectsExcel_Success() throws Exception { + // Arrange + Map validationResult = new HashMap<>(); + validationResult.put("validProjects", testProjects); + validationResult.put("invalidProjects", Arrays.asList()); + validationResult.put("totalValid", 1); + validationResult.put("totalInvalid", 0); + + when(validationService.validateProjectsWithDetails(anyList())).thenReturn(validationResult); + doNothing().when(projectService).importProjects(anyList()); + + // Act & Assert + mockMvc.perform(post("/api/projects") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testProjects))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.totalValid").value(1)) + .andExpect(jsonPath("$.totalInvalid").value(0)); + + verify(validationService, times(1)).validateProjectsWithDetails(anyList()); + verify(projectService, times(1)).importProjects(anyList()); + } + + @Test + @DisplayName("POST /api/projects no debe importar si no hay proyectos válidos") + void testImportProjectsExcel_NoValidProjects() throws Exception { + // Arrange + Map validationResult = new HashMap<>(); + validationResult.put("validProjects", Arrays.asList()); + validationResult.put("invalidProjects", testProjects); + validationResult.put("totalValid", 0); + validationResult.put("totalInvalid", 1); + + when(validationService.validateProjectsWithDetails(anyList())).thenReturn(validationResult); + + // Act & Assert + mockMvc.perform(post("/api/projects") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testProjects))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.totalValid").value(0)) + .andExpect(jsonPath("$.totalInvalid").value(1)); + + verify(validationService, times(1)).validateProjectsWithDetails(anyList()); + verify(projectService, never()).importProjects(anyList()); + } + + @Test + @DisplayName("POST /api/projects/sync-categories debe sincronizar categorías") + void testSyncCategoriesAfterImport_Success() throws Exception { + // Arrange + doNothing().when(projectService).synchronizeCategoriesAfterDataImport(); + + // Act & Assert + mockMvc.perform(post("/api/projects/sync-categories")) + .andExpect(status().isOk()); + + verify(projectService, times(1)).synchronizeCategoriesAfterDataImport(); + } + + @Test + @DisplayName("DELETE /api/projects/{id} debe eliminar proyecto") + void testEsborrarProjecte_Success() throws Exception { + // Arrange + doNothing().when(projectService).esborrarProjecte(1L); + + // Act & Assert + mockMvc.perform(delete("/api/projects/1")) + .andExpect(status().isOk()); + + verify(projectService, times(1)).esborrarProjecte(1L); + } + + @Test + @DisplayName("GET /api/projects debe retornar lista vacía cuando no hay proyectos") + void testGetAllProjects_EmptyList() throws Exception { + // Arrange + when(projectService.llistarProjectesAmbStudents()).thenReturn(Arrays.asList()); + + // Act & Assert + mockMvc.perform(get("/api/projects")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(0)); + + verify(projectService, times(1)).llistarProjectesAmbStudents(); + } + + @Test + @DisplayName("POST /api/projects con body inválido debe retornar 400") + void testImportProjectsExcel_InvalidBody() throws Exception { + // Act & Assert + mockMvc.perform(post("/api/projects") + .contentType(MediaType.APPLICATION_JSON) + .content("invalid json")) + .andExpect(status().isBadRequest()); + + verify(validationService, never()).validateProjectsWithDetails(anyList()); + verify(projectService, never()).importProjects(anyList()); + } +} diff --git a/src/test/java/com/upc/ld_admintool/rest/controllers/StrategicIndicatorsControllerTest.java b/src/test/java/com/upc/ld_admintool/rest/controllers/StrategicIndicatorsControllerTest.java index df8bfcf..1dbb246 100644 --- a/src/test/java/com/upc/ld_admintool/rest/controllers/StrategicIndicatorsControllerTest.java +++ b/src/test/java/com/upc/ld_admintool/rest/controllers/StrategicIndicatorsControllerTest.java @@ -1,111 +1,111 @@ -package com.upc.ld_admintool.rest.controllers; - -import com.upc.ld_admintool.domain.services.StrategicIndicatorsService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.test.web.servlet.MockMvc; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -/** - * Tests de integración para StrategicIndicatorsController - * Valida los endpoints REST de indicadores estratégicos - */ -@WebMvcTest(StrategicIndicatorsController.class) -@DisplayName("StrategicIndicatorsController - Tests de Integración") -class StrategicIndicatorsControllerTest { - - @Autowired - private MockMvc mockMvc; - - @MockBean - private StrategicIndicatorsService strategicIndicatorsService; - - private List> testCategories; - - @BeforeEach - void setUp() { - Map category1 = new HashMap<>(); - category1.put("name", "Quality"); - category1.put("indicators", 5); - - Map category2 = new HashMap<>(); - category2.put("name", "Performance"); - category2.put("indicators", 3); - - testCategories = Arrays.asList(category1, category2); - } - - @Test - @DisplayName("GET /api/strategicIndicators/categories debe retornar todas las categorías") - void testGetAllStrategicIndicatorCategories_Success() throws Exception { - // Arrange - when(strategicIndicatorsService.getAllStrategicIndicatorCategories()).thenReturn(testCategories); - - // Act & Assert - mockMvc.perform(get("/api/strategicIndicators/categories")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$").isArray()) - .andExpect(jsonPath("$.length()").value(2)) - .andExpect(jsonPath("$[0].name").value("Quality")) - .andExpect(jsonPath("$[0].indicators").value(5)); - - verify(strategicIndicatorsService, times(1)).getAllStrategicIndicatorCategories(); - } - - @Test - @DisplayName("GET /api/strategicIndicators/categories debe retornar lista vacía cuando no hay categorías") - void testGetAllStrategicIndicatorCategories_EmptyList() throws Exception { - // Arrange - when(strategicIndicatorsService.getAllStrategicIndicatorCategories()).thenReturn(Arrays.asList()); - - // Act & Assert - mockMvc.perform(get("/api/strategicIndicators/categories")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$").isArray()) - .andExpect(jsonPath("$.length()").value(0)); - - verify(strategicIndicatorsService, times(1)).getAllStrategicIndicatorCategories(); - } - - @Test - @DisplayName("GET /api/strategicIndicators/fetch debe obtener indicadores estratégicos") - void testFetchStrategicIndicators_Success() throws Exception { - // Arrange - doNothing().when(strategicIndicatorsService).fetchStrategicIndicators(); - - // Act & Assert - mockMvc.perform(get("/api/strategicIndicators/fetch")) - .andExpect(status().isOk()); - - verify(strategicIndicatorsService, times(1)).fetchStrategicIndicators(); - } - - @Test - @DisplayName("GET /api/strategicIndicators/fetch debe ser idempotente") - void testFetchStrategicIndicators_Idempotent() throws Exception { - // Arrange - doNothing().when(strategicIndicatorsService).fetchStrategicIndicators(); - - // Act & Assert - Primera llamada - mockMvc.perform(get("/api/strategicIndicators/fetch")) - .andExpect(status().isOk()); - - // Act & Assert - Segunda llamada - mockMvc.perform(get("/api/strategicIndicators/fetch")) - .andExpect(status().isOk()); - - verify(strategicIndicatorsService, times(2)).fetchStrategicIndicators(); - } -} +package com.upc.ld_admintool.rest.controllers; + +import com.upc.ld_admintool.domain.services.StrategicIndicatorsService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests de integración para StrategicIndicatorsController + * Valida los endpoints REST de indicadores estratégicos + */ +@WebMvcTest(StrategicIndicatorsController.class) +@DisplayName("StrategicIndicatorsController - Tests de Integración") +class StrategicIndicatorsControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private StrategicIndicatorsService strategicIndicatorsService; + + private List> testCategories; + + @BeforeEach + void setUp() { + Map category1 = new HashMap<>(); + category1.put("name", "Quality"); + category1.put("indicators", 5); + + Map category2 = new HashMap<>(); + category2.put("name", "Performance"); + category2.put("indicators", 3); + + testCategories = Arrays.asList(category1, category2); + } + + @Test + @DisplayName("GET /api/strategicIndicators/categories debe retornar todas las categorías") + void testGetAllStrategicIndicatorCategories_Success() throws Exception { + // Arrange + when(strategicIndicatorsService.getAllStrategicIndicatorCategories()).thenReturn(testCategories); + + // Act & Assert + mockMvc.perform(get("/api/strategicIndicators/categories")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(2)) + .andExpect(jsonPath("$[0].name").value("Quality")) + .andExpect(jsonPath("$[0].indicators").value(5)); + + verify(strategicIndicatorsService, times(1)).getAllStrategicIndicatorCategories(); + } + + @Test + @DisplayName("GET /api/strategicIndicators/categories debe retornar lista vacía cuando no hay categorías") + void testGetAllStrategicIndicatorCategories_EmptyList() throws Exception { + // Arrange + when(strategicIndicatorsService.getAllStrategicIndicatorCategories()).thenReturn(Arrays.asList()); + + // Act & Assert + mockMvc.perform(get("/api/strategicIndicators/categories")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(0)); + + verify(strategicIndicatorsService, times(1)).getAllStrategicIndicatorCategories(); + } + + @Test + @DisplayName("GET /api/strategicIndicators/fetch debe obtener indicadores estratégicos") + void testFetchStrategicIndicators_Success() throws Exception { + // Arrange + doNothing().when(strategicIndicatorsService).fetchStrategicIndicators(); + + // Act & Assert + mockMvc.perform(get("/api/strategicIndicators/fetch")) + .andExpect(status().isOk()); + + verify(strategicIndicatorsService, times(1)).fetchStrategicIndicators(); + } + + @Test + @DisplayName("GET /api/strategicIndicators/fetch debe ser idempotente") + void testFetchStrategicIndicators_Idempotent() throws Exception { + // Arrange + doNothing().when(strategicIndicatorsService).fetchStrategicIndicators(); + + // Act & Assert - Primera llamada + mockMvc.perform(get("/api/strategicIndicators/fetch")) + .andExpect(status().isOk()); + + // Act & Assert - Segunda llamada + mockMvc.perform(get("/api/strategicIndicators/fetch")) + .andExpect(status().isOk()); + + verify(strategicIndicatorsService, times(2)).fetchStrategicIndicators(); + } +} diff --git a/src/test/java/com/upc/ld_admintool/rest/controllers/WizardControllerTest.java b/src/test/java/com/upc/ld_admintool/rest/controllers/WizardControllerTest.java index 49e2330..ee9e4c4 100644 --- a/src/test/java/com/upc/ld_admintool/rest/controllers/WizardControllerTest.java +++ b/src/test/java/com/upc/ld_admintool/rest/controllers/WizardControllerTest.java @@ -1,110 +1,110 @@ -package com.upc.ld_admintool.rest.controllers; - -import com.upc.ld_admintool.domain.services.WizardService; -import com.upc.ld_admintool.rest.DTO.WizardStatusDTO; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.test.web.servlet.MockMvc; - -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -/** - * Tests de integración para WizardController - * Valida los endpoints REST del asistente de configuración - */ -@WebMvcTest(WizardController.class) -@DisplayName("WizardController - Tests de Integración") -class WizardControllerTest { - - @Autowired - private MockMvc mockMvc; - - @MockBean - private WizardService wizardService; - - private WizardStatusDTO testStatus; - - @BeforeEach - void setUp() { - testStatus = new WizardStatusDTO(true, true, true, true, true); - } - - @Test - @DisplayName("GET /api/wizard/status debe retornar estado completo") - void testGetWizardStatus_Success() throws Exception { - // Arrange - when(wizardService.getWizardStatus()).thenReturn(testStatus); - - // Act & Assert - mockMvc.perform(get("/api/wizard/status")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.hasProjects").value(true)) - .andExpect(jsonPath("$.hasData").value(true)) - .andExpect(jsonPath("$.hasMetricsCategories").value(true)) - .andExpect(jsonPath("$.hasFactorsCategories").value(true)) - .andExpect(jsonPath("$.hasStrategicIndicatorCategories").value(true)); - - verify(wizardService, times(1)).getWizardStatus(); - } - - @Test - @DisplayName("GET /api/wizard/status debe retornar estado parcial") - void testGetWizardStatus_PartialConfiguration() throws Exception { - // Arrange - WizardStatusDTO partialStatus = new WizardStatusDTO(true, false, true, false, false); - when(wizardService.getWizardStatus()).thenReturn(partialStatus); - - // Act & Assert - mockMvc.perform(get("/api/wizard/status")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.hasProjects").value(true)) - .andExpect(jsonPath("$.hasData").value(false)) - .andExpect(jsonPath("$.hasMetricsCategories").value(true)) - .andExpect(jsonPath("$.hasFactorsCategories").value(false)) - .andExpect(jsonPath("$.hasStrategicIndicatorCategories").value(false)); - - verify(wizardService, times(1)).getWizardStatus(); - } - - @Test - @DisplayName("GET /api/wizard/status debe retornar estado sin configuración") - void testGetWizardStatus_NoConfiguration() throws Exception { - // Arrange - WizardStatusDTO emptyStatus = new WizardStatusDTO(false, false, false, false, false); - when(wizardService.getWizardStatus()).thenReturn(emptyStatus); - - // Act & Assert - mockMvc.perform(get("/api/wizard/status")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.hasProjects").value(false)) - .andExpect(jsonPath("$.hasData").value(false)) - .andExpect(jsonPath("$.hasMetricsCategories").value(false)) - .andExpect(jsonPath("$.hasFactorsCategories").value(false)) - .andExpect(jsonPath("$.hasStrategicIndicatorCategories").value(false)); - - verify(wizardService, times(1)).getWizardStatus(); - } - - @Test - @DisplayName("GET /api/wizard/status debe ser idempotente") - void testGetWizardStatus_Idempotent() throws Exception { - // Arrange - when(wizardService.getWizardStatus()).thenReturn(testStatus); - - // Act & Assert - Primera llamada - mockMvc.perform(get("/api/wizard/status")) - .andExpect(status().isOk()); - - // Act & Assert - Segunda llamada - mockMvc.perform(get("/api/wizard/status")) - .andExpect(status().isOk()); - - verify(wizardService, times(2)).getWizardStatus(); - } -} +package com.upc.ld_admintool.rest.controllers; + +import com.upc.ld_admintool.domain.services.WizardService; +import com.upc.ld_admintool.rest.DTO.WizardStatusDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Tests de integración para WizardController + * Valida los endpoints REST del asistente de configuración + */ +@WebMvcTest(WizardController.class) +@DisplayName("WizardController - Tests de Integración") +class WizardControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private WizardService wizardService; + + private WizardStatusDTO testStatus; + + @BeforeEach + void setUp() { + testStatus = new WizardStatusDTO(true, true, true, true, true); + } + + @Test + @DisplayName("GET /api/wizard/status debe retornar estado completo") + void testGetWizardStatus_Success() throws Exception { + // Arrange + when(wizardService.getWizardStatus()).thenReturn(testStatus); + + // Act & Assert + mockMvc.perform(get("/api/wizard/status")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.hasProjects").value(true)) + .andExpect(jsonPath("$.hasData").value(true)) + .andExpect(jsonPath("$.hasMetricsCategories").value(true)) + .andExpect(jsonPath("$.hasFactorsCategories").value(true)) + .andExpect(jsonPath("$.hasStrategicIndicatorCategories").value(true)); + + verify(wizardService, times(1)).getWizardStatus(); + } + + @Test + @DisplayName("GET /api/wizard/status debe retornar estado parcial") + void testGetWizardStatus_PartialConfiguration() throws Exception { + // Arrange + WizardStatusDTO partialStatus = new WizardStatusDTO(true, false, true, false, false); + when(wizardService.getWizardStatus()).thenReturn(partialStatus); + + // Act & Assert + mockMvc.perform(get("/api/wizard/status")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.hasProjects").value(true)) + .andExpect(jsonPath("$.hasData").value(false)) + .andExpect(jsonPath("$.hasMetricsCategories").value(true)) + .andExpect(jsonPath("$.hasFactorsCategories").value(false)) + .andExpect(jsonPath("$.hasStrategicIndicatorCategories").value(false)); + + verify(wizardService, times(1)).getWizardStatus(); + } + + @Test + @DisplayName("GET /api/wizard/status debe retornar estado sin configuración") + void testGetWizardStatus_NoConfiguration() throws Exception { + // Arrange + WizardStatusDTO emptyStatus = new WizardStatusDTO(false, false, false, false, false); + when(wizardService.getWizardStatus()).thenReturn(emptyStatus); + + // Act & Assert + mockMvc.perform(get("/api/wizard/status")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.hasProjects").value(false)) + .andExpect(jsonPath("$.hasData").value(false)) + .andExpect(jsonPath("$.hasMetricsCategories").value(false)) + .andExpect(jsonPath("$.hasFactorsCategories").value(false)) + .andExpect(jsonPath("$.hasStrategicIndicatorCategories").value(false)); + + verify(wizardService, times(1)).getWizardStatus(); + } + + @Test + @DisplayName("GET /api/wizard/status debe ser idempotente") + void testGetWizardStatus_Idempotent() throws Exception { + // Arrange + when(wizardService.getWizardStatus()).thenReturn(testStatus); + + // Act & Assert - Primera llamada + mockMvc.perform(get("/api/wizard/status")) + .andExpect(status().isOk()); + + // Act & Assert - Segunda llamada + mockMvc.perform(get("/api/wizard/status")) + .andExpect(status().isOk()); + + verify(wizardService, times(2)).getWizardStatus(); + } +}