From c5404cbca3fd6aa381620516b82151df74a22dfe Mon Sep 17 00:00:00 2001 From: sergio-utrillaa Date: Fri, 27 Mar 2026 10:13:36 +0100 Subject: [PATCH 1/5] deploy ld_admintool --- .dockerignore | 72 +- .gitattributes | 4 +- .gitignore | 66 +- .mvn/wrapper/maven-wrapper.properties | 6 +- Dockerfile | 92 +- amep.xlsx | Bin 0 -> 12950 bytes equips.xlsx | Bin 11712 -> 0 bytes mytest.xlsx | Bin 0 -> 11622 bytes pom.xml | 288 +-- .../ld_admintool/LdAdmintoolApplication.java | 26 +- .../domain/services/CategoriesService.java | 56 +- .../domain/services/FactorsService.java | 66 +- .../domain/services/LDEvalService.java | 70 +- .../domain/services/LDService.java | 798 ++++----- .../domain/services/MetricsService.java | 68 +- .../domain/services/ProjectService.java | 1550 ++++++++--------- .../services/StrategicIndicatorsService.java | 50 +- .../domain/services/WizardService.java | 118 +- .../exceptions/SaveSyncException.java | 44 +- .../validation/GitHubValidationService.java | 362 ++-- .../validation/ProjectValidationService.java | 626 +++---- .../validation/TaigaValidationService.java | 338 ++-- .../services/validation/ValidationResult.java | 106 +- .../ld_admintool/domain/utils/DataSource.java | 10 +- .../ld_admintool/rest/DTO/CategoryDTO.java | 24 +- .../upc/ld_admintool/rest/DTO/FactorDTO.java | 38 +- .../ld_admintool/rest/DTO/IntervalDTO.java | 16 +- .../upc/ld_admintool/rest/DTO/MetricDTO.java | 32 +- .../upc/ld_admintool/rest/DTO/ProjectDTO.java | 50 +- .../rest/DTO/ProjectIdentityDTO.java | 28 +- .../rest/DTO/SaveSyncResponseDTO.java | 52 +- .../rest/DTO/SaveSyncStepDTO.java | 34 +- .../upc/ld_admintool/rest/DTO/StudentDTO.java | 24 +- .../rest/DTO/StudentIdentityDTO.java | 26 +- .../rest/DTO/StudentValidationDTO.java | 24 +- .../rest/DTO/WizardStatusDTO.java | 30 +- .../controllers/CategoriesController.java | 76 +- .../rest/controllers/FactorsController.java | 104 +- .../rest/controllers/MetricsController.java | 110 +- .../rest/controllers/ProjectController.java | 204 +-- .../StrategicIndicatorsController.java | 58 +- .../rest/controllers/WizardController.java | 44 +- src/main/resources/application.yml | 46 +- .../LdAdmintoolApplicationTests.java | 26 +- .../services/CategoriesServiceTest.java | 194 +-- .../domain/services/FactorsServiceTest.java | 370 ++-- .../domain/services/LDEvalServiceTest.java | 440 ++--- .../domain/services/LDServiceTest.java | 872 +++++----- .../domain/services/MetricsServiceTest.java | 406 ++--- .../domain/services/ProjectServiceTest.java | 1396 +++++++-------- .../StrategicIndicatorsServiceTest.java | 336 ++-- .../domain/services/WizardServiceTest.java | 494 +++--- .../exceptions/SaveSyncExceptionTest.java | 282 +-- .../GitHubValidationServiceTest.java | 814 ++++----- .../ProjectValidationServiceTest.java | 1042 +++++------ .../TaigaValidationServiceTest.java | 532 +++--- .../validation/ValidationResultTest.java | 616 +++---- .../upc/ld_admintool/rest/DTO/DTOTest.java | 1278 +++++++------- .../rest/DTO/IntervalDTOTest.java | 620 +++---- .../rest/DTO/ProjectIdentityDTOTest.java | 602 +++---- .../rest/DTO/SaveSyncDTOTest.java | 596 +++---- .../rest/DTO/StudentIdentityDTOTest.java | 592 +++---- .../rest/DTO/StudentValidationDTOTest.java | 538 +++--- .../controllers/CategoriesControllerTest.java | 232 +-- .../controllers/FactorsControllerTest.java | 326 ++-- .../controllers/MetricsControllerTest.java | 386 ++-- .../controllers/ProjectControllerTest.java | 396 ++--- .../StrategicIndicatorsControllerTest.java | 222 +-- .../controllers/WizardControllerTest.java | 220 +-- 69 files changed, 9832 insertions(+), 9832 deletions(-) create mode 100644 amep.xlsx delete mode 100644 equips.xlsx create mode 100644 mytest.xlsx 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..229adbe 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://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"] diff --git a/amep.xlsx b/amep.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2225e1e5ab3d8df31d472124f6fbd358268e3af8 GIT binary patch literal 12950 zcmeHt1y>yD)^+0$tZ@$z+})kvt_hk1cXubaJHg%E-Q9w_ySqDlOs>qmnfZRfduy$# zr)zbcUENjt_>q$Y14jiw0-yl^03m>lp(WA<1OV`X002+`(4gucEzE88%x%9aIa%u2 zXwo{GnG$4zgOaBMK;N$a-}Zm_2^1?yT6EIGwaPAda*eEAOBx8U;IeYRzWe0N($%$IFgWiQvG+ce!1 z>hC%4YcRE$UA+ZqOe9ujn_H9_**+1MExOPslg8ZE44_rWHB>&}i;trD4hS^4&ePil z@p?|EfXFH4@nY)3DH3SwkZ;M409d|=G}^>k>!SL_p5L|4$kJDnbcn zj^!E8HEGOV^!fHIH3&Pu-+$Kmci5xvHJF$Hbugmxoa z9eUyf>BSF;5Pqdng^%g9rJ-qWXpji$=A+(=#rg22m9MX00J;Ct*&1bfqN_I|O1-Hh z?3>PhwbnDWp`-od_&;6!Kdhkt@z;x^q-DG4;rvd;p8Wf7rE#5l`sWVk>jY;Q18j~4f*!Nq0n(1U)$n+@jTU}RJ-qFRR%zqki0J19z0>mMRk z#p~Tj_S4tXHwmH=PUQA2;gm)7`5BS}t3)D`=Ry_0QQFVgFeo{=e&{?2-_?4hRadm{ zNmb$t(y6KD8;Fp4W_H8|7^f37(ajI z&3*k(f>^stna6hY07sZE!`W)uTFP(;-d zMvlmGoyxg38J{RxUYO3EV=#x5MzTL~=O8YP1$WHS?c%^6`p%em0S2?H%WEZES9Kxi z5;(@uKJTd-lP3tHd}=N5tXe`%UQ~FH2X?y;_+)bVfUkIG#7Rb;SBxA)Q8j^q&?X2! z}5qm%K!T z3WoI5+i)4IE&yR9PVgcr#TKThFs;}&HY!F;|45mZdQ{QbFL&kaGk$t0tqI{c&UK@_ z)N_Zx2opb;317?)Z|xu9)s^HmJlCUxg&ptFu!W`E&)aGMVJ+UsotBn*s-5Wiy`}=j z5fpeNmnPmwBcE|GfA%dEmz3^AG0Dwkd>N6d7=yEx%_pG`K|c67)4t2trsG5rGp#oA zRV)_ow#OqNR*&{G?~VaR*U7V4DLtx~n!pG(&^0ZrO-J6n%7q%~9pS3O8L9~|b)^6< z>g?p48u!}2<$Uf?)T-}L!`}Z(8yl|E_*OMakNhhoZ)hq#WWvh&QjU z9pz6Yc8mr>LK!PI$0S!p(7-16T&KCIDv190pZfeuI|z`WRd`q49&gnBE4jb(#>8E| zebe|xaBKiH$Q!x;;RODZ;{S38AaB9jn~MD3-ij5ZCA;Z?EwC^C^bSe(Xh`$6bRUkC z4-ugUswoyp37OolR&gjBHC1LL>A)=foKA=P>~C2RHo#D>JE-!5kinf%Oi#HWEQT+K z!JxH|OWu2lLZBcY9UK-N!y+WwpfvDC^;4&!vhUoHlVJI0<$e&lYMAh2HXfn3V>GrI z!y9}vh(py|o)12XMb#(-Z6#Cn*4IdY8Ak(~@@W(H)V8prModDx|Q-oA^9l|%z z7tK%l(oQ2L9a#@>+qgxN?+aOYT>Web{QE`mJt(4Vwlfo&luy@f1s?|~+7>UUhptZb zw+8yASKr?BUn!HxEo){31pwgS0sxqASNzi++8FBT+1k+kS{VK?jLAxxZ-x=oEv@<$ zc;6z9D{%+RSWr_=s`f8jZZz0j@|0EM)^oTtx|u>{7fM33F@juzUjxz z3|;i=q|kinKGJ(2=fKraZR>`_j93M7PC1mU1n9u-*V)(!Q)TTH7w2Ke^4UA-Gq!`y zyKA<{>@_tk1O;nYYztH*}U$VO3DS6dg40qb?Qi}X0ynwp&aH32AnstPUtQMp1*k+H(#&MXy+ zV)cL@!TEjLpfnqU$cE&M7l9*biVmIlj_Bn~WS8xg(fD0G*`Y?+_Lvps8HfSHC11T?je>E9f%>t z%v^R=YN8vPiVEw3F+VHmAbt=w&1!o3sAb2LienHI=tjS!9-M)MtfEo2*XoQF(9X6j5$Y%7Nz4kncS!!b5aqG|; zGVoLwAGr^K14h(Mc$C0^=8w;K=#$suPFf{tfu8GY-=ULcrp!2HMZ!E z_T~zcg|C{S(}uN$NL(&wD+#7cBym+inbV{rP0siv=*xq zPuu&aA7}5keRq%64^N5dDxZ|UA9L|pL$wrBU*!}}IW(1KLB6}b(peL2ndwWP9i-fn zCo+zun)uNzhjMTldqx~N=R+45DGeTlxhO;)7%1JYs*tqTkFA&#K*CTJSW3LZYDE_a zv5(1YCl1^@C=?W|Iv<;5(MhjL3fhmiORrlDl1FF?!tGS!>TOqZdE23SJ>QA;dj25m zoeDE7pw~pnIeO*`e0Lxe8%rQxUM3m}uHV#=|1s3jEc!bmj83{V(QrJ%ISAHgJ*BDx zF2EBfp2B>-Cx?Hnwvg*q zmlugwIkzEFc1QxAmjw@dEvIZZ$w9;ivL+Isiy26g-lW6B#~Omg??84#0ThP;1${&Q zo@~^u*r}n2-0rlyK@Y6*#q`mo?jg|C)TTB#4*?NP##B@nBo5i%@woIQAFSnlQkL+FC=^Egh!}!3KMH|%lq!8)8K7%dc_eMC7Bdl*muJO{{$!&ilxe>!e=Rr& z*ikn!!Sfe5uS_TfTp_NYdxF`(pJm&~8S*#H(uPr$3ac%29&iU}YS(;#mp@q@60K>G zM33tC`tG6y!(&k?C(4ZfpeI}68KT+>c?yoGZ$9s4)j=h0DmGq;_9qAA`}a62i}%31 zh`wEarQ?m5(>nM}IOY33Tx@1x{Jb*4lwS6X;pJlx{Yk6jFEYZKIpro1S@{xdMBJ$- zb#flEY`Zy+!Y_E3BidN~i(Z8AA&T%YDpqIQ2SpHuK*3$L7@38 z7+%Q!E~4=0Kh!lOb1DdCfP@XcDJm?~-zxzZGr(hKUk(P(3?-dIkY&bQU!*UF;-7DU z#if3Q0PL@eVKwhfCOPSCX=;Dk*WZj#gS@UK$lh!c59!; z7-$%+h_@L-4>e;ag6?q+80$dq@Emu@iJRj2)712qugumcT2GgQ&p<@nEbNUK9c9<4 z(C?OSyM<%r2&vHs(|ozLRD9XKEJsZ9ug1?)w1aC}BDjE=N$GuFSJ-8LP4UIx>q*{> zlXQUYV$II+K5YzU_=+ty(dh8#A-KfPmdhaJ)DNtO9A?;N4#8565a{2t zG~KNbNRD7M(S?zHpBlEVC3dgG({~AOj*3<#%)D>?xO41o*xGYt&FK?y`j1!pdrq&9 zn@p4j1^@&i{85Md<(_Q~_006>ejR^#=AWMj%opE67>qqW=tj0$?V*ZLc7cuVy$s>+ zjl!cLLNqYKFvL~QZvG!>vPsy6A-lytxo$&2>8xgPQ*I{b zQN7-l4aYT{iVHhRsSNB7dSp-V@!UV{$5Shdz#eos)5hs;8%QV*jf;tVBfUs3Cij1h zQa}~zC=wH`Ah=zt`lO5V?i;tSsnTT8V;$-*WIQ8DmzURb*Wm=49t&NvxdxY(8+W1+9sX{wN%KAImS@n6 zcdCedDJfrz4G`W7U=7_%BjSN;)qk1YLyw9%I-ER1fS9)6619ko(KtTL^3sUY1_QwB z3T-bbhNDS~fwwr3T6Z$zRA%eWy&oXtqPN7w;PA-PsDutD}5YK~S|@auA9jv5)*^ZU`_Y zOmW@1p55{~u3q3GH>A|)VAMMcZi%7kv!@*G%fzhy$E0e%xj5jzZh2`p@fMe_d$m2o5<5m95 zxJu^yu#~9s)f6uJ&q`c&gEu5qH(SHBwl z*-vo`IA5TPrGDOQm82kfCphkJ`P(&V7C{Nwly&QPu6o^wsyo@iZ&-nR-c!%bZs6u4 z7g(Dxpj|I9U7Lt*`(>(s*XOUq+B7V0)w}+;SR4Ky`fOwCWU6QLM~pqCdTPGG4t#Z= zX!#g(%IrZvEzn*#CQ>(HY-j8;;SS#cVuHtIEroGY9bPSKMQ5+WMVJf3Hf&lbp$tm- z`P1UXnH#TVRkzz&sJT5P%8#{NcMO9;SMM3Bt<=5K62}VP1y46`{*tVMQ#R^m(zVW33|>G6 zRmqra`Keq<1H{5VheBqIaBB=Eg}s0SHFq`%Y((8Z?ZFjXt+wFrGKzL#>K&@ zqwkGw3?t<`DlHRA0?x+vFW7c>zLjOnQKR4LT+J9MTUEmuzT?f>lV%;NS5nHL05ura~CL??xzmVn^yQWN}aA6hA5WLXm#=iTLEHSl8jsK{SE z(U&o-?X`=GZ>b`Q!kjd6>Gh4(WXyV0LOO(!eUPS1|CKdWdJ`_X#J_1?iD~;VL){v`m!}fyJf63Js(-NAnej*`>xU&`~d56LVGI^EP6`zCy#M$c`D0 z5?q(JltH?3cAuJk2X^+(LGoS6*67T*=Tp)iWJ;!npW71USA@yLlf6ttsxoR!N^k;JF?awK*&LH&=u8j*1R(uMNTzoI$!P01V@=kLR( zKP8vt)rhAl&zZt^ckD&kuS;`7FQ3>1X(@7i|OG|!sgJNvVxLX zr+7+7oY1ftRe8OXAyy0oRr1lfoqeE6HDdz0GKZ;yaYhnJH@ld{wot!T6Dl`N$GkgD zth$ZC!S=p2t-2kBuJshV30 z{FE&}ITTYJhyQa)UGYgMX2#JgC7GOJdm4)9fsa4avZ3zHLcAu}w*jh~xfj^o0KS)7 zZ&H&^N=5#k!?W?wQrjJ+<`%O1yaOvfn6*sMsnyt?dKCpp1+=(JuK*wT^`wxG*&^TjA;PNpptN!>XpgQTquAvhU;c-peW5-o` z=yV3Mnk_#idVTbmTCWbO$W2L|ak58+iAdz$*Sdh-9xITSE5g$FJgfK<7Q|u|7Gb?9 zOWxleMfy9k2!_89r-}(BG-lVQXrzUJa0rd&^|G2B%$SyxR#*#jqBbVWJa3t@U@sX= zDGN>k3)Yr~K*3oDgVoT;OIXMeV+jPBqFEet@ZO2$t2NM^^TdEhXA4uD;BaeTM)5kp z6vbulcKNU!?ABJZZ1S+rdnA1oo>PA9p6z+%iZuWf%*Aq@e%_8-HoTtX?i;zKA9SuV z;Kv#M&*ci5II@_CLULL5>ox+?xAw>1lrt3cxi0p=0f4V>@e=Ak?W>`dwVtk`t+kQ4 z!7no#^LbbXM;zE%R((2_h#Q65+vOREtQ!*I{hgX1I8Cgn?m~*;WNeaW|AZlavbA@` zKAgicp#IoyMK?{isE_o!5e^m%QNv+TVd+6(*@jF5^}}?OdEO{<#ghKoCywP9igOPLpQTy%^0va5J@EpntM$XsN$F>lfdWs0sorxMTX|Z@nxA~!y(j&F^K*s z0iKR@m-x);rzo-r%Hfq{qOG!(O4J0$mfM;w2&Q-nBNejBWf>v+rPYX+SfqqjJrv-G z4gV@KNx#pC$YRGgU|&+wb1T;j7|Ai*>?E!aSFkZk?V%SdDvL@CbV;R{TKo_B(uuvq zujhuH=Z(rC!FZ|Wb!>|`=Z}bdw8#zZOMUiqGU0oJUTA7=9o-+epi)6xvKZt!d!pne z<}fu>P+6%8Jw(08EDUlJ_2U7o^1a62V(cQ8Rs=hXTp&%;7QL;n`pq8_hlKJKh=^`$ z&>Ph?G8tlEPh?PHF>G`2^9bWj9K>#{e~Mlq+cjcvU7t8=6qSEIN9Q8MNxoJlRW?to z;O>k?t@6Pg&`OD$PDeeg$EE2iE!#`AENb+4=eF|P8*~CwSNNDWw!2*S*;ck*d700b z)_#?11*g}_qxjve5%2IvI@Qp*%2E(D4dKfl0U_jgHa_c9{*lqcg8xr0W5l z%#d@Q>?j<3b$UFKhWuQtCMO8mm{0=z-1iv(S)lvd_j z<0yn=Y2o{!3HJzpzH#GLf4(9uzl!DkY{ zbAdgM*nRT)w~;4H!eC0@TfJZSZNP)*AC~ctuE0MFz5i$v{8{e()gbZ%ZbG^Cfxi_eeFp{4j&GwShD2yNB zXpUs02&Exj2jfhfznc@x)iTuu)c8VfkLy}fZfe4@Hu0#=#!5DjlgCi6mO(G87Pb6! z{oOMjZWr2#Zr_H%2+hk6n~N`Zqwasp1N-eyFja3nU`7J~$o|0t%Qx3(t7omKXKVY1 zkEHwUoPk!`$owc~NTQ4Xt=z=?rI1z$C}c@Un*$-asb^%QR>GXiTw+UtibI$#E>4uW zA*k^Py3uuoc)6qpM2)-wG1Jpu5dwBme z7lx^fgd*(9QVrs^haO77QzFm>UD_L>8$82~9SCkmlwmW{ac6yd>ZN9pGyxMdF^YRg zvfIU=Z&bt^j+1>gwY5Bwb2(|`eZC>3afnz(ne&45z0P5JG}a=1OQl9Smg_sMOhtfl z>%|8f^Zs8G6Jm*q;79zSc`HG?%<5H!M)5qYC{#GHm%bz|$-hbnRE=k{RRa<>kHAbFe zl{yUVxHfcg|FMsVnsSjnFTVNG-{^?D5`RLQQuIo9I}_nrR%9L9Ha0oaQ}{sY_+uCGEcf+%RibtpNFg$Sy7iA za$h~Q(8bY#FlQx!M}!m~uB8%vH*FWci-`$-M26#6v*&s)JZT&bN6XfouPgvpq$P(T zIN`X}I9oed-qzIou1<=EG2AK0)CXEjAvBOQ-1*@8@tZEl^muM|tl?Z|qrqTf^WA-( z!&^HVZf`nNO=YpvWVf~?h^D9@G4KbAvp$WkG}p7Dx#_foh-8gkaS;^`m&~`>7`7SS z&SqLEuPG(>eRY+9#vHt_=Rpp)I@GXJJx|7y&>ZDzDh|j?;A z!k5@t3k^LyvM81qqAGGq4JfORKZZ|MMU`ND^4e*;pXq5ePRM^~lurk`P0~<@$dbLE zZ-8=6*v%#WfMjchEl3ysAwG)Tha-$}qfv4+G>fQ3h@#cVa8}ZIo-9CTErarc@GQDH zvTD>t${p9?wyLlFGl>ajDV4A!VMh{<$l+%TfdT$&&ew%Ybn@EqC{HaW$`%}>K5P*K zH}#(?^NTg)$9S)u$Lb^YRrZTtjQ4p~ZRAm9k*-4moks)QTVK+Rl+$B35I@<%Ey=ZJ ztcB|OMvxqK%%*;)!ON#26?i;eRqhF{QzUlPL4F~Ik(0NiQX-=mvmi8cyd|T#tMdRr zoI2e~wmEkex(+90BDd)BN8^94AoOQZlz<)aA77uIguxSi%el1V`ifLrqNGA05+GB4 z($kO7JN{TNBO-A0-C4`hZx^jE70@&#O=pNnbR5zGf|u^@Y5S!q+AK~)6*o5$5yfHu zBb_xE<~sDp4X#p@25^*+F9i3b6Df`nr&>Z$^q#Gy+*e+fFgk2u>z3KOR!7LAvbo0 z3oWd$Bf1B~uI3`RkNnpX>>US{&jVrajrI=B`~00!X=`)?5p`C@Gm_}QVtl`NYoz@vYG`uQsr;MXyi=H1n!65&6rw z)RBH{6N6B3<(Mf(?oNMb`4EWMNjYwhPFV>hyK}uQOo11N*GXiLKyP>^POLhbQ`FB2 z-bZn54J&d-&1s6thKG%`fr4}3duiXSKeE`ZoR6)I#cNXhYGnX49zU;G^n zW_gF=4~BxBx&dfJ%Y1h$&B0op>hA!5P*jc7; z#$v%0@E?qP5r4?NKi*F%k$!4IJmehnQ4Jn&C$~gBhBNa&gwW z&0db#+MatKZt4*;y}fvQ)J6A(xhi!P{6%ym9C z_2fclErEwtYeBt2Hnmt7eDD%+sNQRrrv!aQtUXo>DnrlcE6_6tu8YJh!pDgwM8GPD z*L%>HXC_!GOY6WBoCw;AQCG@_OBp`y}Y9QQz5+SnDinFDHuI<|E!9Ihz@HbUbkG1;kmj5xd`@7)pql&bPnvdz15bfZzKle*&mr`~vv1tMa?( z?dTnbb{m;=wHJB-x2;hDELzz063!n0R9pp{4W0AHvd1x<*5Hd{GT2`P7>mc#ea0g P-~onjihxP?$Jze_bQs4y literal 0 HcmV?d00001 diff --git a/equips.xlsx b/equips.xlsx deleted file mode 100644 index 8d6de0b04687b5db14315dfc9b3065d63615922f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11712 zcmeHtg;!kJ@^#}*aCeuWL4yQ$cMtA#W5FGQyGw9)5+Jw*2o~I(;O-s*UuWjMmwC*5 zf5GqFwfdfQ*6P~b_nxYKs`e=*Stw{M04x9=0058!xY^p`JRtypAQ%7u695mXD+&TS zn*p8Qs=3>nIq5UI+1ZliK||8z03gBN|KIh0_zhI3$%4MHBDX6p_?9?kS{jB}rPC2z z^8lr4NYMxyqAU~WxR4)zE}f2XKwt;R=F^yr4UxpC|}U-?}1f{l)Q26p8pC(LuAK>i5=$~hjD>O@V@lI@UFl1V zNj<>p@Fv^z_@#d#Nj;u1hgYBola0*EY-^i3JGTZ^^`a+}I!)qD{Q&kyrKV5!q|&3< z?+1iiyyjV*!v*~&v>=pJiv{t_kyXizP3Sh2M*y4#63tFYj;2__NoTj6Gm5Nrw8i>I z>+Ow1U6xWb3}eOCb1iza=Y8*cmzu;qNH!;?daw$k*j=iBAp5pPvNHz~B3b?KhD`xA} zSt&2UjwlBXNhEO0zI8OSbz))ub^l+i{vQs|zdU+Hyu4yJD{}CO)I&)B&Gd3Cnux5c zuv81Vnvb9K(u?}2d@91_4q75KHG)7WN#8b~hrz{V{;2(a^6PbuiUpz+BtXC{Bnrk6 zNb}eEDzCj_d|L%EEuwl<8C=uAowJ`Xn&CT}T(pPsCX`q1a3T%6-^tiwzRIiLmi+3D zKwZ_6*Q&-i%kedZ`&(1n&I_@O4$PlkObQ?RRVX+xZrMjA`>1oSe0AzL&W1AGd$0fW--9GKzQ!yG93b$p0Kg0In{l&caRoWr7=u7Izc#f3HGR+mE0%Y5 z-IL*+fIN}lj5rE5+F0yo1Us7_0cvSIXh?k{w#9yBk2m_XLCNP6_2_d=P2*=xJ6TKR z2m0h=P?N!o_$H3%hCQZb?98)l?RT%PnAzBq2O|-wwIgr7tsQjjC~E2!a?2KS!2=4y z9DsKCckR+n8cI&NGBo71D$O}KB5l|5rTD{gBdJV5q_D!PEOaT2!l9SCtW(y#s>vmF z45@V}q2>WPIUq-abeu}5RY*;xwo(}ub#VkVS?0#&*&IAK!`MPys#Pz@o-KaI6WG}d0-$}SJ(&i7uf}{BGa<6aLr_q~yKU~HS6ueSsRbVIf z(4W}zNXd_Nn>1vn?gsJ@pmfvHN!;YCjSp^z%)H*kbIpyhC?$2CHJ3tD=l3i+!y{rI zJ*rOTy4gf@5j{+2yJ~8ZEAN$kF0JVLvOUr76>nA$*pG7#5NJf zCqK%r(ohQ{RVuYZ&@|kJH#OiA`r`2)J=k9`G35g6xy@iNCIG-gfIaw6rvIxS|C{w8 zz!@4C<^T3op&~Ea!;031^cceGn&EBACN^g!LV}wbK~!Aei-r>NfrEgH#VI1L_(Y$kOS} zx1O3r7q4Nn1Kdf+FYpnh`BB^fzuk_zZFXX;*;uxGWtYHEG zc;IjRi}ai<&CHygSpFE;ezD(&-|UBW*CtIgSPVZsQ>IfWkmEu+`n3t7+~LD~QD@Q5 z8+|?mBN35Ycw>_vA3TrS`qEa9%+Oo?xkQrabB*L6>6!+`niK|ZKr!5y7DaxX;?tDWmDUxi>P&H-_TQN@puD^w(-xB^ zilG<~leP{{JMPXs*Ryw1@?y-b;|vQ9(4GQu<*_qmY>A>pS- zpvo4glr5Yu>ukNbOg=NLKFe4GjRl__Si}{q>EI_VqNU~QT)5v)S`x7Nn7$tEz(r|` z;%G&f?Tlt{&!&3K??_ENzm{p5PnpJ;l4U2J9RJ4QqNyvcS#n8Wp{>J|?M6_0zgN%B zj$B4-ztfhQ>+lpGv0fHIr(W9)=OF4G(zr1Ru|t|rMB^uDg7ZYLyxOg)%u_fY__?$M zcfh1#n##xDdHs9b4bx}bfFDz*DiSbHzzyeMLPt7P-qxXg!{dl;};+0b)i@N=*hhV7MalNI8bT@g3h!{N}c0gDv}o+16a|5-@YG!3Pn5RNK9j($nu5> zVXw!Xj#gT1_r~3jx>hX1y^1z^liawPEv0rGo+tBpnkzawgk|TJivN2e>7uu^CR8=$ z2Yqo(qjEw-+NYQ0-JTk)ZCN11okgV2cWYa;^nxrv_Xh(ywPtakx0{tir;a!S-_bkHE-Ax4Ch*N~Rvia51s1-* z#@s!C%%ps;bXdK2)oEcYEVQddUKf+jY{*ehmg8(pAxJm;5}qy-j5c&A$lgF^=xroy z$j5X-$%q6QJ%a;HWQR2|4SgF$nJgr;7pTzQj7fuL&M|cp6eJa?7MS(f5+P1Xj>=Ce za-Px9nVwMunWiBepK%?%FWjy9r?M%IrJ`WJ$nstm%undk@c`=_UGP0jsxGHP4B37NerAykW_ONLRF<#5bd;#6jT0)BLu(i=<6J1b1iC)X2Qm!k8MEy2CVKIH$QsygMwL7pjB-KIGw@SQ%2 z$4=lZm)~LUts$;Qv4<{HfYJnqpq%-bBHu-c_nZZi>#ngC9#u;Uj%N4X_?DZTLFVc) z_t&63?mu4s^Xk!@=mTXo6aWy0`b+WoV~=pQG_x~f`Q!e__V7(}0JsR=M{#%Gz&CS$ zHXiyI#Us4lvzsgK-z+{FDaM2#jzCrW(>p|zseqb$7`BJj>Ve0r_7vrv*S!+!Nifw~86KQcbIjw=cLEnOjHv)GLd#NwgC6M;JJeX5Vw=87T zhsLEO3~A1DD(FI<;#IK3y2_;_Ka$<7H5lf1pSf`{NR4>MBtR|!U&$n0DiSoLAbUAc z*OMC?muF50ahsO?(3lo*vzb z0>UUl^Ez%W?MZ+|S1UOE{MX)Zv*c+L#=3MLX$3|yE-tR-u42eGd>6VEi!3he*KZ|b zyF$F5GUj`E>`&p@Z?#{%$;^CPVS!2_j6ZZI|AH9WsL^0{7biaP@L=*36=oX5Ckcv6 z)H^!J570|7h613Nift{a#$d}!LASZnIDX+E{8V5%M>0Up$7)Z6%j;XL|GKZn0+%jW zzft)~DxN>jZ6f}|$4U>=%Z#2*vw?FrD2WPqlBR1lb$tFAY6tsLvc|8Nh4%^Nwi$7V z@i=R!r4{!W-&@d1BUJUS(Gy&As>A&D{LNnO5~rC3T`-thrNyri1ar ztd=vc6Yn&;1sY}|stQmAGQ+nyoH?SmPn2z#DEJmS8!NFQJ#QF&xe&fLqs3Wo(UPT~ zYN?I!#TU0rY9Zc)Ng+6`z0G@>6uTeL|BZ@Bystz!j*)>s8JF>G$f^s}MhSU86A!fT zZOtYQ86#;F5!NyjyF3N~eam4oOffm6(Dx_}BbNuok|U-QfbYym^4IOtzDW8aICB=E<%^3sN=<=9|yW?U<8eppUY{c?&3XYz*d9H$%@`nn?0c_T{JMW?~C4wc}Yy-cgm zrS4SoFp+h-!`t9`^I46Q1wsS(3b}9Bo0Xa9foX1gn<4L7^vmJIoT__F{8j_5C3W53 zp{zSVXzuD36*Td`p%Y%4uwedPX}dO&(D9G)9G1^LbPf%UWo!Td<Rw6+z7C4XG31*R!I6eR~UR?^AO2cIHbP=u!2NfGj-&wCeIAk}9!X+VIP@4xN zgw;|OUI6b8STT#F`7@VUyZ9+Rpj9wfgqwE$-$|8_TU$61;_Voh8_VBE;DdK5a zg|1G1=tJCf=E`ZtnXPz!KT7IpA&YzoF^cyg8drqbNY~Qw4dl@Dl=C;KBCYsFM>@KU$+P(Nq_xyNA|REm^qhj>PAZQzO45_EpG2(zI}skof|T8c7(!rQCoj#| zNytDB1#&0h60M9H)stN`&gK(BU(ZA4hNSE|M|5&$m`@#*L3hlK6aQk&n@E$)R3!tI zFLRj+{cmam8ES{oRyU=qR#<3J4}W-Y1ARn{N_{ol^-;)HTY_N8o?6kqh=v?X1BCT?AWZjY(ZYaVbb-Uun=uqF#fvMmt>Ov0`=cru0Nh&D z`a5K8i{q@LiH03Wo=;fz(1J0M$Lr46@0+dcaNu-%+xsb&jTJwvS_*G=udcc^8|68W zQVkl5rse73j^^^FbLc8E-6+rcn%^yAxT%dm3^^_WwFKy*S$#foGsEP|E{jxeUw`Mo zr*UvBBxh^?tO%3>F{H?uqZaG_o=piRGq*FIOEs54OP3xV&!)1-&aV$)yFiGKThCKGI2{7YlsZB0Gv80ew2Aggd4m=YyxG z%lhtjbd2=yv!K}(bA_jW)KD_*gfM@A^%HjRX&2VN$kEcs(acoU+0hDU@drm1sLO*E zgwWbF_kFc^L#W_td4v)r*5JouH7KwLB@464dP%-<9q3!Q$|H6Z&60QwR%$+KS!4zU zprET$M{Z^|oV2B7YB!9w7YE1*6IPk_Az8nR@rzXZW;SsK1l>P|Nk*Enn~O;Ay1imu z(Z0QwU&NEOujU{I8QZzYl!np7va71%OGQQ|DWvskW-29E8GcLy%&Cp#d;Cl-ij<8J zV@UbDQ6DW3FYA*ndSaD3fBEI}cO%`9Baxjc)0ZONRO~s$Ywt--Vv}RsdyESQx=?DI zPhxw7dLHk*M_w6|DNn?5)f;6{#QZ=xV=0yrpjD+|{oWh`io=k~O;GMTxCzXe-oIM< zbj)a#ZLdF^W^YrMQb{bqm zs26q9{mR;BK1Bx2sH+>E z0yWE0HRzvZ$m9FAQSX5wu-7DbDce1X$FU|Ym5b4PA1Evg3+eW)pVLyGEs5(+zg7j1 zGFH20f2P{q`mN%^lw$}w!=E{%>T`$~5k37G@ZP#>&X=zlTIdyV3>(7OBT$ZUr(tOg z6yHwQI21fXH6+Xr;!s&WpEX5G%*RE+tz_k_1FEGV9=e$zFS-xHo;(%%!MrbVbH~q4 z8ni<{An3*%ur)ZM=gu_)?b)^+V|CnpC;i7vf|3(JnFtP}C9q^p`Dd{Fk{bV|9{s63 z{??HGP#(Xj%iApdsub=?Baf432597ET4k+t;5kyfK6F=b*pxHMiV%R}eOJ#!1i7fN zMCUfaTh)(YPY_9d!kzw3IA@Vvq;^&}{**(<`QqB@<4_Y>QuB zj>s`hq3 zr;Z!hT+diOg99?N-B?QZ@L*l8j*jlTT00f>93!KVG7qCSVj!(`ekCXi*AgQLXNBUc z(03>BQ_Keh&GSm3Gq&25a1k0QM7P`Hf~I^pKi;bkxmLk$S-RHu9~aeSO9P=6yr>-D z^pN(?MYRW0pR<{xs+qI%FAijRHfLitwgMg|4#{*2VFf>N7?d%qp^4d(GZ(_hZkSnF z=~QwQaa1}}V-b?)NJ~@Zt&8aWfL!msME3${GV6uCSd(}P!&X)cZ$uvndbPuDF(M}z zY|_=uECRi&bd6?&ihWP=Fc*!dj*cnr#aRd8y^9k?FHkAm4PW&&(lla*hae349c8Z5 zNY}07%}Ia`C}RR4d}5U7fO@B!&D^S7Fov+;a%yvVr0`a=-diy!WyI?0&E3BHxmnK%>Vc0%a+ot#|V>$((u~DU+J+qHk zo)#5aOg!dIk#eKQM5m9Se}p(h7AbkP@^hw@(ft7h)FfYCF*x&3%w32$voLTdt$6TYCe~ z&V@CEZ_2$aUp#UE4N^o19v*_63_8dU?JpCdH_!&)RsFjZ3{U+v9t~XPUm*Nv=W{kP z2A2gae++-hz#9Ll^U(&$hlP{P=s(-`AQ;nHLlp?eZRw3o%$ZZhBTBnljaTni&OT^K zp2y1r!}iIBM9GN`0WLwH*t zEb#LBJRSFsDZ@frAT7HVeccooogBd8nMO$q?2P-U_JQD`oF;1odrVCc0(rDmqjvt9 z$LxzX`K6mN)R)p`7Dw(l{^H;$t*_rceQ|_W^uWa=S1&C)d5NW6VhGi)1nT6KO`LMH zC4k=^I7_8ImB=UcKs2wY+eToxRjjziL3;NQt4%^~nsKm?bogp({iLRqa~ZFC|1u#E`YtT&>1CsGp0ilUlw&=j7TSfrvh7;UB+C6P#mF3gdz1#0rUDcLg_p% zO|f@3t;{()j4I(hWWaj4w$e5$Y~{GUhQv@|2!dTc^YjIKs2kC(lz$bXilR=2(cvH5bnn9+cCvl8wo&8G2aBIyYebJn<7TmJ_(+nwOG`i|_(eR6fTRX(s=tfCJy(Q75 zHp5fPT~nV}NBU5@*mEbBIk-md)J5y|k)arimS^sm4hn+#N+1bO4n3FWfdq^vhZ)mN z*pqKtYeCXysAXec`3~CGs=4Ei1V8Q}NttYAz5|`ksy!KcoR5^FN*^4#v|+ELn!(~4 zzsx?`Hf_nkT%F!Ra@$QGbpMvfRlYavo*St@!JTWv+rbvT+!71!Wqi~k+Ffyu`wY91Olea6gAY3`lk(D5)p-?6OqiihEM-3wSXe+RJy>S_J3(vnzI2KQyVeUZNu&MMy^QSb|L0v`xBA!h zA$}By$%gh7>|KFdo*VMQ*x{{JO0r2%p`itBgeSJf_O?T>w5eNBM!!U`wUieTuJzbG z9&va4{Ca=g_#!VbK(JS5#3Bg`P2bvUY3AJF!v0DDk&2LzP$ftw3$Rt`{!^w}W>Bdg z3NblUYlv9cfbKmI&NJc_Q1ZC0^pMFx=QBfnrK^4RlF^H}sdwtjbUYY6u<7MqIEne| zdHGM+cJT*yr&dmb$hMb1J_NcZ-?9wTp2&QeiodmYMypHb;+(APA`UN3D6LB`@;I{f zmF3`A8?c8Yj1})XXBHAr7yclrVuD6~8pq z?A~0NgKVifyNg(4B7EoT3Yx;3w6BkqX)mCN^iZ!MDPM3iOLJ#IuI;#(A^n8%5`-be zlQ=}*@sD9_2W(@-k$!3PmU->g$@esOKHhh7o65bng2A8Ru03^-$uw5;WKlZ`(`HI4 zyDNU|!UfEA&$??F&wjM>S~d51RlcI;H7zY)o|0ng1NPKE{TKq030zM9a}Vji*X`fq zKXjBT$^JLMe>b%K+wj*|2mZ;ww6{GseBR{q+jJgW!#r#Gd2amQ{T07W0e}o}&h-E9 zws?;7{LuY3(g@Oj{>0yo;Gd&BKbHB8a*qCwhcwRtp6kHB0lt7!fAHh{R)wFNK37S8 zo30Z4Y5H6x|)^-OA65JhvJHZL=?ry=o(Z(S_kS185(E!0ExFoo{2G`)h2@nYGd_9@D zcV?LRe!+Wdt*+B&Rqb6>=WKoUR#kw7!vP=w5CH%HC4l!u3&i&c01yHX0AK?UVGJak z9NjD(-HbK8oh?B|tX>X4iX1o?`YZqp^#1=H|A$wgR8zsJll@t%%6vefYr2g|m~ARO z$vK~+Y$Z7cQC+l6JU!2|`@6-XF|H@LK?=FFKQAgt(fjral(jL&_|n3~=JAo{7H!Rk z6#YhpeMX;)scV-Bdds7A?Lcy@j5g{38Nc!7-z34maoAQOcaotRwZIcKYNw`C{1 z+CLc;Se(UPetEXJMT>)1o4R7bmqm*<;i6^$w@S6)(>1x= zC~oM0Sd-sZcDD%OzzN+asu~}K39O!JP?(w1Z>Ws`xL-*(f)ZUVaKMQ_FWYBS*sJM2 z8Xc^)){}JD$kH;7eYBfvGMqi`3+-8Kkn$nhn3(Fu$&cpnsJM9+&>Y3V8cd90dndJu zM%pcez~Nt!jA8l`5yHd0S~f{0*7TkUS5zgusjj&pux-wWK?gJvnCH>p{*`Kn-`{^C zepT>z3enjc_gtkq`iD0{kSGFL%&G2YasuxSLn99x>r{l-AqKsKFR!6m`S1V>Q2m?E z)@ZR)ok9gs5vr0XP@Oe)wFH9LSbsbJPgnmBE9hT7y);fqrHlO;_)zvXtp8$qDHcOq z!Bb4OiBi))P;L>uCOVgzc&Uw!1VfW37*-~r#s7A2VM#Fhdq3s*8dqr~7LEW_t!EiH z`P#+(DI=|GlC(?dw{A?2>9gtc6d8GMdXJVE#*+HNY=wapD(T6el2sU^tU5$U*!d)2 zJfV~~`n^he%VwA5Po~8+4$8olb-Y>M<44m1W|InbQAOVJEACIE;P!*etiP7~^#du- zu86cWZ1`;}%`#kHKKC}Z0Jfh}g>D$_@kbrzss&GmCA-MgC? zsjH;_Xdoz(yl@%nzW%8sg4>DTDxnI(i~s4Ys!-sgfOYK)QUl$x4Mr-3y@-)iG}G+%@wqe>B1(XNN{xE=M1WF(`b_57*BX=et7TP4kF91&Dld35zk{*jJ_Sf3|HzTfQXt>Fg*^k{rb zHwdG^oCx^22ECSUB7Jl~kO7(hrMx)f$S(OrZK`E}~ggC9LC?*6nTn!F^ zqZTfG5%TmUH6L1H83lesK435n8I{9)8RY>V-0T^e(#LuPeb7HTy0bebjXX|UG7ncp z>?sefs6;qDRo;x9Xp)&1$j@b$zn!d~z3^SX?{6b8?!KoWI_s5NjG?oRr+bDF$2D78 zwOM|R_L547`3$o6bis{`s34j?!&VU7)aQ9ql7Ax95V-WA3DeIZ!+lNB3B;tCJ*zA| z0Lb;(0cJ|ulp7qD(F4O}Mmm@87kF8dXw(Z_R!FpHZ=7JNJa{P9;ff^`kSrhEGccSqz< z(fy}zzY$JIK7n3ogbFwj0PzV_xPNy7e--inID{wA;0>xG|F@4)btQ#vc8nI3`!IIT zG!II~g%MbA zKG?uR0eGk3lVMmyvx73SAQ^aUto`qMB?l;IsUYkI;kbV03>?0#OL`i@u-t;@lBW$5 zU@p56c6Sat&=~0;)F2L3Zvo5s#a1t(3e+>^pL=pKX$qD3BF%3;BWFD8l+>$O9PSK$|%!O(J z1T~B}uYVcF79_MyjSxF~t+EL+)utHh^~Jzxz7e2?$>jY_c{&>{HBJNtPwi;K_OkDX zo+YuZ4q_WBO$Y`Nnk)cbr-+^h>FWOAr=w5>YvkRP$P6)KaW##}NPvqQ&EyH40H&KSKlK^%avr;k z{>cmMygd6XtL|JDH3)qBMJ@kI@J^2_lE5itx%Mcb!ARUj=AM*AhZKsc&^KiKd9J4m|> zKcmOWeo4IAP?Cie*9KE3NmL>!R>iF~wGsQi9sP+fhy$J3piLBQ5hfy!+WGz!)7f`4 z;cpWFn#9t;)l2mpg+;dbgm!yDr(Jf%~qg!MXCDv)^=`5yKB*eWAO6})w4!Kja1 zWHo7{b`S8y`V(q=9jL9G?Vn#HAV2)q*+@X4U71_g}%Nc0$nT(zKY`m8R$hZ5c3BySp)}?g4XPupFR9eC~ z8AaBQ?K+l^r(cc_$mH{czXSsPZ`=01JVv-M&9Epr=Oa@or{{EB{YSHTw@Ib)=_s_q zH4p6_1M~U04(N2zD`;&)e|RTX9%Nm(oJy=Pj;(T51~O;#Su1W`wgKXL3gzIJ8*Z@e zi~JJwU#HT?>$P z{MJcXOWaEMI!3tgeO(`2o`upxowv-_xz&Bb>zs3U+aC5)Avlv+AYDG*I@`QeB`{7h zm@e5qthe2>Qd#x0@_ml{y>;ASg#l+O2$@AlNa~z}T}91grmf#c6o`}pGULQ>YvHQm z!z>9I4ws}MQO;xh=K5R%DF(Yio|#XrFl01bokp}uAhG_N&=2m2H-+d#aGFbY7_nz+ z!GYx}uxtH4qB~O)>J|3sVX=G458D+53U0%864A^NlHv7lvuo=vsVmAVF|&;k z{Mx9Y>od1B+)taPFs~Uj)~iJfzvCobERggm$TIjf6$X>n370c|tVN+T zk|fJdyxE`hE=qc#vVU^A;F(9G^)^0p@_hljrv=iB9vAt%>mTN+uljNg3f?)(M~+Rs zOtJOjzpgM>{P>;w!!|G)vUZN=(saMP*lPWgs=IEor4ug9-p^>zcle4+A7>f0P`7V_L>l5d-OPSb?u-6Z1UwinRj}SR7_0UDq(~V25 z(a6LIhpv>+N#RWEU(N2|#Uu$Fk zIQD{-E=443IM>u76r7=PaW0~$@5Ro)jxPbGL6GC{R?&(|jXqA4$RmYe4;Ny(_3;+m z*IvHr-6DDVM!28$Fs^V!7|lQcN1pI>zmP{9M*o8T2}6XeglNTUG$eb@4vc~N2Dp66WaHxG%F#AH9M7YA82wLz%|w>xp$Vl&Y;n19 zz1uoevuAlOFyCHZh7;v`!4$xQ^sNyCVz)_0k$R-7IVKSIu~kMF`665v3AAdQ!<-oV zJ*fW+HHlPTp%{dTQ7{Rg$vAAq19rWTvY&+yPVBOB1CN4GVO#WClRY&w=NKv37ld1Q>-iFGM(!I9y4$Yw- z?a^w7Qo4x{pPV@}$5r|q7E(V>vGs&txR(shD|=#IiQk{xd@s5=Eu0zG&G|a4D5G^c zMS?eZL41r?;`sJuX_VV~w1S6zolP|wk-M{ev;V2VRO2v-U8;*QxW;N$J9(b?6=JF4 zm-CIXbj;usuicF>_a>u~r;?zGZu7vEpmP}mZ+Fx+mnS+q1_gNyf+F-{s}t6&-^zfi z6Y*`o4DMr;2!;GzW-J^4fR70Pp#HAUAUAKICFr*(?~CrC;{p%HgRse==uuO#NUh;e zrD3;R2rhjDYlo?lLv zYBzQ=7>Dy@T`pNbx3jXi2SM?iR7T%k(Z z;E<&rW*1%*=VwqOJLAc~O=4^WGoR=ZSRyXhflnQCp7ByaU&3({QWm!RU3H)|nT zs>Ah~z;%sIcmO?B^u{~2a}GU+>Y?ttqLPI1xUZE&eP4bYr+f5yBbHzqo;pFaYXc^2 zk0^W{C|*U}A1}oSW{}HixZoP4k&eU)W`DGxVMCp=Mn8{l2=O%*3RVr$G z9~Y*`Ykk?m6~yFwJ>4sFKrm-;^PFzNpS$Ra&KNok=(ymnK89ixF}B{+GvI4lmKO-vJI-W;ciH^#@*^;IuEnCM!G&Apf3uRv z#l>5`?HoRQ=%aHp?*e*O`nn~}#z1h-X>OhEOWg#wnZW_DrbwnN@}Pb_rz|Y*jG!#v z%vf}4M#{If@}TC~&;ef6XkHMfa-`s4Un{X9*gYI5c2c$w7$YfQf*-cYZIv*&q&;Ur?~zp%Vj!R zJ5OQx{LRSL1;NPvHbd7o|s%5M4G!1}0sbuKTBg(6( zqi$2Iw$qRVmm9G()X2)^8TF+56-_)UEcjOI!b5BoUW}jn1&NU!1@NEFZuPHmnI7;b;QE$Kg?8e)SNNHFfzn2e7Z0Reb>h+ z&Oh-*l}mSBxqs*(-T#^5IoXchb=?c>=)>-kA55`9nrm;{8!4DWQ{gi;a)BJ7Ka9V> zg9aS`3`JU;Ef43R&_>%+008F?(`94oYH6Y2=4$I`{mX*oX&I?3{GO+pgg#xRbdf+Y zD?}9sTE@?TdAu?VZ+N)*JgfvPk7CZ7>spTcJkrIpZ6ls5$v@v*28}k5-xH8MFyf>H zyHD(>CD69K4cwBJO^?Yb8FKL>z}!y3QzS&q-9a;S`_S^5(`^JhYT6NJxS1`6-rWxY z9u&?XmTPSX>=3-4|Ij!AgYAg0iXwI4(*mo{UPRTYlz&H0L?W3tacm9$5k=yGeGsf{ zC6R_|pH8L2@$Oo7qkV*OP#9_Kl`@TWr|u^ZQt8}04b0p5`=ifsG%m5k2mP_vKv@W# zApT_$z4}T#zlN?eTc?O4j{2M*M?~*B*V2lJ5UP~`&pewmoi+z9u)5$#ukfqs_?KXW zBuyF>QO#=hpyfegxos@Rcvo#Qg%?Eo=-0d7nk=7l;o!Z>G;%yRMry#rL=5b zJ+)JYDT6R#S1*yp@id&#d?|wy({j2Kl4jmFESN~P!bBh`=Qw&c?dZSD+{5^kNCsi+ zryM96s9`YtoP=MGY(G;9z$v|%K#iywDITEReR%$JLfi#(7*FARL{T0sxBLjw<#(Fbfa_jB)szN^$>52lycszaU#b;v9lPt7l$wp7NPn=#zekn1ikaP*2&F0<^NMnCEY8ucW>cNOVHz1Kani@}|)wFf&;9D0n zk?DkI@b+`C(qEx@S+_--0al_gX*GNxo|pLvGjjj0ICciAkV-!E${`fTrutnMegpab zz-@j<{r*I6eqnxJ5<8$_Iu!heJx2roNLu4LXv|h-b$A1_U}--Z`h2{WCf?ug@~O?^hKn8q`i8)o!VN{7Kw8}}2bLd)j)N#_)H60U=!i9Q z;#fmHH=fDH9rh#1>f2_Ft~iP{JMY)&^5b^1X63+$?>$pRTeh0>w51$o8sXhJ($bl( zxxu<5-OP>-EITt>x1I~{IUo2vY>2E^8Q*6=-Oy1lltm-lA!fI3Pmr*;dt*r0((o8zteG!$(Ry=bvH2U7}Z2e2J2`IWelati$K_H!+ zc7i{P5|M#@ZlZJUwe?`%7^NdkEzXRk1Yn}mYQqbz>nlpx`IWQW>Us2)KyiF{q1V)x z`5OJkO8jM*SlR^urfP$IWL;lPek!6UCxs2aBvl7mF}k);LyZRUQxy&mmtCNcNLr>g zK6wl*U*__|!)o&9-Dw|t7_)Qlf!NFXwUGxZ-$Q<~ciRL)XIAka&IY?WZkO-m>@JEd z{5^Up3(+!)dOZI%So7G_M*0lhWiDI*fbREQc7~cdH%nIyOE?j3;^CbpCxclDFk zJ9yCyLSRe7LhZ2MB#AmRN0`Bj!V~z!ytX9v~sj^?GYdZ=JxwgFQ@O%DXsMDrx`&MqCsT@d+gbTu30sUnZg= zaIc`d`cF@XNYfjK0ri0=NdNV8Zl-3?Af4^k@H--_{okw`V~}!KEXk6g7TAqsMrQ|` zCkELx9GjT4qKZS7^R^wY_+B=9t1ELHr{oy^onlDhXA7@WH)V`6o*)&cYO370=dJW4 zB?VRr$BSJ7&$=pNeo10R^;EehudR{h`T6}He)Ny2A;4LqEO{36ZU~J|4&d-jqoxG6 zL#i}C5Z#v0W{lvDX{tPVHrlNH`Rh5KWv3qHsh1gSXHg@YD=$RwBRE>O_sgeFS40&b zd~8asqT)kl9KAvl*j81ic7BC~DOVs7;?}^=_cTY+x#T{`R;ATjNQ{>%rRR7k?o~J~ z(u&hegMH+~XPaw>mCf8s1Qp+F32$~W#Q16PbIXh?#J1IK#t`E<(yiURwM2?d7Cn{k zmXpW$Uuikee-1)X!qP(w7N2K(nlS}$@7={t(5*(&AZ9=2(se@b`qZUvJ)auTTCgtaJ|K(G;pZ65cTMD^W7HXA>$PzS*dhgJ3a$qSEV{vVuW8k{NM$e zEHd?iNC3UAGMC~Qmb!lwQJb`#D8K>7dm^yUxD~8<72ZrU zCR5NFXjQ*MT*%+tCe_0`r@UM;T4< zX4~_(y@=Qhqy#J#Nme zmP$`X&H1+Pe>Bid``I^cz>e)L&xBa=%e4=_vR#RmaFK6W48#`r<~xgv>D-G4&JjS+$+ zHYUXGjtwD!$SccUyjwz4jT3*006a>tjPmz|&HDL(7Bv_?xl;-HSzx}XQ5*Nv#nq`L zBdUwtLV~~d?G@o`y=9Si9}DJxG{uRgz7|P8G9@fAv{#*N+Co#!lE(`~)E#dF%MZMG z&fAww?YJ2hWjq1$1~uu&7W7BzrG8E@Dm`X;h0|>wO8#k}_4t`V zaZu4hhZ<>&zlxWsv-AI?3o2HB93SFF9kE|v^d8DK2XFeWD~aJoG?%L?B*MOZo7X~o z2sCpB4sq(yG^37oM!sk&DIi|$cDO&_ZM*BeKCeg52@VqO(I2r+#KADK^IM!bb~$lA zQ%0sHCMH%7(a!*EmU-XFSI7^l_QN74z11BeReMDr>iE<*lG9Ptb;V-V|-C{YJtxIFi-%GrwkQQzXj(M3+OaIcpW6|S-sP)#t3tVUVES) zQGu7;S7d1&SqC8~At#QfL?bMEZ+UH{#znDMk=a2iJ`u6qyNsbMlG1x$mVypTVu*GQ zLxs-ED#x1vv%2kJiE;<)Ck#(cAia;d{U(N~6|jW^A@8hzE&tN1UEpEvc)aiM@;&eF zG8CNh)|=W(XBn$}u>L#>-(o?oup{-ugU2!3JL9TpJoDbxZ^g==vt(J*Z(2^NBsm%A z|KF7Q2@DG~eg5}O&;RV(f5w05`&3o{;1qWt$I{!tBo%;j;BX8T^USJ|=yPiu_5MNc21DV~pf6hsU|(pB%DDAFsi`W|of`K2BTy zWEci51wy~(QPT2w{ws-j%=B?g{3p`@%3nmZ iujCZa-0Z*Pe|P{@1$d|w|Aw)n0&Jj)u*2}%?Ee9*^)~hZ literal 0 HcmV?d00001 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/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..f56e470 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,102 @@ -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.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(); + } +} 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..82c693d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,23 +1,23 @@ -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} + +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(); + } +} From 9e1b970ac5dc9037f58f57d6abddaec5129cea52 Mon Sep 17 00:00:00 2001 From: sergio-utrillaa Date: Sat, 25 Apr 2026 12:52:00 +0200 Subject: [PATCH 2/5] Updated Healthcheck in Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 229adbe..c91549a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,7 +36,7 @@ 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 + 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 From fbaf00ac4249d602344329e41ff922bce27a410c Mon Sep 17 00:00:00 2001 From: sergio-utrillaa Date: Mon, 27 Apr 2026 15:15:39 +0200 Subject: [PATCH 3/5] Added recovery service from LDConnect --- .../domain/services/RecoveryService.java | 90 +++++++++++++++++++ .../rest/controllers/ProjectController.java | 16 ++++ src/main/resources/application.yml | 2 + 3 files changed, 108 insertions(+) create mode 100644 src/main/java/com/upc/ld_admintool/domain/services/RecoveryService.java 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..958aaed --- /dev/null +++ b/src/main/java/com/upc/ld_admintool/domain/services/RecoveryService.java @@ -0,0 +1,90 @@ +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.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 = new RestTemplate(); + + public RecoveryService(LDService ldService) { + this.ldService = ldService; + } + + 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"); + } + + String url = ldConnectUrl + "/admin/recovery/team"; + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + Map payload = new HashMap<>(); + payload.put("prj", prj); + payload.put("github_url", githubUrl); + payload.put("taiga_url", taigaUrl); + + if (tokenOverrides != null) { + String githubToken = tokenOverrides.get("githubToken"); + String taigaToken = tokenOverrides.get("taigaToken"); + + if (githubToken != null && !githubToken.isBlank()) { + payload.put("github_token", githubToken); + } + if (taigaToken != null && !taigaToken.isBlank()) { + payload.put("taiga_token", taigaToken); + } + } + + HttpEntity> request = new HttpEntity<>(payload, headers); + ResponseEntity response = restTemplate.postForEntity(url, request, Map.class); + + Map result = new HashMap<>(); + result.put("projectId", projectId); + result.put("projectExternalId", prj); + result.put("recovery", response.getBody()); + return result; + } + + 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/rest/controllers/ProjectController.java b/src/main/java/com/upc/ld_admintool/rest/controllers/ProjectController.java index f56e470..881c071 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 @@ -3,6 +3,7 @@ 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; @@ -30,6 +31,9 @@ public class ProjectController { @Autowired private ProjectValidationService validationService; + @Autowired + private RecoveryService recoveryService; + // Llista tots els projectes @GetMapping public List getAllProjects() { @@ -74,6 +78,18 @@ public ResponseEntity validateStudent(@RequestBody StudentVali return ResponseEntity.ok(result); } + @PostMapping("/{id}/recover") + public ResponseEntity triggerRecovery(@PathVariable Long id, + @RequestBody(required = false) Map tokenOverrides) { + try { + return ResponseEntity.ok(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())); + } + } + // Modifica un projecte per id @PutMapping("/{id}") public ResponseEntity modificarProjecte(@PathVariable Long id, @RequestBody ProjectDTO projecte) { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 82c693d..e2ad0f6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -6,6 +6,8 @@ ld: 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: From c977e866bca8d0b545efdfa6b5f459557ba4d5b5 Mon Sep 17 00:00:00 2001 From: sergio-utrillaa Date: Mon, 18 May 2026 11:40:53 +0200 Subject: [PATCH 4/5] Add individual GitHub and Taiga recovery endpoints --- .../domain/services/RecoveryService.java | 86 +++++++++++++++++++ .../rest/controllers/ProjectController.java | 24 ++++++ 2 files changed, 110 insertions(+) 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 index 958aaed..5994df0 100644 --- a/src/main/java/com/upc/ld_admintool/domain/services/RecoveryService.java +++ b/src/main/java/com/upc/ld_admintool/domain/services/RecoveryService.java @@ -80,6 +80,92 @@ public Map runTeamRecovery(Long projectId, Map t 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"); + } + + String url = ldConnectUrl + "/admin/recovery/github"; + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + Map payload = new HashMap<>(); + payload.put("prj", prj); + payload.put("github_url", githubUrl); + + if (tokenOverrides != null) { + String githubToken = tokenOverrides.get("githubToken"); + if (githubToken != null && !githubToken.isBlank()) { + payload.put("github_token", githubToken); + } + } + + HttpEntity> request = new HttpEntity<>(payload, headers); + ResponseEntity response = restTemplate.postForEntity(url, request, Map.class); + + Map result = new HashMap<>(); + result.put("projectId", projectId); + result.put("projectExternalId", prj); + result.put("recovery", response.getBody()); + 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"); + } + + String url = ldConnectUrl + "/admin/recovery/taiga"; + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + Map payload = new HashMap<>(); + payload.put("prj", prj); + payload.put("taiga_url", taigaUrl); + + if (tokenOverrides != null) { + String taigaToken = tokenOverrides.get("taigaToken"); + if (taigaToken != null && !taigaToken.isBlank()) { + payload.put("taiga_token", taigaToken); + } + } + + HttpEntity> request = new HttpEntity<>(payload, headers); + ResponseEntity response = restTemplate.postForEntity(url, request, Map.class); + + Map result = new HashMap<>(); + result.put("projectId", projectId); + result.put("projectExternalId", prj); + result.put("recovery", response.getBody()); + return result; + } + private String extractIdentityUrl(Map identities, DataSource source) { if (identities == null) { return null; 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 881c071..0bb7c05 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 @@ -90,6 +90,30 @@ public ResponseEntity triggerRecovery(@PathVariable Long id, } } + @PostMapping("/{id}/recover/github") + public ResponseEntity triggerGithubRecovery(@PathVariable Long id, + @RequestBody(required = false) Map tokenOverrides) { + try { + return ResponseEntity.ok(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.ok(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())); + } + } + // Modifica un projecte per id @PutMapping("/{id}") public ResponseEntity modificarProjecte(@PathVariable Long id, @RequestBody ProjectDTO projecte) { From 891ea9021814f443eb98393f8498cfe873719077 Mon Sep 17 00:00:00 2001 From: sergio-utrillaa Date: Fri, 22 May 2026 16:44:51 +0200 Subject: [PATCH 5/5] Fix error 504 on GitHub recovery: return job_id immediately, add GET status endpoint --- .../domain/services/RecoveryService.java | 141 +++++++----------- .../rest/controllers/ProjectController.java | 15 +- 2 files changed, 70 insertions(+), 86 deletions(-) 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 index 5994df0..3dc5f9c 100644 --- a/src/main/java/com/upc/ld_admintool/domain/services/RecoveryService.java +++ b/src/main/java/com/upc/ld_admintool/domain/services/RecoveryService.java @@ -8,6 +8,7 @@ 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; @@ -21,155 +22,129 @@ public class RecoveryService { private String ldConnectUrl; private final LDService ldService; - private final RestTemplate restTemplate = new RestTemplate(); + 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"); - } + 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"); - } + 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"); - } - - String url = ldConnectUrl + "/admin/recovery/team"; - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); + 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); - if (tokenOverrides != null) { - String githubToken = tokenOverrides.get("githubToken"); - String taigaToken = tokenOverrides.get("taigaToken"); - - if (githubToken != null && !githubToken.isBlank()) { - payload.put("github_token", githubToken); - } - if (taigaToken != null && !taigaToken.isBlank()) { - payload.put("taiga_token", taigaToken); - } - } - - HttpEntity> request = new HttpEntity<>(payload, headers); - ResponseEntity response = restTemplate.postForEntity(url, request, Map.class); + String jobId = startJob(ldConnectUrl + "/admin/recovery/team", payload); Map result = new HashMap<>(); result.put("projectId", projectId); result.put("projectExternalId", prj); - result.put("recovery", response.getBody()); + 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"); - } + 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"); - } + 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"); - } - - String url = ldConnectUrl + "/admin/recovery/github"; - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); + 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); - if (tokenOverrides != null) { - String githubToken = tokenOverrides.get("githubToken"); - if (githubToken != null && !githubToken.isBlank()) { - payload.put("github_token", githubToken); - } - } - - HttpEntity> request = new HttpEntity<>(payload, headers); - ResponseEntity response = restTemplate.postForEntity(url, request, Map.class); + String jobId = startJob(ldConnectUrl + "/admin/recovery/github", payload); Map result = new HashMap<>(); result.put("projectId", projectId); result.put("projectExternalId", prj); - result.put("recovery", response.getBody()); + 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"); - } + 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"); - } + 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"); - } - - String url = ldConnectUrl + "/admin/recovery/taiga"; - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); + 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); - if (tokenOverrides != null) { - String taigaToken = tokenOverrides.get("taigaToken"); - if (taigaToken != null && !taigaToken.isBlank()) { - payload.put("taiga_token", taigaToken); - } - } - - HttpEntity> request = new HttpEntity<>(payload, headers); - ResponseEntity response = restTemplate.postForEntity(url, request, Map.class); + String jobId = startJob(ldConnectUrl + "/admin/recovery/taiga", payload); Map result = new HashMap<>(); result.put("projectId", projectId); result.put("projectExternalId", prj); - result.put("recovery", response.getBody()); + result.put("job_id", jobId); + result.put("status", "running"); return result; } - private String extractIdentityUrl(Map identities, DataSource source) { - if (identities == null) { - return null; + @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/rest/controllers/ProjectController.java b/src/main/java/com/upc/ld_admintool/rest/controllers/ProjectController.java index 0bb7c05..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 @@ -82,7 +82,7 @@ public ResponseEntity validateStudent(@RequestBody StudentVali public ResponseEntity triggerRecovery(@PathVariable Long id, @RequestBody(required = false) Map tokenOverrides) { try { - return ResponseEntity.ok(recoveryService.runTeamRecovery(id, tokenOverrides)); + return ResponseEntity.accepted().body(recoveryService.runTeamRecovery(id, tokenOverrides)); } catch (IllegalArgumentException e) { return ResponseEntity.badRequest().body(Map.of("error", e.getMessage())); } catch (Exception e) { @@ -94,7 +94,7 @@ public ResponseEntity triggerRecovery(@PathVariable Long id, public ResponseEntity triggerGithubRecovery(@PathVariable Long id, @RequestBody(required = false) Map tokenOverrides) { try { - return ResponseEntity.ok(recoveryService.runGithubRecovery(id, tokenOverrides)); + return ResponseEntity.accepted().body(recoveryService.runGithubRecovery(id, tokenOverrides)); } catch (IllegalArgumentException e) { return ResponseEntity.badRequest().body(Map.of("error", e.getMessage())); } catch (Exception e) { @@ -106,7 +106,7 @@ public ResponseEntity triggerGithubRecovery(@PathVariable Long id, public ResponseEntity triggerTaigaRecovery(@PathVariable Long id, @RequestBody(required = false) Map tokenOverrides) { try { - return ResponseEntity.ok(recoveryService.runTaigaRecovery(id, tokenOverrides)); + return ResponseEntity.accepted().body(recoveryService.runTaigaRecovery(id, tokenOverrides)); } catch (IllegalArgumentException e) { return ResponseEntity.badRequest().body(Map.of("error", e.getMessage())); } catch (Exception e) { @@ -114,6 +114,15 @@ public ResponseEntity triggerTaigaRecovery(@PathVariable Long id, } } + @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) {